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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import CopyToClipboard from 'react-copy-to-clipboard'; import CopyToClipboard from 'react-copy-to-clipboard';
import type {JSX} from 'react';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
@@ -13,7 +14,7 @@ import Modal from './Modal';
import styles from './Table.module.scss'; import styles from './Table.module.scss';
function Table() { function Table(): JSX.Element {
const [shortenedLinksState, shortenedLinksDispatch] = useShortenedLinks(); const [shortenedLinksState, shortenedLinksDispatch] = useShortenedLinks();
const [QRView, setQRView] = useState<boolean>(false); const [QRView, setQRView] = useState<boolean>(false);
const [copied, setCopied] = useState<boolean>(false); const [copied, setCopied] = useState<boolean>(false);
@@ -72,76 +73,70 @@ function Table() {
</thead> </thead>
<tbody className={styles.tbody}> <tbody className={styles.tbody}>
{!(shortenedLinksState.total === 0) ? ( {!(shortenedLinksState.total === 0) ? (
shortenedLinksState.items.map((item) => { shortenedLinksState.items.map((item) => (
return ( <tr key={item.id} className={styles.tr}>
<tr key={item.id} className={styles.tr}> <td className={clsx(styles.td, styles.tdOriginal)}>
<td className={clsx(styles.td, styles.tdOriginal)}> <a
className={styles.link}
href={item.target}
target="_blank"
rel="noopener noreferrer nofollow"
>
{item.target}
</a>
</td>
<td className={clsx(styles.td, styles.tdShort)}>
{copied &&
shortenedLinksState.selected?.id === item.id && (
<div className={styles.copiedNotification}>
Copied to clipboard!
</div>
)}
<div className={styles.shortUrlWrapper}>
<a <a
className={styles.link} className={styles.link}
href={item.target} href={item.link}
target="_blank" target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
> >
{item.target} {item.link}
</a> </a>
</td> </div>
</td>
<td className={styles.td}>
<div className={styles.actionsWrapper}>
{/* // **** COPY TO CLIPBOARD **** // */}
<td className={clsx(styles.td, styles.tdShort)}>
{copied && {copied &&
shortenedLinksState.selected?.id === item.id && ( shortenedLinksState.selected?.id === item.id ? (
<div className={styles.copiedNotification}> <Icon name="tick" className={styles.actionIcon} />
Copied to clipboard! ) : (
</div> <CopyToClipboard
)} text={item.link}
onCopy={(): void => handleCopyToClipboard(item.id)}
<div className={styles.shortUrlWrapper}>
<a
className={styles.link}
href={item.link}
target="_blank"
rel="noopener noreferrer nofollow"
> >
{item.link} <Icon name="copy" className={styles.actionIcon} />
</a> </CopyToClipboard>
</div> )}
</td>
<td className={styles.td}> <Icon
<div className={styles.actionsWrapper}> onClick={(): void => handleQRCodeViewToggle(item.id)}
{/* // **** COPY TO CLIPBOARD **** // */} className={styles.actionIcon}
name="qrcode"
/>
</div>
{copied && {/* // **** QR CODE MODAL **** // */}
shortenedLinksState.selected?.id === item.id ? ( {QRView &&
<Icon name="tick" className={styles.actionIcon} /> shortenedLinksState.selected?.id === item.id && (
) : ( <Modal link={item.link} setModalView={setQRView} />
<CopyToClipboard )}
text={item.link} </td>
onCopy={(): void => { </tr>
return handleCopyToClipboard(item.id); ))
}}
>
<Icon name="copy" className={styles.actionIcon} />
</CopyToClipboard>
)}
<Icon
onClick={(): void =>
handleQRCodeViewToggle(item.id)
}
className={styles.actionIcon}
name="qrcode"
/>
</div>
{/* // **** QR CODE MODAL **** // */}
{QRView &&
shortenedLinksState.selected?.id === item.id && (
<Modal link={item.link} setModalView={setQRView} />
)}
</td>
</tr>
);
})
) : ( ) : (
<tr> <tr>
<td className={styles.emptyRow}>No URLs History</td> <td className={styles.emptyRow}>No URLs History</td>

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import type {JSX} from 'react';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import {getExtensionSettings} from '../util/settings'; import {getExtensionSettings} from '../util/settings';
@@ -21,7 +22,7 @@ import Form from './Form';
import styles from './Options.module.scss'; import styles from './Options.module.scss';
function Options() { function Options(): JSX.Element {
const [, extensionSettingsDispatch] = useExtensionSettings(); const [, extensionSettingsDispatch] = useExtensionSettings();
const [requestStatusState, requestStatusDispatch] = useRequestStatus(); const [requestStatusState, requestStatusDispatch] = useRequestStatus();
const [hostUrl, setHostUrl] = useState<string>(Kutt.hostUrl); const [hostUrl, setHostUrl] = useState<string>(Kutt.hostUrl);
@@ -36,11 +37,12 @@ function Options() {
(advancedSettings && (advancedSettings &&
(settings?.host as string) && (settings?.host as string) &&
isValidUrl(settings.host as string) && { isValidUrl(settings.host as string) && {
hostDomain: (settings.host as string) hostDomain:
.replace('http://', '') (settings.host as string)
.replace('https://', '') .replace('http://', '')
.replace('www.', '') .replace('https://', '')
.split(/[/?#]/)[0] || '', // extract domain .replace('www.', '')
.split(/[/?#]/)[0] || '', // extract domain
hostUrl: (settings.host as string).endsWith('/') hostUrl: (settings.host as string).endsWith('/')
? (settings.host as string).slice(0, -1) ? (settings.host as string).slice(0, -1)
: (settings.host as string), // slice `/` at the end : (settings.host as string), // slice `/` at the end
@@ -49,7 +51,10 @@ function Options() {
// inject existing keys (if field doesn't exist, use default) // inject existing keys (if field doesn't exist, use default)
// For history: default to true for new users, but respect existing user preference // 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) ? (settings.history as boolean)
: true; : true;

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
import { isNull, EMPTY_STRING } from '@abhijithvijayan/ts-utils'; import {isNull, EMPTY_STRING} from '@abhijithvijayan/ts-utils';
import { useEffect } from 'react'; import type {JSX} from 'react';
import {useEffect} from 'react';
import { Kutt, UserSettingsResponseProperties } from '../Background'; import {Kutt, UserSettingsResponseProperties} from '../Background';
import { openExtOptionsPage } from '../util/tabs'; import {openExtOptionsPage} from '../util/tabs';
import { isValidUrl } from '../util/link'; import {isValidUrl} from '../util/link';
import { import {
ExtensionSettingsActionTypes, ExtensionSettingsActionTypes,
@@ -21,15 +22,15 @@ import BodyWrapper from '../components/BodyWrapper';
import ResponseBody from './ResponseBody'; import ResponseBody from './ResponseBody';
import PopupHeader from './Header'; import PopupHeader from './Header';
import Loader from '../components/Loader'; import Loader from '../components/Loader';
import Form, { CONSTANTS } from './Form'; import Form, {CONSTANTS} from './Form';
import styles from './Popup.module.scss'; import styles from './Popup.module.scss';
function Popup() { function Popup(): JSX.Element {
const [extensionSettingsState, extensionSettingsDispatch] = const [extensionSettingsState, extensionSettingsDispatch] =
useExtensionSettings(); useExtensionSettings();
const [requestStatusState, requestStatusDispatch] = useRequestStatus(); const [requestStatusState, requestStatusDispatch] = useRequestStatus();
const { reload: liveReloadFlag } = extensionSettingsState; const {reload: liveReloadFlag} = extensionSettingsState;
// re-renders on `liveReloadFlag` change // re-renders on `liveReloadFlag` change
useEffect((): void => { useEffect((): void => {
@@ -54,9 +55,7 @@ function Popup() {
}); });
// Open options page // Open options page
setTimeout(() => { setTimeout(() => openExtOptionsPage(), 1300);
return openExtOptionsPage();
}, 1300);
return; return;
} }
@@ -75,11 +74,12 @@ function Popup() {
isValidUrl(settings.host as string) isValidUrl(settings.host as string)
) { ) {
defaultHost = { defaultHost = {
hostDomain: (settings.host as string) hostDomain:
.replace('http://', EMPTY_STRING) (settings.host as string)
.replace('https://', EMPTY_STRING) .replace('http://', EMPTY_STRING)
.replace('www.', EMPTY_STRING) .replace('https://', EMPTY_STRING)
.split(/[/?#]/)[0] || EMPTY_STRING, .replace('www.', EMPTY_STRING)
.split(/[/?#]/)[0] || EMPTY_STRING,
hostUrl: (settings.host as string).endsWith('/') hostUrl: (settings.host as string).endsWith('/')
? (settings.host as string).slice(0, -1) ? (settings.host as string).slice(0, -1)
: (settings.host as string), : (settings.host as string),
@@ -114,10 +114,10 @@ function Popup() {
Object.prototype.hasOwnProperty.call(settings, 'user') && Object.prototype.hasOwnProperty.call(settings, 'user') &&
(settings.user as UserSettingsResponseProperties) (settings.user as UserSettingsResponseProperties)
) { ) {
const { user }: { user: UserSettingsResponseProperties } = settings; const {user}: {user: UserSettingsResponseProperties} = settings;
let optionsList: DomainOptionsProperties[] = user.domains.map( let optionsList: DomainOptionsProperties[] = user.domains.map(
({ id, address, homepage, banned }) => { ({id, address, homepage, banned}) => {
return { return {
id, id,
option: homepage, option: homepage,

View File

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

View File

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

View File

@@ -1,20 +1,18 @@
import React from 'react'; import React from 'react';
const ChevronDown: React.FC = () => { const ChevronDown: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={2}
strokeWidth={2} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" >
> <path d="M6 9l6 6 6-6" />
<path d="M6 9l6 6 6-6" /> </svg>
</svg> );
);
};
export default React.memo(ChevronDown); export default React.memo(ChevronDown);

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Clock: React.FC = () => { const Clock: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="clock_svg__feather clock_svg__feather-clock"
className="clock_svg__feather clock_svg__feather-clock" >
> <circle cx={12} cy={12} r={10} />
<circle cx={12} cy={12} r={10} /> <path d="M12 6v6l4 2" />
<path d="M12 6v6l4 2" /> </svg>
</svg> );
);
};
export default React.memo(Clock); export default React.memo(Clock);

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Copy: React.FC = () => { const Copy: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="copy_svg__feather copy_svg__feather-copy"
className="copy_svg__feather copy_svg__feather-copy" >
> <rect x={9} y={9} width={13} height={13} rx={2} ry={2} />
<rect x={9} y={9} width={13} height={13} rx={2} ry={2} /> <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" /> </svg>
</svg> );
);
};
export default React.memo(Copy); export default React.memo(Copy);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const Cross: React.FC = () => { const Cross: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="x_svg__feather x_svg__feather-x"
className="x_svg__feather x_svg__feather-x" >
> <path d="M18 6L6 18M6 6l12 12" />
<path d="M18 6L6 18M6 6l12 12" /> </svg>
</svg> );
);
};
export default React.memo(Cross); export default React.memo(Cross);

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Eye: React.FC = () => { const Eye: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={2}
strokeWidth={2} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="eye_svg__feather eye_svg__feather-eye"
className="eye_svg__feather eye_svg__feather-eye" >
> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /> <circle cx={12} cy={12} r={3} />
<circle cx={12} cy={12} r={3} /> </svg>
</svg> );
);
};
export default React.memo(Eye); export default React.memo(Eye);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const EyeClosed: React.FC = () => { const EyeClosed: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={2}
strokeWidth={2} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="eye-off_svg__feather eye-off_svg__feather-eye-off"
className="eye-off_svg__feather eye-off_svg__feather-eye-off" >
> <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" />
<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>
</svg> );
);
};
export default React.memo(EyeClosed); export default React.memo(EyeClosed);

View File

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

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Info: React.FC = () => { const Info: React.FC = () => (
return ( <svg
<svg width={14}
width={14} height={14}
height={14} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={2}
strokeWidth={2} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" >
> <circle cx="12" cy="12" r="10" />
<circle cx="12" cy="12" r="10" /> <line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="16" x2="12" y2="12" /> <line x1="12" y1="8" x2="12.01" y2="8" />
<line x1="12" y1="8" x2="12.01" y2="8" /> </svg>
</svg> );
);
};
export default React.memo(Info); export default React.memo(Info);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const QRCode: React.FC = () => { const QRCode: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="maximize_svg__feather maximize_svg__feather-maximize"
className="maximize_svg__feather maximize_svg__feather-maximize" >
> <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" />
<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>
</svg> );
);
};
export default React.memo(QRCode); export default React.memo(QRCode);

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Refresh: React.FC = () => { const Refresh: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="refresh-ccw_svg__feather refresh-ccw_svg__feather-refresh-ccw"
className="refresh-ccw_svg__feather refresh-ccw_svg__feather-refresh-ccw" >
> <path d="M1 4v6h6M23 20v-6h-6" />
<path d="M1 4v6h6M23 20v-6h-6" /> <path d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15" />
<path d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15" /> </svg>
</svg> );
);
};
export default React.memo(Refresh); export default React.memo(Refresh);

View File

@@ -1,22 +1,20 @@
import React from 'react'; import React from 'react';
const Settings: React.FC = () => { const Settings: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="settings_svg__feather settings_svg__feather-settings"
className="settings_svg__feather settings_svg__feather-settings" >
> <circle cx={12} cy={12} r={3} />
<circle cx={12} cy={12} r={3} /> <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" />
<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>
</svg> );
);
};
export default React.memo(Settings); export default React.memo(Settings);

View File

@@ -1,32 +1,30 @@
import React from 'react'; import React from 'react';
const Spinner: React.FC = () => { const Spinner: React.FC = () => (
return ( <>
<> <svg
<svg id="spinner"
id="spinner" xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" width="20"
width="20" height="20"
height="20" fill="none"
fill="none" stroke="#b8b8b8"
stroke="#b8b8b8" strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" strokeWidth="2"
strokeWidth="2" className="feather feather-loader"
className="feather feather-loader" viewBox="0 0 24 24"
viewBox="0 0 24 24" >
> <path d="M12 2L12 6" />
<path d="M12 2L12 6" /> <path d="M12 18L12 22" />
<path d="M12 18L12 22" /> <path d="M4.93 4.93L7.76 7.76" />
<path d="M4.93 4.93L7.76 7.76" /> <path d="M16.24 16.24L19.07 19.07" />
<path d="M16.24 16.24L19.07 19.07" /> <path d="M2 12L6 12" />
<path d="M2 12L6 12" /> <path d="M18 12L22 12" />
<path d="M18 12L22 12" /> <path d="M4.93 19.07L7.76 16.24" />
<path d="M4.93 19.07L7.76 16.24" /> <path d="M16.24 7.76L19.07 4.93" />
<path d="M16.24 7.76L19.07 4.93" /> </svg>
</svg> </>
</> );
);
};
export default React.memo(Spinner); export default React.memo(Spinner);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const StarWhite: React.FC = () => { const StarWhite: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="currentColor"
fill="currentColor" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="star_svg__feather star_svg__feather-star"
className="star_svg__feather star_svg__feather-star" >
> <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" />
<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>
</svg> );
);
};
export default React.memo(StarWhite); export default React.memo(StarWhite);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const StarYellow: React.FC = () => { const StarYellow: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="#ecc94b"
fill="#ecc94b" stroke="#ecc94b"
stroke="#ecc94b" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="star_svg__feather star_svg__feather-star"
className="star_svg__feather star_svg__feather-star" >
> <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" />
<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>
</svg> );
);
};
export default React.memo(StarYellow); export default React.memo(StarYellow);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const Tick: React.FC = () => { const Tick: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="check_svg__feather check_svg__feather-check"
className="check_svg__feather check_svg__feather-check" >
> <path d="M20 6L9 17l-5-5" />
<path d="M20 6L9 17l-5-5" /> </svg>
</svg> );
);
};
export default React.memo(Tick); export default React.memo(Tick);

View File

@@ -1,21 +1,19 @@
import React from 'react'; import React from 'react';
const Zap: React.FC = () => { const Zap: React.FC = () => (
return ( <svg
<svg width={16}
width={16} height={16}
height={16} viewBox="0 0 24 24"
viewBox="0 0 24 24" fill="none"
fill="none" stroke="currentColor"
stroke="currentColor" strokeWidth={1.5}
strokeWidth={1.5} strokeLinecap="round"
strokeLinecap="round" strokeLinejoin="round"
strokeLinejoin="round" className="zap_svg__feather zap_svg__feather-zap"
className="zap_svg__feather zap_svg__feather-zap" >
> <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" /> </svg>
</svg> );
);
};
export default React.memo(Zap); export default React.memo(Zap);

View File

@@ -1,7 +1,8 @@
import type {JSX} from 'react';
import Icon from './Icon'; import Icon from './Icon';
import styles from './Loader.module.scss'; import styles from './Loader.module.scss';
function Loader() { function Loader(): JSX.Element {
return ( return (
<div className={styles.loader}> <div className={styles.loader}>
<Icon name="spinner" /> <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 {createContext, useReducer, useContext, type ReactNode} from 'react';
import {Kutt} from '../Background'; import {Kutt} from '../Background';
@@ -126,7 +126,7 @@ type ExtensionSettingsProviderProps = {
function ExtensionSettingsProvider({ function ExtensionSettingsProvider({
children, children,
}: ExtensionSettingsProviderProps) { }: ExtensionSettingsProviderProps): JSX.Element {
const [state, dispatch] = useReducer(extensionSettingsReducer, initialValues); const [state, dispatch] = useReducer(extensionSettingsReducer, initialValues);
return ( return (
@@ -138,6 +138,6 @@ function ExtensionSettingsProvider({
</ExtensionSettingsStateContext.Provider> </ExtensionSettingsStateContext.Provider>
</> </>
); );
}; }
export {ExtensionSettingsProvider, useExtensionSettings}; 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'; import {createContext, useReducer, useContext, type ReactNode} from 'react';
export enum RequestStatusActionTypes { export enum RequestStatusActionTypes {
@@ -89,7 +89,9 @@ type RequestStatusProviderProps = {
children: ReactNode; children: ReactNode;
}; };
function RequestStatusProvider({children}: RequestStatusProviderProps) { function RequestStatusProvider({
children,
}: RequestStatusProviderProps): JSX.Element {
const [state, dispatch] = useReducer(requestStatusReducer, initialValues); const [state, dispatch] = useReducer(requestStatusReducer, initialValues);
return ( return (
@@ -101,6 +103,6 @@ function RequestStatusProvider({children}: RequestStatusProviderProps) {
</RequestStatusStateContext.Provider> </RequestStatusStateContext.Provider>
</> </>
); );
}; }
export {RequestStatusProvider, useRequestStatus}; 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 {createContext, useContext, useReducer, type ReactNode} from 'react';
import {UserShortenedLinkStats} from '../Background'; import {UserShortenedLinkStats} from '../Background';
@@ -96,7 +96,9 @@ type ShortenedLinksProviderProps = {
children: ReactNode; children: ReactNode;
}; };
function ShortenedLinksProvider({children}: ShortenedLinksProviderProps) { function ShortenedLinksProvider({
children,
}: ShortenedLinksProviderProps): JSX.Element {
const [state, dispatch] = useReducer(shortenedLinksReducer, initialValues); const [state, dispatch] = useReducer(shortenedLinksReducer, initialValues);
return ( return (
@@ -108,6 +110,6 @@ function ShortenedLinksProvider({children}: ShortenedLinksProviderProps) {
</ShortenedLinksStateContext.Provider> </ShortenedLinksStateContext.Provider>
</> </>
); );
}; }
export {useShortenedLinks, ShortenedLinksProvider}; export {useShortenedLinks, ShortenedLinksProvider};

View File

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

View File

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