diff --git a/auth-demo/app/api-demo/page.tsx b/auth-demo/app/api-demo/page.tsx index af7ee38..d491af6 100644 --- a/auth-demo/app/api-demo/page.tsx +++ b/auth-demo/app/api-demo/page.tsx @@ -1,7 +1,8 @@ 'use client'; import { Protect, useHyperyAuth } from '@hyperyai/sdk'; -import Link from 'next/link'; +import { Terminal, TriangleAlert } from 'lucide-react'; +import { BackLink, C, Card, CodeBlock, Note, Page, PageHeader, button } from '@/components/ui'; import { useState } from 'react'; export default function ApiDemoPage() { @@ -64,121 +65,53 @@ function ApiDemoContent() { }; return ( -
-
- - - - - Back to Home - - -
-
-
- - - -
-

API Demo

-
- -

- Test authenticated API requests using the getAccessToken() hook. -

- - {/* Actions */} -
-
- -

- Retrieves your current access token (auto-refreshes if expired) -

-
- -
- -

- Calls /api/user/me with Bearer token -

+ ))} + + + {token && ( +
+

Access token

+
{token}
+

Send it in the Authorization header.

+ )} - {/* Token Display */} - {token && ( -
-

Access Token:

-
- {token} -
-

- This token is automatically included in the Authorization header -

-
- )} - - {/* Response Display */} - {response && ( -
-

API Response:

-
-
-                  {JSON.stringify(response, null, 2)}
-                
-
-
- )} - - {/* Error Display */} - {error && ( -
-
-

Error:

-

{error}

-
-
- )} + {response && } - {/* Code Example */} -
-

Example Code:

-
-{`import { useHyperyAuth } from '@hyperyai/sdk';
+        {error && (
+          } title="Request failed">
+            {error}
+          
+        )}
+
+         {
     // Get valid token (auto-refreshes if needed)
     const token = await getAccessToken();
-    
-    // Make authenticated request
+
     const response = await fetch('/api/endpoint', {
-      headers: {
-        Authorization: \`Bearer \${token}\`,
-      },
+      headers: { Authorization: \`Bearer \${token}\` },
     });
-    
     return response.json();
   };
 }`}
-            
-
-
+ />
-
+ ); } - diff --git a/auth-demo/app/callback/page.tsx b/auth-demo/app/callback/page.tsx index 2e466d0..37ec6f8 100644 --- a/auth-demo/app/callback/page.tsx +++ b/auth-demo/app/callback/page.tsx @@ -3,6 +3,8 @@ import { useEffect } from 'react'; import { useHyperyAuth } from '@hyperyai/sdk'; import { useRouter } from 'next/navigation'; +import { X } from 'lucide-react'; +import { Spinner } from '@/components/ui'; export default function CallbackPage() { const { isAuthenticated, isLoading, error } = useHyperyAuth(); @@ -31,38 +33,25 @@ export default function CallbackPage() { }, [isAuthenticated, isLoading, error, router]); return ( -
-
+
+
{error ? ( <> -
- - - +
+
-

Authentication Failed

-

{error}

-

Redirecting back to home...

+

Authentication failed

+

{error}

+

Redirecting back to home…

) : ( <> -
-

Completing Sign In

-

Please wait while we authenticate you...

+ +

Completing sign in

+

One moment while we authenticate you…

)}
); } - diff --git a/auth-demo/app/checkout/page.tsx b/auth-demo/app/checkout/page.tsx index 63700f4..10e899c 100644 --- a/auth-demo/app/checkout/page.tsx +++ b/auth-demo/app/checkout/page.tsx @@ -10,6 +10,8 @@ import { BuyButton, useCheckout, useHyperyAuth } from '@hyperyai/sdk'; import { useState } from 'react'; +import { CreditCard } from 'lucide-react'; +import { C, Card, Page, PageHeader, button } from '@/components/ui'; export default function CheckoutDemoPage() { const { isAuthenticated, interactionMode } = useHyperyAuth(); @@ -31,51 +33,49 @@ export default function CheckoutDemoPage() { }; return ( -
-

Auth + charge demo

-

- Signed in: {isAuthenticated ? 'yes' : 'no'} · interaction mode:{' '} - {interactionMode} -

- -
-

Marketplace purchase (<BuyButton>)

-

- Charges a seller app via Stripe Connect. Logs you in and adds a card first if needed. -

- alert(`Paid! paymentIntent=${r.paymentIntentId}`)} - onError={(e) => alert(`Purchase failed: ${e.message}`)} - /> -
- -
-

Buy AI credits (useCheckout topup)

-

- Tops up your own credit balance. Same login → add-card → charge chain. -

- - {topupResult &&

{topupResult}

} -
-
+ + } + title="Auth + charge" + description="Neither button needs you signed in: the SDK walks you through sign-in, adding a card and the charge." + /> +
+ + Signed in: {isAuthenticated ? 'yes' : 'no'} + + + Interaction mode: {interactionMode} + +
+
+ +

