diff --git a/apps/next/app/privacy/page.tsx b/apps/next/app/privacy/page.tsx
new file mode 100644
index 00000000..69d84fdf
--- /dev/null
+++ b/apps/next/app/privacy/page.tsx
@@ -0,0 +1,73 @@
+import { PolicyPageShell } from 'components/policy-page-shell'
+import { env } from '@/lib/env'
+
+export default function PrivacyPage() {
+ return (
+
+
+ 1. Information We Collect
+
+ We collect information you provide when creating an account (email, name, username), data
+ from sign-in methods (Google, GitHub, Facebook, Twitter, passkeys, magic links, or
+ wallets), and usage data such as requests and session information.
+
+
+
+ 2. How We Use It
+
+ We use this information to provide, operate, and improve the Service, authenticate users,
+ communicate with you, and comply with legal obligations.
+
+
+
+
+ 3. OAuth and Third-Party Login
+
+
+ When you sign in with Google, GitHub, Facebook, or Twitter, we receive basic profile
+ information (e.g., email, name) as permitted by the provider. Each provider has its own
+ privacy policy governing how they handle your data.
+
+
+
+ 4. Cookies
+
+ We use cookies and similar technologies to maintain your session and preferences. You can
+ control cookies through your browser settings.
+
+
+
+ 5. Data Retention
+
+ We retain your account data for as long as your account is active. You may request
+ deletion of your data by contacting us.
+
+
+
+ 6. Your Rights
+
+ Depending on your location, you may have rights to access, correct, delete, or export your
+ data. Contact us to exercise these rights.
+
+
+
+ 7. Security
+
+ We use reasonable measures to protect your data. No method of transmission over the
+ internet is completely secure.
+
+
+
+ 8. Changes
+
+ We may update this policy from time to time. We will notify you of material changes by
+ posting the updated policy or by other reasonable means.
+
+
+
+ )
+}
diff --git a/apps/next/app/terms/page.tsx b/apps/next/app/terms/page.tsx
new file mode 100644
index 00000000..15fee054
--- /dev/null
+++ b/apps/next/app/terms/page.tsx
@@ -0,0 +1,69 @@
+import { PolicyPageShell } from 'components/policy-page-shell'
+import { env } from '@/lib/env'
+
+export default function TermsPage() {
+ return (
+
+
+ 1. Acceptance
+
+ By accessing or using Basilic ("the Service"), you agree to these Terms of
+ Service. If you do not agree, do not use the Service.
+
+
+
+ 2. Use of Service
+
+ You may use the Service only for lawful purposes. You are responsible for maintaining the
+ confidentiality of your account and for all activity under your account.
+
+
+
+ 3. User Content
+
+ You retain ownership of content you submit. By submitting content, you grant us a limited
+ license to use, store, and process it as necessary to provide the Service.
+
+
+
+ 4. Prohibited Conduct
+
+ You may not misuse the Service, violate laws, infringe rights, transmit malware, or
+ attempt to gain unauthorized access to our systems or other accounts.
+
+
+
+ 5. Termination
+
+ We may suspend or terminate your access at any time for violation of these terms or for
+ any other reason. You may stop using the Service at any time.
+
+
+
+ 6. Disclaimers
+
+ The Service is provided "as is" without warranties of any kind. We do not
+ warrant that the Service will be uninterrupted, secure, or error-free.
+
+
+
+ 7. Limitation of Liability
+
+ To the maximum extent permitted by law, we are not liable for any indirect, incidental,
+ special, or consequential damages arising from your use of the Service.
+
+
+
+ 8. Changes
+
+ We may update these terms from time to time. Continued use of the Service after changes
+ constitutes acceptance of the updated terms.
+
+
+
+ )
+}
diff --git a/apps/next/components/policy-page-shell.tsx b/apps/next/components/policy-page-shell.tsx
new file mode 100644
index 00000000..152b7fac
--- /dev/null
+++ b/apps/next/components/policy-page-shell.tsx
@@ -0,0 +1,40 @@
+import { ChevronLeft } from 'lucide-react'
+import Link from 'next/link'
+
+type PolicyPageShellProps = {
+ title: string
+ updatedAt: string
+ contactEmail: string
+ children: React.ReactNode
+}
+
+export function PolicyPageShell({
+ title,
+ updatedAt,
+ contactEmail,
+ children,
+}: PolicyPageShellProps) {
+ return (
+
+
+
+
+ Back to login
+
+
{title}
+
Last updated: {updatedAt}
+
{children}
+
+ For questions, contact us at{' '}
+
+ {contactEmail}
+
+ .
+
+
+
+ )
+}
diff --git a/apps/next/e2e/auth-helpers.ts b/apps/next/e2e/auth-helpers.ts
index fcf2769b..b1baf3f6 100644
--- a/apps/next/e2e/auth-helpers.ts
+++ b/apps/next/e2e/auth-helpers.ts
@@ -50,9 +50,44 @@ export const authHelpers = {
return response
},
+ async extractMagicLinkData(
+ page: Page,
+ ): Promise<{ token: string; verificationId: string } | null> {
+ await new Promise(r => setTimeout(r, 500))
+ const maxRetries = 12
+ const delayMs = 500
+ for (let attempt = 0; attempt < maxRetries; attempt++)
+ try {
+ const response = await page.request.get(`${apiUrl}/test/magic-link/last`)
+ if (!response.ok()) {
+ if (attempt < maxRetries - 1) {
+ await new Promise(r => setTimeout(r, delayMs))
+ continue
+ }
+ return null
+ }
+ const data = (await response.json()) as {
+ token?: string
+ verificationId?: string
+ }
+ if (data.token && data.verificationId)
+ return data as { token: string; verificationId: string }
+ if (attempt < maxRetries - 1) {
+ await new Promise(r => setTimeout(r, delayMs))
+ continue
+ }
+ return null
+ } catch {
+ if (attempt < maxRetries - 1) {
+ await new Promise(r => setTimeout(r, delayMs))
+ continue
+ }
+ return null
+ }
+ return null
+ },
+
async extractToken(page: Page): Promise
{
- // Stabilize e2e auth flows: 500ms delay and 12 retries chosen empirically for async
- // backend/session propagation and flaky CI timing. Adjust if tests are stabilized.
await new Promise(r => setTimeout(r, 500))
const maxRetries = 12
const delayMs = 500
@@ -66,7 +101,7 @@ export const authHelpers = {
}
return null
}
- const data = await response.json()
+ const data = (await response.json()) as { token?: string }
if (data.token) return data.token
if (attempt < maxRetries - 1) {
await new Promise(r => setTimeout(r, delayMs))
@@ -80,13 +115,28 @@ export const authHelpers = {
}
return null
}
-
return null
},
- async verifyMagicLink(page: Page, token: string) {
- const verifyUrl = `/auth/callback/magiclink?token=${encodeURIComponent(token)}&callbackURL=/`
+ async enterLoginCodeAndSubmit(page: Page, code: string) {
+ await page.getByTestId('login-code-input').fill(code)
+ await page.getByTestId('submit-login-code').click()
+ },
+
+ async verifyMagicLink(page: Page, code: string, verificationIdParam?: string) {
+ let verificationId = verificationIdParam
+ if (!verificationId) {
+ const data = await this.extractMagicLinkData(page)
+ verificationId = data?.verificationId ?? undefined
+ }
+ if (!verificationId) throw new Error('Need verificationId for link-click flow')
+ const verifyUrl = `/auth/callback/magiclink?verificationId=${encodeURIComponent(verificationId)}&callbackURL=/`
await page.goto(verifyUrl)
+ await page
+ .getByRole('heading', { name: 'Enter your code' })
+ .waitFor({ state: 'visible', timeout: 5000 })
+ await page.getByLabel('Code').fill(code)
+ await page.getByRole('button', { name: 'Verify' }).click()
await page.waitForURL(
url => {
const path = new URL(url).pathname
@@ -118,11 +168,18 @@ export const authHelpers = {
async loginAsTestUser(page: Page) {
const response = await this.sendMagicLink(page)
if (response.status() !== 200) throw new Error('Magic link request failed')
- const successMessage = page.getByText(/check your email for the magic link/i)
+ const successMessage = page.getByRole('heading', { name: 'Check your email' })
await successMessage.waitFor({ state: 'visible', timeout: 10000 })
await new Promise(r => setTimeout(r, 200))
const token = await this.extractToken(page)
if (!token) throw new Error('Failed to extract magic link token')
- await this.verifyMagicLink(page, token)
+ await this.enterLoginCodeAndSubmit(page, token)
+ await page.waitForURL(
+ url => {
+ const path = new URL(url).pathname
+ return path === '/' || path === ''
+ },
+ { timeout: 10000 },
+ )
},
}
diff --git a/apps/next/e2e/magic-link-auth.spec.ts b/apps/next/e2e/magic-link-auth.spec.ts
index b1e4f107..c6389f7b 100644
--- a/apps/next/e2e/magic-link-auth.spec.ts
+++ b/apps/next/e2e/magic-link-auth.spec.ts
@@ -24,23 +24,50 @@ test.describe('Magic Link Authentication', () => {
test.describe('Valid Magic Link Flow', () => {
test.describe.configure({ mode: 'serial' })
- test('should complete full magic link authentication flow', async ({ page }) => {
+ test('should complete full magic link authentication flow (code entry)', async ({ page }) => {
const response = await authHelpers.sendMagicLink(page)
expect(response.status()).toBe(200)
expect(response.ok()).toBe(true)
await page
- .getByText(/check your email for the magic link/i)
+ .getByRole('heading', { name: 'Check your email' })
.waitFor({ state: 'visible', timeout: 10000 })
+ await page.getByTestId('login-code-input').waitFor({ state: 'visible', timeout: 5000 })
await new Promise(r => setTimeout(r, 500))
- const token = await authHelpers.extractToken(page)
- expect(token).toBeTruthy()
- expect(typeof token).toBe('string')
+ const code = await authHelpers.extractToken(page)
+ expect(code).toBeTruthy()
+ expect(typeof code).toBe('string')
+ expect(code).toMatch(/^\d{6}$/)
- if (!token) throw new Error('Failed to extract magic link token')
+ if (!code) throw new Error('Failed to extract login code')
- await authHelpers.verifyMagicLink(page, token)
+ await authHelpers.enterLoginCodeAndSubmit(page, code)
+ await page.waitForURL(
+ url => {
+ const path = new URL(url).pathname
+ return path === '/' || path === ''
+ },
+ { timeout: 10000 },
+ )
+ await checkAuthenticated(page).run()
+ })
+
+ test('should complete magic link authentication flow (link click)', async ({ page }) => {
+ const response = await authHelpers.sendMagicLink(page)
+ expect(response.status()).toBe(200)
+ expect(response.ok()).toBe(true)
+
+ await page
+ .getByRole('heading', { name: 'Check your email' })
+ .waitFor({ state: 'visible', timeout: 10000 })
+ await new Promise(r => setTimeout(r, 500))
+
+ const code = await authHelpers.extractToken(page)
+ expect(code).toBeTruthy()
+ if (!code) throw new Error('Failed to extract login code')
+
+ await authHelpers.verifyMagicLink(page, code)
await checkAuthenticated(page).run()
})
})
@@ -49,7 +76,7 @@ test.describe('Magic Link Authentication', () => {
test('should redirect to login with error message for invalid token displayed below input', async ({
page,
}) => {
- await page.goto('/auth/callback/magiclink?token=invalid-token-12345')
+ await page.goto('/auth/callback/magiclink?token=000000')
await page.waitForURL(/\/auth\/login\?.*(message|error)=/, { timeout: 5000 })
const emailInput = page.locator('input[type="email"]')
@@ -85,7 +112,7 @@ test.describe('Magic Link Authentication', () => {
test('should redirect to login with error message for expired token displayed below input', async ({
page,
}) => {
- await page.goto('/auth/callback/magiclink?token=expired-token-abc123')
+ await page.goto('/auth/callback/magiclink?token=999999')
await page.waitForURL(/\/auth\/login\?.*(message|error)=/, { timeout: 5000 })
const emailInput = page.locator('input[type="email"]')
diff --git a/apps/next/e2e/passkey-auth.spec.ts b/apps/next/e2e/passkey-auth.spec.ts
index 8a3e6c35..ffd518be 100644
--- a/apps/next/e2e/passkey-auth.spec.ts
+++ b/apps/next/e2e/passkey-auth.spec.ts
@@ -4,7 +4,7 @@ test.describe('Passkey sign-in', () => {
test.describe.configure({ mode: 'serial' })
test('should sign in with passkey after adding one', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=passkeys')
+ await authenticatedPage.goto('/settings/security/passkeys')
await expect(authenticatedPage.getByRole('button', { name: /add passkey/i })).toBeVisible({
timeout: 10000,
})
diff --git a/apps/next/e2e/security/api-keys.spec.ts b/apps/next/e2e/security/api-keys.spec.ts
index 829ad7cc..b709724e 100644
--- a/apps/next/e2e/security/api-keys.spec.ts
+++ b/apps/next/e2e/security/api-keys.spec.ts
@@ -5,30 +5,27 @@ test.describe('API Keys', () => {
test('should redirect to login when unauthenticated', async ({ page }) => {
await page.context().clearCookies()
- await page.goto('/settings/security?section=apikeys')
+ await page.goto('/settings/security/apikeys')
await page.waitForURL(/\/auth\/login/, { timeout: 5000 })
await expect(page.locator('input[type="email"]')).toBeVisible()
})
test('should reach API keys tab and see card title', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=apikeys')
+ await authenticatedPage.goto('/settings/security/apikeys')
await expect(authenticatedPage.getByRole('tab', { name: 'API keys' })).toBeVisible()
- await authenticatedPage.getByRole('tab', { name: 'API keys' }).click()
await expect(authenticatedPage.getByRole('heading', { name: 'API keys' })).toBeVisible({
timeout: 5000,
})
})
test('should show empty state and Create key button', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=apikeys')
- await authenticatedPage.getByRole('tab', { name: 'API keys' }).click()
+ await authenticatedPage.goto('/settings/security/apikeys')
await expect(authenticatedPage.getByText('No API keys yet.')).toBeVisible({ timeout: 5000 })
await expect(authenticatedPage.getByRole('button', { name: /create key/i })).toBeVisible()
})
test('should create API key and show in table', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=apikeys')
- await authenticatedPage.getByRole('tab', { name: 'API keys' }).click()
+ await authenticatedPage.goto('/settings/security/apikeys')
await authenticatedPage.getByRole('button', { name: /create key/i }).click()
await expect(authenticatedPage.getByRole('dialog')).toBeVisible({ timeout: 3000 })
await authenticatedPage.getByLabel('Name').fill('E2E Test Key')
@@ -41,8 +38,7 @@ test.describe('API Keys', () => {
})
test('should revoke API key', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=apikeys')
- await authenticatedPage.getByRole('tab', { name: 'API keys' }).click()
+ await authenticatedPage.goto('/settings/security/apikeys')
await expect(authenticatedPage.getByRole('button', { name: /create key/i })).toBeVisible()
await authenticatedPage.getByRole('button', { name: /create key/i }).click()
await expect(authenticatedPage.getByRole('dialog')).toBeVisible({ timeout: 3000 })
diff --git a/apps/next/e2e/security/authenticator.spec.ts b/apps/next/e2e/security/authenticator.spec.ts
index 45157b40..9812fde1 100644
--- a/apps/next/e2e/security/authenticator.spec.ts
+++ b/apps/next/e2e/security/authenticator.spec.ts
@@ -6,14 +6,14 @@ test.describe('Security - Authenticator', () => {
test('should redirect to login when unauthenticated', async ({ page }) => {
await page.context().clearCookies()
- await page.goto('/settings/security?section=totp')
+ await page.goto('/settings/security/totp')
await page.waitForURL(/\/auth\/login/, { timeout: 5000 })
await expect(page.locator('input[type="email"]')).toBeVisible()
})
test('should reach page and see layout when authenticated', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=totp')
- await expect(authenticatedPage).toHaveURL(/\/settings\/security/)
+ await authenticatedPage.goto('/settings/security/totp')
+ await expect(authenticatedPage).toHaveURL(/\/settings\/security\/totp/)
await expect(authenticatedPage.getByRole('tab', { name: /passkeys/i })).toBeVisible()
await expect(authenticatedPage.getByRole('tab', { name: /authenticator/i })).toBeVisible()
await expect(authenticatedPage.getByRole('tab', { name: /api keys/i })).toBeVisible()
@@ -23,7 +23,7 @@ test.describe('Security - Authenticator', () => {
test('should show setup flow with QR and InputOTP when TOTP not configured', async ({
authenticatedPage,
}) => {
- await authenticatedPage.goto('/settings/security?section=totp')
+ await authenticatedPage.goto('/settings/security/totp')
await expect(authenticatedPage.getByText('Authenticator app')).toBeVisible({ timeout: 10000 })
await expect(authenticatedPage.getByText(/scan qr code/i)).toBeVisible({ timeout: 15000 })
await expect(authenticatedPage.locator('[data-slot="input-otp-group"]')).toBeVisible({
@@ -34,7 +34,7 @@ test.describe('Security - Authenticator', () => {
})
test('should complete full TOTP setup and unlink', async ({ authenticatedPage }) => {
- await authenticatedPage.goto('/settings/security?section=totp')
+ await authenticatedPage.goto('/settings/security/totp')
await expect(authenticatedPage.getByText('Authenticator app')).toBeVisible({ timeout: 10000 })
await expect(authenticatedPage.getByText(/scan qr code/i)).toBeVisible({ timeout: 15000 })
diff --git a/apps/next/e2e/security/passkeys.spec.ts b/apps/next/e2e/security/passkeys.spec.ts
index 14cba93a..93034a4a 100644
--- a/apps/next/e2e/security/passkeys.spec.ts
+++ b/apps/next/e2e/security/passkeys.spec.ts
@@ -5,7 +5,7 @@ test.describe('Security - Passkeys', () => {
test('should redirect to login when unauthenticated', async ({ page }) => {
await page.context().clearCookies()
- await page.goto('/settings/security?section=passkeys')
+ await page.goto('/settings/security/passkeys')
await page.waitForURL(/\/auth\/login/, { timeout: 5000 })
await expect(page.locator('input[type="email"]')).toBeVisible()
})
@@ -13,8 +13,8 @@ test.describe('Security - Passkeys', () => {
test('should reach page and see Passkeys card when authenticated', async ({
authenticatedPage,
}) => {
- await authenticatedPage.goto('/settings/security?section=passkeys')
- await expect(authenticatedPage).toHaveURL(/\/settings\/security/)
+ await authenticatedPage.goto('/settings/security/passkeys')
+ await expect(authenticatedPage).toHaveURL(/\/settings\/security\/passkeys/)
await expect(authenticatedPage.getByRole('heading', { name: 'Passkeys' })).toBeVisible()
await expect(authenticatedPage.getByText(/no passkeys configured/i)).toBeVisible()
await expect(authenticatedPage.getByRole('button', { name: /add passkey/i })).toBeVisible()
@@ -23,7 +23,7 @@ test.describe('Security - Passkeys', () => {
test('should add passkey with CDP virtual authenticator and remove it', async ({
authenticatedPage,
}) => {
- await authenticatedPage.goto('/settings/security?section=passkeys')
+ await authenticatedPage.goto('/settings/security/passkeys')
await expect(authenticatedPage.getByRole('button', { name: /add passkey/i })).toBeVisible({
timeout: 10000,
})
diff --git a/apps/next/lib/auth/auth-utils.ts b/apps/next/lib/auth/auth-utils.ts
index f05cccd3..fb702891 100644
--- a/apps/next/lib/auth/auth-utils.ts
+++ b/apps/next/lib/auth/auth-utils.ts
@@ -7,8 +7,10 @@ const userResponseSchema = z
.object({
user: z
.object({
+ id: z.string(),
email: z.string().nullable().optional(),
name: z.string().nullable().optional(),
+ username: z.string().nullable().optional(),
emailVerified: z.boolean().nullable().optional(),
})
.passthrough()
@@ -40,8 +42,10 @@ export async function getAuthStatus(): Promise<{
}
export async function getUserInfo(): Promise<{
+ id?: string
email?: string | null
name?: string | null
+ username?: string | null
emailVerified?: boolean | null
} | null> {
const { token } = await getServerAuthToken()
diff --git a/apps/next/lib/env.ts b/apps/next/lib/env.ts
index e3392fe3..65977f9e 100644
--- a/apps/next/lib/env.ts
+++ b/apps/next/lib/env.ts
@@ -30,6 +30,8 @@ export const env = createEnv({
// Logging configuration
NEXT_PUBLIC_LOG_ENABLED: z.coerce.boolean().optional(),
NEXT_PUBLIC_LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error', 'silent']).optional(),
+ // Policy pages (privacy, terms)
+ NEXT_PUBLIC_LEGAL_EMAIL: z.string().email().default('legal@example.com'),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
@@ -48,6 +50,7 @@ export const env = createEnv({
ERROR_REPORTING_ENVIRONMENT: process.env.ERROR_REPORTING_ENVIRONMENT,
NEXT_PUBLIC_LOG_ENABLED: process.env.NEXT_PUBLIC_LOG_ENABLED,
NEXT_PUBLIC_LOG_LEVEL: process.env.NEXT_PUBLIC_LOG_LEVEL,
+ NEXT_PUBLIC_LEGAL_EMAIL: process.env.NEXT_PUBLIC_LEGAL_EMAIL,
},
emptyStringAsUndefined: true,
})
diff --git a/apps/next/next.config.mjs b/apps/next/next.config.mjs
index 3ed11e50..ffffa1c8 100644
--- a/apps/next/next.config.mjs
+++ b/apps/next/next.config.mjs
@@ -62,6 +62,7 @@ const nextConfig = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'assets.coingecko.com', pathname: '/coins/**' },
+ { protocol: 'https', hostname: 'coin-images.coingecko.com', pathname: '/coins/**' },
],
},
async redirects() {
diff --git a/apps/next/package.json b/apps/next/package.json
index 3677e242..f3709f96 100644
--- a/apps/next/package.json
+++ b/apps/next/package.json
@@ -40,6 +40,7 @@
"@repo/react": "workspace:*",
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*",
+ "@sentry/nextjs": "^10.38.0",
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.39",
@@ -48,6 +49,7 @@
"@t3-oss/env-nextjs": "^0.13.10",
"@tanstack/react-query": "^5.90.21",
"@vercel/analytics": "1.6.1",
+ "ahooks": "^3.9.6",
"ai-elements": "^1.8.4",
"autoprefixer": "^10.4.24",
"bs58": "^6.0.0",
@@ -55,16 +57,14 @@
"jose": "^6.1.3",
"lucide-react": "^0.564.0",
"next": "16.1.6",
- "@sentry/nextjs": "^10.38.0",
"next-themes": "^0.4.6",
- "sonner": "^2.0.7",
"nuqs": "^2.8.8",
"react": "^19.2.4",
"react-dom": "^19.2.4",
- "ahooks": "^3.9.6",
"react-error-boundary": "^6.1.1",
"react-markdown": "^10.1.0",
"require-in-the-middle": "^8.0.1",
+ "sonner": "^2.0.7",
"use-stick-to-bottom": "^1.1.3",
"viem": "^2.45.3",
"wagmi": "^3.4.3",
diff --git a/apps/next/proxy.ts b/apps/next/proxy.ts
index 811b504f..739e0808 100644
--- a/apps/next/proxy.ts
+++ b/apps/next/proxy.ts
@@ -56,8 +56,13 @@ export async function proxy(request: NextRequest) {
const allowedImagePaths = ['/images/auth-login-hero.webp'] as const
const isAllowedImage = (allowedImagePaths as readonly string[]).includes(pathname)
- // Allow callbacks, logout, and explicitly listed image assets without auth
- if (pathname.startsWith('/auth/callback') || pathname === '/auth/logout' || isAllowedImage)
+ // Allow callbacks, logout, legal pages, and explicitly listed image assets without auth
+ const publicPaths = ['/auth/logout', '/terms', '/privacy'] as const
+ if (
+ pathname.startsWith('/auth/callback') ||
+ (publicPaths as readonly string[]).includes(pathname) ||
+ isAllowedImage
+ )
return NextResponse.next()
const authCheck = await checkAuthStatus(request)
diff --git a/packages/core/src/api-client.gen.ts b/packages/core/src/api-client.gen.ts
index 51e30672..be3c895d 100644
--- a/packages/core/src/api-client.gen.ts
+++ b/packages/core/src/api-client.gen.ts
@@ -30,6 +30,8 @@ import type {
AccountLinkWalletVerifyResponse,
AccountPasskeysListData,
AccountPasskeysListResponse,
+ AccountProfileUpdateData,
+ AccountProfileUpdateResponse,
AuthPasskeyExchangeData,
AuthPasskeyExchangeResponse,
AuthPasskeyResolveUserData,
@@ -111,7 +113,8 @@ export type CoreApiClient = {
verify: (opts: Options) => Promise
}
};
- passkeys: (opts?: Options) => Promise
+ passkeys: (opts?: Options) => Promise;
+ profile: (opts: Options) => Promise
};
ai: {
chat: (opts: Options) => Promise
diff --git a/packages/core/src/api-wrapper.gen.ts b/packages/core/src/api-wrapper.gen.ts
index 9e61432d..97a15626 100644
--- a/packages/core/src/api-wrapper.gen.ts
+++ b/packages/core/src/api-wrapper.gen.ts
@@ -31,6 +31,7 @@ account: {
},
},
passkeys: gen.accountPasskeysList,
+ profile: gen.accountProfileUpdate,
},
ai: {
chat: gen.chat,
diff --git a/packages/core/src/gen/index.ts b/packages/core/src/gen/index.ts
index 4db8d6cc..3e6fb173 100644
--- a/packages/core/src/gen/index.ts
+++ b/packages/core/src/gen/index.ts
@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
-export { accountApikeysCreate, accountApikeysList, accountApikeysRevoke, accountLinkEmailRequest, accountLinkEmailVerify, accountLinkPasskeyDelete, accountLinkPasskeyFinish, accountLinkPasskeyStart, accountLinkTotpSetup, accountLinkTotpUnlink, accountLinkTotpVerify, accountLinkWalletUnlink, accountLinkWalletVerify, accountPasskeysList, authPasskeyExchange, authPasskeyResolveUser, authPasskeyStart, authPasskeyVerify, chat, getUser, healthCheck, logout, magiclinkRequest, magiclinkVerify, oauthFacebookAuthorizeUrl, oauthFacebookExchange, oauthGithubAuthorize, oauthGithubAuthorizeUrl, oauthGithubExchange, oauthGoogleVerifyIdToken, oauthProviders, oauthTwitterAuthorizeUrl, oauthTwitterExchange, type Options, refresh, web3Eip155Nonce, web3Eip155Verify, web3Exchange, web3Nonce, web3SolanaNonce, web3SolanaVerify } from './sdk.gen';
-export type { AccountApikeysCreateData, AccountApikeysCreateError, AccountApikeysCreateErrors, AccountApikeysCreateResponse, AccountApikeysCreateResponses, AccountApikeysListData, AccountApikeysListError, AccountApikeysListErrors, AccountApikeysListResponse, AccountApikeysListResponses, AccountApikeysRevokeData, AccountApikeysRevokeError, AccountApikeysRevokeErrors, AccountApikeysRevokeResponse, AccountApikeysRevokeResponses, AccountLinkEmailRequestData, AccountLinkEmailRequestError, AccountLinkEmailRequestErrors, AccountLinkEmailRequestResponse, AccountLinkEmailRequestResponses, AccountLinkEmailVerifyData, AccountLinkEmailVerifyError, AccountLinkEmailVerifyErrors, AccountLinkEmailVerifyResponse, AccountLinkEmailVerifyResponses, AccountLinkPasskeyDeleteData, AccountLinkPasskeyDeleteError, AccountLinkPasskeyDeleteErrors, AccountLinkPasskeyDeleteResponse, AccountLinkPasskeyDeleteResponses, AccountLinkPasskeyFinishData, AccountLinkPasskeyFinishError, AccountLinkPasskeyFinishErrors, AccountLinkPasskeyFinishResponse, AccountLinkPasskeyFinishResponses, AccountLinkPasskeyStartData, AccountLinkPasskeyStartError, AccountLinkPasskeyStartErrors, AccountLinkPasskeyStartResponse, AccountLinkPasskeyStartResponses, AccountLinkTotpSetupData, AccountLinkTotpSetupError, AccountLinkTotpSetupErrors, AccountLinkTotpSetupResponse, AccountLinkTotpSetupResponses, AccountLinkTotpUnlinkData, AccountLinkTotpUnlinkError, AccountLinkTotpUnlinkErrors, AccountLinkTotpUnlinkResponse, AccountLinkTotpUnlinkResponses, AccountLinkTotpVerifyData, AccountLinkTotpVerifyError, AccountLinkTotpVerifyErrors, AccountLinkTotpVerifyResponse, AccountLinkTotpVerifyResponses, AccountLinkWalletUnlinkData, AccountLinkWalletUnlinkError, AccountLinkWalletUnlinkErrors, AccountLinkWalletUnlinkResponse, AccountLinkWalletUnlinkResponses, AccountLinkWalletVerifyData, AccountLinkWalletVerifyError, AccountLinkWalletVerifyErrors, AccountLinkWalletVerifyResponse, AccountLinkWalletVerifyResponses, AccountPasskeysListData, AccountPasskeysListError, AccountPasskeysListErrors, AccountPasskeysListResponse, AccountPasskeysListResponses, AuthPasskeyExchangeData, AuthPasskeyExchangeError, AuthPasskeyExchangeErrors, AuthPasskeyExchangeResponse, AuthPasskeyExchangeResponses, AuthPasskeyResolveUserData, AuthPasskeyResolveUserError, AuthPasskeyResolveUserErrors, AuthPasskeyResolveUserResponse, AuthPasskeyResolveUserResponses, AuthPasskeyStartData, AuthPasskeyStartError, AuthPasskeyStartErrors, AuthPasskeyStartResponse, AuthPasskeyStartResponses, AuthPasskeyVerifyData, AuthPasskeyVerifyError, AuthPasskeyVerifyErrors, AuthPasskeyVerifyResponse, AuthPasskeyVerifyResponses, ChatData, ChatError, ChatErrors, ChatResponse, ChatResponses, ClientOptions, GetUserData, GetUserError, GetUserErrors, GetUserResponse, GetUserResponses, HealthCheckData, HealthCheckResponse, HealthCheckResponses, LogoutData, LogoutError, LogoutErrors, LogoutResponse, LogoutResponses, MagiclinkRequestData, MagiclinkRequestError, MagiclinkRequestErrors, MagiclinkRequestResponse, MagiclinkRequestResponses, MagiclinkVerifyData, MagiclinkVerifyError, MagiclinkVerifyErrors, MagiclinkVerifyResponse, MagiclinkVerifyResponses, OauthFacebookAuthorizeUrlData, OauthFacebookAuthorizeUrlError, OauthFacebookAuthorizeUrlErrors, OauthFacebookAuthorizeUrlResponse, OauthFacebookAuthorizeUrlResponses, OauthFacebookExchangeData, OauthFacebookExchangeError, OauthFacebookExchangeErrors, OauthFacebookExchangeResponse, OauthFacebookExchangeResponses, OauthGithubAuthorizeData, OauthGithubAuthorizeError, OauthGithubAuthorizeErrors, OauthGithubAuthorizeUrlData, OauthGithubAuthorizeUrlError, OauthGithubAuthorizeUrlErrors, OauthGithubAuthorizeUrlResponse, OauthGithubAuthorizeUrlResponses, OauthGithubExchangeData, OauthGithubExchangeError, OauthGithubExchangeErrors, OauthGithubExchangeResponse, OauthGithubExchangeResponses, OauthGoogleVerifyIdTokenData, OauthGoogleVerifyIdTokenError, OauthGoogleVerifyIdTokenErrors, OauthGoogleVerifyIdTokenResponse, OauthGoogleVerifyIdTokenResponses, OauthProvidersData, OauthProvidersResponse, OauthProvidersResponses, OauthTwitterAuthorizeUrlData, OauthTwitterAuthorizeUrlError, OauthTwitterAuthorizeUrlErrors, OauthTwitterAuthorizeUrlResponse, OauthTwitterAuthorizeUrlResponses, OauthTwitterExchangeData, OauthTwitterExchangeError, OauthTwitterExchangeErrors, OauthTwitterExchangeResponse, OauthTwitterExchangeResponses, RefreshData, RefreshError, RefreshErrors, RefreshResponse, RefreshResponses, Web3Eip155NonceData, Web3Eip155NonceError, Web3Eip155NonceErrors, Web3Eip155NonceResponse, Web3Eip155NonceResponses, Web3Eip155VerifyData, Web3Eip155VerifyError, Web3Eip155VerifyErrors, Web3Eip155VerifyResponse, Web3Eip155VerifyResponses, Web3ExchangeData, Web3ExchangeError, Web3ExchangeErrors, Web3ExchangeResponse, Web3ExchangeResponses, Web3NonceData, Web3NonceError, Web3NonceErrors, Web3NonceResponse, Web3NonceResponses, Web3SolanaNonceData, Web3SolanaNonceError, Web3SolanaNonceErrors, Web3SolanaNonceResponse, Web3SolanaNonceResponses, Web3SolanaVerifyData, Web3SolanaVerifyError, Web3SolanaVerifyErrors, Web3SolanaVerifyResponse, Web3SolanaVerifyResponses } from './types.gen';
+export { accountApikeysCreate, accountApikeysList, accountApikeysRevoke, accountLinkEmailRequest, accountLinkEmailVerify, accountLinkPasskeyDelete, accountLinkPasskeyFinish, accountLinkPasskeyStart, accountLinkTotpSetup, accountLinkTotpUnlink, accountLinkTotpVerify, accountLinkWalletUnlink, accountLinkWalletVerify, accountPasskeysList, accountProfileUpdate, authPasskeyExchange, authPasskeyResolveUser, authPasskeyStart, authPasskeyVerify, chat, getUser, healthCheck, logout, magiclinkRequest, magiclinkVerify, oauthFacebookAuthorizeUrl, oauthFacebookExchange, oauthGithubAuthorize, oauthGithubAuthorizeUrl, oauthGithubExchange, oauthGoogleVerifyIdToken, oauthProviders, oauthTwitterAuthorizeUrl, oauthTwitterExchange, type Options, refresh, web3Eip155Nonce, web3Eip155Verify, web3Exchange, web3Nonce, web3SolanaNonce, web3SolanaVerify } from './sdk.gen';
+export type { AccountApikeysCreateData, AccountApikeysCreateError, AccountApikeysCreateErrors, AccountApikeysCreateResponse, AccountApikeysCreateResponses, AccountApikeysListData, AccountApikeysListError, AccountApikeysListErrors, AccountApikeysListResponse, AccountApikeysListResponses, AccountApikeysRevokeData, AccountApikeysRevokeError, AccountApikeysRevokeErrors, AccountApikeysRevokeResponse, AccountApikeysRevokeResponses, AccountLinkEmailRequestData, AccountLinkEmailRequestError, AccountLinkEmailRequestErrors, AccountLinkEmailRequestResponse, AccountLinkEmailRequestResponses, AccountLinkEmailVerifyData, AccountLinkEmailVerifyError, AccountLinkEmailVerifyErrors, AccountLinkEmailVerifyResponse, AccountLinkEmailVerifyResponses, AccountLinkPasskeyDeleteData, AccountLinkPasskeyDeleteError, AccountLinkPasskeyDeleteErrors, AccountLinkPasskeyDeleteResponse, AccountLinkPasskeyDeleteResponses, AccountLinkPasskeyFinishData, AccountLinkPasskeyFinishError, AccountLinkPasskeyFinishErrors, AccountLinkPasskeyFinishResponse, AccountLinkPasskeyFinishResponses, AccountLinkPasskeyStartData, AccountLinkPasskeyStartError, AccountLinkPasskeyStartErrors, AccountLinkPasskeyStartResponse, AccountLinkPasskeyStartResponses, AccountLinkTotpSetupData, AccountLinkTotpSetupError, AccountLinkTotpSetupErrors, AccountLinkTotpSetupResponse, AccountLinkTotpSetupResponses, AccountLinkTotpUnlinkData, AccountLinkTotpUnlinkError, AccountLinkTotpUnlinkErrors, AccountLinkTotpUnlinkResponse, AccountLinkTotpUnlinkResponses, AccountLinkTotpVerifyData, AccountLinkTotpVerifyError, AccountLinkTotpVerifyErrors, AccountLinkTotpVerifyResponse, AccountLinkTotpVerifyResponses, AccountLinkWalletUnlinkData, AccountLinkWalletUnlinkError, AccountLinkWalletUnlinkErrors, AccountLinkWalletUnlinkResponse, AccountLinkWalletUnlinkResponses, AccountLinkWalletVerifyData, AccountLinkWalletVerifyError, AccountLinkWalletVerifyErrors, AccountLinkWalletVerifyResponse, AccountLinkWalletVerifyResponses, AccountPasskeysListData, AccountPasskeysListError, AccountPasskeysListErrors, AccountPasskeysListResponse, AccountPasskeysListResponses, AccountProfileUpdateData, AccountProfileUpdateError, AccountProfileUpdateErrors, AccountProfileUpdateResponse, AccountProfileUpdateResponses, AuthPasskeyExchangeData, AuthPasskeyExchangeError, AuthPasskeyExchangeErrors, AuthPasskeyExchangeResponse, AuthPasskeyExchangeResponses, AuthPasskeyResolveUserData, AuthPasskeyResolveUserError, AuthPasskeyResolveUserErrors, AuthPasskeyResolveUserResponse, AuthPasskeyResolveUserResponses, AuthPasskeyStartData, AuthPasskeyStartError, AuthPasskeyStartErrors, AuthPasskeyStartResponse, AuthPasskeyStartResponses, AuthPasskeyVerifyData, AuthPasskeyVerifyError, AuthPasskeyVerifyErrors, AuthPasskeyVerifyResponse, AuthPasskeyVerifyResponses, ChatData, ChatError, ChatErrors, ChatResponse, ChatResponses, ClientOptions, GetUserData, GetUserError, GetUserErrors, GetUserResponse, GetUserResponses, HealthCheckData, HealthCheckResponse, HealthCheckResponses, LogoutData, LogoutError, LogoutErrors, LogoutResponse, LogoutResponses, MagiclinkRequestData, MagiclinkRequestError, MagiclinkRequestErrors, MagiclinkRequestResponse, MagiclinkRequestResponses, MagiclinkVerifyData, MagiclinkVerifyError, MagiclinkVerifyErrors, MagiclinkVerifyResponse, MagiclinkVerifyResponses, OauthFacebookAuthorizeUrlData, OauthFacebookAuthorizeUrlError, OauthFacebookAuthorizeUrlErrors, OauthFacebookAuthorizeUrlResponse, OauthFacebookAuthorizeUrlResponses, OauthFacebookExchangeData, OauthFacebookExchangeError, OauthFacebookExchangeErrors, OauthFacebookExchangeResponse, OauthFacebookExchangeResponses, OauthGithubAuthorizeData, OauthGithubAuthorizeError, OauthGithubAuthorizeErrors, OauthGithubAuthorizeUrlData, OauthGithubAuthorizeUrlError, OauthGithubAuthorizeUrlErrors, OauthGithubAuthorizeUrlResponse, OauthGithubAuthorizeUrlResponses, OauthGithubExchangeData, OauthGithubExchangeError, OauthGithubExchangeErrors, OauthGithubExchangeResponse, OauthGithubExchangeResponses, OauthGoogleVerifyIdTokenData, OauthGoogleVerifyIdTokenError, OauthGoogleVerifyIdTokenErrors, OauthGoogleVerifyIdTokenResponse, OauthGoogleVerifyIdTokenResponses, OauthProvidersData, OauthProvidersResponse, OauthProvidersResponses, OauthTwitterAuthorizeUrlData, OauthTwitterAuthorizeUrlError, OauthTwitterAuthorizeUrlErrors, OauthTwitterAuthorizeUrlResponse, OauthTwitterAuthorizeUrlResponses, OauthTwitterExchangeData, OauthTwitterExchangeError, OauthTwitterExchangeErrors, OauthTwitterExchangeResponse, OauthTwitterExchangeResponses, RefreshData, RefreshError, RefreshErrors, RefreshResponse, RefreshResponses, Web3Eip155NonceData, Web3Eip155NonceError, Web3Eip155NonceErrors, Web3Eip155NonceResponse, Web3Eip155NonceResponses, Web3Eip155VerifyData, Web3Eip155VerifyError, Web3Eip155VerifyErrors, Web3Eip155VerifyResponse, Web3Eip155VerifyResponses, Web3ExchangeData, Web3ExchangeError, Web3ExchangeErrors, Web3ExchangeResponse, Web3ExchangeResponses, Web3NonceData, Web3NonceError, Web3NonceErrors, Web3NonceResponse, Web3NonceResponses, Web3SolanaNonceData, Web3SolanaNonceError, Web3SolanaNonceErrors, Web3SolanaNonceResponse, Web3SolanaNonceResponses, Web3SolanaVerifyData, Web3SolanaVerifyError, Web3SolanaVerifyErrors, Web3SolanaVerifyResponse, Web3SolanaVerifyResponses } from './types.gen';
diff --git a/packages/core/src/gen/sdk.gen.ts b/packages/core/src/gen/sdk.gen.ts
index 14bd001f..5ddbec8d 100644
--- a/packages/core/src/gen/sdk.gen.ts
+++ b/packages/core/src/gen/sdk.gen.ts
@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
-import type { AccountApikeysCreateData, AccountApikeysCreateErrors, AccountApikeysCreateResponses, AccountApikeysListData, AccountApikeysListErrors, AccountApikeysListResponses, AccountApikeysRevokeData, AccountApikeysRevokeErrors, AccountApikeysRevokeResponses, AccountLinkEmailRequestData, AccountLinkEmailRequestErrors, AccountLinkEmailRequestResponses, AccountLinkEmailVerifyData, AccountLinkEmailVerifyErrors, AccountLinkEmailVerifyResponses, AccountLinkPasskeyDeleteData, AccountLinkPasskeyDeleteErrors, AccountLinkPasskeyDeleteResponses, AccountLinkPasskeyFinishData, AccountLinkPasskeyFinishErrors, AccountLinkPasskeyFinishResponses, AccountLinkPasskeyStartData, AccountLinkPasskeyStartErrors, AccountLinkPasskeyStartResponses, AccountLinkTotpSetupData, AccountLinkTotpSetupErrors, AccountLinkTotpSetupResponses, AccountLinkTotpUnlinkData, AccountLinkTotpUnlinkErrors, AccountLinkTotpUnlinkResponses, AccountLinkTotpVerifyData, AccountLinkTotpVerifyErrors, AccountLinkTotpVerifyResponses, AccountLinkWalletUnlinkData, AccountLinkWalletUnlinkErrors, AccountLinkWalletUnlinkResponses, AccountLinkWalletVerifyData, AccountLinkWalletVerifyErrors, AccountLinkWalletVerifyResponses, AccountPasskeysListData, AccountPasskeysListErrors, AccountPasskeysListResponses, AuthPasskeyExchangeData, AuthPasskeyExchangeErrors, AuthPasskeyExchangeResponses, AuthPasskeyResolveUserData, AuthPasskeyResolveUserErrors, AuthPasskeyResolveUserResponses, AuthPasskeyStartData, AuthPasskeyStartErrors, AuthPasskeyStartResponses, AuthPasskeyVerifyData, AuthPasskeyVerifyErrors, AuthPasskeyVerifyResponses, ChatData, ChatErrors, ChatResponses, GetUserData, GetUserErrors, GetUserResponses, HealthCheckData, HealthCheckResponses, LogoutData, LogoutErrors, LogoutResponses, MagiclinkRequestData, MagiclinkRequestErrors, MagiclinkRequestResponses, MagiclinkVerifyData, MagiclinkVerifyErrors, MagiclinkVerifyResponses, OauthFacebookAuthorizeUrlData, OauthFacebookAuthorizeUrlErrors, OauthFacebookAuthorizeUrlResponses, OauthFacebookExchangeData, OauthFacebookExchangeErrors, OauthFacebookExchangeResponses, OauthGithubAuthorizeData, OauthGithubAuthorizeErrors, OauthGithubAuthorizeUrlData, OauthGithubAuthorizeUrlErrors, OauthGithubAuthorizeUrlResponses, OauthGithubExchangeData, OauthGithubExchangeErrors, OauthGithubExchangeResponses, OauthGoogleVerifyIdTokenData, OauthGoogleVerifyIdTokenErrors, OauthGoogleVerifyIdTokenResponses, OauthProvidersData, OauthProvidersResponses, OauthTwitterAuthorizeUrlData, OauthTwitterAuthorizeUrlErrors, OauthTwitterAuthorizeUrlResponses, OauthTwitterExchangeData, OauthTwitterExchangeErrors, OauthTwitterExchangeResponses, RefreshData, RefreshErrors, RefreshResponses, Web3Eip155NonceData, Web3Eip155NonceErrors, Web3Eip155NonceResponses, Web3Eip155VerifyData, Web3Eip155VerifyErrors, Web3Eip155VerifyResponses, Web3ExchangeData, Web3ExchangeErrors, Web3ExchangeResponses, Web3NonceData, Web3NonceErrors, Web3NonceResponses, Web3SolanaNonceData, Web3SolanaNonceErrors, Web3SolanaNonceResponses, Web3SolanaVerifyData, Web3SolanaVerifyErrors, Web3SolanaVerifyResponses } from './types.gen';
+import type { AccountApikeysCreateData, AccountApikeysCreateErrors, AccountApikeysCreateResponses, AccountApikeysListData, AccountApikeysListErrors, AccountApikeysListResponses, AccountApikeysRevokeData, AccountApikeysRevokeErrors, AccountApikeysRevokeResponses, AccountLinkEmailRequestData, AccountLinkEmailRequestErrors, AccountLinkEmailRequestResponses, AccountLinkEmailVerifyData, AccountLinkEmailVerifyErrors, AccountLinkEmailVerifyResponses, AccountLinkPasskeyDeleteData, AccountLinkPasskeyDeleteErrors, AccountLinkPasskeyDeleteResponses, AccountLinkPasskeyFinishData, AccountLinkPasskeyFinishErrors, AccountLinkPasskeyFinishResponses, AccountLinkPasskeyStartData, AccountLinkPasskeyStartErrors, AccountLinkPasskeyStartResponses, AccountLinkTotpSetupData, AccountLinkTotpSetupErrors, AccountLinkTotpSetupResponses, AccountLinkTotpUnlinkData, AccountLinkTotpUnlinkErrors, AccountLinkTotpUnlinkResponses, AccountLinkTotpVerifyData, AccountLinkTotpVerifyErrors, AccountLinkTotpVerifyResponses, AccountLinkWalletUnlinkData, AccountLinkWalletUnlinkErrors, AccountLinkWalletUnlinkResponses, AccountLinkWalletVerifyData, AccountLinkWalletVerifyErrors, AccountLinkWalletVerifyResponses, AccountPasskeysListData, AccountPasskeysListErrors, AccountPasskeysListResponses, AccountProfileUpdateData, AccountProfileUpdateErrors, AccountProfileUpdateResponses, AuthPasskeyExchangeData, AuthPasskeyExchangeErrors, AuthPasskeyExchangeResponses, AuthPasskeyResolveUserData, AuthPasskeyResolveUserErrors, AuthPasskeyResolveUserResponses, AuthPasskeyStartData, AuthPasskeyStartErrors, AuthPasskeyStartResponses, AuthPasskeyVerifyData, AuthPasskeyVerifyErrors, AuthPasskeyVerifyResponses, ChatData, ChatErrors, ChatResponses, GetUserData, GetUserErrors, GetUserResponses, HealthCheckData, HealthCheckResponses, LogoutData, LogoutErrors, LogoutResponses, MagiclinkRequestData, MagiclinkRequestErrors, MagiclinkRequestResponses, MagiclinkVerifyData, MagiclinkVerifyErrors, MagiclinkVerifyResponses, OauthFacebookAuthorizeUrlData, OauthFacebookAuthorizeUrlErrors, OauthFacebookAuthorizeUrlResponses, OauthFacebookExchangeData, OauthFacebookExchangeErrors, OauthFacebookExchangeResponses, OauthGithubAuthorizeData, OauthGithubAuthorizeErrors, OauthGithubAuthorizeUrlData, OauthGithubAuthorizeUrlErrors, OauthGithubAuthorizeUrlResponses, OauthGithubExchangeData, OauthGithubExchangeErrors, OauthGithubExchangeResponses, OauthGoogleVerifyIdTokenData, OauthGoogleVerifyIdTokenErrors, OauthGoogleVerifyIdTokenResponses, OauthProvidersData, OauthProvidersResponses, OauthTwitterAuthorizeUrlData, OauthTwitterAuthorizeUrlErrors, OauthTwitterAuthorizeUrlResponses, OauthTwitterExchangeData, OauthTwitterExchangeErrors, OauthTwitterExchangeResponses, RefreshData, RefreshErrors, RefreshResponses, Web3Eip155NonceData, Web3Eip155NonceErrors, Web3Eip155NonceResponses, Web3Eip155VerifyData, Web3Eip155VerifyErrors, Web3Eip155VerifyResponses, Web3ExchangeData, Web3ExchangeErrors, Web3ExchangeResponses, Web3NonceData, Web3NonceErrors, Web3NonceResponses, Web3SolanaNonceData, Web3SolanaNonceErrors, Web3SolanaNonceResponses, Web3SolanaVerifyData, Web3SolanaVerifyErrors, Web3SolanaVerifyResponses } from './types.gen';
export type Options = Options2 & {
/**
@@ -203,6 +203,21 @@ export const accountPasskeysList = (option
...options
});
+/**
+ * Update profile
+ *
+ * Update profile (name, username)
+ */
+export const accountProfileUpdate = (options: Options) => (options.client ?? client).patch({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/account/profile/',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
/**
* Generate AI chat response
*
diff --git a/packages/core/src/gen/types.gen.ts b/packages/core/src/gen/types.gen.ts
index c84f4474..541dafd0 100644
--- a/packages/core/src/gen/types.gen.ts
+++ b/packages/core/src/gen/types.gen.ts
@@ -623,6 +623,51 @@ export type AccountPasskeysListResponses = {
export type AccountPasskeysListResponse = AccountPasskeysListResponses[keyof AccountPasskeysListResponses];
+export type AccountProfileUpdateData = {
+ body: {
+ name?: string;
+ username?: string | unknown;
+ };
+ path?: never;
+ query?: never;
+ url: '/account/profile/';
+};
+
+export type AccountProfileUpdateErrors = {
+ /**
+ * Default Response
+ */
+ 401: {
+ code: string;
+ message: string;
+ };
+ /**
+ * Default Response
+ */
+ 409: {
+ code: string;
+ message: string;
+ };
+};
+
+export type AccountProfileUpdateError = AccountProfileUpdateErrors[keyof AccountProfileUpdateErrors];
+
+export type AccountProfileUpdateResponses = {
+ /**
+ * Default Response
+ */
+ 200: {
+ user: {
+ id: string;
+ email: string | unknown;
+ name: string | unknown;
+ username: string | unknown;
+ };
+ };
+};
+
+export type AccountProfileUpdateResponse = AccountProfileUpdateResponses[keyof AccountProfileUpdateResponses];
+
export type ChatData = {
body: {
messages: Array<{
@@ -715,7 +760,18 @@ export type MagiclinkRequestResponse = MagiclinkRequestResponses[keyof Magiclink
export type MagiclinkVerifyData = {
body: {
+ /**
+ * 6-digit code
+ */
token: string;
+ /**
+ * Verification row id (from magic link URL)
+ */
+ verificationId?: string;
+ /**
+ * Email (for code entry on login page)
+ */
+ email?: string;
};
path?: never;
query?: never;
@@ -723,6 +779,13 @@ export type MagiclinkVerifyData = {
};
export type MagiclinkVerifyErrors = {
+ /**
+ * Default Response
+ */
+ 400: {
+ code: string;
+ message: string;
+ };
/**
* Default Response
*/
@@ -737,6 +800,13 @@ export type MagiclinkVerifyErrors = {
code: string;
message: string;
};
+ /**
+ * Default Response
+ */
+ 429: {
+ code: string;
+ message: string;
+ };
};
export type MagiclinkVerifyError = MagiclinkVerifyErrors[keyof MagiclinkVerifyErrors];
@@ -1389,6 +1459,7 @@ export type GetUserResponses = {
id: string;
email: string | unknown;
name: string | unknown;
+ username: string | unknown;
emailVerified: boolean | unknown;
wallet?: {
chain: string;
diff --git a/packages/email/emails/magic-link-login.tsx b/packages/email/emails/magic-link-login.tsx
index a4ec3630..3250f09e 100644
--- a/packages/email/emails/magic-link-login.tsx
+++ b/packages/email/emails/magic-link-login.tsx
@@ -11,11 +11,17 @@ import {
interface Props {
magicLink: string
+ loginCode: string
expirationMinutes?: number
fullName?: string
}
-export function MagicLinkLoginEmail({ magicLink, expirationMinutes = 15, fullName = '' }: Props) {
+export function MagicLinkLoginEmail({
+ magicLink,
+ loginCode,
+ expirationMinutes = 15,
+ fullName = '',
+}: Props): React.JSX.Element {
const firstName = fullName ? fullName.split(' ').at(0) : ''
const previewText = `${firstName ? `Hi ${firstName}, ` : ''}Sign in to your account`
const themeClasses = getEmailThemeClasses()
@@ -47,10 +53,18 @@ export function MagicLinkLoginEmail({ magicLink, expirationMinutes = 15, fullNam
{firstName ? `Hi ${firstName}` : 'Hello'},
- Click the button below to sign in. This link will expire in {expirationMinutes} minutes.
+ Use the code below or click the button to sign in. This link will expire in{' '}
+ {expirationMinutes} minutes.
-
+
diff --git a/packages/react/README.md b/packages/react/README.md
index d366abc9..864f2fec 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -4,18 +4,28 @@ Provides React Query hooks for `@repo/core` API functions.
## Overview
-This package provides React Query hooks that wrap `@repo/core` API client methods. All types are imported from `@repo/core`, ensuring a single source of truth for API types and eliminating duplication.
+This package provides React Query hooks that wrap `@repo/core` API client methods. **Hooks and helpers only—no UI components.** Route-specific UI (e.g. login form) lives in apps, collocated by route. All types are imported from `@repo/core`, ensuring a single source of truth for API types and eliminating duplication.
## Exports
- `ApiProvider` - Provider component that makes API client available to hooks
+- `createReactApiConfig` - Utility function to normalize API configuration
- `useReactApiConfig` - Hook to access API client and query defaults from context
+- `useApiKeysList`, `useCreateApiKey`, `useRevokeApiKey` - API keys CRUD hooks
+- `useChatFromConfig` - Chat hook with AI SDK integration
- `useHealthCheck` - React Query hook for health check endpoint
-- `useVerifyWeb3Auth` - Mutation hook: given `{ chain, message, signature, domain }`, calls auth verify endpoint (SIWE/SIWS)
-- `useVerifyLinkWallet` - Mutation hook: given `{ chain, message, signature }`, calls link wallet verify endpoint
-- `useMagicLink` - React Query mutation hook for magic link request endpoint
-- `LoginForm` - Framework-agnostic login form component
-- `createReactApiConfig` - Utility function to normalize API configuration
+- `useLinkEmail` - Mutation hook for link-email request
+- `useMagicLink` - Mutation hook for magic link request endpoint
+- `useMagicLinkVerify` - Mutation hook for magic link verification
+- `useOAuthLogin`, `useOAuthProviders` - OAuth login and provider detection hooks
+- `usePasskeyAuth`, `usePasskeyDiscovery`, `usePasskeyRegister`, `usePasskeyRemove`, `usePasskeysList` - Passkey hooks
+- `useProfileUpdate` - Mutation hook for profile update
+- `useSession` - Session hook (decoded JWT claims)
+- `useTotpSetup`, `useTotpUnlink`, `useTotpVerify` - TOTP hooks
+- `useUser` - Query hook for current user (GET /auth/session/user)
+- `useVerifyLinkWallet` - Mutation hook for link wallet verify
+- `useVerifyWeb3Auth` - Mutation hook for Web3 auth verify (SIWE/SIWS)
+- `useWebAuthnAvailable` - Hook to check WebAuthn availability
## Usage
@@ -95,23 +105,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}
```
-#### Using Components
-
-Components from `@repo/react` (like `LoginForm`) are client components and can be used directly in your pages:
-
-```tsx
-// app/login/page.tsx
-import { LoginForm } from '@repo/react'
-
-export default function LoginPage() {
- return (
-
-
-
- )
-}
-```
-
#### Using Hooks
Hooks must be used in client components. Mark components with `'use client'`:
diff --git a/packages/react/src/components/login-form.spec.tsx b/packages/react/src/components/login-form.spec.tsx
deleted file mode 100644
index 82fb1c6e..00000000
--- a/packages/react/src/components/login-form.spec.tsx
+++ /dev/null
@@ -1,314 +0,0 @@
-import type { MagiclinkRequestData, MagiclinkRequestResponse } from '@repo/core'
-import { createClient } from '@repo/core'
-import type { UseMutationResult } from '@tanstack/react-query'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { act, render, screen, waitFor } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import * as useMagicLinkModule from '../hooks/use-magic-link'
-import { ApiProvider } from '../provider'
-import { LoginForm } from './login-form'
-
-// Mock the useMagicLink hook
-vi.mock('../hooks/use-magic-link', () => ({
- useMagicLink: vi.fn(),
-}))
-
-describe('LoginForm', () => {
- let queryClient: QueryClient
- let mockMutate: ReturnType
- let mockClient: ReturnType
- let capturedOnSuccess:
- | ((
- data: { ok: boolean },
- variables: MagiclinkRequestData['body'],
- onMutateResult: unknown,
- context: unknown,
- ) => void)
- | undefined
- let capturedOnError:
- | ((
- error: Error,
- variables: MagiclinkRequestData['body'],
- onMutateResult: unknown,
- context: unknown,
- ) => void)
- | undefined
-
- beforeEach(() => {
- queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- retry: false,
- },
- mutations: {
- retry: false,
- },
- },
- })
- mockMutate = vi.fn()
- mockClient = createClient({ baseUrl: 'http://localhost:3000' })
- capturedOnSuccess = undefined
- capturedOnError = undefined
-
- // Setup default mock return value
- // Capture onSuccess and onError callbacks from hook options
- vi.mocked(useMagicLinkModule.useMagicLink).mockImplementation(options => {
- if (options?.onSuccess) capturedOnSuccess = options.onSuccess
-
- if (options?.onError) capturedOnError = options.onError
-
- return {
- mutate: mockMutate,
- isPending: false,
- error: null,
- data: undefined,
- isError: false,
- isSuccess: false,
- reset: vi.fn(),
- mutateAsync: vi.fn(),
- status: 'idle',
- failureCount: 0,
- failureReason: null,
- submittedAt: 0,
- variables: undefined,
- context: undefined,
- isPaused: false,
- isIdle: true,
- } as UseMutationResult
- })
- })
-
- afterEach(() => {
- vi.clearAllMocks()
- })
-
- function renderLoginForm(opts?: {
- initialError?: string
- callbackUrl?: string
- defaultEmail?: string
- onMagicLinkSent?: (email: string) => void
- }) {
- return render(
-
-
-
-
- ,
- )
- }
-
- it('should call mutate with email and callbackUrl on form submit', async () => {
- renderLoginForm()
-
- const emailInput = screen.getByLabelText(/email/i)
- const submitButton = screen.getByRole('button', { name: /send magic link/i })
-
- await userEvent.type(emailInput, 'test@example.com')
- await userEvent.click(submitButton)
-
- await waitFor(() => {
- expect(mockMutate).toHaveBeenCalledWith({
- email: 'test@example.com',
- callbackUrl: expect.any(String),
- })
- })
- })
-
- it('should use provided callbackUrl prop', async () => {
- renderLoginForm({ callbackUrl: 'https://example.com/custom-callback' })
-
- const emailInput = screen.getByLabelText(/email/i)
- const submitButton = screen.getByRole('button', { name: /send magic link/i })
-
- await userEvent.type(emailInput, 'test@example.com')
- await userEvent.click(submitButton)
-
- await waitFor(() => {
- expect(mockMutate).toHaveBeenCalledWith({
- email: 'test@example.com',
- callbackUrl: 'https://example.com/custom-callback',
- })
- })
- })
-
- it('should display email validation errors below input field using FieldError', async () => {
- renderLoginForm()
-
- const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- const form = emailInput.closest('form') as HTMLFormElement
-
- // Remove required attribute to allow any submission
- emailInput.removeAttribute('required')
-
- // Type invalid email
- await userEvent.type(emailInput, 'invalid-email')
- form.requestSubmit()
-
- // Wait for validation error to appear
- await waitFor(() => {
- const errorElement = screen.getByRole('alert')
- expect(errorElement).toBeInTheDocument()
- expect(errorElement).toHaveTextContent(/valid email/i)
- expect(errorElement).toHaveAttribute('data-slot', 'field-error')
- })
-
- // Verify error is below the input (within the same Field component)
- const fieldElement = emailInput.closest('[data-slot="field"]')
- expect(fieldElement).toBeInTheDocument()
- const errorInField = fieldElement?.querySelector('[data-slot="field-error"]')
- expect(errorInField).toBeInTheDocument()
- })
-
- it('should display API validation errors below input field when error has VALIDATION_ERROR code', async () => {
- const validationError = new Error('Email is required') as Error & { code?: string }
- validationError.code = 'VALIDATION_ERROR'
-
- renderLoginForm()
-
- const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- const submitButton = screen.getByRole('button', { name: /send magic link/i })
-
- await userEvent.type(emailInput, 'test@example.com')
- await userEvent.click(submitButton)
-
- // Simulate mutation error by calling captured onError callback
- await act(async () => {
- if (capturedOnError)
- capturedOnError(
- validationError,
- { email: 'test@example.com', callbackUrl: '/' },
- undefined,
- undefined,
- )
- })
-
- // Wait for error to appear
- await waitFor(() => {
- const errorElement = screen.getByRole('alert')
- expect(errorElement).toBeInTheDocument()
- expect(errorElement).toHaveTextContent(/email is required/i)
- })
- })
-
- it('should display initialError prop below input field', () => {
- renderLoginForm({ initialError: 'Invalid or expired magic link' })
-
- const errorElement = screen.getByRole('alert')
- expect(errorElement).toBeInTheDocument()
- expect(errorElement).toHaveTextContent(/invalid or expired magic link/i)
-
- // Verify error is below the input field
- const emailInput = screen.getByLabelText(/email/i)
- const fieldElement = emailInput.closest('[data-slot="field"]')
- expect(fieldElement).toBeInTheDocument()
- const errorInField = fieldElement?.querySelector('[data-slot="field-error"]')
- expect(errorInField).toBeInTheDocument()
- })
-
- it('should display success message below input field when magic link is sent', async () => {
- renderLoginForm()
-
- const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- const submitButton = screen.getByRole('button', { name: /send magic link/i })
-
- await userEvent.type(emailInput, 'test@example.com')
- await userEvent.click(submitButton)
-
- await act(async () => {
- if (capturedOnSuccess)
- capturedOnSuccess(
- { ok: true },
- { email: 'test@example.com', callbackUrl: '/' },
- undefined,
- undefined,
- )
- })
-
- // Wait for success message to appear
- const successMessage = await screen.findByText(
- /check your email for the magic link/i,
- {},
- { timeout: 10000 },
- )
- expect(successMessage).toBeInTheDocument()
-
- // Verify success message is below the input (within the same Field component)
- const currentEmailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- const fieldElement = currentEmailInput.closest('[data-slot="field"]')
- expect(fieldElement).toBeInTheDocument()
- expect(fieldElement).toHaveTextContent(/check your email for the magic link/i)
-
- // Verify email input is cleared
- expect(currentEmailInput.value).toBe('')
- })
-
- it('should show pending state when mutation is in progress', async () => {
- vi.mocked(useMagicLinkModule.useMagicLink).mockImplementation(options => {
- if (options?.onSuccess) capturedOnSuccess = options.onSuccess
-
- if (options?.onError) capturedOnError = options.onError
-
- return {
- mutate: mockMutate,
- isPending: true,
- error: null,
- data: undefined,
- isError: false,
- isSuccess: false,
- reset: vi.fn(),
- mutateAsync: vi.fn(),
- status: 'pending',
- failureCount: 0,
- failureReason: null,
- submittedAt: Date.now(),
- variables: { email: 'test@example.com', callbackUrl: '/' },
- context: undefined,
- isPaused: false,
- isIdle: false,
- } as UseMutationResult
- })
-
- renderLoginForm()
-
- const submitButton = screen.getByRole('button', { name: /sending/i })
- expect(submitButton).toBeInTheDocument()
- expect(submitButton).toBeDisabled()
- })
-
- it('should pre-fill email when defaultEmail prop is provided', () => {
- renderLoginForm({ defaultEmail: 'prev@example.com' })
-
- const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- expect(emailInput.value).toBe('prev@example.com')
- })
-
- it('should call onMagicLinkSent with email when magic link request succeeds', async () => {
- const onMagicLinkSent = vi.fn()
-
- renderLoginForm({ onMagicLinkSent })
-
- const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement
- const submitButton = screen.getByRole('button', { name: /send magic link/i })
-
- await userEvent.type(emailInput, 'sent@example.com')
- await userEvent.click(submitButton)
-
- await act(async () => {
- if (capturedOnSuccess)
- capturedOnSuccess(
- { ok: true },
- { email: 'sent@example.com', callbackUrl: '/' },
- undefined,
- undefined,
- )
- })
-
- expect(onMagicLinkSent).toHaveBeenCalledWith('sent@example.com')
- })
-})
diff --git a/packages/react/src/hooks/use-magic-link-verify.ts b/packages/react/src/hooks/use-magic-link-verify.ts
new file mode 100644
index 00000000..7ccca595
--- /dev/null
+++ b/packages/react/src/hooks/use-magic-link-verify.ts
@@ -0,0 +1,32 @@
+import type { MagiclinkVerifyData, MagiclinkVerifyResponse } from '@repo/core'
+import type { UseMutationOptions } from '@tanstack/react-query'
+import { useMutation } from '@tanstack/react-query'
+import { useReactApiConfig } from '../context'
+
+/**
+ * React Query mutation hook for magic link verify endpoint.
+ *
+ * Exchanges 6-digit login code for JWTs. Uses the API client configured in
+ * `ReactApiProvider` and applies default mutation options from context.
+ *
+ * @param options - Additional TanStack Query mutation options (merged with context defaults)
+ * @returns TanStack Query mutation result
+ */
+export function useMagicLinkVerify(
+ options?: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+ >,
+) {
+ const { client, queryClientDefaults } = useReactApiConfig()
+
+ return useMutation({
+ mutationFn: async variables =>
+ client.auth.magiclink.verify({
+ body: variables,
+ throwOnError: true,
+ }),
+ ...queryClientDefaults,
+ ...options,
+ })
+}
diff --git a/packages/react/src/hooks/use-profile-update.ts b/packages/react/src/hooks/use-profile-update.ts
new file mode 100644
index 00000000..0220c217
--- /dev/null
+++ b/packages/react/src/hooks/use-profile-update.ts
@@ -0,0 +1,23 @@
+'use client'
+
+import type { AccountProfileUpdateData } from '@repo/core'
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { useReactApiConfig } from '../context'
+
+const userQueryKey = ['auth', 'session', 'user'] as const
+
+export function useProfileUpdate() {
+ const { client } = useReactApiConfig()
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (body: AccountProfileUpdateData['body']) =>
+ client.account.profile({
+ body,
+ throwOnError: true,
+ }),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: userQueryKey })
+ },
+ })
+}
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 42c96a63..da0bfb74 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -1,5 +1,3 @@
-// Export components
-export { LoginForm } from './components/login-form'
export { useReactApiConfig } from './context'
export { useApiKeysList, useCreateApiKey, useRevokeApiKey } from './hooks/use-api-keys'
// Export hooks
@@ -7,11 +5,13 @@ export { useChatFromConfig } from './hooks/use-chat'
export { useHealthCheck } from './hooks/use-health-check'
export { useLinkEmail } from './hooks/use-link-email'
export { useMagicLink } from './hooks/use-magic-link'
+export { useMagicLinkVerify } from './hooks/use-magic-link-verify'
export { type OAuthRedirectProvider, useOAuthLogin } from './hooks/use-oauth-login'
export { useOAuthProviders } from './hooks/use-oauth-providers'
export { usePasskeyAuth } from './hooks/use-passkey-auth'
export { usePasskeyDiscovery } from './hooks/use-passkey-discovery'
export { usePasskeyRegister, usePasskeyRemove, usePasskeysList } from './hooks/use-passkeys'
+export { useProfileUpdate } from './hooks/use-profile-update'
export { useSession } from './hooks/use-session'
export { useTotpSetup, useTotpUnlink, useTotpVerify } from './hooks/use-totp'
export { useUser } from './hooks/use-user'
diff --git a/packages/ui/src/components/scroll-area.tsx b/packages/ui/src/components/scroll-area.tsx
index 527e87bd..caaca770 100644
--- a/packages/ui/src/components/scroll-area.tsx
+++ b/packages/ui/src/components/scroll-area.tsx
@@ -1,27 +1,38 @@
'use client'
-import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
import { cn } from '@repo/ui/lib/utils'
-import type * as React from 'react'
+import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui'
+import type { ComponentProps } from 'react'
function ScrollArea({
className,
children,
+ orientation = 'both',
...props
-}: React.ComponentProps) {
+}: ComponentProps & {
+ orientation?: 'vertical' | 'horizontal' | 'both'
+}) {
return (
{children}
-
+ {(orientation === 'vertical' || orientation === 'both') && }
+ {(orientation === 'horizontal' || orientation === 'both') && (
+
+ )}
)
@@ -31,7 +42,7 @@ function ScrollBar({
className,
orientation = 'vertical',
...props
-}: React.ComponentProps) {
+}: ComponentProps) {
return (
)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7964d890..1f96c2e9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -140,6 +140,9 @@ importers:
'@ai-sdk/openai':
specifier: ^3.0.0
version: 3.0.41(zod@4.3.6)
+ '@faker-js/faker':
+ specifier: ^9.4.0
+ version: 9.9.0
'@fastify/autoload':
specifier: ^6.3.1
version: 6.3.1
@@ -2165,6 +2168,10 @@ packages:
'@noble/hashes':
optional: true
+ '@faker-js/faker@9.9.0':
+ resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==}
+ engines: {node: '>=18.0.0', npm: '>=9.0.0'}
+
'@fastify/accept-negotiator@2.0.1':
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
@@ -14366,6 +14373,8 @@ snapshots:
optionalDependencies:
'@noble/hashes': 2.0.1
+ '@faker-js/faker@9.9.0': {}
+
'@fastify/accept-negotiator@2.0.1': {}
'@fastify/ajv-compiler@4.0.5':
@@ -19807,14 +19816,6 @@ snapshots:
optionalDependencies:
vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
- '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
- dependencies:
- '@vitest/spy': 4.0.18
- estree-walker: 3.0.3
- magic-string: 0.30.21
- optionalDependencies:
- vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
-
'@vitest/pretty-format@4.0.18':
dependencies:
tinyrainbow: 3.0.3
@@ -23220,7 +23221,7 @@ snapshots:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
- minimatch: 9.0.7
+ minimatch: 10.2.3
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
@@ -28146,7 +28147,7 @@ snapshots:
vitest@4.0.18(@edge-runtime/vm@3.2.0)(@opentelemetry/api@1.9.0)(@types/node@25.3.5)(@vitest/ui@4.0.18)(jiti@2.6.1)(jsdom@28.1.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.18
- '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
+ '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
'@vitest/pretty-format': 4.0.18
'@vitest/runner': 4.0.18
'@vitest/snapshot': 4.0.18