fix: linting issues

This commit is contained in:
abhijithvijayan
2026-01-04 03:19:26 +05:30
parent 444ca0a97c
commit 41e893b337
38 changed files with 625 additions and 511 deletions

View File

@@ -1,5 +0,0 @@
node_modules/
dist/
extension/
.yarn/
.pnp.js

View File

@@ -1,34 +0,0 @@
{
"env": {
"webextensions": true
},
"extends": [
"@abhijithvijayan/eslint-config/typescript",
"@abhijithvijayan/eslint-config/node",
"@abhijithvijayan/eslint-config/react"
],
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
},
"rules": {
"no-console": "off",
"no-shadow": ["error", {
"builtinGlobals": false,
"hoist": "functions",
"allow": []
}],
"react/jsx-props-no-spreading": "off",
"jsx-a11y/label-has-associated-control": "off",
"@typescript-eslint/no-explicit-any": "warn",
"react/no-array-index-key": "warn",
"node/no-unsupported-features/es-syntax": ["error", {
"ignores": ["modules"]
}]
},
"settings": {
"node": {
"tryExtensions": [".tsx"] // append tsx to the list as well
}
}
}

48
eslint.config.mjs Normal file
View File

@@ -0,0 +1,48 @@
import nodeConfig from '@abhijithvijayan/eslint-config/node';
import tsConfig from '@abhijithvijayan/eslint-config/typescript';
import reactConfig from '@abhijithvijayan/eslint-config/react';
export default [
{
ignores: [
'node_modules/**',
'dist/**',
'extension/**',
'.yarn/**',
'.pnp.js',
'*.js',
'*.mjs',
'vite.config.ts',
],
},
...nodeConfig({
files: ['**/*.ts', '**/*.tsx'],
}),
...tsConfig({
files: ['**/*.ts', '**/*.tsx'],
}),
...reactConfig({
files: ['**/*.tsx'],
}),
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-console': 'off',
'@typescript-eslint/no-use-before-define': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
// Disable due to resolver issues in ESM
'import-x/no-duplicates': 'off',
// Browser extension code uses browser APIs, not Node.js
'n/no-unsupported-features/node-builtins': 'off',
},
},
{
files: ['**/*.tsx'],
rules: {
'react/jsx-props-no-spreading': 'off',
'react/react-in-jsx-scope': 'off',
'react/no-array-index-key': 'warn',
'jsx-a11y/label-has-associated-control': 'off',
},
},
];

View File

