@llmnative/react 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/bin/cli.js +117 -117
  2. package/dist/index.css +1 -1
  3. package/dist/index.js +21 -21
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +10468 -8488
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/types/src/App.d.ts +19 -1
  8. package/dist/types/src/I18n.d.ts +91 -0
  9. package/dist/types/src/Theme.d.ts +16 -0
  10. package/dist/types/src/components/auth/AuthGate.d.ts +26 -0
  11. package/dist/types/src/components/auth/AuthLayout.d.ts +38 -0
  12. package/dist/types/src/components/auth/ForgotPassword.d.ts +19 -0
  13. package/dist/types/src/components/auth/OneTimeCodeInput.d.ts +27 -0
  14. package/dist/types/src/components/auth/ResetPassword.d.ts +31 -0
  15. package/dist/types/src/components/auth/SignIn.d.ts +42 -0
  16. package/dist/types/src/components/auth/SignUp.d.ts +43 -0
  17. package/dist/types/src/components/auth/VerifyCode.d.ts +32 -0
  18. package/dist/types/src/components/auth/VerifyEmail.d.ts +31 -0
  19. package/dist/types/src/components/auth/index.d.ts +12 -0
  20. package/dist/types/src/components/auth/providers.d.ts +22 -0
  21. package/dist/types/src/components/auth/shared.d.ts +124 -0
  22. package/dist/types/src/components/auth/useAuthFlows.d.ts +46 -0
  23. package/dist/types/src/index.d.ts +5 -1
  24. package/dist/types/src/providers/auth/AuthProvider.d.ts +51 -0
  25. package/dist/types/src/providers/auth/firebase/FirebaseAuthProvider.d.ts +8 -1
  26. package/dist/types/src/providers/auth/github/GitHubAuthProvider.d.ts +2 -0
  27. package/dist/types/src/providers/auth/mock/MockAuthProvider.d.ts +55 -0
  28. package/dist/types/src/providers/auth/supabase/SupabaseAuthProvider.d.ts +16 -1
  29. package/dist/types/src/providers/manifest.d.ts +4 -1
  30. package/llms-full.txt +96 -2
  31. package/package.json +1 -1
  32. package/scripts/cli/setup-project.js +678 -647
  33. package/templates/_shared/conf/access.tsx +23 -0
  34. package/templates/_shared/pages/access/ForgotPasswordPage.tsx +13 -0
  35. package/templates/_shared/pages/access/ResetPasswordPage.tsx +19 -0
  36. package/templates/_shared/pages/access/SignInPage.tsx +21 -0
  37. package/templates/_shared/pages/access/SignUpPage.tsx +23 -0
  38. package/templates/_shared/pages/access/VerifyEmailPage.tsx +20 -0
  39. package/templates/_shared/pages/access/paths.ts +12 -0
  40. package/templates/_shared/sections/Footer.tsx +1 -1
  41. package/templates/_shared/sections/Header.tsx +2 -2
  42. package/themes/default.ts +13 -12
  43. package/themes/tokyo-night-light.ts +24 -2
  44. package/themes/tokyo-night.ts +24 -2
  45. package/themes/vscode-dark-modern.ts +24 -2
  46. package/themes/vscode-dark-plus.ts +24 -2
  47. package/themes/vscode-light-modern.ts +24 -2
  48. package/themes/vscode-light-plus.ts +24 -2