Marketplace purchase

+

+ {''} charges a seller app via Stripe Connect. +

+
+ alert(`Paid! paymentIntent=${r.paymentIntentId}`)} + onError={(e) => alert(`Purchase failed: ${e.message}`)} + /> +
+
+ +

Buy AI credits

+

+ useCheckout() tops up your own balance. +

+
+ +
+ {topupResult &&

{topupResult}

} +
+
+
); } diff --git a/auth-demo/app/examples/page.tsx b/auth-demo/app/examples/page.tsx index 2dfa1d6..9c0c914 100644 --- a/auth-demo/app/examples/page.tsx +++ b/auth-demo/app/examples/page.tsx @@ -10,431 +10,311 @@ import { UserProfile, useAuth, useUser, - Protect, - RedirectToSignIn, AuthModal, AuthButton, } from '@hyperyai/sdk'; import Link from 'next/link'; import { useState } from 'react'; +import { CircleCheck, Info, TriangleAlert } from 'lucide-react'; +import { Card, CodeBlock, Note, Page, PageHeader, Section, button, cx } from '@/components/ui'; + +function Hint({ children }: { children: React.ReactNode }) { + return

{children}

; +} + +const SIGNED_IN_HINT = "You're already signed in. Sign out to try this."; export default function ExamplesPage() { return ( -
- {/* Header */} -
-
-
-
- -
-

Hypery Auth

- - | - Examples -
- - - - -
-
-
- -
-
-

Component Examples

-

- Complete showcase of all @hyperyai/sdk components and hooks -

-
+ + -
- {/* Modal Components */} -
-
- +
+
+ router.push('/dashboard')} > Sign In `} - > - -
- Sign In - - Get Started - - - Outline Style - -
-
- -

- You're already signed in! Sign out to test this. -

-
-
- - + +
+ Sign In + + Get Started + + + Outline Style + +
+
+ + {SIGNED_IN_HINT} + +
+ + setIsOpen(false)} initialMode="signin" - onSuccess={() => console.log('Success!')} + showEmailPassword + branding={{ appName: 'Acme' }} />`} - > - - - - -

- You're already signed in! Sign out to test this. -

-
-
-
-
- - {/* Authentication Components */} -
-
- + + + + + {SIGNED_IN_HINT} + + +
+
+ +
+
+ `} - > - + > + +
- - -

- You're already signed in! Sign out to test this. -

-
- - - + + + {SIGNED_IN_HINT} + + + + `} + > + +
+ +
+
+ + {SIGNED_IN_HINT} + +
+ + console.log('Signed in!')} />`} - > - - - - -

- You're already signed in! Sign out to test this. -

-
-
- - `} - > - - - - -

- You're already signed in! Sign out to test this. -

-
-
- + > + + + + + {SIGNED_IN_HINT} + + + +
`} > - +
+ +
-

- Sign in to see the user button -

+ Sign in to see the user button.
`} + description="The signed-in user as a card." + code={``} > -

- Sign in to see your profile -

+ Sign in to see your profile.
-
- - {/* Control Components */} -
-
- +
+
+ +
+
+

You can see this!

`} - > -
- -
-

- ✓ You're signed in, so you can see this! -

-
-
- -
-

- You're not signed in. The SignedIn component won't show. -

-
-
-
-
- - + > + + }> + You're signed in, so you can see this. + + + + }>You're signed out, so SignedIn renders nothing. + + + + `} - > -
- -
-