@@ -340,8 +340,7 @@ type MessageRequest = {
browser.runtime.onMessage.addListener(
(message: unknown, _sender: Runtime.MessageSender): void | Promise<any> => {
const request = message as MessageRequest;
// eslint-disable-next-line consistent-return
// eslint-disable-next-line default-case
switch (request.action) {
case constants.CHECK_API_KEY: {
return checkApiKey(request.params);

View File

@@ -1,3 +1,4 @@
import type {JSX} from 'react';
import {useEffect, useState} from 'react';
import {
@@ -31,7 +32,7 @@ import Table from './Table';
import styles from './History.module.scss';
function History() {
function History(): JSX.Element {
const [, shortenedLinksDispatch] = useShortenedLinks();
const [, extensionSettingsDispatch] = useExtensionSettings();
const [requestStatusState, requestStatusDispatch] = useRequestStatus();
@@ -54,7 +55,8 @@ function History() {
(advancedSettings &&
(settings?.host as string) &&
isValidUrl(settings.host as string) && {
hostDomain: (settings.host as string)
hostDomain:
(settings.host as string)
.replace('http://', '')
.replace('https://', '')
.replace('www.', '')
@@ -137,7 +139,7 @@ function History() {
<div className={styles.historyContent}>
<Header subtitle="Recent Links" hostUrl={hostUrl} />
{/* eslint-disable-next-line no-nested-ternary */}
{}
{!requestStatusState.loading ? (
!errored.error ? (
<Table />

View File

@@ -1,4 +1,5 @@
import {QRCodeSVG} from 'qrcode.react';
import type {JSX} from 'react';
import {Dispatch, SetStateAction} from 'react';
import styles from './Modal.module.scss';
@@ -8,16 +9,24 @@ type Props = {
setModalView: Dispatch<SetStateAction<boolean>>;
};
function Modal({link, setModalView}: Props) {
function Modal({link, setModalView}: Props): JSX.Element {
return (
<>
<div
className={styles.modalOverlay}
onClick={(): void => setModalView(false)}
onKeyDown={(e): void => {
if (e.key === 'Escape') setModalView(false);
}}
role="button"
tabIndex={0}
>
<div
className={styles.modalContent}
onClick={(e): void => e.stopPropagation()}
onKeyDown={(e): void => e.stopPropagation()}
role="button"
tabIndex={0}
>
<div className={styles.qrCodeWrapper}>
<QRCodeSVG size={196} value={link} />

View File

@@ -1,4 +1,5 @@
import CopyToClipboard from 'react-copy-to-clipboard';
import type {JSX} from 'react';
import {useEffect, useState} from 'react';
import clsx from 'clsx';
@@ -13,7 +14,7 @@ import Modal from './Modal';
import styles from './Table.module.scss';
function Table() {
function Table(): JSX.Element {
const [shortenedLinksState, shortenedLinksDispatch] = useShortenedLinks();
const [QRView, setQRView] = useState<boolean>(false);
const [copied, setCopied] = useState<boolean>(false);
@@ -72,8 +73,7 @@ function Table() {
</thead>
<tbody className={styles.tbody}>
{!(shortenedLinksState.total === 0) ? (
shortenedLinksState.items.map((item) => {
return (
shortenedLinksState.items.map((item) => (
<tr key={item.id} className={styles.tr}>
<td className={clsx(styles.td, styles.tdOriginal)}>
<a
@@ -116,18 +116,14 @@ function Table() {
) : (
<CopyToClipboard
text={item.link}
onCopy={(): void => {
return handleCopyToClipboard(item.id);
}}
onCopy={(): void => handleCopyToClipboard(item.id)}
>
<Icon name="copy" className={styles.actionIcon} />
</CopyToClipboard>
)}
<Icon
onClick={(): void =>
handleQRCodeViewToggle(item.id)
}
onClick={(): void => handleQRCodeViewToggle(item.id)}
className={styles.actionIcon}
name="qrcode"
/>
@@ -140,8 +136,7 @@ function Table() {
)}
</td>
</tr>
);
})
))
) : (
<tr>
<td className={styles.emptyRow}>No URLs History</td>

View File

@@ -1,3 +1,4 @@
import type {JSX} from 'react';
import {memo} from 'react';
import clsx from 'clsx';
@@ -8,7 +9,7 @@ import Icon from '../components/Icon';
import styles from './Footer.module.scss';
function Footer() {
function Footer(): JSX.Element {
return (
<>
<footer className={styles.footer}>
@@ -25,11 +26,26 @@ function Footer() {
className={styles.ratingLink}
>
<div className={styles.starsContainer}>
<Icon className={clsx(styles.starIcon, styles.gray)} name="star-white" />
<Icon className={clsx(styles.starIcon, styles.gray)} name="star-white" />
<Icon className={clsx(styles.starIcon, styles.gray)} name="star-white" />
<Icon className={clsx(styles.starIcon, styles.gray)} name="star-white" />
<Icon className={clsx(styles.starIcon, styles.gray)} name="star-white" />
<Icon
className={clsx(styles.starIcon, styles.gray)}
name="star-white"
/>
<Icon
className={clsx(styles.starIcon, styles.gray)}
name="star-white"
/>
<Icon
className={clsx(styles.starIcon, styles.gray)}
name="star-white"
/>
<Icon
className={clsx(styles.starIcon, styles.gray)}
name="star-white"
/>
<Icon
className={clsx(styles.starIcon, styles.gray)}
name="star-white"
/>
</div>
<p className={styles.ratingText}>Rate on Store</p>
</a>

View File

@@ -1,9 +1,13 @@
import {isNull, isUndefined} from '@abhijithvijayan/ts-utils';
import type {JSX} from 'react';
import {useState, useEffect, useRef, ChangeEvent} from 'react';
import clsx from 'clsx';
import {useExtensionSettings} from '../contexts/extension-settings-context';
import {updateExtensionSettings, clearExtensionSettings} from '../util/settings';
import {
updateExtensionSettings,
clearExtensionSettings,
} from '../util/settings';
import {CHECK_API_KEY} from '../Background/constants';
import messageUtil from '../util/mesageUtil';
import {isValidUrl} from '../util/link';
@@ -38,12 +42,10 @@ type FormValidity = {
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSave = (values: OptionsFormValuesProperties): Promise<any> => {
const onSave = (values: OptionsFormValuesProperties): Promise<any> =>
// should always return a Promise
return updateExtensionSettings(values); // update local settings
};
function Form() {
updateExtensionSettings(values); // update local settings
function Form(): JSX.Element {
const extensionSettingsState = useExtensionSettings()[0];
const hostInputRef = useRef<HTMLInputElement>(null);
const [submitting, setSubmitting] = useState<boolean>(false);
@@ -86,51 +88,83 @@ function Form() {
}, [formValues]);
function handleApiKeyInputChange(apikey: string): void {
setFormValues((prev) => ({...prev, apikey}));
setFormValues((prev) => {
return {...prev, apikey};
});
// ToDo: Remove special symbols
if (!(apikey.trim().length > 0)) {
setFormErrors((prev) => ({...prev, apikey: 'API key missing'}));
setFormValidity((prev) => ({...prev, apikey: false}));
setFormErrors((prev) => {
return {...prev, apikey: 'API key missing'};
});
setFormValidity((prev) => {
return {...prev, apikey: false};
});
} else if (apikey && apikey.trim().length < 40) {
setFormErrors((prev) => ({...prev, apikey: 'API key must be 40 characters'}));
setFormValidity((prev) => ({...prev, apikey: false}));
setFormErrors((prev) => {
return {...prev, apikey: 'API key must be 40 characters'};
});
setFormValidity((prev) => {
return {...prev, apikey: false};
});
} else if (apikey && apikey.trim().length > 40) {
setFormErrors((prev) => ({...prev, apikey: 'API key cannot exceed 40 characters'}));
setFormValidity((prev) => ({...prev, apikey: false}));
setFormErrors((prev) => {
return {...prev, apikey: 'API key cannot exceed 40 characters'};
});
setFormValidity((prev) => {
return {...prev, apikey: false};
});
} else {
setFormErrors((prev) => {
const {apikey: _, ...rest} = prev;
return rest;
});
setFormValidity((prev) => ({...prev, apikey: true}));
setFormValidity((prev) => {
return {...prev, apikey: true};
});
}
}
function handleHostUrlInputChange(host: string): void {
if (!formValues.advanced) {
setFormErrors((prev) => ({...prev, host: 'Enable Advanced Options first'}));
setFormValidity((prev) => ({...prev, host: false}));
setFormErrors((prev) => {
return {...prev, host: 'Enable Advanced Options first'};
});
setFormValidity((prev) => {
return {...prev, host: false};
});
return;
}
setFormValues((prev) => ({...prev, host}));
setFormValues((prev) => {
return {...prev, host};
});
if (!(host.trim().length > 0)) {
setFormErrors((prev) => ({...prev, host: 'Custom URL cannot be empty'}));
setFormValidity((prev) => ({...prev, host: false}));
setFormErrors((prev) => {
return {...prev, host: 'Custom URL cannot be empty'};
});
setFormValidity((prev) => {
return {...prev, host: false};
});
return;
}
if (!isValidUrl(host.trim()) || host.trim().length < 10) {
setFormErrors((prev) => ({...prev, host: 'Please enter a valid url'}));
setFormValidity((prev) => ({...prev, host: false}));
setFormErrors((prev) => {
return {...prev, host: 'Please enter a valid url'};
});
setFormValidity((prev) => {
return {...prev, host: false};
});
} else {
setFormErrors((prev) => {
const {host: _, ...rest} = prev;
return rest;
});
setFormValidity((prev) => ({...prev, host: true}));
setFormValidity((prev) => {
return {...prev, host: true};
});
}
}
@@ -190,8 +224,7 @@ function Form() {
<span className={styles.labelLinkWrapper}>
<a
href={`${
(formValues.advanced && formValues.host) ||
Kutt.hostUrl
(formValues.advanced && formValues.host) || Kutt.hostUrl
}/login`}
target="blank"
rel="nofollow noopener noreferrer"
@@ -258,7 +291,12 @@ function Form() {
</button>
{!isNull(errored.error) && (
<div className={clsx(styles.validationFeedback, errored.error ? styles.error : styles.success)}>
<div
className={clsx(
styles.validationFeedback,
errored.error ? styles.error : styles.success
)}
>
<Icon
className={styles.feedbackIcon}
name={errored.error ? 'cross' : 'tick'}
@@ -294,7 +332,9 @@ function Form() {
type="checkbox"
checked={formValues.history}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setFormValues((prev) => ({...prev, history: e.target.checked}));
setFormValues((prev) => {
return {...prev, history: e.target.checked};
});
}}
className={styles.toggleInput}
/>
@@ -308,7 +348,8 @@ function Form() {
<span className={styles.infoIcon}>
<Icon name="info" />
<span className={styles.tooltip}>
Returns the existing short link if the same URL was shortened before
Returns the existing short link if the same URL was shortened
before
</span>
</span>
</span>
@@ -327,7 +368,9 @@ function Form() {
type="checkbox"
checked={formValues.reuse}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setFormValues((prev) => ({...prev, reuse: e.target.checked}));
setFormValues((prev) => {
return {...prev, reuse: e.target.checked};
});
}}
className={styles.toggleInput}
/>
@@ -360,7 +403,9 @@ function Form() {
type="checkbox"
checked={formValues.advanced}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setFormValues((prev) => ({...prev, advanced: e.target.checked}));
setFormValues((prev) => {
return {...prev, advanced: e.target.checked};
});
if (e.target.checked) {
setTimeout(() => hostInputRef.current?.focus(), 350);
}
@@ -371,7 +416,12 @@ function Form() {
</span>
</label>
<div className={clsx(styles.advancedSection, !formValues.advanced && styles.hidden)}>
<div
className={clsx(
styles.advancedSection,
!formValues.advanced && styles.hidden
)}
>
<div className={styles.inputGroup}>
<label htmlFor="host" className={styles.label}>
<span className={styles.labelWithInfo}>
@@ -379,7 +429,8 @@ function Form() {
<span className={styles.infoIcon}>
<Icon name="info" />
<span className={styles.tooltip}>
URL of your self-hosted Kutt instance (e.g., https://kutt.example.com)
URL of your self-hosted Kutt instance (e.g.,
https://kutt.example.com)
</span>
</span>
</span>
@@ -424,14 +475,29 @@ function Form() {
</div>
{showResetConfirm && (
<div className={styles.modalOverlay} onClick={() => setShowResetConfirm(false)}>
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
<div
className={styles.modalOverlay}
onClick={() => setShowResetConfirm(false)}
onKeyDown={(e) => {
if (e.key === 'Escape') setShowResetConfirm(false);
}}
role="button"
tabIndex={0}
>
<div
className={styles.modal}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
role="button"
tabIndex={0}
>
<div className={styles.modalHeader}>
<Icon name="info" className={styles.modalIcon} />
<span className={styles.modalTitle}>Reset Settings?</span>
</div>
<p className={styles.modalText}>
This will permanently delete your API key and all preferences. You will need to reconfigure the extension.
This will permanently delete your API key and all preferences. You
will need to reconfigure the extension.
</p>
<div className={styles.modalActions}>
<button

View File

@@ -1,3 +1,4 @@
import type {JSX} from 'react';
import {memo} from 'react';
import {Kutt} from '../Background';
@@ -9,7 +10,10 @@ type Props = {
hostUrl?: string;
};
function Header({subtitle = 'Extension Settings', hostUrl = Kutt.hostUrl}: Props) {
function Header({
subtitle = 'Extension Settings',
hostUrl = Kutt.hostUrl,
}: Props): JSX.Element {
return (
<header className={styles.header}>
<a

View File

@@ -1,3 +1,4 @@
import type {JSX} from 'react';
import {useEffect, useState} from 'react';
import {getExtensionSettings} from '../util/settings';
@@ -21,7 +22,7 @@ import Form from './Form';
import styles from './Options.module.scss';
function Options() {
function Options(): JSX.Element {
const [, extensionSettingsDispatch] = useExtensionSettings();
const [requestStatusState, requestStatusDispatch] = useRequestStatus();
const [hostUrl, setHostUrl] = useState<string>(Kutt.hostUrl);
@@ -36,7 +37,8 @@ function Options() {
(advancedSettings &&
(settings?.host as string) &&
isValidUrl(settings.host as string) && {
hostDomain: (settings.host as string)
hostDomain:
(settings.host as string)
.replace('http://', '')
.replace('https://', '')
.replace('www.', '')
@@ -49,7 +51,10 @@ function Options() {
// inject existing keys (if field doesn't exist, use default)
// For history: default to true for new users, but respect existing user preference
const historyEnabled = Object.prototype.hasOwnProperty.call(settings, 'history')
const historyEnabled = Object.prototype.hasOwnProperty.call(
settings,
'history'
)
? (settings.history as boolean)
: true;

View File

@@ -1,10 +1,6 @@
import type {JSX} from 'react';
import {useState, useRef, useEffect, type ChangeEvent} from 'react';
import {
EMPTY_STRING,
isEmpty,
isNull,
get,
} from '@abhijithvijayan/ts-utils';
import {EMPTY_STRING, isEmpty, isNull, get} from '@abhijithvijayan/ts-utils';
import clsx from 'clsx';
import {useExtensionSettings} from '../contexts/extension-settings-context';
@@ -30,7 +26,7 @@ export enum CONSTANTS {
DefaultDomainId = 'default',
}
function Form() {
function Form(): JSX.Element {
const extensionSettingsState = useExtensionSettings()[0];
const requestStatusDispatch = useRequestStatus()[1];
const [showPassword, setShowPassword] = useState<boolean>(false);
@@ -45,13 +41,17 @@ function Form() {
// Close dropdown when clicking outside
useEffect(() => {
function handleClickOutside(event: MouseEvent): void {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsDropdownOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
return (): void =>
document.removeEventListener('mousedown', handleClickOutside);
}, []);
const [formState, setFormState] = useState({
@@ -68,9 +68,7 @@ function Form() {
}>({});
const isFormValid: boolean =
!formErrors.customurl &&
!formErrors.password &&
true;
!formErrors.customurl && !formErrors.password && true;
async function handleFormSubmit(): Promise<void> {
// enable loading screen
@@ -131,7 +129,9 @@ function Form() {
},
});
// reset form fields (keep domain selection)
setFormState((prev) => ({...prev, customurl: '', password: ''}));
setFormState((prev) => {
return {...prev, customurl: '', password: ''};
});
setFormErrors({});
} else {
// errored
@@ -146,51 +146,76 @@ function Form() {
}
function handleCustomUrlInputChange(url: string): void {
setFormState((prev) => ({...prev, customurl: url}));
setFormState((prev) => {
return {...prev, customurl: url};
});
// ToDo: Remove special symbols
if (url.length > 0 && url.length < 3) {
setFormErrors((prev) => ({
setFormErrors((prev) => {
return {
...prev,
customurl: 'Custom URL must be at-least 3 characters',
}));
};
});
} else {
setFormErrors((prev) => ({...prev, customurl: undefined}));
setFormErrors((prev) => {
return {...prev, customurl: undefined};
});
}
}
function handlePasswordInputChange(password: string): void {
setFormState((prev) => ({...prev, password}));
setFormState((prev) => {
return {...prev, password};
});
// ToDo: Remove special symbols
if (password.length > 0 && password.length < 3) {
setFormErrors((prev) => ({
setFormErrors((prev) => {
return {
...prev,
password: 'Password must be at-least 3 characters',
}));
};
});
} else {
setFormErrors((prev) => ({...prev, password: undefined}));
setFormErrors((prev) => {
return {...prev, password: undefined};
});
}
}
return (
<div className={styles.formContainer}>
<div className={styles.formGroup}>
<label className={styles.label}>
Domain
</label>
<label className={styles.label}>Domain</label>
<div className={styles.dropdown} ref={dropdownRef}>
<button
type="button"
className={clsx(styles.dropdownTrigger, isDropdownOpen && styles.open)}
className={clsx(
styles.dropdownTrigger,
isDropdownOpen && styles.open
)}
onClick={() => !isSubmitting && setIsDropdownOpen(!isDropdownOpen)}
disabled={isSubmitting}
>
<span className={clsx(styles.dropdownValue, formState.domain && styles.hasValue)}>
{domainOptions.find(({value}) => value === formState.domain)?.option || 'Select domain'}
<span
className={clsx(
styles.dropdownValue,
formState.domain && styles.hasValue
)}
>
{domainOptions.find(({value}) => value === formState.domain)
?.option || 'Select domain'}
</span>
<Icon name="chevron-down" className={clsx(styles.dropdownIcon, isDropdownOpen && styles.open)} />
<Icon
name="chevron-down"
className={clsx(
styles.dropdownIcon,
isDropdownOpen && styles.open
)}
/>
</button>
{isDropdownOpen && (
@@ -206,7 +231,9 @@ function Form() {
formState.domain === value && styles.selected
)}
onClick={() => {
setFormState((prev) => ({...prev, domain: value}));
setFormState((prev) => {
return {...prev, domain: value};
});
setIsDropdownOpen(false);
}}
>
@@ -233,7 +260,10 @@ function Form() {
}}
disabled={isSubmitting}
spellCheck="false"
className={clsx(styles.input, formErrors.customurl && styles.inputError)}
className={clsx(
styles.input,
formErrors.customurl && styles.inputError
)}
/>
<span className={styles.errorText}>{formErrors.customurl}</span>
@@ -267,7 +297,10 @@ function Form() {
handlePasswordInputChange(e.target.value);
}}
disabled={isSubmitting}
className={clsx(styles.input, formErrors.password && styles.inputError)}
className={clsx(
styles.input,
formErrors.password && styles.inputError
)}
/>
</div>

View File

@@ -1,4 +1,5 @@
import {isNull, EMPTY_STRING} from '@abhijithvijayan/ts-utils';
import type {JSX} from 'react';
import {useState} from 'react';
import clsx from 'clsx';
@@ -20,7 +21,7 @@ import {
import Icon from '../components/Icon';
import styles from './Header.module.scss';
function Header() {
function Header(): JSX.Element {
const [extensionSettingsState, extensionSettingsDispatch] =
useExtensionSettings();
const [loading, setLoading] = useState<boolean>(false);

View File

@@ -1,4 +1,5 @@
import {isNull, EMPTY_STRING} from '@abhijithvijayan/ts-utils';
import type {JSX} from 'react';
import {useEffect} from 'react';
import {Kutt, UserSettingsResponseProperties} from '../Background';
@@ -25,7 +26,7 @@ import Form, { CONSTANTS } from './Form';
import styles from './Popup.module.scss';
function Popup() {
function Popup(): JSX.Element {
const [extensionSettingsState, extensionSettingsDispatch] =
useExtensionSettings();
const [requestStatusState, requestStatusDispatch] = useRequestStatus();
@@ -54,9 +55,7 @@ function Popup() {
});
// Open options page
setTimeout(() => {
return openExtOptionsPage();
}, 1300);
setTimeout(() => openExtOptionsPage(), 1300);
return;
}
@@ -75,7 +74,8 @@ function Popup() {
isValidUrl(settings.host as string)
) {
defaultHost = {
hostDomain: (settings.host as string)
hostDomain:
(settings.host as string)
.replace('http://', EMPTY_STRING)
.replace('https://', EMPTY_STRING)
.replace('www.', EMPTY_STRING)

View File

@@ -1,4 +1,5 @@
import CopyToClipboard from 'react-copy-to-clipboard';
import type {JSX} from 'react';
import {useState, useEffect} from 'react';
import {QRCodeSVG} from 'qrcode.react';
import clsx from 'clsx';
@@ -14,7 +15,7 @@ export type ProcessedRequestProperties = {
message: string;
};
function ResponseBody() {
function ResponseBody(): JSX.Element {
const [{error, message}] = useRequestStatus();
const [copied, setCopied] = useState<boolean>(false);
const [QRView, setQRView] = useState<boolean>(false);
@@ -42,29 +43,29 @@ function ResponseBody() {
<Icon
className={clsx(styles.icon, styles.qrIcon)}
name="qrcode"
onClick={(): void => {
return setQRView(!QRView);
}}
onClick={(): void => setQRView(!QRView)}
/>
{!copied ? (
<CopyToClipboard
text={message}
onCopy={(): void => {
return setCopied(true);
}}
onCopy={(): void => setCopied(true)}
>
<Icon className={clsx(styles.icon, styles.copyIcon)} name="copy" />
<Icon
className={clsx(styles.icon, styles.copyIcon)}
name="copy"
/>
</CopyToClipboard>
) : (
<Icon className={clsx(styles.icon, styles.copyIcon)} name="tick" />
<Icon
className={clsx(styles.icon, styles.copyIcon)}
name="tick"
/>
)}
<CopyToClipboard
text={message}
onCopy={(): void => {
return setCopied(true);
}}
onCopy={(): void => setCopied(true)}
>
<h1 className={styles.link}>{removeProtocol(message)}</h1>
</CopyToClipboard>

View File

@@ -1,11 +1,11 @@
import type {ReactNode} from 'react';
import type {JSX, ReactNode} from 'react';
import styles from './BodyWrapper.module.scss';
type WrapperProperties = {
children: ReactNode;
};
function BodyWrapper({children}: WrapperProperties) {
function BodyWrapper({children}: WrapperProperties): JSX.Element {
return <div className={styles.wrapper}>{children}</div>;
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
const ChevronDown: React.FC = () => {
return (
const ChevronDown: React.FC = () => (
<svg
width={16}
height={16}
@@ -15,6 +14,5 @@ const ChevronDown: React.FC = () => {
<path d="M6 9l6 6 6-6" />
</svg>
);
};
export default React.memo(ChevronDown);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Clock: React.FC = () => {
return (
const Clock: React.FC = () => (
<svg
width={16}
height={16}
@@ -17,6 +16,5 @@ const Clock: React.FC = () => {
<path d="M12 6v6l4 2" />
</svg>
);
};
export default React.memo(Clock);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Copy: React.FC = () => {
return (
const Copy: React.FC = () => (
<svg
width={16}
height={16}
@@ -17,6 +16,5 @@ const Copy: React.FC = () => {
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</svg>
);
};
export default React.memo(Copy);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Cross: React.FC = () => {
return (
const Cross: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const Cross: React.FC = () => {
<path d="M18 6L6 18M6 6l12 12" />
</svg>
);
};
export default React.memo(Cross);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Eye: React.FC = () => {
return (
const Eye: React.FC = () => (
<svg
width={16}
height={16}
@@ -17,6 +16,5 @@ const Eye: React.FC = () => {
<circle cx={12} cy={12} r={3} />
</svg>
);
};
export default React.memo(Eye);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const EyeClosed: React.FC = () => {
return (
const EyeClosed: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const EyeClosed: React.FC = () => {
<path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24M1 1l22 22" />
</svg>
);
};
export default React.memo(EyeClosed);

View File

@@ -48,8 +48,8 @@ type Props = {
onClick?: () => void;
};
const Icon: React.FC<Props> = ({name, ...rest}) => {
return <div {...rest}>{React.createElement(icons[name])}</div>;
};
const Icon: React.FC<Props> = ({name, ...rest}) => (
<div {...rest}>{React.createElement(icons[name])}</div>
);
export default Icon;

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Info: React.FC = () => {
return (
const Info: React.FC = () => (
<svg
width={14}
height={14}
@@ -17,6 +16,5 @@ const Info: React.FC = () => {
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
);
};
export default React.memo(Info);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const QRCode: React.FC = () => {
return (
const QRCode: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const QRCode: React.FC = () => {
<path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" />
</svg>
);
};
export default React.memo(QRCode);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Refresh: React.FC = () => {
return (
const Refresh: React.FC = () => (
<svg
width={16}
height={16}
@@ -17,6 +16,5 @@ const Refresh: React.FC = () => {
<path d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15" />
</svg>
);
};
export default React.memo(Refresh);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Settings: React.FC = () => {
return (
const Settings: React.FC = () => (
<svg
width={16}
height={16}
@@ -17,6 +16,5 @@ const Settings: React.FC = () => {
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" />
</svg>
);
};
export default React.memo(Settings);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Spinner: React.FC = () => {
return (
const Spinner: React.FC = () => (
<>
<svg
id="spinner"
@@ -27,6 +26,5 @@ const Spinner: React.FC = () => {
</svg>
</>
);
};
export default React.memo(Spinner);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const StarWhite: React.FC = () => {
return (
const StarWhite: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const StarWhite: React.FC = () => {
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
);
};
export default React.memo(StarWhite);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const StarYellow: React.FC = () => {
return (
const StarYellow: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const StarYellow: React.FC = () => {
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
);
};
export default React.memo(StarYellow);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Tick: React.FC = () => {
return (
const Tick: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const Tick: React.FC = () => {
<path d="M20 6L9 17l-5-5" />
</svg>
);
};
export default React.memo(Tick);

View File

@@ -1,7 +1,6 @@
import React from 'react';
const Zap: React.FC = () => {
return (
const Zap: React.FC = () => (
<svg
width={16}
height={16}
@@ -16,6 +15,5 @@ const Zap: React.FC = () => {
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
</svg>
);
};
export default React.memo(Zap);

View File

@@ -1,7 +1,8 @@
import type {JSX} from 'react';
import Icon from './Icon';
import styles from './Loader.module.scss';
function Loader() {
function Loader(): JSX.Element {
return (
<div className={styles.loader}>
<Icon name="spinner" />

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type {JSX} from 'react';
import {createContext, useReducer, useContext, type ReactNode} from 'react';
import {Kutt} from '../Background';
@@ -126,7 +126,7 @@ type ExtensionSettingsProviderProps = {
function ExtensionSettingsProvider({
children,
}: ExtensionSettingsProviderProps) {
}: ExtensionSettingsProviderProps): JSX.Element {
const [state, dispatch] = useReducer(extensionSettingsReducer, initialValues);
return (
@@ -138,6 +138,6 @@ function ExtensionSettingsProvider({
</ExtensionSettingsStateContext.Provider>
</>
);
};
}
export {ExtensionSettingsProvider, useExtensionSettings};

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type {JSX} from 'react';
import {createContext, useReducer, useContext, type ReactNode} from 'react';
export enum RequestStatusActionTypes {
@@ -89,7 +89,9 @@ type RequestStatusProviderProps = {
children: ReactNode;
};
function RequestStatusProvider({children}: RequestStatusProviderProps) {
function RequestStatusProvider({
children,
}: RequestStatusProviderProps): JSX.Element {
const [state, dispatch] = useReducer(requestStatusReducer, initialValues);
return (
@@ -101,6 +103,6 @@ function RequestStatusProvider({children}: RequestStatusProviderProps) {
</RequestStatusStateContext.Provider>
</>
);
};
}
export {RequestStatusProvider, useRequestStatus};

View File

@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type {JSX} from 'react';
import {createContext, useContext, useReducer, type ReactNode} from 'react';
import {UserShortenedLinkStats} from '../Background';
@@ -96,7 +96,9 @@ type ShortenedLinksProviderProps = {
children: ReactNode;
};
function ShortenedLinksProvider({children}: ShortenedLinksProviderProps) {
function ShortenedLinksProvider({
children,
}: ShortenedLinksProviderProps): JSX.Element {
const [state, dispatch] = useReducer(shortenedLinksReducer, initialValues);
return (
@@ -108,6 +110,6 @@ function ShortenedLinksProvider({children}: ShortenedLinksProviderProps) {
</ShortenedLinksStateContext.Provider>
</>
);
};
}
export {useShortenedLinks, ShortenedLinksProvider};

View File

@@ -37,7 +37,7 @@ export function detectBrowser(): Browser | null {
}
const [name, match] = matchedRule;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let versionParts = match[1] && match[1].split(/[._]/).slice(0, 3);
if (!versionParts) {
versionParts = [];

View File

@@ -24,7 +24,6 @@ export function getExtensionSettings(): Promise<{[s: string]: any}> {
return browser.storage.local.get('settings');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function updateExtensionSettings(newFields?: {
[s: string]: any;
}): Promise<void> {