@optare/react 0.1.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.
@@ -0,0 +1,396 @@
1
+ import { ReactNode } from 'react';
2
+ import { OptareBootstrap, OptareBranding, OptareProjectInfo, OptareAuthMethods, Session, User, OptareSocialProvider } from '@optare/client';
3
+ export { DEFAULT_AUTH_METHODS, DEFAULT_OPTARE_ORIGIN, OptareAuthMethods, OptareBootstrap, OptareBranding, OptareConfigError, OptareProjectInfo, OptareSocialProvider, Session, User, resolveOptareConfig } from '@optare/client';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+
6
+ interface CreateReactAuthClientOptions {
7
+ bootstrap: OptareBootstrap;
8
+ publishableKey: string;
9
+ /** Custom fetch (tests, RN, older Node). */
10
+ fetch?: typeof fetch;
11
+ /** Extra headers on every auth request. */
12
+ headers?: Record<string, string>;
13
+ }
14
+ /**
15
+ * Structural type for the bits of the better-auth React client the SDK and its
16
+ * components actually touch, plus an escape hatch for everything else (the
17
+ * client is a dynamic RPC proxy, so arbitrary property access is meaningful).
18
+ *
19
+ * An explicit annotation is required here: better-auth's fully-inferred client
20
+ * type is too large for TypeScript to serialise into a `.d.ts` (TS7056/TS2742).
21
+ */
22
+ interface OptareReactAuthClient {
23
+ getSession: (...args: any[]) => Promise<any>;
24
+ useSession: () => {
25
+ data: any;
26
+ isPending: boolean;
27
+ isRefetching: boolean;
28
+ error: unknown;
29
+ refetch: (...args: any[]) => Promise<void>;
30
+ };
31
+ signIn: {
32
+ email: (body: {
33
+ email: string;
34
+ password: string;
35
+ callbackURL?: string;
36
+ rememberMe?: boolean;
37
+ }) => Promise<{
38
+ data?: any;
39
+ error?: {
40
+ message?: string;
41
+ code?: string;
42
+ } | null;
43
+ }>;
44
+ magicLink: (body: {
45
+ email: string;
46
+ callbackURL?: string;
47
+ }) => Promise<{
48
+ data?: any;
49
+ error?: {
50
+ message?: string;
51
+ code?: string;
52
+ } | null;
53
+ }>;
54
+ [key: string]: (...args: any[]) => Promise<any>;
55
+ };
56
+ signUp: {
57
+ email: (body: {
58
+ email: string;
59
+ password: string;
60
+ name: string;
61
+ callbackURL?: string;
62
+ }) => Promise<{
63
+ data?: any;
64
+ error?: {
65
+ message?: string;
66
+ code?: string;
67
+ } | null;
68
+ }>;
69
+ };
70
+ signOut: (...args: any[]) => Promise<any>;
71
+ organization: {
72
+ setActive: (body: {
73
+ organizationId: string | null;
74
+ }) => Promise<any>;
75
+ [key: string]: (...args: any[]) => Promise<any>;
76
+ };
77
+ twoFactor: Record<string, (...args: any[]) => Promise<any>>;
78
+ emailOtp: Record<string, (...args: any[]) => Promise<any>>;
79
+ [key: string]: any;
80
+ }
81
+ declare function createReactAuthClient(options: CreateReactAuthClientOptions): OptareReactAuthClient;
82
+
83
+ /**
84
+ * White-label theming — E2, expanded in E11.
85
+ *
86
+ * Every component renders against CSS custom properties. The provider resolves
87
+ * `branding` (from the publishable key, via `@optare/client`) once and exposes
88
+ * these variables; a host app can override any of them with plain CSS.
89
+ *
90
+ * Only two values come from branding — the primary colour and the corner
91
+ * radius — and both are regex-gated (`safeColor` / `safeLength`) so a project's
92
+ * branding string can never inject CSS. Everything else is a fixed neutral
93
+ * palette tuned for legibility on a white card.
94
+ */
95
+ declare const DEFAULT_PRIMARY = "#4f46e5";
96
+ declare const DEFAULT_RADIUS = "10px";
97
+ /** Reject anything that isn't a plain colour/length so branding can't inject CSS. */
98
+ declare function safeColor(value: string | null | undefined, fallback: string): string;
99
+ declare function safeLength(value: string | null | undefined, fallback: string): string;
100
+ /**
101
+ * Pick readable text (`#ffffff` or a near-black) to sit on `hex`, choosing
102
+ * whichever has the higher WCAG contrast ratio. This is what keeps the primary
103
+ * button and the avatar legible whatever colour the project picked — the E2
104
+ * version compared raw luminance and washed out on mid-tones.
105
+ */
106
+ declare function contrastText(hex: string): string;
107
+ type OptareCssVars = Record<`--optare-${string}`, string>;
108
+ declare function brandingToCssVars(branding: OptareBranding | null): OptareCssVars;
109
+
110
+ /**
111
+ * `<OptareProvider>` — E2.
112
+ *
113
+ * Give it a publishable key; it resolves the endpoint + branding (once) and
114
+ * builds the React auth client. Children read the client and branding through
115
+ * the hooks in this file. Pass `bootstrap` to skip the network entirely (SSR,
116
+ * tests, or when the host already fetched `GET /api/public/config`).
117
+ */
118
+ type OptareStatus = "loading" | "ready" | "error";
119
+ interface OptareContextValue {
120
+ status: OptareStatus;
121
+ error: Error | null;
122
+ /** Resolved endpoint + project info + branding. `null` until ready. */
123
+ bootstrap: OptareBootstrap | null;
124
+ branding: OptareBranding | null;
125
+ project: OptareProjectInfo | null;
126
+ /** Enabled sign-in methods. Falls back to email+password before `ready`. */
127
+ authMethods: OptareAuthMethods;
128
+ cssVars: OptareCssVars;
129
+ /** The better-auth React client. `null` until ready. */
130
+ auth: OptareReactAuthClient | null;
131
+ publishableKey: string;
132
+ /** Re-run config resolution after an error. */
133
+ retry: () => void;
134
+ }
135
+ interface OptareProviderProps {
136
+ publishableKey: string;
137
+ bootstrap?: OptareBootstrap;
138
+ /** Override the auth API origin (self-hosted / preview). */
139
+ baseURL?: string;
140
+ /** Where `GET /api/public/config` lives. Defaults to `baseURL`, then hosted. */
141
+ configURL?: string;
142
+ fetch?: typeof fetch;
143
+ headers?: Record<string, string>;
144
+ /** Rendered while branding resolves. Default: children (optimistic). */
145
+ loadingFallback?: ReactNode;
146
+ /** Rendered if resolution fails. Default: children, with `status === "error"`. */
147
+ errorFallback?: ReactNode | ((error: Error, retry: () => void) => ReactNode);
148
+ children: ReactNode;
149
+ }
150
+ declare function OptareProvider(props: OptareProviderProps): ReactNode;
151
+ declare function useOptare(): OptareContextValue;
152
+ /** The better-auth React client. Throws if the provider isn't ready yet. */
153
+ declare function useOptareAuth(): OptareReactAuthClient;
154
+ declare function useOptareConfig(): {
155
+ bootstrap: OptareBootstrap | null;
156
+ project: OptareProjectInfo | null;
157
+ branding: OptareBranding | null;
158
+ status: OptareStatus;
159
+ };
160
+ declare function useBranding(): OptareBranding | null;
161
+ /** The instance's enabled sign-in methods (social providers, magic-link, …). */
162
+ declare function useAuthMethods(): OptareAuthMethods;
163
+
164
+ /**
165
+ * Session + identity hooks — E2.
166
+ */
167
+ interface UseSessionResult {
168
+ data: Session | null;
169
+ user: User | null;
170
+ isPending: boolean;
171
+ isAuthenticated: boolean;
172
+ error: Error | null;
173
+ refetch: () => void;
174
+ }
175
+ declare function useSession(): UseSessionResult;
176
+ declare function useUser(): User | null;
177
+ interface UseSignOutResult {
178
+ signOut: () => Promise<void>;
179
+ isPending: boolean;
180
+ error: Error | null;
181
+ }
182
+ declare function useSignOut(): UseSignOutResult;
183
+ interface UseActiveOrganizationResult {
184
+ activeOrganizationId: string | null;
185
+ setActive: (organizationId: string | null) => Promise<void>;
186
+ isPending: boolean;
187
+ }
188
+ /**
189
+ * Thin wrapper over the organization plugin's `setActive`. Reads the active id
190
+ * off the session (`session.activeOrganizationId`, added in F8).
191
+ */
192
+ declare function useActiveOrganization(): UseActiveOrganizationResult;
193
+
194
+ declare function notifySessionChanged(): void;
195
+
196
+ /**
197
+ * Client-side form validation — E2.
198
+ *
199
+ * Deliberately lenient: the server is the authority. These only exist to give
200
+ * immediate feedback and stop obviously-doomed round trips. Pure functions so
201
+ * they're trivially testable without a DOM.
202
+ */
203
+ declare function isValidEmail(value: string): boolean;
204
+ interface PasswordCheck {
205
+ ok: boolean;
206
+ /** 0-4 — for a strength meter. */
207
+ score: number;
208
+ message: string | null;
209
+ }
210
+ declare function checkPassword(value: string, min?: number): PasswordCheck;
211
+ interface SignInFields {
212
+ email: string;
213
+ password: string;
214
+ }
215
+ declare function validateSignIn(fields: SignInFields): Partial<Record<keyof SignInFields, string>>;
216
+ interface SignUpFields {
217
+ name: string;
218
+ email: string;
219
+ password: string;
220
+ }
221
+ declare function validateSignUp(fields: SignUpFields, passwordMin?: number): Partial<Record<keyof SignUpFields, string>>;
222
+
223
+ /**
224
+ * `<SignIn />` — E2, expanded in E11.
225
+ *
226
+ * Renders whatever the project's Optare instance has enabled: email + password,
227
+ * an "email me a link" (magic-link) path, an email one-time-code path, and the
228
+ * configured social providers. Branding (logo, name, colour, radius) comes from
229
+ * the publishable key. On success it fires `onSuccess` (or navigates to
230
+ * `redirectTo`). A `twoFactorRedirect` response surfaces `onTwoFactor` so the
231
+ * host can route to `<TwoFactorChallenge>`.
232
+ */
233
+ interface SignInProps {
234
+ /** Called after a completed sign-in (no 2FA pending). */
235
+ onSuccess?: () => void;
236
+ /** `window.location` target after sign-in, if `onSuccess` isn't given. */
237
+ redirectTo?: string;
238
+ /**
239
+ * Where the magic-link / social / email-verification round-trip lands the
240
+ * user. Defaults to the current page URL — set this only to send them
241
+ * somewhere else (e.g. a dedicated post-login route).
242
+ */
243
+ callbackURL?: string;
244
+ /** Invoked when the server says a 2FA challenge is required. */
245
+ onTwoFactor?: () => void;
246
+ /** Invoked when the user clicks "Forgot password?" — route to `<ForgotPassword>`. */
247
+ onForgotPassword?: () => void;
248
+ /** Show the "Email me a link instead" option. Default: follows instance config. */
249
+ allowMagicLink?: boolean;
250
+ /** Show the "Email me a code instead" option. Default: follows instance config. */
251
+ allowEmailOtp?: boolean;
252
+ /** Show social provider buttons. Default: follows instance config. */
253
+ allowSocial?: boolean;
254
+ /** Offer "Sign in with SSO" (work-email → IdP). Default: follows instance config. */
255
+ allowSso?: boolean;
256
+ /** Slot under the form — e.g. a link to `<SignUp />`. */
257
+ footer?: ReactNode;
258
+ title?: string;
259
+ subtitle?: string;
260
+ }
261
+ declare function SignIn(props: SignInProps): react_jsx_runtime.JSX.Element;
262
+
263
+ /**
264
+ * `<SignUp />` — E2.
265
+ *
266
+ * Name + email + password. The organization the new user lands in is decided
267
+ * server-side from the publishable key (F6 `resolveParentTenant`), so there is
268
+ * nothing to configure here. If the project requires email verification the
269
+ * server returns success with no session — `onVerificationRequired` fires.
270
+ */
271
+ interface SignUpProps {
272
+ onSuccess?: () => void;
273
+ redirectTo?: string;
274
+ callbackURL?: string;
275
+ /** Fired when signup succeeded but a verification email must be clicked first. */
276
+ onVerificationRequired?: (email: string) => void;
277
+ /** Minimum password length hint (server is authoritative). Default: 8. */
278
+ passwordMinLength?: number;
279
+ /** Show social provider buttons. Default: follows instance config. */
280
+ allowSocial?: boolean;
281
+ footer?: ReactNode;
282
+ title?: string;
283
+ subtitle?: string;
284
+ }
285
+ declare function SignUp(props: SignUpProps): react_jsx_runtime.JSX.Element;
286
+
287
+ /**
288
+ * `<AccountButton />` — E2.
289
+ *
290
+ * Avatar + name trigger with a dropdown (profile slot, sign out). Renders
291
+ * nothing while the session is loading and a `signInSlot` (or nothing) when
292
+ * signed out. Themed from the publishable key's branding.
293
+ */
294
+ interface AccountButtonProps {
295
+ /** Shown when there is no session. */
296
+ signInSlot?: ReactNode;
297
+ /** Extra menu items above "Sign out" (e.g. links to your own pages). */
298
+ menuItems?: ReactNode;
299
+ /** Called after sign-out completes. */
300
+ onSignedOut?: () => void;
301
+ /** Hide the name label, show only the avatar. */
302
+ compact?: boolean;
303
+ }
304
+ declare function AccountButton(props: AccountButtonProps): react_jsx_runtime.JSX.Element | null;
305
+
306
+ /**
307
+ * `<SocialButtons />` — E11.
308
+ *
309
+ * Renders one button per social provider the instance has enabled
310
+ * (`authMethods.social`, resolved from the publishable key). Clicking a button
311
+ * starts the OAuth redirect via better-auth's `signIn.social`. Dependency-free:
312
+ * the provider marks are inline SVG, so `@optare/react` doesn't pull an icon
313
+ * library into the customer's bundle.
314
+ *
315
+ * Renders nothing when no providers are enabled, so callers can drop it in
316
+ * unconditionally.
317
+ */
318
+ interface SocialButtonsProps {
319
+ /** URL to land on after the provider round-trip. Default: current page. */
320
+ callbackURL?: string;
321
+ /** URL to land on if the provider flow errors. */
322
+ errorCallbackURL?: string;
323
+ /** Verb in the button label. Default: "Continue with". */
324
+ label?: string;
325
+ /** Show a divider above the buttons. Default: true. */
326
+ divider?: boolean;
327
+ /** Divider text. Default: "or". */
328
+ dividerLabel?: string;
329
+ /** Restrict to a subset / order of the enabled providers. */
330
+ only?: OptareSocialProvider[];
331
+ }
332
+ declare function SocialButtons(props: SocialButtonsProps): react_jsx_runtime.JSX.Element | null;
333
+
334
+ /**
335
+ * `<ForgotPassword />` — E11.
336
+ *
337
+ * Collects an email and asks the server to send a reset link
338
+ * (`auth.forgetPassword`). The link lands on `redirectTo` with a `?token=…`
339
+ * that `<ResetPassword>` consumes. Always shows the same confirmation whether
340
+ * or not the address exists — no account-enumeration signal.
341
+ */
342
+ interface ForgotPasswordProps {
343
+ /** Path the reset link should point at (your `<ResetPassword>` route). Default: `/reset-password`. */
344
+ redirectTo?: string;
345
+ /** Back-to-sign-in handler. */
346
+ onBack?: () => void;
347
+ title?: string;
348
+ subtitle?: string;
349
+ footer?: ReactNode;
350
+ }
351
+ declare function ForgotPassword(props: ForgotPasswordProps): react_jsx_runtime.JSX.Element;
352
+
353
+ /**
354
+ * `<ResetPassword />` — E11.
355
+ *
356
+ * The landing screen for the link `<ForgotPassword>` sends. Reads the reset
357
+ * `token` from the URL (`?token=…`, better-auth's default) unless one is passed
358
+ * explicitly, takes a new password, and calls `auth.resetPassword`.
359
+ */
360
+ interface ResetPasswordProps {
361
+ /** Reset token. Default: read from `?token=` / `?t=` in the current URL. */
362
+ token?: string;
363
+ /** Called after the password is changed. */
364
+ onSuccess?: () => void;
365
+ /** `window.location` target after success, if `onSuccess` isn't given. */
366
+ redirectTo?: string;
367
+ /** Back-to-sign-in handler, shown when the token is missing/invalid. */
368
+ onBack?: () => void;
369
+ /** Minimum length hint (server is authoritative). Default: 12. */
370
+ passwordMinLength?: number;
371
+ title?: string;
372
+ footer?: ReactNode;
373
+ }
374
+ declare function ResetPassword(props: ResetPasswordProps): react_jsx_runtime.JSX.Element;
375
+
376
+ /**
377
+ * `<TwoFactorChallenge />` — E11.
378
+ *
379
+ * Shown after `<SignIn>` reports `onTwoFactor` (better-auth returned
380
+ * `twoFactorRedirect`). Verifies a TOTP code from the user's authenticator, or
381
+ * a one-time backup code. On success the session is live — fires `onSuccess`.
382
+ */
383
+ interface TwoFactorChallengeProps {
384
+ onSuccess?: () => void;
385
+ redirectTo?: string;
386
+ /** "Use a different account" / cancel handler. */
387
+ onCancel?: () => void;
388
+ /** Offer the "use a backup code" switch. Default: true. */
389
+ allowBackupCode?: boolean;
390
+ title?: string;
391
+ subtitle?: string;
392
+ footer?: ReactNode;
393
+ }
394
+ declare function TwoFactorChallenge(props: TwoFactorChallengeProps): react_jsx_runtime.JSX.Element;
395
+
396
+ export { AccountButton, type AccountButtonProps, type CreateReactAuthClientOptions, DEFAULT_PRIMARY, DEFAULT_RADIUS, ForgotPassword, type ForgotPasswordProps, type OptareContextValue, type OptareCssVars, OptareProvider, type OptareProviderProps, type OptareReactAuthClient, type OptareStatus, type PasswordCheck, ResetPassword, type ResetPasswordProps, SignIn, type SignInFields, type SignInProps, SignUp, type SignUpFields, type SignUpProps, SocialButtons, type SocialButtonsProps, TwoFactorChallenge, type TwoFactorChallengeProps, type UseActiveOrganizationResult, type UseSessionResult, type UseSignOutResult, brandingToCssVars, checkPassword, contrastText, createReactAuthClient, isValidEmail, notifySessionChanged, safeColor, safeLength, useActiveOrganization, useAuthMethods, useBranding, useOptare, useOptareAuth, useOptareConfig, useSession, useSignOut, useUser, validateSignIn, validateSignUp };