feat: prettier integration

This commit is contained in:
2025-05-29 17:17:38 +02:00
parent 83c26bb94a
commit 725cc74102
19 changed files with 71 additions and 47 deletions

5
web/.prettierignore Normal file
View File

@ -0,0 +1,5 @@
# Ignore artifacts:
build
coverage
node_modules
public

1
web/.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -24,31 +24,31 @@ export default tseslint.config({
languageOptions: { languageOptions: {
// other options... // other options...
parserOptions: { parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'], project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname, tsconfigRootDir: import.meta.dirname,
}, },
}, },
}) });
``` ```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js ```js
// eslint.config.js // eslint.config.js
import reactX from 'eslint-plugin-react-x' import reactX from "eslint-plugin-react-x";
import reactDom from 'eslint-plugin-react-dom' import reactDom from "eslint-plugin-react-dom";
export default tseslint.config({ export default tseslint.config({
plugins: { plugins: {
// Add the react-x and react-dom plugins // Add the react-x and react-dom plugins
'react-x': reactX, "react-x": reactX,
'react-dom': reactDom, "react-dom": reactDom,
}, },
rules: { rules: {
// other rules... // other rules...
// Enable its recommended typescript rules // Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules, ...reactX.configs["recommended-typescript"].rules,
...reactDom.configs.recommended.rules, ...reactDom.configs.recommended.rules,
}, },
}) });
``` ```

View File

@ -1,28 +1,28 @@
import js from '@eslint/js' import js from "@eslint/js";
import globals from 'globals' import globals from "globals";
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from 'typescript-eslint' import tseslint from "typescript-eslint";
export default tseslint.config( export default tseslint.config(
{ ignores: ['dist'] }, { ignores: ["dist"] },
{ {
extends: [js.configs.recommended, ...tseslint.configs.recommended], extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'], files: ["**/*.{ts,tsx}"],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
}, },
plugins: { plugins: {
'react-hooks': reactHooks, "react-hooks": reactHooks,
'react-refresh': reactRefresh, "react-refresh": reactRefresh,
}, },
rules: { rules: {
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [ "react-refresh/only-export-components": [
'warn', "warn",
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
}, },
}, },
) );

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

17
web/package-lock.json generated
View File

@ -34,6 +34,7 @@
"eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0", "globals": "^16.0.0",
"path": "^0.12.7", "path": "^0.12.7",
"prettier": "3.5.3",
"sass": "^1.89.0", "sass": "^1.89.0",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"typescript-eslint": "^8.30.1", "typescript-eslint": "^8.30.1",
@ -4262,6 +4263,22 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",

View File

@ -36,6 +36,7 @@
"eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0", "globals": "^16.0.0",
"path": "^0.12.7", "path": "^0.12.7",
"prettier": "3.5.3",
"sass": "^1.89.0", "sass": "^1.89.0",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"typescript-eslint": "^8.30.1", "typescript-eslint": "^8.30.1",

View File

@ -13,7 +13,7 @@ export const codeApi = async (accessToken: string, nonce: string) => {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
}, },
} },
); );
if (response.status !== 200 && response.status !== 201) if (response.status !== 200 && response.status !== 201)

View File

@ -28,7 +28,7 @@ const processRefreshQueue = async (token: string | null) => {
const refreshToken = async ( const refreshToken = async (
accountId: string, accountId: string,
refreshToken: string refreshToken: string,
): Promise<{ access: string; refresh: string }> => { ): Promise<{ access: string; refresh: string }> => {
const db = useDbStore.getState().db; const db = useDbStore.getState().db;
const loadAccounts = useAuth.getState().loadAccounts; const loadAccounts = useAuth.getState().loadAccounts;
@ -79,7 +79,7 @@ axios.interceptors.request.use(
try { try {
const { access } = await refreshToken( const { access } = await refreshToken(
account!.accountId, account!.accountId,
account!.refresh account!.refresh,
); );
token = access; token = access;
} catch (err) { } catch (err) {
@ -97,7 +97,7 @@ axios.interceptors.request.use(
request.headers["Authorization"] = `Bearer ${token}`; request.headers["Authorization"] = `Bearer ${token}`;
return request; return request;
}, },
(error) => Promise.reject(error) (error) => Promise.reject(error),
); );
export const handleApiError = async (response: AxiosResponse) => { export const handleApiError = async (response: AxiosResponse) => {

View File

@ -15,7 +15,7 @@ export const refreshTokenApi = async (refreshToken: string) => {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${refreshToken}`, Authorization: `Bearer ${refreshToken}`,
}, },
} },
); );
if (response.status !== 200 && response.status !== 201) if (response.status !== 200 && response.status !== 201)

View File

@ -29,7 +29,7 @@ export const OAuthProvider: FC<IOAuthProvider> = ({ children }) => {
window.location.replace(`${redirectURI}?${params.toString()}`); window.location.replace(`${redirectURI}?${params.toString()}`);
} }
}, },
[active, nonce, redirectURI, state] [active, nonce, redirectURI, state],
); );
return ( return (

View File

@ -14,7 +14,7 @@ const AccountList: FC = () => {
(account: LocalAccount) => { (account: LocalAccount) => {
updateActiveAccount(account); updateActiveAccount(account);
}, },
[updateActiveAccount] [updateActiveAccount],
); );
return ( return (

View File

@ -73,7 +73,7 @@ const AuthLayout = () => {
if (!active) { if (!active) {
console.log( console.log(
"setting search params:", "setting search params:",
Object.fromEntries(searchParams.entries()) Object.fromEntries(searchParams.entries()),
); );
setActive(true); setActive(true);
setClientID(searchParams.get("client_id") ?? ""); setClientID(searchParams.get("client_id") ?? "");

View File

@ -9,5 +9,5 @@ const root = document.getElementById("root")!;
createRoot(root).render( createRoot(root).render(
<OAuthProvider> <OAuthProvider>
<App /> <App />
</OAuthProvider> </OAuthProvider>,
); );

View File

@ -67,13 +67,13 @@ export default function LoginPage() {
console.log(err); console.log(err);
setError( setError(
"Failed to create account. " + "Failed to create account. " +
(err.message ?? "Unexpected error happened") (err.message ?? "Unexpected error happened"),
); );
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, },
[repo, reset, updateActiveAccount] [repo, reset, updateActiveAccount],
); );
return ( return (

View File

@ -56,11 +56,11 @@ export default function RegisterPage() {
setError( setError(
`Failed to create an account. ${ `Failed to create an account. ${
text[0].toUpperCase() + text.slice(1) text[0].toUpperCase() + text.slice(1)
}` }`,
); );
} else { } else {
setSuccess( setSuccess(
"Account has been created. You can now log into your new account" "Account has been created. You can now log into your new account",
); );
reset(); reset();
} }
@ -71,7 +71,7 @@ export default function RegisterPage() {
setLoading(false); setLoading(false);
} }
}, },
[reset] [reset],
); );
return ( return (

View File

@ -61,7 +61,7 @@ export const encryptToken = async (token: string) => {
const cipherText = await crypto.subtle.encrypt( const cipherText = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, { name: "AES-GCM", iv },
deviceKey, deviceKey,
encoder.encode(token) encoder.encode(token),
); );
return { return {
@ -84,7 +84,7 @@ export const decryptToken = async (encrypted: {
iv: new Uint8Array(encrypted.iv), iv: new Uint8Array(encrypted.iv),
}, },
deviceKey, deviceKey,
new Uint8Array(encrypted.data) new Uint8Array(encrypted.data),
); );
return decoder.decode(decrypted); return decoder.decode(decrypted);
@ -92,7 +92,7 @@ export const decryptToken = async (encrypted: {
export const saveAccount = async ( export const saveAccount = async (
db: IDBPDatabase, db: IDBPDatabase,
req: CreateAccountRequest req: CreateAccountRequest,
): Promise<LocalAccount> => { ): Promise<LocalAccount> => {
const access = await encryptToken(req.access); const access = await encryptToken(req.access);
const refresh = await encryptToken(req.refresh); const refresh = await encryptToken(req.refresh);
@ -141,7 +141,7 @@ export const getAllAccounts = async (db: IDBPDatabase) => {
} catch (err) { } catch (err) {
console.warn(`Failed to decrypt account ${account.label}:`, err); console.warn(`Failed to decrypt account ${account.label}:`, err);
} }
}) }),
) )
).filter((acc) => acc !== undefined); ).filter((acc) => acc !== undefined);
@ -186,7 +186,7 @@ export const getAccountRaw = async (db: IDBPDatabase, accountId: string) => {
export const updateAccountTokens = async ( export const updateAccountTokens = async (
db: IDBPDatabase, db: IDBPDatabase,
req: UpdateAccountTokensRequest req: UpdateAccountTokensRequest,
) => { ) => {
const account = await getAccountRaw(db, req.accountId); const account = await getAccountRaw(db, req.accountId);
@ -204,7 +204,7 @@ export const updateAccountTokens = async (
export const updateAccountInfo = async ( export const updateAccountInfo = async (
db: IDBPDatabase, db: IDBPDatabase,
req: UpdateAccountInfoRequest req: UpdateAccountInfoRequest,
) => { ) => {
const account = await getAccountRaw(db, req.accountId); const account = await getAccountRaw(db, req.accountId);
await db?.put?.("accounts", { await db?.put?.("accounts", {
@ -229,7 +229,7 @@ export const useAccountRepo = () => {
return saveAccount(db, req); return saveAccount(db, req);
}, },
[db] [db],
); );
const loadAll = useCallback(async () => { const loadAll = useCallback(async () => {
@ -244,7 +244,7 @@ export const useAccountRepo = () => {
return getAccount(db, accountId); return getAccount(db, accountId);
}, },
[db] [db],
); );
return { save, loadAll, getOne }; return { save, loadAll, getOne };

View File

@ -37,7 +37,7 @@ export const deriveDeviceKey = async (deviceKeyId: string) => {
encoder.encode(deviceKeyId), encoder.encode(deviceKeyId),
{ name: "PBKDF2" }, { name: "PBKDF2" },
false, false,
["deriveKey"] ["deriveKey"],
); );
return crypto.subtle.deriveKey( return crypto.subtle.deriveKey(
{ {
@ -49,6 +49,6 @@ export const deriveDeviceKey = async (deviceKeyId: string) => {
baseKey, baseKey,
{ name: "AES-GCM", length: 256 }, { name: "AES-GCM", length: 256 },
false, false,
["encrypt", "decrypt"] ["encrypt", "decrypt"],
); );
}; };

View File

@ -5,14 +5,14 @@ export type EncryptedToken = {
export const encryptToken = async ( export const encryptToken = async (
token: string, token: string,
key: CryptoKey key: CryptoKey,
): Promise<EncryptedToken> => { ): Promise<EncryptedToken> => {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12)); const iv = crypto.getRandomValues(new Uint8Array(12));
const cipherText = await crypto.subtle.encrypt( const cipherText = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, { name: "AES-GCM", iv },
key, key,
encoder.encode(token) encoder.encode(token),
); );
return { cipherText, iv }; return { cipherText, iv };
}; };