@@ -0,0 +1,46 @@
1
+ import type { AuthProviderAdapter, UserProfile } from '../../providers/auth/AuthProvider';
2
+ import type { SignInProps } from './SignIn';
3
+ import type { SignUpProps } from './SignUp';
4
+ import type { ForgotPasswordProps } from './ForgotPassword';
5
+ import type { ResetPasswordProps } from './ResetPassword';
6
+ import type { VerifyEmailProps } from './VerifyEmail';
7
+ export interface AuthSession {
8
+ /** The signed-in user, `null` without a session. */
9
+ user: UserProfile | null;
10
+ /** `false` while the session of the last visit is being restored: show neither the page nor the sign-in. */
11
+ ready: boolean;
12
+ signOut: () => Promise<void>;
13
+ adapter: AuthProviderAdapter;
14
+ }
15
+ /** Who is signed in with an auth provider of the app, and whether it is known yet (CR-127). */
16
+ export declare function useAuthSession(provider?: string): AuthSession;
17
+ export interface AuthFlowsOptions {
18
+ /** The page the confirmation link of a new account opens (your `VerifyEmail`), e.g. `/verify-email`. */
19
+ verifyEmailPath?: string;
20
+ /** The page the link to choose a new password opens (your `ResetPassword`), e.g. `/reset-password`. */
21
+ resetPasswordPath?: string;
22
+ }
23
+ export interface AuthFlows {
24
+ adapter: AuthProviderAdapter;
25
+ /** For `<SignIn {...flows.signIn} />`: password, code by email, providers, as the adapter offers them. */
26
+ signIn: Pick<SignInProps, 'onPasswordSignIn' | 'onRequestCode' | 'onVerifyCode' | 'onProviderSignIn' | 'providers'>;
27
+ /** For `<SignUp {...flows.signUp} />`; `undefined` when the adapter cannot create accounts. */
28
+ signUp?: Pick<SignUpProps, 'onSignUp' | 'onResendVerification'>;
29
+ /** For `<ForgotPassword {...flows.forgotPassword} />`; `undefined` without password reset. */
30
+ forgotPassword?: Pick<ForgotPasswordProps, 'onSendReset'>;
31
+ /** For `<ResetPassword {...flows.resetPassword(code)} />`, with the code of the link. */
32
+ resetPassword: (code: string | null | undefined) => Pick<ResetPasswordProps, 'onResetPassword' | 'invalidLink'>;
33
+ /** For `<VerifyEmail {...flows.verifyEmail} code={…} />`. */
34
+ verifyEmail: Pick<VerifyEmailProps, 'onVerify' | 'onResend'>;
35
+ }
36
+ /**
37
+ * Binds the access screens to an auth provider of the app (CR-127): each screen gets the functions of
38
+ * the methods the adapter has. An adapter that can reset a password (Firebase, Supabase, mock) signs in
39
+ * with email and password; one that cannot (Google, GitHub, Dropbox) offers its own button.
40
+ */
41
+ export declare function useAuthFlows(provider?: string, options?: AuthFlowsOptions): AuthFlows;
42
+ /**
43
+ * The code of the link an email opened, whatever the service calls it: `oobCode` (Firebase),
44
+ * `token_hash` (Supabase), `code`.
45
+ */
46
+ export declare function linkCode(search?: string | URLSearchParams): string | null;
@@ -13,6 +13,7 @@ export type { I18nDict, I18nConfig, I18nController, I18nTranslations, I18nLocale
13
13
  export * as locales from './conf/i18n';
14
14
  export type { AppThemeProviderConfig, BuiltInThemeId, CardTheme, ColorScale, GalleryTheme, ListCardTheme, MenuTheme, ModalTheme, ProviderSwitcherTheme, ToolbarTheme, TableTheme, Theme, ThemeConfig, ThemeController, ThemeMode, ThemeDefinition, ThemePresetConfig, } from './Theme';
15
15
  export { AuthButton, useAccessToken, getAccessToken } from './auth';
16
+ export * from './components/auth';
16
17
  export { useGlobalVars, getGlobalVars, setGlobalVars, removeGlobalVars } from './Global';
17
18
  export type { DataProviderAdapter, FieldValue, RecordProps, RecordArray, DatabaseOptions, DBConfig, ReadOptions, WhereClause, OrderClause, Condition } from './providers/data/DataProvider';
18
19
  export type { OrderConfig, OrderDirection } from './libs/order';
@@ -50,7 +51,10 @@ export { FirebaseStorageProvider } from './providers/storage/firebase';
50
51
  export type { FirebaseStorageProviderConfig } from './providers/storage/firebase';
51
52
  export { SupabaseStorageProvider } from './providers/storage/supabase';
52
53
  export { useStorageProvider, StorageProvider } from './providers/storage/StorageProviderContext';
53
- export type { AuthProviderAdapter, AuthIntent, AuthSignInOptions, UserProfile } from './providers/auth/AuthProvider';
54
+ export type { AuthProviderAdapter, AuthIntent, AuthSignInOptions, UserProfile, AuthSignUpInput, AuthSignUpResult, AuthLinkOptions, AuthEmailCodeOptions, AuthErrorReason, } from './providers/auth/AuthProvider';
55
+ export { AuthFlowError } from './providers/auth/AuthProvider';
56
+ export { MockAuthProvider } from './providers/auth/mock/MockAuthProvider';
57
+ export type { MockAuthProviderOptions, MockAuthAccount } from './providers/auth/mock/MockAuthProvider';
54
58
  export { useAuthProvider, AuthProvider } from './providers/auth/AuthProviderContext';
55
59
  export { GoogleAuthProvider } from './providers/auth/google/GoogleAuthProvider';
56
60
  export type { FirebaseAuthProviderOptions, FirebaseSignInOptions } from './providers/auth/firebase/FirebaseAuthProvider';
@@ -27,4 +27,55 @@ export interface AuthProviderAdapter extends ProviderConfigurable {
27
27
  * re-verifies the raw token itself rather than trusting client-read claims.
28
28
  */
29
29
  getIdTokenClaims?(forceRefresh?: boolean): Promise<Record<string, unknown> | null>;
30
+ /** Resolves once the session of the last visit is restored (signed in or not): `AuthGate` waits for it. */
31
+ whenReady?(): Promise<void>;
32
+ /** Creates an account with email and password; says whether the email must be confirmed first. */
33
+ signUp?(input: AuthSignUpInput): Promise<AuthSignUpResult>;
34
+ /** Sends the link to choose a new password. Never says whether the account exists. */
35
+ sendPasswordReset?(email: string, options?: AuthLinkOptions): Promise<void>;
36
+ /** Sets the new password with the code of the link (`oobCode`, `token_hash`, …). */
37
+ confirmPasswordReset?(code: string, newPassword: string): Promise<void>;
38
+ /** Sends again the link that confirms the email: of the signed-in user, or of `email` when the service allows it. */
39
+ sendEmailVerification?(options?: AuthLinkOptions & {
40
+ email?: string;
41
+ }): Promise<void>;
42
+ /** Confirms the email with the code of the link. */
43
+ verifyEmail?(code: string): Promise<void>;
44
+ /** Sends a one-time code by email to sign in. */
45
+ requestEmailCode?(email: string, options?: AuthEmailCodeOptions): Promise<void>;
46
+ /** Signs in with the code received by email. */
47
+ verifyEmailCode?(email: string, code: string): Promise<UserProfile | null>;
48
+ }
49
+ export interface AuthSignUpInput {
50
+ email: string;
51
+ password: string;
52
+ /** The full name, when the screen asks for it. */
53
+ name?: string;
54
+ /** Where the confirmation link brings back (the page that calls `verifyEmail`). */
55
+ redirectTo?: string;
56
+ }
57
+ export interface AuthSignUpResult {
58
+ user: UserProfile | null;
59
+ /** The account works once the email is confirmed: the screen says to check the inbox. */
60
+ needsEmailVerification: boolean;
61
+ }
62
+ export interface AuthLinkOptions {
63
+ /** The page the link of the email opens. */
64
+ redirectTo?: string;
65
+ }
66
+ export interface AuthEmailCodeOptions {
67
+ /** Creates the account when the email has none (default `false`: only existing accounts sign in). */
68
+ createUser?: boolean;
69
+ }
70
+ /** Why an access flow failed, in words a screen can translate (`authErrorMessage`). */
71
+ export type AuthErrorReason = 'credentials' | 'emailInUse' | 'weakPassword' | 'invalidEmail' | 'tooManyRequests' | 'network' | 'invalidCode' | 'expiredLink' | 'disabled' | 'cancelled' | 'notSupported' | 'generic';
72
+ /**
73
+ * An error of an access flow with its reason: a screen shows the translated message of the reason,
74
+ * never the raw text of the service. Throw it from your own functions (e.g. a code checked by your server).
75
+ */
76
+ export declare class AuthFlowError extends Error {
77
+ readonly reason: AuthErrorReason;
78
+ constructor(reason: AuthErrorReason, message?: string, options?: {
79
+ cause?: unknown;
80
+ });
30
81
  }
@@ -1,4 +1,4 @@
1
- import type { AuthProviderAdapter, AuthSignInOptions, UserProfile } from '../AuthProvider';
1
+ import type { AuthLinkOptions, AuthProviderAdapter, AuthSignInOptions, AuthSignUpInput, AuthSignUpResult, UserProfile } from '../AuthProvider';
2
2
  import type { ProviderConfigurationState } from '../../ProviderConfiguration';
3
3
  import type { FirebaseConfig } from '../../../Config';
4
4
  /**
@@ -46,4 +46,11 @@ export declare class FirebaseAuthProvider implements AuthProviderAdapter {
46
46
  onAuthChange(callback: (user: UserProfile | null) => void): () => void;
47
47
  getAccessToken(): Promise<string>;
48
48
  getIdTokenClaims(forceRefresh?: boolean): Promise<Record<string, unknown> | null>;
49
+ private requireAuth;
50
+ whenReady(): Promise<void>;
51
+ signUp({ email, password, name, redirectTo }: AuthSignUpInput): Promise<AuthSignUpResult>;
52
+ sendPasswordReset(email: string, options?: AuthLinkOptions): Promise<void>;
53
+ confirmPasswordReset(code: string, newPassword: string): Promise<void>;
54
+ sendEmailVerification(options?: AuthLinkOptions): Promise<void>;
55
+ verifyEmail(code: string): Promise<void>;
49
56
  }
@@ -32,6 +32,8 @@ export declare class GitHubAuthProvider implements AuthProviderAdapter {
32
32
  refresh(): Promise<UserProfile | null>;
33
33
  /** Leaves for GitHub through the server, and comes back to the page it left from. */
34
34
  signIn(options?: AuthSignInOptions): Promise<UserProfile | null>;
35
+ /** Resolves once the server said who is signed in (CR-127): `AuthGate` waits for it. */
36
+ whenReady(): Promise<void>;
35
37
  signOut(): Promise<void>;
36
38
  onAuthChange(callback: (user: UserProfile | null) => void): () => void;
37
39
  private setUser;
@@ -0,0 +1,55 @@
1
+ import type { AuthEmailCodeOptions, AuthProviderAdapter, AuthSignInOptions, AuthSignUpInput, AuthSignUpResult, UserProfile } from '../AuthProvider';
2
+ import type { ProviderConfigurationState } from '../../ProviderConfiguration';
3
+ /** An account of the mock: its password, and whether its email is confirmed. */
4
+ export interface MockAuthAccount {
5
+ email: string;
6
+ password: string;
7
+ displayName?: string;
8
+ emailVerified?: boolean;
9
+ }
10
+ export interface MockAuthProviderOptions {
11
+ /**
12
+ * The accounts that exist. Omitted: any email and any password sign in (a demo); given: only these,
13
+ * and a wrong password fails as a real service would.
14
+ */
15
+ accounts?: MockAuthAccount[];
16
+ /** The code every email "receives" (default `123456`); any other code fails. */
17
+ code?: string;
18
+ /** Where the session is kept between reloads (default `sessionStorage`); `null` keeps it in memory only. */
19
+ storage?: Storage | null;
20
+ /** A pause before each answer, in ms, to see the loading states (default `400`). */
21
+ latency?: number;
22
+ }
23
+ /**
24
+ * An auth provider for demos and tests, with no service behind it (CR-127): every flow of the access
25
+ * screens works in memory. Never use it in production: it trusts any credential it is configured to.
26
+ */
27
+ export declare class MockAuthProvider implements AuthProviderAdapter {
28
+ private user;
29
+ private readonly listeners;
30
+ private readonly accounts;
31
+ private readonly open;
32
+ private readonly code;
33
+ private readonly storage;
34
+ private readonly latency;
35
+ constructor(options?: MockAuthProviderOptions);
36
+ getConfigurationState(): ProviderConfigurationState;
37
+ isConfigured(): boolean;
38
+ getUser(): UserProfile | null;
39
+ isAuthenticated(): boolean;
40
+ whenReady(): Promise<void>;
41
+ onAuthChange(callback: (user: UserProfile | null) => void): () => void;
42
+ signIn(options?: AuthSignInOptions): Promise<UserProfile | null>;
43
+ signOut(): Promise<void>;
44
+ signUp({ email, password, name }: AuthSignUpInput): Promise<AuthSignUpResult>;
45
+ sendPasswordReset(): Promise<void>;
46
+ confirmPasswordReset(code: string, newPassword: string): Promise<void>;
47
+ sendEmailVerification(): Promise<void>;
48
+ verifyEmail(code: string): Promise<void>;
49
+ requestEmailCode(email: string, options?: AuthEmailCodeOptions): Promise<void>;
50
+ verifyEmailCode(email: string, code: string): Promise<UserProfile | null>;
51
+ private profile;
52
+ private setUser;
53
+ private restore;
54
+ private pause;
55
+ }
@@ -1,4 +1,4 @@
1
- import type { AuthProviderAdapter, AuthSignInOptions, UserProfile } from '../AuthProvider';
1
+ import type { AuthEmailCodeOptions, AuthLinkOptions, AuthProviderAdapter, AuthSignInOptions, AuthSignUpInput, AuthSignUpResult, UserProfile } from '../AuthProvider';
2
2
  import type { ProviderConfigurationState } from '../../ProviderConfiguration';
3
3
  export interface SupabaseAuthConfig {
4
4
  url: string;
@@ -45,4 +45,19 @@ export declare class SupabaseAuthProvider implements AuthProviderAdapter {
45
45
  signOut(): Promise<void>;
46
46
  onAuthChange(callback: (user: UserProfile | null) => void): () => void;
47
47
  getAccessToken(): Promise<string>;
48
+ whenReady(): Promise<void>;
49
+ signUp({ email, password, name, redirectTo }: AuthSignUpInput): Promise<AuthSignUpResult>;
50
+ sendPasswordReset(email: string, options?: AuthLinkOptions): Promise<void>;
51
+ /**
52
+ * `code` is the `token_hash` of the recovery email (template `{{ .TokenHash }}`); empty when the link
53
+ * already opened a recovery session (the default template), and then only the password is set.
54
+ */
55
+ confirmPasswordReset(code: string, newPassword: string): Promise<void>;
56
+ sendEmailVerification(options?: AuthLinkOptions & {
57
+ email?: string;
58
+ }): Promise<void>;
59
+ /** `code` is the `token_hash` of the confirmation email (template `{{ .TokenHash }}`). */
60
+ verifyEmail(code: string): Promise<void>;
61
+ requestEmailCode(email: string, options?: AuthEmailCodeOptions): Promise<void>;
62
+ verifyEmailCode(email: string, code: string): Promise<UserProfile | null>;
48
63
  }
@@ -1,5 +1,6 @@
1
1
  import type { CredentialsAdapter } from './credentials/CredentialsProvider';
2
2
  import { MockDataProvider } from './data/mock';
3
+ import { type MockAuthProviderOptions } from './auth/mock/MockAuthProvider';
3
4
  import type { AIProviderAdapter } from './ai/AIProvider';
4
5
  import type { DataProviderAdapter } from './data/DataProvider';
5
6
  import type { StorageProviderAdapter } from './storage/StorageProvider';
@@ -23,6 +24,8 @@ export type GoogleProviderConfig = GoogleOAuth2 & {
23
24
  export type SupabaseProviderConfig = SupabaseConfig;
24
25
  export type MockProviderConfig = {
25
26
  data?: ConstructorParameters<typeof MockDataProvider>[0];
27
+ /** Registers `mockAuth` (CR-127), a provider of access with no service behind it: demos and tests only. */
28
+ auth?: MockAuthProviderOptions | boolean;
26
29
  };
27
30
  export declare const FIREBASE_MANIFEST: DriverManifest<FirebaseConfig>;
28
31
  export declare const GOOGLE_MANIFEST: DriverManifest<GoogleProviderConfig>;
@@ -34,7 +37,7 @@ export declare const AI_MANIFEST: DriverManifest<AIConfig>;
34
37
  export declare const PROVIDER_MANIFESTS: Record<string, DriverManifest<any>>;
35
38
  export type DataDriverName = 'dbRealtime' | 'firestoreDb' | 'supabaseDb' | 'mock';
36
39
  export type StorageDriverName = 'firestorage' | 'supabaseStorage';
37
- export type AuthDriverName = 'googleAuth' | 'firebaseAuth' | 'dropboxAuth' | 'supabaseAuth' | 'githubAuth';
40
+ export type AuthDriverName = 'googleAuth' | 'firebaseAuth' | 'dropboxAuth' | 'supabaseAuth' | 'githubAuth' | 'mockAuth';
38
41
  export type EmailDriverName = 'gmail';
39
42
  export type AIDriverName = 'openai' | 'openrouter' | 'opencode' | 'opencode-go' | 'openai-compatible' | 'deepseek' | 'gemini' | 'anthropic' | 'mistral' | 'cloudflare' | 'groq';
40
43
  export type CredentialsDriverName = 'googleServiceAccount';
package/llms-full.txt CHANGED
@@ -98,6 +98,15 @@ The VS Code and Tokyo Night themes are compiled from the original VS Code theme
98
98
 
99
99
  `ColorScale` tokens: `background`, `foreground`, `canvas` (page floor behind cards and panels, `bg-canvas`; falls back to `background`), `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `success`, `warning`, `info` (each with its `*Foreground`), `border`, `input` (border of form fields), `ring`. A consumer bridging the tokens in its own `@theme inline` adds `--color-canvas: hsl(var(--rf-canvas, var(--rf-background)));`.
100
100
 
101
+ **The frame of the app** (CR-126), as the title bar, side bar and status bar of the IDE: `header`, `headerForeground`, `headerBorder`, `sidebar`, `sidebarForeground`, `sidebarBorder`, `sidebarAccent` (the selected entry), `sidebarAccentForeground`, `footer`, `footerForeground`, `footerBorder`. The IDE themes carry them (VS Code Light+: gray title bar, blue status bar); a theme without them falls back to the base tokens (header and footer on `background`, sidebar on `card`, borders on `border`). Utilities: `bg-header text-header-foreground border-header-border`, `bg-sidebar …`, `bg-footer …`. `SideNav` draws itself with the sidebar tokens. The top bar and status bar of a layout:
102
+
103
+ ```tsx
104
+ <header className="flex h-14 shrink-0 items-center gap-3 border-b border-header-border bg-header px-4 text-header-foreground">…</header>
105
+ <footer className="shrink-0 border-t border-footer-border bg-footer px-4 py-1.5 text-xs text-footer-foreground">…</footer>
106
+ ```
107
+
108
+ A border without a color class (`border`, `border-b`) is the theme's `border`, not the text color. The scaffold (`npx @llmnative/react create`) starts in `vscode-light-modern` and bridges all these tokens in `src/styles/globals.css`; a consumer scaffolded before adds the `--color-header…`/`--color-sidebar…`/`--color-footer…` lines of `src/globals.css` to its own `@theme inline`.
109
+
101
110
  ---
102
111
 
103
112
  ## Provider System
@@ -147,6 +156,27 @@ export const menuConfig: MenuConfig = {
147
156
 
148
157
  La home va dichiarata con `path: '/'`: senza quella voce, `/` mostra "Missing Page".
149
158
 
159
+ ### Voci di menu e rotte
160
+
161
+ `SideNav menuKey="main"` e `Menu menuKey="main"` mostrano solo le **voci**: le route di quella chiave con `title`,
162
+ senza `hidden: true`, senza parametri (`:token`) e senza `*`. Le altre sono rotte e basta (CR-126). Ogni voce
163
+ mostrata ha `title` **e** `icon`. Una pagina raggiunta da un link (accesso, invito, impostazioni dal menu utente)
164
+ sta in una chiave che nessun menu mostra, oppure ha `hidden: true`:
165
+
166
+ ```tsx
167
+ export const menuConfig: MenuConfig = {
168
+ main: [
169
+ { path: '/', title: 'Dashboard', icon: 'layout-dashboard', page: HomePage },
170
+ { path: '/contacts', title: 'Contatti', icon: 'users', page: ContactsPage },
171
+ { path: '/settings', title: 'Impostazioni', icon: 'settings', page: SettingsPage, hidden: true },
172
+ ],
173
+ routes: [
174
+ { path: '/invite/:token', page: InvitePage, layout: AccessLayout },
175
+ { path: '*', page: NotFoundPage },
176
+ ],
177
+ };
178
+ ```
179
+
150
180
  ### Hooks
151
181
 
152
182
  ```ts
@@ -218,6 +248,66 @@ await auth.signIn({ method: 'oauth', provider: 'github' })
218
248
  const token = await auth.getAccessToken('https://www.googleapis.com/auth/gmail.send')
219
249
  ```
220
250
 
251
+ ### Schermate di accesso e gate senza redirect (CR-127)
252
+
253
+ Le schermate sono pronte: non scriverne a mano. Non conoscono il backend: ricevono le funzioni da
254
+ chiamare (una promessa) e mostrano l'errore lanciato tradotto, mai il testo grezzo del servizio
255
+ (`authErrorMessage`: codici di Firebase e Supabase, `AuthFlowError(reason)` per le funzioni tue).
256
+ Testi nel namespace `access` delle 6 lingue. Ognuna ha `brand`, `toolbar`, `footer`, `bare` (solo il
257
+ contenuto, in una modale), `errorMessage(error)`.
258
+
259
+ | Componente | Cosa fa | Funzioni |
260
+ |---|---|---|
261
+ | `SignIn` | provider esterni, email + password, codice via email, in qualsiasi combinazione | `onPasswordSignIn({email,password})`, `onRequestCode(email)` + `onVerifyCode(email,code)`, `providers` + `onProviderSignIn(id)` |
262
+ | `SignUp` | nome, email, password con i requisiti mentre si scrive, termini; poi "controlla la tua email" | `onSignUp({name,email,password})` → `{ needsEmailVerification }`, `onResendVerification(email)` |
263
+ | `ForgotPassword` | email → stessa risposta con o senza account, nuovo invio dopo un'attesa | `onSendReset(email)` |
264
+ | `ResetPassword` | nuova password dal link dell'email; link scaduto | `onResetPassword(password)`, `invalidLink` |
265
+ | `VerifyEmail` | attivazione: con `code` verifica subito, senza aspetta la conferma | `onVerify(code)`, `onResend()`, `onContinue()` |
266
+ | `VerifyCode` | codice di N cifre in caselle, inviato a codice completo | `onVerify(code)`, `onResend()`, `onChangeEmail()` |
267
+ | `OneTimeCodeInput` | le caselle da sole (incolla, avanti/indietro, `one-time-code`) | `value`, `onChange`, `onComplete` |
268
+ | `AuthLayout` | la cornice di una schermata (marchio, card, titolo, piè di pagina) | — |
269
+ | `AuthGate` | la pagina solo con la sessione; senza, `fallback` al suo posto | `allow(user)`, `denied`, `loading` |
270
+
271
+ ```tsx
272
+ import { App, SignIn, AuthFlowError } from '@llmnative/react';
273
+
274
+ // Tutta l'app riservata: senza sessione ogni pagina mostra l'accesso al suo posto, l'indirizzo
275
+ // resta quello aperto e dopo l'accesso compare la pagina. Nessuna pagina /login, nessun redirect.
276
+ <App
277
+ menuConfig={{ main: [...], access: [{ path: '/invito/:token', page: InvitePage, public: true }] }}
278
+ authGate={{
279
+ fallback: <SignIn providers={['google', 'apple']} onProviderSignIn={enter}
280
+ onRequestCode={sendCode} onVerifyCode={checkCode} appName="CRM" />,
281
+ allow: async (user) => (await isMember(user)), // altrimenti "Accesso non consentito"
282
+ }}
283
+ />
284
+
285
+ // Una funzione tua che rifiuta: la schermata dice "Il codice non è corretto o è scaduto."
286
+ async function checkCode(email: string, code: string) {
287
+ const { token } = await callFunction('verifyLoginCode', { email, code }).catch(() => {
288
+ throw new AuthFlowError('invalidCode');
289
+ });
290
+ await signInWithCustomToken(token);
291
+ }
292
+ ```
293
+
294
+ Con gli adapter del framework `useAuthFlows(provider?, { verifyEmailPath, resetPasswordPath })` dà le
295
+ funzioni pronte (`<SignIn {...flows.signIn} />`, `flows.signUp`, `flows.forgotPassword`,
296
+ `flows.resetPassword(linkCode())`, `flows.verifyEmail`). Metodi opzionali di `AuthProviderAdapter`:
297
+
298
+ | Metodo | Firebase | Supabase | `mockAuth` |
299
+ |---|---|---|---|
300
+ | `whenReady` (il gate aspetta la sessione) | ✅ | ✅ | ✅ (anche `githubAuth`) |
301
+ | `signUp`, `sendPasswordReset`, `confirmPasswordReset` | ✅ | ✅ | ✅ |
302
+ | `sendEmailVerification`, `verifyEmail` | ✅ | ✅ | ✅ |
303
+ | `requestEmailCode`, `verifyEmailCode` | — (serve una funzione tua) | ✅ | ✅ |
304
+
305
+ `useAuthSession(provider?)` dà `{ user, ready, signOut }`. `linkCode()` legge il codice del link
306
+ (`oobCode`, `token_hash`, `code`). `mockAuth` (`providers.mock.auth: true`) è solo per demo e test:
307
+ qualsiasi email e password, codice `123456`; con `accounts` rifiuta le credenziali sbagliate.
308
+ Link delle email: in Firebase imposta l'URL di azione sulle tue pagine (`/reset-password`,
309
+ `/verify-email`); in Supabase usa `{{ .TokenHash }}` nei template delle email.
310
+
221
311
  ### CredentialsAdapter (Google Service Account)
222
312
  ```tsx
223
313
  // Chiama Google APIs lato browser senza Node.js
@@ -1436,6 +1526,7 @@ src/
1436
1526
  conf/
1437
1527
  app.ts ← providers config letta da .env (VITE_PROVIDER, VITE_AI_PROVIDER...)
1438
1528
  menu.ts ← MenuConfig con path, title, icon, page (layout opzionale: c'è LayoutDefault)
1529
+ access.tsx ← rotte pubbliche delle pagine di accesso (CR-127)
1439
1530
  data/
1440
1531
  mockData.ts ← dati fake per MockDataProvider
1441
1532
  layouts/
@@ -1444,6 +1535,7 @@ src/
1444
1535
  Header.tsx / Sidebar.tsx / PageHeader.tsx / Footer.tsx ← solo componenti pubblici (SideNav, AuthButton, ActionButton, Brand, Breadcrumbs)
1445
1536
  pages/
1446
1537
  home/HomePage.tsx
1538
+ access/ ← SignInPage (il fallback del gate), SignUp, ForgotPassword, ResetPassword, VerifyEmail
1447
1539
  [altre pagine dal template scelto]
1448
1540
  styles/
1449
1541
  globals.css ← @import '@llmnative/react/dist/index.css' + Tailwind override
@@ -1461,11 +1553,12 @@ const env = import.meta.env
1461
1553
  export const providers: AppProvidersConfig = {
1462
1554
  mock: { data: mockData },
1463
1555
  ai: { openaiApiKey: env.VITE_OPENAI_API_KEY ?? '' }, // AI dentro providers, non prop <App>
1464
- services: { data: 'mock', auth: 'googleAuth' },
1556
+ mock: { data: mockData, auth: true }, // mockAuth: accesso di prova
1557
+ services: { data: 'mock', auth: 'mockAuth' },
1465
1558
  }
1466
1559
  ```
1467
1560
 
1468
- Driver per `VITE_PROVIDER`: `firebase` → `firestoreDb` + `firestorage` + `firebaseAuth`; `supabase` → `supabaseDb` + `supabaseStorage` + `supabaseAuth`; `mock` → `mock` + `googleAuth`. `index.tsx` passa `LayoutDefault={Default}` ad `<App>`.
1561
+ Driver per `VITE_PROVIDER`: `firebase` → `firestoreDb` + `firestorage` + `firebaseAuth`; `supabase` → `supabaseDb` + `supabaseStorage` + `supabaseAuth`; `mock` → `mock` + `mockAuth`. `index.tsx` passa `LayoutDefault={Default}`, le traduzioni del framework (`translations: { ...locales }`) e `authGate={{ fallback: <SignInPage /> }}`: senza sessione ogni pagina mostra l'accesso al suo posto. Le pagine di accesso (`pages/access/`: SignInPage, SignUpPage, ForgotPasswordPage, ResetPasswordPage, VerifyEmailPage) e le loro rotte pubbliche (`conf/access.tsx`) sono uguali per ogni template; per un'app aperta a tutti togli `authGate`.
1469
1562
 
1470
1563
  ---
1471
1564
 
@@ -1477,6 +1570,7 @@ Driver per `VITE_PROVIDER`: `firebase` → `firestoreDb` + `firestorage` + `fire
1477
1570
  | `inputType` o `Input.Number`/`Input.Email` non esistono | Il tipo HTML è la prop `type` | `<Input type="number">`, `<Input type="email">`, `<Input type="date">` |
1478
1571
  | `Menu context=` non esiste | La prop si chiama `menuKey` | `<Menu menuKey="main" />` |
1479
1572
  | `SignInButton` non esiste | Rinominato | `<AuthButton />` |
1573
+ | Pagina `/login` con redirect, form di accesso fatti a mano | Le schermate e il gate ci sono (CR-127) | `<App authGate={{ fallback: <SignIn … /> }}>`, rotte aperte con `public: true` |
1480
1574
  | `firebase: { config: ... }` errato | FirebaseConfig è flat | `firebase: { apiKey, authDomain, databaseURL, ... }` |
1481
1575
  | `google: { oAuth2: { clientId } }` errato | GoogleProviderConfig è flat | `google: { clientId, scope }` |
1482
1576
  | `children` su `Grid` | Grid non accetta children | Usa `form={<Fields />}` per il form inline |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmnative/react",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "LLM-native React framework for deterministic UI generation. Data-driven by default, schema-driven optional. Ultra-low token consumption — built for AI agents, not just humans.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",