diff --git a/.env.example b/.env.example index 50a8c82..1b6a5f6 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ -# team: devjs, project: forms CONVEX_DEPLOYMENT= # Clerk configuration, get this key from your [Dashboard](dashboard.clerk.com) @@ -6,6 +5,4 @@ VITE_CLERK_PUBLISHABLE_KEY= CLERK_JWT_ISSUER_DOMAIN= VITE_CONVEX_URL= -VITE_CONVEX_SITE_URL= - -VITE_LOG_LEVEL=debug \ No newline at end of file +VITE_CONVEX_SITE_URL= \ No newline at end of file diff --git a/.eslintrc.perfectionist.js b/.eslintrc.perfectionist.js new file mode 100644 index 0000000..35ec307 --- /dev/null +++ b/.eslintrc.perfectionist.js @@ -0,0 +1,143 @@ +export default { + 'perfectionist/sort-enums': [ + 'error', + { + order: 'asc', + sortByValue: 'always', + type: 'natural', + }, + ], + 'perfectionist/sort-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-imports': [ + 'error', + { + order: 'asc', + type: 'natural', + customGroups: [ + { + elementNamePattern: ['^react$', '^react-.+'], + groupName: 'react', + }, + { + elementNamePattern: ['^expo$', '^expo-.+'], + groupName: 'expo', + }, + ], + groups: [ + 'side-effect', + 'react', + 'external', + 'internal', + 'parent', + 'sibling', + 'style', + ], + }, + ], + 'perfectionist/sort-jsx-props': [ + 'error', + { + order: 'asc', + type: 'natural', + customGroups: [ + { + elementNamePattern: ['^key$'], + groupName: 'key', + }, + { + elementNamePattern: ['^id$', '^testID$'], + groupName: 'main-identifier', + }, + { + elementNamePattern: ['.*Id$', '.*ID$'], + groupName: 'secondary-identifier', + }, + { + elementNamePattern: ['^on[A-Z][a-zA-Z]*$'], + groupName: 'method', + }, + { + elementNamePattern: ['^style$'], + groupName: 'style', + }, + ], + groups: [ + 'key', + 'main-identifier', + 'secondary-identifier', + 'unknown', + 'method', + 'multiline-prop', + 'shorthand-prop', + 'style', + ], + }, + ], + 'perfectionist/sort-object-types': [ + 'error', + { + order: 'asc', + type: 'natural', + customGroups: [ + { + elementNamePattern: ['^id$', '^testID$'], + groupName: 'main-identifier', + }, + { + elementNamePattern: ['.*Id$', '.*ID$'], + groupName: 'secondary-identifier', + }, + ], + groups: [ + 'main-identifier', + 'secondary-identifier', + 'required-property', + 'optional-property', + 'method', + 'multiline-member', + ], + }, + ], + 'perfectionist/sort-objects': [ + 'error', + { + order: 'asc', + type: 'natural', + customGroups: [ + { + elementNamePattern: ['^id$', '^testID$'], + groupName: 'main-identifier', + }, + { + elementNamePattern: ['.*Id$', '.*ID$'], + groupName: 'secondary-identifier', + }, + ], + groups: [ + 'main-identifier', + 'secondary-identifier', + 'unknown', + 'method', + 'multiline-member', + ], + }, + ], + 'perfectionist/sort-switch-case': [ + 'error', + { + type: 'unsorted', + }, + ], + 'perfectionist/sort-union-types': [ + 'error', + { + type: 'unsorted', + }, + ], +} diff --git a/README.md b/README.md index ed87589..fee3812 100644 --- a/README.md +++ b/README.md @@ -1,240 +1,98 @@ -Welcome to your new TanStack Start app! - -# Getting Started - -To run this application: - -```bash -npm install -npm run dev -``` - -# Building For Production - -To build this application for production: - -```bash -npm run build -``` - -## Testing - -This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: - -```bash -npm run test -``` - -## Styling - -This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. - -### Removing Tailwind CSS - -If you prefer not to use Tailwind CSS: - -1. Remove the demo pages in `src/routes/demo/` -2. Replace the Tailwind import in `src/styles.css` with your own styles -3. Remove `tailwindcss()` from the plugins array in `vite.config.ts` -4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D` - -## Linting & Formatting - -This project uses [eslint](https://eslint.org/) and [prettier](https://prettier.io/) for linting and formatting. Eslint is configured using [tanstack/eslint-config](https://tanstack.com/config/latest/docs/eslint). The following scripts are available: - -```bash -npm run lint -npm run format -npm run check +# Tanstack Start - With E2E Encryption Demo + +A TanStack Start starter with Convex, Clerk, and end-to-end encryption. + +Full-stack React starter with zero-knowledge architecture. Sensitive data is encrypted client-side before it reaches the server; the backend never has access to decryption keys or plaintext. + +## Tech Stack + +- **Frontend:** TanStack Start, TanStack Router, TanStack Query +- **Backend:** Convex (real-time database) +- **Auth:** Clerk +- **Encryption:** OpenPGP.js (ECC P-256), @scure/bip39 (passphrase generation) +- **UI:** Tailwind CSS, shadcn, Radix UI +- **State:** Jotai + +## Zero-Knowledge Architecture + +```mermaid +flowchart TD + subgraph client [Client] + SignUp[Sign Up] + GenKeys[Generate PGP Key Pair] + EncPass[Encrypt Passphrase with Password] + StoreKeys[Store to Convex] + SignUp --> GenKeys --> EncPass --> StoreKeys + end + + subgraph convex [Convex] + UserKeys[(userKeys table)] + StoreKeys -->|encryptedPrivateKey, publicKey, encryptedPassphrase| UserKeys + end + + subgraph zk [Zero Knowledge] + NoPlaintext[Convex never sees plaintext] + NoPrivateKey[Private key never leaves client] + end ``` -## Setting up Clerk - -- Set the `VITE_CLERK_PUBLISHABLE_KEY` in your `.env.local`. - -## Setting up Convex - -- Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `npx -y convex init` to set them automatically.) -- Run `npx -y convex dev` to start the Convex server. +- **Key generation:** PGP key pairs (ECC nistP256) generated client-side in `src/lib/crypto.ts` during signup +- **Passphrase:** 12-word BIP39 mnemonic (128-bit entropy), encrypted with the user's Clerk password before storage +- **Storage:** Convex stores only encrypted blobs—`encryptedPrivateKey`, `publicKey`, `encryptedPassphrase` +- **Usage:** `useEncryption` hook (`src/hooks/useEncryption.ts`) encrypts/decrypts data client-side; Convex cannot decrypt -# Paraglide i18n +## Prerequisites -This add-on wires up ParaglideJS for localized routing and message formatting. +- Node.js 18+ +- [Clerk](https://clerk.com) account +- [Convex](https://convex.dev) account -- Messages live in `project.inlang/messages`. -- URLs are localized through the Paraglide Vite plugin and router `rewrite` hooks. -- Run the dev server or build to regenerate the `src/paraglide` outputs. - -## Shadcn - -Add components using the latest version of [Shadcn](https://ui.shadcn.com/). +## Getting Started ```bash -pnpm dlx shadcn@latest add button -``` - -## T3Env - -- You can use T3Env to add type safety to your environment variables. -- Add Environment variables to the `src/env.mjs` file. -- Use the environment variables in your code. - -### Usage - -```ts -import { env } from '@/env' - -console.log(env.VITE_APP_TITLE) -``` - -## Routing - -This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`. - -### Adding A Route - -To add a new route to your application just add a new file in the `./src/routes` directory. - -TanStack will automatically generate the content of the route file for you. - -Now that you have two routes you can use a `Link` component to navigate between them. - -### Adding Links - -To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. - -```tsx -import { Link } from '@tanstack/react-router' +npm install # Install dependencies packages +cp .env.example .env.local # Edit .env.local with your keys +npm run start # Starts Convex dev + Vite (port 3666) ``` -Then anywhere in your JSX you can use it like so: +**Required environment variables** (from `.env.example`): -```tsx -About -``` - -This will create a link that will navigate to the `/about` route. - -More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). - -### Using A Layout - -In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`. - -Here is an example layout that includes a header: - -```tsx -import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router' - -export const Route = createRootRoute({ - head: () => ({ - meta: [ - { charSet: 'utf-8' }, - { name: 'viewport', content: 'width=device-width, initial-scale=1' }, - { title: 'My App' }, - ], - }), - shellComponent: ({ children }) => ( - - - - - -
- -
- {children} - - - - ), -}) -``` - -More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). +| Variable | Description | +| -------------------------------------- | ------------------------------------- | +| `VITE_CLERK_PUBLISHABLE_KEY` | From Clerk Dashboard | +| `CLERK_JWT_ISSUER_DOMAIN` | For Convex JWT verification | +| `VITE_CONVEX_URL`, `CONVEX_DEPLOYMENT` | Run `npx convex init` or set manually | -## Server Functions +## Scripts -TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components. +| Script | Description | +| ---------------- | ---------------------------- | +| `npm run start` | Convex dev + Vite dev server | +| `npm run dev` | Vite only (port 3666) | +| `npm run build` | Production build | +| `npm run test` | Vitest | +| `npm run lint` | ESLint | +| `npm run format` | Prettier | -```tsx -import { createServerFn } from '@tanstack/react-start' +## Project Structure -const getServerTime = createServerFn({ - method: 'GET', -}).handler(async () => { - return new Date().toISOString() -}) - -// Use in a component -function MyComponent() { - const [time, setTime] = useState('') - - useEffect(() => { - getServerTime().then(setTime) - }, []) - - return
Server time: {time}
-} -``` - -## API Routes - -You can create API routes by using the `server` property in your route definitions: - -```tsx -import { createFileRoute } from '@tanstack/react-router' -import { json } from '@tanstack/react-start' - -export const Route = createFileRoute('/api/hello')({ - server: { - handlers: { - GET: () => json({ message: 'Hello, World!' }), - }, - }, -}) -``` - -## Data Fetching - -There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. - -For example: - -```tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/people')({ - loader: async () => { - const response = await fetch('https://swapi.dev/api/people') - return response.json() - }, - component: PeopleComponent, -}) - -function PeopleComponent() { - const data = Route.useLoaderData() - return ( - - ) -} -``` +- `src/routes/` — TanStack Router file-based routing +- `src/hooks/` — `useSignUp`, `useSignIn`, `useEncryption` +- `src/lib/crypto.ts` — PGP key generation, encrypt/decrypt +- `convex/` — Convex backend (userKeys, auth config) +- `src/integrations/` — Clerk, Convex providers -Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). +## Additional Features -# Demo files +**Paraglide i18n:** Messages in `project.inlang/messages`; URLs localized via Paraglide Vite plugin and router `rewrite` hooks. -Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. +**shadcn:** Add components with `pnpm dlx shadcn@latest add button` -# Learn More +**T3 Env:** Type-safe environment variables. Add variables in `src/env.ts`, use via `import { env } from '@/env'`. -You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). +## Learn More -For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start). +- [TanStack Start](https://tanstack.com/start) +- [Convex](https://convex.dev) +- [Clerk](https://clerk.com) +- [OpenPGP.js](https://openpgpjs.org) diff --git a/convex/auth.config.ts b/convex/auth.config.ts index 4b0a8c9..c98967b 100644 --- a/convex/auth.config.ts +++ b/convex/auth.config.ts @@ -3,12 +3,12 @@ import type { AuthConfig } from 'convex/server' export default { providers: [ { + applicationID: 'convex', // Replace with your own Clerk Issuer URL from your "convex" JWT template // or with `process.env.CLERK_JWT_ISSUER_DOMAIN` // and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard // See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, - applicationID: 'convex', }, ], } satisfies AuthConfig diff --git a/convex/schema.ts b/convex/schema.ts index bbcab02..e8f0d54 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -9,9 +9,9 @@ export default defineSchema({ */ userKeys: defineTable({ userId: v.string(), + createdAt: v.number(), + encryptedPassphrase: v.string(), encryptedPrivateKey: v.string(), publicKey: v.string(), - encryptedPassphrase: v.string(), - createdAt: v.number(), }).index('by_user', ['userId']), }) diff --git a/convex/userKeys.ts b/convex/userKeys.ts index a67ad9a..7d7fe88 100644 --- a/convex/userKeys.ts +++ b/convex/userKeys.ts @@ -1,4 +1,5 @@ import { v } from 'convex/values' + import { mutation, query } from './_generated/server' /** @@ -22,11 +23,6 @@ export const getUserKeys = query({ * Create the user's encryption data (PGP keys + encrypted passphrase) on signup */ export const createUserKeys = mutation({ - args: { - encryptedPrivateKey: v.string(), - publicKey: v.string(), - encryptedPassphrase: v.string(), - }, handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity() if (!identity) throw new Error('Unauthorized') @@ -42,10 +38,15 @@ export const createUserKeys = mutation({ return await ctx.db.insert('userKeys', { userId: identity.subject, + createdAt: Date.now(), + encryptedPassphrase: args.encryptedPassphrase, encryptedPrivateKey: args.encryptedPrivateKey, publicKey: args.publicKey, - encryptedPassphrase: args.encryptedPassphrase, - createdAt: Date.now(), }) }, + args: { + encryptedPassphrase: v.string(), + encryptedPrivateKey: v.string(), + publicKey: v.string(), + }, }) diff --git a/eslint.config.js b/eslint.config.js index c595c5d..75162c1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,9 +1,20 @@ // @ts-check - import { tanstackConfig } from '@tanstack/eslint-config' +import perfectionistPlugin from 'eslint-plugin-perfectionist' +import perfectionistRules from './.eslintrc.perfectionist.js' export default [ ...tanstackConfig, + { + plugins: { + perfectionist: perfectionistPlugin, + }, + rules: { + ...perfectionistRules, + // Disable import/order - it conflicts with perfectionist/sort-imports (causes circular fixes) + 'import/order': 'off', + }, + }, { ignores: [ 'convex/_generated/**', @@ -14,6 +25,7 @@ export default [ 'src/routes/__root.tsx', 'src/routes/demo/**', '.output/**', + 'eslint.config.js', ], }, ] diff --git a/package-lock.json b/package-lock.json index 369b015..9b41ce7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,8 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.4", + "eslint": "^10.0.1", + "eslint-plugin-perfectionist": "^5.6.0", "husky": "^9.1.7", "jotai-babel": "^0.1.0", "jsdom": "^28.1.0", @@ -1122,152 +1124,68 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", + "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.2", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", + "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.1.0", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@exodus/bytes": { @@ -1347,7 +1265,6 @@ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18.0" } @@ -1358,7 +1275,6 @@ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" @@ -1373,7 +1289,6 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=12.22" }, @@ -1388,7 +1303,6 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18" }, @@ -4300,6 +4214,56 @@ } } }, + "node_modules/@tanstack/eslint-config/node_modules/eslint-plugin-import-x": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.35.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.0.1", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/@tanstack/eslint-config/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tanstack/history": { "version": "1.154.14", "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.154.14.tgz", @@ -5031,6 +4995,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -5042,8 +5013,7 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.23", @@ -5770,9 +5740,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6038,17 +6008,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001770", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", @@ -6189,20 +6148,6 @@ "color-string": "^1.6.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", @@ -6359,7 +6304,6 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6559,8 +6503,7 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dequal": { "version": "2.0.3", @@ -6764,7 +6707,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6773,34 +6715,30 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.1.tgz", + "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.2", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.1", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -6810,8 +6748,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -6819,7 +6756,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -6909,56 +6846,6 @@ "eslint": ">=8" } }, - "node_modules/eslint-plugin-import-x": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", - "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "^8.35.0", - "comment-parser": "^1.4.1", - "debug": "^4.4.1", - "eslint-import-context": "^0.1.9", - "is-glob": "^4.0.3", - "minimatch": "^9.0.3 || ^10.0.1", - "semver": "^7.7.2", - "stable-hash-x": "^0.2.0", - "unrs-resolver": "^1.9.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-import-x" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "eslint-import-resolver-node": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, - "eslint-import-resolver-node": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-import-x/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-n": { "version": "17.24.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", @@ -7012,18 +6899,37 @@ "node": ">=10" } }, + "node_modules/eslint-plugin-perfectionist": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.6.0.tgz", + "integrity": "sha512-pxrLrfRp5wl1Vol1fAEa/G5yTXxefTPJjz07qC7a8iWFXcOZNuWBItMQ2OtTzfQIvMq6bMyYcrzc3Wz++na55Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.56.0", + "natural-orderby": "^5.0.0" + }, + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", + "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -7048,7 +6954,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7060,39 +6965,35 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint/node_modules/espree": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", + "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/glob-parent": { @@ -7101,7 +7002,6 @@ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -7114,8 +7014,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/espree": { "version": "10.4.0", @@ -7200,7 +7099,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7226,24 +7124,21 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", @@ -7268,7 +7163,6 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -7294,7 +7188,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -7312,7 +7205,6 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -7326,8 +7218,7 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -7474,17 +7365,6 @@ } } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/highlight.js": { "version": "11.11.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", @@ -7626,31 +7506,12 @@ "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.19" } @@ -7724,8 +7585,7 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/javascript-stringify": { "version": "2.1.0", @@ -7970,16 +7830,14 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", @@ -8014,7 +7872,6 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -8046,7 +7903,6 @@ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -8310,7 +8166,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -8327,14 +8182,6 @@ "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==", "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -8442,6 +8289,16 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-orderby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz", + "integrity": "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/nf3": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/nf3/-/nf3-0.3.10.tgz", @@ -8679,7 +8536,6 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -8698,7 +8554,6 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -8715,7 +8570,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -8726,20 +8580,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -8795,7 +8635,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8806,7 +8645,6 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8869,7 +8707,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -9228,17 +9065,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -9396,7 +9222,6 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -9410,7 +9235,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -9520,34 +9344,6 @@ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/swr": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", @@ -9812,7 +9608,6 @@ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -9987,7 +9782,6 @@ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -10404,7 +10198,6 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -10438,7 +10231,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10508,7 +10300,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 974bdf6..3e52f04 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.4", + "eslint": "^10.0.1", + "eslint-plugin-perfectionist": "^5.6.0", "husky": "^9.1.7", "jotai-babel": "^0.1.0", "jsdom": "^28.1.0", diff --git a/src/__tests__/auth.test.ts b/src/__tests__/auth.test.ts index b12c86a..763aabf 100644 --- a/src/__tests__/auth.test.ts +++ b/src/__tests__/auth.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from 'vitest' + import { createSession } from '@/hooks/auth.helpers' describe('Authentication Helpers', () => { diff --git a/src/__tests__/crypto.test.ts b/src/__tests__/crypto.test.ts index 0b3dcef..0fe429f 100644 --- a/src/__tests__/crypto.test.ts +++ b/src/__tests__/crypto.test.ts @@ -3,6 +3,7 @@ * OpenPGP has Uint8Array/instanceof issues in jsdom - run in Node */ import { describe, expect, it } from 'vitest' + import { createPassphrase, decryptPassphrase, diff --git a/src/__tests__/useEncryption.test.tsx b/src/__tests__/useEncryption.test.tsx index a4c0c55..1503f50 100644 --- a/src/__tests__/useEncryption.test.tsx +++ b/src/__tests__/useEncryption.test.tsx @@ -1,15 +1,16 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' import { renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + import { useEncryption } from '@/hooks/useEncryption' const mockEncryptWithPublicKey = vi.fn() const mockDecryptWithPrivateKey = vi.fn() vi.mock('@/lib/crypto', () => ({ - encryptWithPublicKey: (...args: Array) => - mockEncryptWithPublicKey(...args), decryptWithPrivateKey: (...args: Array) => mockDecryptWithPrivateKey(...args), + encryptWithPublicKey: (...args: Array) => + mockEncryptWithPublicKey(...args), })) const mockUserKeys = vi.fn() @@ -25,8 +26,8 @@ describe('useEncryption', () => { describe('encryptData / decryptData', () => { it('encrypts data when userKeys are available', async () => { mockUserKeys.mockReturnValue({ - publicKey: 'armored-pub-key', encryptedPrivateKey: 'armored-priv-key', + publicKey: 'armored-pub-key', }) mockEncryptWithPublicKey.mockResolvedValue('-----BEGIN PGP MESSAGE-----') @@ -42,8 +43,8 @@ describe('useEncryption', () => { it('decrypts data when userKeys and passphrase are available', async () => { mockUserKeys.mockReturnValue({ - publicKey: 'armored-pub-key', encryptedPrivateKey: 'armored-priv-key', + publicKey: 'armored-pub-key', }) mockDecryptWithPrivateKey.mockResolvedValue('decrypted plaintext') @@ -83,8 +84,8 @@ describe('useEncryption', () => { it('throws when decryptData called with empty passphrase', async () => { mockUserKeys.mockReturnValue({ - publicKey: 'pub', encryptedPrivateKey: 'priv', + publicKey: 'pub', }) const { result } = renderHook(() => useEncryption()) diff --git a/src/__tests__/useSignIn.test.tsx b/src/__tests__/useSignIn.test.tsx index dc2f972..aabb2dc 100644 --- a/src/__tests__/useSignIn.test.tsx +++ b/src/__tests__/useSignIn.test.tsx @@ -1,5 +1,6 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + import { useSignIn } from '@/hooks/useSignIn' const mockSignInCreate = vi.fn() @@ -15,12 +16,12 @@ vi.mock('@clerk/clerk-react', () => ({ useClerk: () => ({ user: { id: 'user_1' } }), useSignIn: () => ({ isLoaded: true, + setActive: mockSetActive, signIn: { + attemptSecondFactor: mockAttemptSecondFactor, create: mockSignInCreate, prepareSecondFactor: mockPrepareSecondFactor, - attemptSecondFactor: mockAttemptSecondFactor, }, - setActive: mockSetActive, }), })) @@ -61,8 +62,8 @@ describe('useSignIn', () => { it('handles sign-in success and redirects', async () => { mockSignInCreate.mockResolvedValue({ - status: 'complete', createdSessionId: 'sess_123', + status: 'complete', }) mockSetActive.mockResolvedValue(undefined) mockUserKeys.mockReturnValue({ @@ -83,9 +84,9 @@ describe('useSignIn', () => { }) expect(mockSignInCreate).toHaveBeenCalledWith({ - strategy: 'password', identifier: 'test@example.com', password: 'password', + strategy: 'password', }) expect(mockSetActive).toHaveBeenCalled() expect(mockSetPassphrase).toHaveBeenCalledWith('decrypted-passphrase') @@ -153,8 +154,8 @@ describe('useSignIn', () => { it('handleMfaVerify verifies code, decrypts passphrase and redirects', async () => { mockAttemptSecondFactor.mockResolvedValue({ - status: 'complete', createdSessionId: 'sess_mfa', + status: 'complete', }) mockSetActive.mockResolvedValue(undefined) mockUserKeys.mockReturnValue({ @@ -175,8 +176,8 @@ describe('useSignIn', () => { }) expect(mockAttemptSecondFactor).toHaveBeenCalledWith({ - strategy: 'email_code', code: '654321', + strategy: 'email_code', }) expect(mockSetActive).toHaveBeenCalled() expect(mockSetPassphrase).toHaveBeenCalledWith('decrypted-passphrase') diff --git a/src/__tests__/useSignOut.test.tsx b/src/__tests__/useSignOut.test.tsx index 42e597d..8105402 100644 --- a/src/__tests__/useSignOut.test.tsx +++ b/src/__tests__/useSignOut.test.tsx @@ -1,6 +1,7 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' import { RESET } from 'jotai/utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' + import { useSignOut } from '@/hooks/useSignOut' const mockSignOut = vi.fn().mockResolvedValue(undefined) diff --git a/src/__tests__/useSignUp.test.tsx b/src/__tests__/useSignUp.test.tsx index d5220f0..aeac2a4 100644 --- a/src/__tests__/useSignUp.test.tsx +++ b/src/__tests__/useSignUp.test.tsx @@ -1,5 +1,6 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + import { useSignUp } from '@/hooks/useSignUp' const mockSetActive = vi.fn() @@ -18,12 +19,12 @@ vi.mock('@clerk/clerk-react', () => ({ useClerk: () => ({ user: { id: 'user_1' } }), useSignUp: () => ({ isLoaded: true, + setActive: mockSetActive, signUp: { + attemptEmailAddressVerification: mockAttemptEmailVerification, create: mockSignUpCreate, prepareEmailAddressVerification: mockPrepareEmailVerification, - attemptEmailAddressVerification: mockAttemptEmailVerification, }, - setActive: mockSetActive, }), })) @@ -96,8 +97,8 @@ describe('useSignUp', () => { it('verifies email and creates keys on handleVerification', async () => { mockAttemptEmailVerification.mockResolvedValue({ - status: 'complete', createdSessionId: 'sess_456', + status: 'complete', }) mockSetActive.mockResolvedValue(undefined) diff --git a/src/__tests__/waitFor.test.ts b/src/__tests__/waitFor.test.ts index 5bc1cf3..1788106 100644 --- a/src/__tests__/waitFor.test.ts +++ b/src/__tests__/waitFor.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from 'vitest' + import { waitForValue } from '@/lib/waitFor' describe('waitForValue', () => { @@ -26,14 +27,14 @@ describe('waitForValue', () => { .fn() .mockReturnValueOnce(undefined) .mockReturnValueOnce('done') - await waitForValue(getValue, { pollInterval: 5, onRetry }) + await waitForValue(getValue, { onRetry, pollInterval: 5 }) expect(onRetry).toHaveBeenCalledTimes(1) }) it('throws after timeout when value never appears', async () => { const getValue = vi.fn().mockReturnValue(undefined) await expect( - waitForValue(getValue, { timeout: 50, pollInterval: 10 }), + waitForValue(getValue, { pollInterval: 10, timeout: 50 }), ).rejects.toThrow(/Timeout waiting for value/) }) }) diff --git a/src/components/Forms/SignIn/SignInCredsForm.tsx b/src/components/Forms/SignIn/SignInCredsForm.tsx index e878a1e..3e00174 100644 --- a/src/components/Forms/SignIn/SignInCredsForm.tsx +++ b/src/components/Forms/SignIn/SignInCredsForm.tsx @@ -1,4 +1,3 @@ -import type { useSignIn } from '@/hooks/useSignIn' import { Button } from '@/components/ui/button' import { Field, @@ -8,6 +7,7 @@ import { FieldLabel, } from '@/components/ui/field' import { Input } from '@/components/ui/input' +import type { useSignIn } from '@/hooks/useSignIn' import { m } from '@/paraglide/messages' import { RoutesPath } from '@/types/routes' @@ -15,12 +15,12 @@ type SignInCredsFormProps = ReturnType export const SignInCredsForm = ({ email, - setEmail, + error, + handleSignIn, isLoading, password, + setEmail, setPassword, - handleSignIn, - error, }: SignInCredsFormProps) => { return (
@@ -29,8 +29,8 @@ export const SignInCredsForm = ({ {m['sign_in.email']()} setEmail(e.target.value)} required @@ -42,8 +42,8 @@ export const SignInCredsForm = ({ {m['sign_in.password']()} {m['sign_in.forgot_password']()} @@ -58,7 +58,7 @@ export const SignInCredsForm = ({ {error} - diff --git a/src/components/Forms/SignIn/SignInMfaForm.tsx b/src/components/Forms/SignIn/SignInMfaForm.tsx index bb9b3f8..399febc 100644 --- a/src/components/Forms/SignIn/SignInMfaForm.tsx +++ b/src/components/Forms/SignIn/SignInMfaForm.tsx @@ -1,4 +1,4 @@ -import type { useSignIn } from '@/hooks/useSignIn' +import { Button } from '@/components/ui/button' import { Field, FieldError, @@ -6,17 +6,17 @@ import { FieldLabel, } from '@/components/ui/field' import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' +import type { useSignIn } from '@/hooks/useSignIn' import { m } from '@/paraglide/messages' type SignInMfaFormProps = ReturnType export const SignInMfaForm = ({ - isLoading, code, - setCode, - handleMfaVerify, error, + handleMfaVerify, + isLoading, + setCode, }: SignInMfaFormProps) => { return ( @@ -25,18 +25,18 @@ export const SignInMfaForm = ({ {m['sign_in.mfa.code']()} setCode(e.target.value)} required - maxLength={6} - autoComplete="one-time-code" /> {error} - diff --git a/src/components/Forms/SignUp/SignUpCredsForm.tsx b/src/components/Forms/SignUp/SignUpCredsForm.tsx index cfd088b..a7f6983 100644 --- a/src/components/Forms/SignUp/SignUpCredsForm.tsx +++ b/src/components/Forms/SignUp/SignUpCredsForm.tsx @@ -1,4 +1,4 @@ -import type { useSignUp } from '@/hooks/useSignUp' +import { Button } from '@/components/ui/button' import { Field, FieldDescription, @@ -7,22 +7,22 @@ import { FieldLabel, } from '@/components/ui/field' import { Input } from '@/components/ui/input' -import { RoutesPath } from '@/types/routes' +import type { useSignUp } from '@/hooks/useSignUp' import { m } from '@/paraglide/messages' -import { Button } from '@/components/ui/button' +import { RoutesPath } from '@/types/routes' type SignUpCredsFormProps = ReturnType export const SignUpCredsForm = ({ + confirmPassword, email, - setEmail, + error, + handleSignUp, isLoading, password, - setPassword, - confirmPassword, setConfirmPassword, - handleSignUp, - error, + setEmail, + setPassword, }: SignUpCredsFormProps) => { return ( @@ -31,8 +31,8 @@ export const SignUpCredsForm = ({ {m['sign_up.email']()} setEmail(e.target.value)} required @@ -45,8 +45,8 @@ export const SignUpCredsForm = ({ {m['sign_up.password']()} setPassword(e.target.value)} required @@ -61,8 +61,8 @@ export const SignUpCredsForm = ({ setConfirmPassword(e.target.value)} required @@ -71,7 +71,7 @@ export const SignUpCredsForm = ({ {error} - diff --git a/src/components/Forms/SignUp/SignUpVerificationForm.tsx b/src/components/Forms/SignUp/SignUpVerificationForm.tsx index dc7cca2..ab252eb 100644 --- a/src/components/Forms/SignUp/SignUpVerificationForm.tsx +++ b/src/components/Forms/SignUp/SignUpVerificationForm.tsx @@ -1,4 +1,3 @@ -import type { useSignUp } from '@/hooks/useSignUp' import { Button } from '@/components/ui/button' import { Field, @@ -8,17 +7,18 @@ import { FieldLabel, } from '@/components/ui/field' import { Input } from '@/components/ui/input' +import type { useSignUp } from '@/hooks/useSignUp' import { m } from '@/paraglide/messages' type SignUpVerificationFormProps = ReturnType export const SignUpVerificationForm = ({ email, + error, + handleVerification, isLoading, - verificationCode, setVerificationCode, - handleVerification, - error, + verificationCode, }: SignUpVerificationFormProps) => { return ( @@ -32,18 +32,18 @@ export const SignUpVerificationForm = ({ setVerificationCode(e.target.value)} required - maxLength={6} - autoComplete="one-time-code" /> {error} - diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx index 317f7b7..6b5018e 100644 --- a/src/components/Layouts/Layout.tsx +++ b/src/components/Layouts/Layout.tsx @@ -1,6 +1,7 @@ +import { SidebarProvider } from '@/components/ui/sidebar' + import { LayoutAside } from './LayoutAside' import { LayoutContent } from './LayoutContent' -import { SidebarProvider } from '@/components/ui/sidebar' export const Layout = ({ children }: { children: React.ReactNode }) => { return ( diff --git a/src/components/Layouts/LayoutAside.tsx b/src/components/Layouts/LayoutAside.tsx index f26f175..ee0a081 100644 --- a/src/components/Layouts/LayoutAside.tsx +++ b/src/components/Layouts/LayoutAside.tsx @@ -1,5 +1,3 @@ -import { UserNavigation } from './Navigation' -import { MainNavigation } from './Navigation/MainNavigation' import { Sidebar, SidebarContent, @@ -7,6 +5,9 @@ import { SidebarHeader, } from '@/components/ui/sidebar' +import { UserNavigation } from './Navigation' +import { MainNavigation } from './Navigation/MainNavigation' + export const LayoutAside = () => { return ( diff --git a/src/components/Layouts/LayoutContent.tsx b/src/components/Layouts/LayoutContent.tsx index 3d16865..7160f20 100644 --- a/src/components/Layouts/LayoutContent.tsx +++ b/src/components/Layouts/LayoutContent.tsx @@ -15,8 +15,8 @@ export const LayoutContent = ({ children }: { children: React.ReactNode }) => {
diff --git a/src/components/Layouts/Navigation/MainNavigation.tsx b/src/components/Layouts/Navigation/MainNavigation.tsx index 8612b60..6050c0a 100644 --- a/src/components/Layouts/Navigation/MainNavigation.tsx +++ b/src/components/Layouts/Navigation/MainNavigation.tsx @@ -1,6 +1,6 @@ -import { LayoutDashboardIcon, MessageSquareLockIcon } from 'lucide-react' import { useNavigate } from '@tanstack/react-router' -import { NavigationLink } from './NavigationLink' +import { LayoutDashboardIcon, MessageSquareLockIcon } from 'lucide-react' + import { SidebarGroup, SidebarGroupContent, @@ -9,6 +9,8 @@ import { import { m } from '@/paraglide/messages' import { RoutesPath } from '@/types/routes' +import { NavigationLink } from './NavigationLink' + export const MainNavigation = () => { const navigate = useNavigate() return ( @@ -16,17 +18,17 @@ export const MainNavigation = () => { navigate({ to: RoutesPath.HOME.toString() })} icon={} label={m['navigation.dashboard']()} + tooltip={m['navigation.dashboard']()} + onClick={() => navigate({ to: RoutesPath.HOME.toString() })} /> navigate({ to: RoutesPath.DEMO_ENCRYPT.toString() })} icon={} label={m['navigation.encrypt']()} + tooltip={m['navigation.encrypt']()} + onClick={() => navigate({ to: RoutesPath.DEMO_ENCRYPT.toString() })} /> diff --git a/src/components/Layouts/Navigation/NavigationLink.tsx b/src/components/Layouts/Navigation/NavigationLink.tsx index 9ff58f8..9603da8 100644 --- a/src/components/Layouts/Navigation/NavigationLink.tsx +++ b/src/components/Layouts/Navigation/NavigationLink.tsx @@ -1,17 +1,17 @@ import { SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar' type NavigationLinkProps = { - icon?: React.ReactNode label: string - onClick?: () => void tooltip: string + icon?: React.ReactNode + onClick?: () => void } export const NavigationLink = ({ - tooltip, - onClick, icon, label, + onClick, + tooltip, }: NavigationLinkProps) => { return ( diff --git a/src/components/Layouts/Navigation/UserNavigation.tsx b/src/components/Layouts/Navigation/UserNavigation.tsx index ecfe33c..29dac4a 100644 --- a/src/components/Layouts/Navigation/UserNavigation.tsx +++ b/src/components/Layouts/Navigation/UserNavigation.tsx @@ -1,5 +1,7 @@ import { useUser } from '@clerk/clerk-react' import { EllipsisVerticalIcon, LogOut } from 'lucide-react' + +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { DropdownMenu, DropdownMenuContent, @@ -7,7 +9,6 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarMenuButton, useSidebar } from '@/components/ui/sidebar' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { useSignOut } from '@/hooks/useSignOut' import { m } from '@/paraglide/messages' @@ -24,8 +25,8 @@ export const UserNavigation = () => { {avatar && } @@ -38,12 +39,12 @@ export const UserNavigation = () => { - + {m['navigation.logout']()} diff --git a/src/components/LocaleSwitcher.tsx b/src/components/LocaleSwitcher.tsx index 4d150f0..d0c112d 100644 --- a/src/components/LocaleSwitcher.tsx +++ b/src/components/LocaleSwitcher.tsx @@ -1,21 +1,21 @@ +import { m } from '@/paraglide/messages' // Locale switcher refs: // - Paraglide docs: https://inlang.com/m/gerre34r/library-inlang-paraglideJs // - Router example: https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#switching-locale import { getLocale, locales, setLocale } from '@/paraglide/runtime' -import { m } from '@/paraglide/messages' export default function ParaglideLocaleSwitcher() { const currentLocale = getLocale() return (
{m.current_locale({ locale: currentLocale })} @@ -24,17 +24,17 @@ export default function ParaglideLocaleSwitcher() { {locales.map((locale) => (