- ✓ You're signed out, so you can see this! -

- -
-
- -
-

- You're signed in. The SignedOut component won't show. -

-
-
+ > + +
+ }> + You're signed out, so you can see this. + +
- - - } -> +
+ + }>You're signed in, so SignedOut renders nothing. + + + + }> `} - > -
-

- See the protected page for a live example -

-
-
- - `} - > - -

- You're signed in, so it won't redirect -

-
- -

- ⚠️ Disabled in this example to prevent redirect loop -

-
-
-
-
- - {/* Hooks */} -
-
- + }> + See the{' '} + + protected page + {' '} + for a live example. + + + + `} + > + + You're signed in, so it won't redirect. + + + }> + Disabled on this page to avoid a redirect loop. + + + +
+
+ +
+
+ - - + > + + - - - -
-
- - {/* Styling Examples */} -
-
- `} - > - - - - -
- Primary Style -
-
-
- + > + + +
+
+ +
+
+ {(['primary', 'secondary', 'outline'] as const).map((variant) => ( `} + key={variant} + title={`variant="${variant}"`} + description={ + variant === 'primary' ? 'The brand fill.' : variant === 'secondary' ? 'A quiet filled button.' : 'A hairline outline.' + } + code={``} > - - - -
- Secondary Style +
+
- - - - `} - > - - -
- Outline Style -
+ {SIGNED_IN_HINT}
-
-
-
- - {/* Back to Home */} -
- - - - - Back to Home - -
-
-
- ); -} - -function Section({ - title, - description, - children, -}: { - title: string; - description: string; - children: React.ReactNode; -}) { - return ( -
-
-

{title}

-

{description}

+ ))} +
+ +
- {children} - + ); } @@ -449,64 +329,74 @@ function ExampleCard({ code: string; children: React.ReactNode; }) { - const [showCode, setShowCode] = useState(false); + const [tab, setTab] = useState<'preview' | 'code'>('preview'); return ( -
-
-
-
-

{title}

-

{description}

-
- + +
+
+

{title}

+

{description}

+
+ {(['preview', 'code'] as const).map((t) => ( + + ))} +
+
- {showCode && ( -
-
-              {code}
-            
+
+ {tab === 'code' ? ( + + ) : ( +
+
{children}
)} - -
{children}
+ + ); +} + +function Row({ k, v, tone }: { k: string; v: string; tone?: 'success' | 'danger' | 'muted' }) { + return ( +
+ {k} + + {v} +
); } function UseAuthExample() { - const { isAuthenticated, isLoading, user, login, logout } = useAuth(); + const { isAuthenticated, isLoading, user } = useAuth(); return ( -
-
- isAuthenticated: - - {String(isAuthenticated)} - -
-
- isLoading: - {String(isLoading)} -
-
- user: - - {user ? 'object' : 'null'} - -
-
-
Methods:
-
• login()
-
• logout()
-
+
+ + + +
); } @@ -514,27 +404,16 @@ function UseAuthExample() { function UseUserExample() { const { user, isLoading } = useUser(); - if (isLoading) { - return

Loading...

; - } - - if (!user) { - return

Not signed in

; - } + if (isLoading) return Loading…; + if (!user) return Not signed in, so user is null.; return ( -
-
-
user.name
-
{user.name}
-
-
-
user.email
-
{user.email}
-
-
-
user.id
-
{user.id}
+
+ + +
+ user.id + {user.id}
); @@ -544,28 +423,24 @@ function AuthModalExample() { const [isOpen, setIsOpen] = useState(false); return ( - <> - - + setIsOpen(false)} initialMode="signin" + showEmailPassword onSuccess={() => { setIsOpen(false); alert('Successfully signed in!'); }} branding={{ appName: 'Hypery', - primaryColor: '#8b5cf6', }} /> - +
); } - diff --git a/auth-demo/app/gallery/page.tsx b/auth-demo/app/gallery/page.tsx new file mode 100644 index 0000000..582c490 --- /dev/null +++ b/auth-demo/app/gallery/page.tsx @@ -0,0 +1,305 @@ +'use client'; + +/** + * Every SDK component in its own frame, for screenshots (scripts/screenshots.mjs) + * and for eyeballing the whole kit at once. + * + * /gallery all components + * /gallery?theme=dark dark theme + * /gallery?shot=auth-modal one component, on a transparent page + * + * Auth state comes from HyperyTestProvider (@hyperyai/sdk/testing), and the two + * gateway calls the components make are answered with sample data below, so no + * Hypery account or network is needed. + */ + +import { + AuthButton, + AuthModal, + BuyButton, + ErrorBoundary, + InsufficientCreditsAlert, + ModernAuthForm, + RestrictionModal, + SignIn, + SignInForm, + SignUp, + SpendingLimitAlert, + SubscribeButton, + UserButton, + UserProfile, + WorkspaceSwitcher, + parseError, +} from '@hyperyai/sdk'; +import { HyperyTestProvider } from '@hyperyai/sdk/testing'; +import { Suspense, useEffect, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; + +const GATEWAY = 'https://hypery.test'; + +const ADA = { id: 'usr_8f3k2m9q1x', name: 'Ada Lovelace', email: 'ada@analytical.engine' }; + +const MEMBERSHIPS = { + activeOrganizationId: 'team_personal', + activeWorkspaceId: 'ws_research', + memberships: [ + { + team: { id: 'team_personal', name: "Ada's Workspace", slug: 'ada', isPersonal: true, role: 'owner' }, + workspaces: [ + { id: 'ws_research', name: 'Research', slug: 'research', isDefault: true, icon: '🧮', role: 'owner', isActive: true }, + { id: 'ws_notes', name: 'Notes', slug: 'notes', isDefault: false, icon: '📝', role: 'owner', isActive: false }, + ], + }, + { + team: { id: 'team_engine', name: 'Analytical Engine', slug: 'engine', isPersonal: false, role: 'admin' }, + workspaces: [ + { id: 'ws_prod', name: 'Production', slug: 'prod', isDefault: true, icon: '⚙️', role: 'admin', isActive: false }, + { id: 'ws_staging', name: 'Staging', slug: 'staging', isDefault: false, icon: null, role: 'developer', isActive: false }, + ], + }, + ], +}; + +const WALLET = { + mode: 'prepaid', + balance: { current: 120, reserved: 0, monthlySpent: 880, monthlyLimit: 1000 }, + paymentMethod: { exists: true, last4: '4242', brand: 'Visa' }, + autoTopUp: { enabled: false }, + lowBalance: { isLow: true, threshold: 200, current: 120 }, + topupTiers: [], + settingsUrls: { billing: '/billing', topup: '/billing/topup', addPaymentMethod: '/billing/card' }, +}; + +// Answer the gateway calls with the sample data above. +if (typeof window !== 'undefined' && !(window as any).__galleryFetch) { + (window as any).__galleryFetch = true; + const real = window.fetch.bind(window); + window.fetch = async (input, init) => { + const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url; + if (url.startsWith(GATEWAY)) { + const body = url.includes('list_memberships') ? MEMBERSHIPS : url.includes('wallet/state') ? WALLET : {}; + return new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' } }); + } + return real(input, init); + }; +} + +const insufficient = parseError({ + error: { + code: 'INSUFFICIENT_CREDITS', + type: 'insufficient_credits', + message: 'This request needs more credits than your team has.', + available: 120, + required: 450, + }, +}); + +const spending = parseError({ + error: { + code: 'SPENDING_LIMIT_EXCEEDED', + type: 'spending_limit_error', + message: "You've reached your team's daily spending limit.", + limitType: 'daily', + limit: 1000, + current: 1000, + requested: 40, + resetsAt: new Date(Date.now() + 5 * 3600_000).toISOString(), + }, +}); + +function Boom(): never { + throw new Error('Could not load the conversation. Check your connection and try again.'); +} + +type Shot = { name: string; label: string; width?: number; modal?: boolean; click?: boolean; node: () => React.ReactNode }; + +const SHOTS: Shot[] = [ + { + name: 'auth-modal', + label: 'AuthModal', + modal: true, + node: () => {}} showEmailPassword branding={{ appName: 'Acme' }} />, + }, + { + name: 'sign-in-form', + label: 'SignInForm', + width: 400, + node: () => , + }, + { + name: 'modern-auth-form', + label: 'ModernAuthForm', + width: 440, + node: () => , + }, + { + name: 'sign-in', + label: 'SignIn (primary, secondary, outline)', + node: () => ( +
+ + + +
+ ), + }, + { + name: 'sign-up', + label: 'SignUp (primary, secondary, outline)', + node: () => ( +
+ + + +
+ ), + }, + { + name: 'auth-button', + label: 'AuthButton (primary, secondary, outline, ghost)', + node: () => ( +
+ Sign in + Sign in + Sign in + Sign in +
+ ), + }, + { name: 'user-button', label: 'UserButton', node: () => }, + { + name: 'user-button-open', + label: 'UserButton (open)', + click: true, + width: 264, + node: () => ( +
+ +
+ ), + }, + { name: 'user-profile', label: 'UserProfile', width: 440, node: () => }, + { + name: 'workspace-switcher', + label: 'WorkspaceSwitcher (open)', + click: true, + width: 288, + node: () => , + }, + { + name: 'buy-button', + label: 'BuyButton', + node: () => , + }, + { + name: 'subscribe-button', + label: 'SubscribeButton', + node: () => , + }, + { + name: 'insufficient-credits-alert', + label: 'InsufficientCreditsAlert', + width: 440, + node: () => {}} />, + }, + { + name: 'spending-limit-alert', + label: 'SpendingLimitAlert', + width: 440, + node: () => {}} onUpgradeLimits={() => {}} />, + }, + { + name: 'restriction-modal', + label: 'RestrictionModal', + modal: true, + node: () => ( + 'test-access-token'} + onClose={() => {}} + onRetry={() => {}} + /> + ), + }, + { + name: 'error-boundary', + label: 'ErrorBoundary (fallback)', + width: 440, + node: () => ( + {}}> + + + ), + }, +]; + +function Gallery() { + const params = useSearchParams(); + const only = params.get('shot'); + const theme = params.get('theme'); + const [ready, setReady] = useState(false); + + useEffect(() => { + if (theme === 'dark' || theme === 'light') { + const root = document.documentElement; + root.classList.toggle('dark', theme === 'dark'); + root.classList.toggle('light', theme === 'light'); + } + setReady(true); + }, [theme]); + + const shots = only ? SHOTS.filter((s) => s.name === only) : SHOTS; + + return ( + + {only && ( + // Single-shot mode: transparent page, and modal scrims hidden so only the dialog is captured. + + )} +
+ {!only && ( +
+

Component gallery

+

+ Every SDK component, signed in as a sample user with{' '} + HyperyTestProvider. Add ?shot=name{' '} + to show one, ?theme=dark for dark. +

+
+ )} +
+ {shots.map((s) => ( +
+ {!only &&

{s.label}

} + {s.modal && !only ? ( +

+ Open ?shot={s.name} to see this dialog. +

+ ) : ( +
+ {s.node()} +
+ )} +
+ ))} +
+
+
+ ); +} + +export default function GalleryPage() { + return ( + + + + ); +} diff --git a/auth-demo/app/globals.css b/auth-demo/app/globals.css index 4d7f4e0..0565b44 100644 --- a/auth-demo/app/globals.css +++ b/auth-demo/app/globals.css @@ -1,94 +1,156 @@ +/* Tailwind 4, CSS-first: no tailwind.config file. */ @import "tailwindcss"; @import "tw-animate-css"; -@custom-variant dark (&:is(.dark *)); +@custom-variant dark (&:where(.dark, .dark *)); + +/* Hypery design tokens (same values as hypery.ai). The SDK components read + the matching --hy-* tokens; brand both at once by setting them here. */ +:root { + color-scheme: light; + --radius: 0.625rem; + + --background: oklch(0.982 0.006 240); + --surface-sunken: oklch(0.962 0.01 240); + --card: oklch(1 0 0); + --popover: oklch(1 0 0); + --foreground: oklch(0.27 0.06 255); + --muted-foreground: oklch(0.49 0.035 252); + --subtle-foreground: oklch(0.63 0.025 250); + --muted: oklch(0.955 0.01 240); + --accent: oklch(0.955 0.012 240); + --accent-foreground: oklch(0.30 0.06 255); + --field: oklch(0.938 0.012 240); + --field-hover: oklch(0.918 0.014 240); + --border: oklch(0.27 0.06 255 / 0.07); + --border-strong: oklch(0.27 0.06 255 / 0.18); + --ring: oklch(0.62 0.15 232); + + --primary: oklch(0.50 0.14 236); + --primary-foreground: oklch(0.99 0 0); + --primary-hover: oklch(0.43 0.13 240); + --primary-subtle: oklch(0.955 0.03 225); + --primary-subtle-foreground: oklch(0.42 0.13 240); + + --success: oklch(0.56 0.13 155); + --success-subtle: oklch(0.955 0.04 155); + --success-foreground: oklch(0.38 0.10 155); + --warning: oklch(0.70 0.15 75); + --warning-subtle: oklch(0.96 0.05 85); + --warning-foreground: oklch(0.42 0.10 65); + --danger-subtle: oklch(0.955 0.03 25); + --danger-foreground: oklch(0.42 0.16 27); + --destructive: oklch(0.55 0.21 27); + + --code: oklch(0.21 0.034 258); + --code-foreground: oklch(0.92 0.01 240); + --syntax-comment: oklch(0.60 0.03 250); + + --brand-1: oklch(0.70 0.15 210); + --brand-2: oklch(0.58 0.19 262); + --brand-3: oklch(0.62 0.21 300); + + --elevation-xs: 0 1px 2px 0 oklch(0.3 0.08 262 / 0.08), 0 1px 1px 0 oklch(0 0 0 / 0.03); + --elevation-card: 0 1px 2px 0 oklch(0.3 0.08 262 / 0.07), 0 4px 14px -4px oklch(0.3 0.08 262 / 0.10); + --elevation-card-hover: 0 8px 16px -4px oklch(0.3 0.08 262 / 0.14), 0 20px 40px -16px oklch(0.3 0.08 262 / 0.22); +} + +.dark { + color-scheme: dark; + --background: oklch(0.17 0.03 258); + --surface-sunken: oklch(0.145 0.028 258); + --card: oklch(0.21 0.034 258); + --popover: oklch(0.245 0.038 258); + --foreground: oklch(0.96 0.008 240); + --muted-foreground: oklch(0.74 0.03 248); + --subtle-foreground: oklch(0.60 0.03 250); + --muted: oklch(0.26 0.036 258); + --accent: oklch(0.27 0.04 258); + --accent-foreground: oklch(0.96 0.008 240); + --field: oklch(0.255 0.036 258); + --field-hover: oklch(0.30 0.04 258); + --border: oklch(1 0 0 / 0.06); + --border-strong: oklch(1 0 0 / 0.16); + --ring: oklch(0.74 0.14 222); + --primary: oklch(0.74 0.14 222); + --primary-foreground: oklch(0.17 0.04 250); + --primary-hover: oklch(0.80 0.13 220); + --primary-subtle: oklch(0.30 0.07 240); + --primary-subtle-foreground: oklch(0.86 0.09 220); + --success: oklch(0.72 0.15 155); + --success-subtle: oklch(0.26 0.05 155); + --success-foreground: oklch(0.85 0.12 155); + --warning: oklch(0.80 0.14 80); + --warning-subtle: oklch(0.28 0.05 75); + --warning-foreground: oklch(0.88 0.11 85); + --danger-subtle: oklch(0.27 0.06 25); + --danger-foreground: oklch(0.86 0.10 25); + --destructive: oklch(0.64 0.19 25); + --code: oklch(0.145 0.028 258); + --elevation-xs: 0 1px 2px 0 oklch(0 0 0 / 0.4); + --elevation-card: 0 0 0 1px oklch(1 0 0 / 0.05), 0 8px 24px -12px oklch(0 0 0 / 0.5); + --elevation-card-hover: 0 0 0 1px oklch(1 0 0 / 0.08), 0 20px 40px -16px oklch(0 0 0 / 0.75); +} @theme inline { + --font-sans: var(--font-instrument-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-mono: var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, Menlo, monospace; + --color-background: var(--background); + --color-surface-sunken: var(--surface-sunken); + --color-card: var(--card); + --color-popover: var(--popover); --color-foreground: var(--foreground); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); + --color-muted-foreground: var(--muted-foreground); + --color-subtle-foreground: var(--subtle-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-field: var(--field); + --color-field-hover: var(--field-hover); + --color-border: var(--border); + --color-border-strong: var(--border-strong); + --color-ring: var(--ring); --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); + --color-primary-foreground: var(--primary-foreground); + --color-primary-hover: var(--primary-hover); + --color-primary-subtle: var(--primary-subtle); + --color-primary-subtle-foreground: var(--primary-subtle-foreground); + --color-success: var(--success); + --color-success-subtle: var(--success-subtle); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-subtle: var(--warning-subtle); + --color-warning-foreground: var(--warning-foreground); + --color-danger-subtle: var(--danger-subtle); + --color-danger-foreground: var(--danger-foreground); + --color-destructive: var(--destructive); + --color-code: var(--code); + --color-code-foreground: var(--code-foreground); + --color-syntax-comment: var(--syntax-comment); + --color-brand-1: var(--brand-1); + --color-brand-2: var(--brand-2); + --color-brand-3: var(--brand-3); + --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); -} + --radius-2xl: calc(var(--radius) + 6px); -:root { - color-scheme: light; - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.129 0.042 264.695); - --card: oklch(1 0 0); - --card-foreground: oklch(0.129 0.042 264.695); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.129 0.042 264.695); - --primary: oklch(0.208 0.042 265.755); - --primary-foreground: oklch(0.984 0.003 247.858); - --secondary: oklch(0.968 0.007 247.896); - --secondary-foreground: oklch(0.208 0.042 265.755); - --muted: oklch(0.968 0.007 247.896); - --muted-foreground: oklch(0.554 0.046 257.417); - --accent: oklch(0.968 0.007 247.896); - --accent-foreground: oklch(0.208 0.042 265.755); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.929 0.013 255.508); - --input: oklch(0.929 0.013 255.508); - --ring: oklch(0.704 0.04 256.788); + --shadow-xs: var(--elevation-xs); + --shadow-card: var(--elevation-card); + --shadow-card-hover: var(--elevation-card-hover); } @layer base { * { @apply border-border outline-ring/50; } - body { - @apply bg-background text-foreground; - } -} - -@layer utilities { - @keyframes collapsible-down { - from { - height: 0; - opacity: 0; - } - to { - height: var(--radix-collapsible-content-height); - opacity: 1; - } - } - - @keyframes collapsible-up { - from { - height: var(--radix-collapsible-content-height); - opacity: 1; - } - to { - height: 0; - opacity: 0; - } + @apply bg-background font-sans text-foreground antialiased; } - - .animate-collapsible-down { - animation: collapsible-down 0.2s ease-out; - } - - .animate-collapsible-up { - animation: collapsible-up 0.2s ease-out; + ::selection { + @apply bg-primary-subtle text-primary-subtle-foreground; } } diff --git a/auth-demo/app/layout.tsx b/auth-demo/app/layout.tsx index 2d94463..454d301 100644 --- a/auth-demo/app/layout.tsx +++ b/auth-demo/app/layout.tsx @@ -1,20 +1,33 @@ import type { Metadata } from 'next'; +import { Instrument_Sans, JetBrains_Mono } from 'next/font/google'; import { HyperyProvider } from '@hyperyai/sdk'; +// The SDK's components ship their own stylesheet: import it once. +import '@hyperyai/sdk/styles.css'; import './globals.css'; +import { SiteHeader } from '@/components/site-header'; + +const sans = Instrument_Sans({ subsets: ['latin'], variable: '--font-instrument-sans' }); +const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-jetbrains-mono' }); export const metadata: Metadata = { title: 'Hypery Auth Demo', description: 'Demo app showcasing @hyperyai/sdk package', }; +// Picks light/dark before paint: a saved choice, else the OS preference. +const themeScript = `(function(){try{var t=localStorage.getItem('theme');if(!t){t=matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}document.documentElement.classList.add(t)}catch(e){}})()`; + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - - + + +