@mr.dj2u/library-registry 0.2.0 → 0.3.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 (86) hide show
  1. package/README.md +3 -0
  2. package/assets/mds/env/convex.env.example +1 -0
  3. package/assets/mds/env/firebase.env.example +6 -0
  4. package/assets/mds/env/supabase.env.example +3 -0
  5. package/assets/mds/project/auth-base.md +17 -0
  6. package/assets/mds/project/auth-convex.md +33 -0
  7. package/assets/mds/project/auth-firebase.md +22 -0
  8. package/assets/mds/project/auth-supabase.md +46 -0
  9. package/assets/mds/src/app/(auth)/reset-password.tsx +5 -0
  10. package/assets/mds/src/app/(auth)/sign-in.tsx +5 -0
  11. package/assets/mds/src/app/(auth)/sign-up.tsx +5 -0
  12. package/assets/mds/src/app/legal/updates.tsx +1 -0
  13. package/assets/mds/src/app/onboarding/complete.tsx +2 -0
  14. package/assets/mds/src/app/onboarding/features.tsx +1 -0
  15. package/assets/mds/src/app/onboarding/legal.tsx +2 -0
  16. package/assets/mds/src/app/onboarding.tsx +1 -1
  17. package/assets/mds/src/app/settings.tsx +10 -1
  18. package/assets/mds/src/db/adapter.ts +195 -0
  19. package/assets/mds/src/db/firebase.ts +77 -0
  20. package/assets/mds/src/db/index.firebase.ts +19 -0
  21. package/assets/mds/src/db/index.supabase.ts +22 -0
  22. package/assets/mds/src/db/supabase.ts +337 -0
  23. package/assets/mds/src/features/auth/adapters/base-auth-adapter.tsx +82 -0
  24. package/assets/mds/src/features/auth/adapters/convex-auth-adapter.tsx +156 -0
  25. package/assets/mds/src/features/auth/adapters/firebase-auth-adapter.tsx +147 -0
  26. package/assets/mds/src/features/auth/adapters/supabase-auth-adapter.tsx +166 -0
  27. package/assets/mds/src/features/auth/auth-guard-logic.ts +19 -0
  28. package/assets/mds/src/features/auth/auth-guard.tsx +77 -0
  29. package/assets/mds/src/features/auth/auth-provider.tsx +60 -0
  30. package/assets/mds/src/features/auth/auth-screen.tsx +265 -0
  31. package/assets/mds/src/features/auth/auth-types.ts +45 -0
  32. package/assets/mds/src/features/exposition/data-screen.tsx +4 -2
  33. package/assets/mds/src/features/exposition/expo-sdk-56-screen.tsx +119 -76
  34. package/assets/mds/src/features/exposition/exposition-screen.tsx +10 -11
  35. package/assets/mds/src/features/exposition/stylist-screen.tsx +62 -11
  36. package/assets/mds/src/features/legal/legal-acceptance-adapter.ts +190 -0
  37. package/assets/mds/src/features/legal/legal-acceptance-config.ts +79 -0
  38. package/assets/mds/src/features/legal/legal-agreement-screen.tsx +9 -4
  39. package/assets/mds/src/features/legal/legal-document-modal.tsx +7 -3
  40. package/assets/mds/src/features/legal/legal-document-view.tsx +1 -0
  41. package/assets/mds/src/features/legal/legal-documents.ts +17 -3
  42. package/assets/mds/src/features/legal/legal-update-screen.tsx +297 -0
  43. package/assets/mds/src/features/legal/use-legal-acceptance.ts +1 -38
  44. package/assets/mds/src/features/onboarding/complete-screen.tsx +138 -0
  45. package/assets/mds/src/features/onboarding/features-screen.tsx +129 -0
  46. package/assets/mds/src/features/onboarding/legal-review-screen.tsx +242 -0
  47. package/assets/mds/src/features/onboarding/onboarding-config-with-legal.ts +104 -0
  48. package/assets/mds/src/features/onboarding/onboarding-config.ts +104 -0
  49. package/assets/mds/src/features/onboarding/onboarding-persistence-sync.tsx +1 -0
  50. package/assets/mds/src/features/onboarding/welcome-screen.tsx +130 -0
  51. package/assets/mds/src/features/onboarding-state/adapters/memory-onboarding-state-adapter.ts +15 -0
  52. package/assets/mds/src/features/onboarding-state/adapters/supabase-onboarding-state-adapter.ts +45 -0
  53. package/assets/mds/src/features/onboarding-state/adapters/zustand-onboarding-state-adapter.ts +16 -0
  54. package/assets/mds/src/features/onboarding-state/adapters/zustand-supabase-onboarding-state-adapter.ts +53 -0
  55. package/assets/mds/src/features/onboarding-state/onboarding-state-core.ts +108 -0
  56. package/assets/mds/src/features/onboarding-state/onboarding-state-memory.ts +106 -0
  57. package/assets/mds/src/features/onboarding-state/onboarding-state-supabase.ts +246 -0
  58. package/assets/mds/src/features/onboarding-state/onboarding-state-types.ts +64 -0
  59. package/assets/mds/src/features/onboarding-state/onboarding-state-zustand-supabase.ts +120 -0
  60. package/assets/mds/src/features/onboarding-state/onboarding-state-zustand.ts +71 -0
  61. package/assets/mds/src/features/onboarding-state/onboarding-state.ts +22 -0
  62. package/assets/mds/src/features/onboarding-state/onboarding-store.ts +39 -0
  63. package/assets/mds/src/features/settings/settings-screen-logic.ts +122 -0
  64. package/assets/mds/src/features/settings/settings-screen.tsx +274 -28
  65. package/assets/mds/src/services/convex.ts +40 -0
  66. package/assets/mds/src/services/firebase.ts +70 -0
  67. package/assets/mds/src/services/supabase.ts +50 -0
  68. package/assets/mds/src/theme/color-utils.ts +42 -0
  69. package/assets/mds/src/theme/provider.tsx +15 -5
  70. package/assets/mds/src/types/database.ts +13 -0
  71. package/assets/mds/supabase/migrations/0001_mds_auth_onboarding.sql +107 -0
  72. package/dist/catalog.d.ts.map +1 -1
  73. package/dist/catalog.js +444 -29
  74. package/dist/catalog.js.map +1 -1
  75. package/dist/types.d.ts +5 -1
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/assets/mds/src/app/onboarding/account-setup.tsx +0 -1
  79. package/assets/mds/src/app/onboarding/agreement.tsx +0 -1
  80. package/assets/mds/src/app/onboarding/terms.tsx +0 -1
  81. package/assets/mds/src/features/onboarding/account-setup-screen.tsx +0 -54
  82. package/assets/mds/src/features/onboarding/agreement-screen.tsx +0 -6
  83. package/assets/mds/src/features/onboarding/components/legal-document-view.tsx +0 -103
  84. package/assets/mds/src/features/onboarding/legal-documents.ts +0 -80
  85. package/assets/mds/src/features/onboarding/onboarding-screen.tsx +0 -167
  86. package/assets/mds/src/features/onboarding/terms-screen.tsx +0 -6
@@ -0,0 +1,337 @@
1
+ import {
2
+ DatabaseAdapterError,
3
+ DatabaseConflictError,
4
+ DatabaseNotFoundError,
5
+ DatabaseTimeoutError,
6
+ DatabaseUnauthorizedError,
7
+ DatabaseUnsupportedError,
8
+ DatabaseValidationError,
9
+ type DatabaseAdapter,
10
+ type DatabaseChangeEvent,
11
+ type DatabaseChangeType,
12
+ type DatabaseFilter,
13
+ type DatabaseMutationInput,
14
+ type DatabaseMutationResult,
15
+ type DatabaseQueryInput,
16
+ type DatabaseSchema,
17
+ type DatabaseSubscribeInput,
18
+ type DatabaseTableName,
19
+ type DatabaseTableRow,
20
+ } from './adapter';
21
+
22
+ type SupabaseError = {
23
+ code?: string;
24
+ message?: string;
25
+ details?: string;
26
+ hint?: string;
27
+ };
28
+
29
+ type SupabaseResult<T> = {
30
+ data: T | null;
31
+ error: SupabaseError | null;
32
+ count?: number | null;
33
+ };
34
+
35
+ type SupabaseBuilder<T = unknown> = PromiseLike<SupabaseResult<T>> & {
36
+ [key: string]: unknown;
37
+ };
38
+
39
+ export type SupabaseDatabaseClient = {
40
+ from(table: string): object;
41
+ channel?(name: string): {
42
+ on(
43
+ type: string,
44
+ filter: Record<string, unknown>,
45
+ callback: (payload: Record<string, unknown>) => void,
46
+ ): { subscribe(): unknown };
47
+ subscribe(): unknown;
48
+ };
49
+ removeChannel?(channel: unknown): Promise<unknown> | unknown;
50
+ };
51
+
52
+ export type SupabaseDatabaseClientFactory = () => SupabaseDatabaseClient;
53
+
54
+ export interface SupabaseDatabaseAdapterOptions {
55
+ version?: string;
56
+ schema?: string;
57
+ transactionMode?: 'callback' | 'unsupported';
58
+ }
59
+
60
+ function selectColumns(select?: string | readonly string[]): string {
61
+ if (typeof select === 'string') {
62
+ return select;
63
+ }
64
+ if (select) {
65
+ return [...select].join(', ');
66
+ }
67
+ return '*';
68
+ }
69
+
70
+ function requireBuilderMethod<T extends (...args: never[]) => unknown>(
71
+ builder: object,
72
+ method: string,
73
+ table: string,
74
+ ): T {
75
+ const fn = (builder as Record<string, unknown>)[method];
76
+ if (typeof fn !== 'function') {
77
+ throw new DatabaseUnsupportedError(`Supabase builder does not support ${method} for ${table}.`, {
78
+ table,
79
+ });
80
+ }
81
+ return fn.bind(builder) as T;
82
+ }
83
+
84
+ function applyFilters<Row extends DatabaseTableRow, Result>(
85
+ builder: SupabaseBuilder<Result>,
86
+ filters: readonly DatabaseFilter<Row>[] | undefined,
87
+ table: string,
88
+ ): SupabaseBuilder<Result> {
89
+ let next = builder;
90
+ for (const filter of filters ?? []) {
91
+ const operator = String(filter.operator ?? 'eq');
92
+ const method = requireBuilderMethod<(column: string, value: unknown) => SupabaseBuilder<Result>>(
93
+ next,
94
+ operator,
95
+ table,
96
+ );
97
+ next = method(filter.column, filter.value);
98
+ }
99
+ return next;
100
+ }
101
+
102
+ function applyQueryOptions<Row extends DatabaseTableRow, Result>(
103
+ builder: SupabaseBuilder<Result>,
104
+ input: Pick<DatabaseQueryInput<DatabaseSchema, string>, 'limit' | 'orderBy'> & {
105
+ filters?: readonly DatabaseFilter<Row>[];
106
+ },
107
+ table: string,
108
+ ): SupabaseBuilder<Result> {
109
+ let next = applyFilters(builder, input.filters, table);
110
+ if (input.orderBy) {
111
+ const order = requireBuilderMethod<
112
+ (column: string, options?: { ascending?: boolean }) => SupabaseBuilder<Result>
113
+ >(next, 'order', table);
114
+ next = order(input.orderBy.column, { ascending: input.orderBy.ascending ?? true });
115
+ }
116
+ if (input.limit !== undefined) {
117
+ const limit = requireBuilderMethod<(count: number) => SupabaseBuilder<Result>>(next, 'limit', table);
118
+ next = limit(input.limit);
119
+ }
120
+ return next;
121
+ }
122
+
123
+ function mapSupabaseError(error: SupabaseError, table?: string): DatabaseAdapterError {
124
+ const message = error.message ?? 'Supabase database operation failed.';
125
+ const providerCode = error.code;
126
+ const options = { cause: error, providerCode, table };
127
+
128
+ if (providerCode === 'PGRST116' || /not found|no rows?/iu.test(message)) {
129
+ return new DatabaseNotFoundError(message, options);
130
+ }
131
+ if (providerCode === '23505' || providerCode === '23503' || /duplicate|conflict/iu.test(message)) {
132
+ return new DatabaseConflictError(message, options);
133
+ }
134
+ if (providerCode === '57014' || /timeout|timed out|canceling statement/iu.test(message)) {
135
+ return new DatabaseTimeoutError(message, options);
136
+ }
137
+ if (
138
+ providerCode === '42501' ||
139
+ providerCode === 'PGRST301' ||
140
+ /permission|not authorized|unauthorized|row-level security/iu.test(message)
141
+ ) {
142
+ return new DatabaseUnauthorizedError(message, options);
143
+ }
144
+ if (/invalid|violates check constraint|null value|bad request/iu.test(message)) {
145
+ return new DatabaseValidationError(message, options);
146
+ }
147
+ return new DatabaseAdapterError('unknown', message, options);
148
+ }
149
+
150
+ function throwIfError<T>(result: SupabaseResult<T>, table: string): T | null {
151
+ if (result.error) {
152
+ throw mapSupabaseError(result.error, table);
153
+ }
154
+ return result.data;
155
+ }
156
+
157
+ function rowsFromResult<Row extends DatabaseTableRow>(data: Row[] | Row | null): Row[] {
158
+ if (!data) {
159
+ return [];
160
+ }
161
+ return Array.isArray(data) ? data : [data];
162
+ }
163
+
164
+ function toPostgresEvent(event: DatabaseChangeType | undefined): string {
165
+ if (!event || event === '*') return '*';
166
+ return event.toUpperCase();
167
+ }
168
+
169
+ function fromPostgresEvent(eventType: unknown): Exclude<DatabaseChangeType, '*'> {
170
+ if (eventType === 'INSERT') return 'insert';
171
+ if (eventType === 'UPDATE') return 'update';
172
+ return 'delete';
173
+ }
174
+
175
+ export function createSupabaseDatabaseAdapter<Schema extends DatabaseSchema>(
176
+ getClient: SupabaseDatabaseClientFactory,
177
+ options: SupabaseDatabaseAdapterOptions = {},
178
+ ): DatabaseAdapter<Schema> {
179
+ const adapter: DatabaseAdapter<Schema> = {
180
+ name: 'supabase',
181
+ version: options.version ?? '1.0.0',
182
+ capabilities: {
183
+ transactions: false,
184
+ subscriptions: true,
185
+ rls: true,
186
+ authIntegration: true,
187
+ },
188
+
189
+ async query<Table extends DatabaseTableName<Schema>>(
190
+ input: DatabaseQueryInput<Schema, Table>,
191
+ ): Promise<Schema[Table][]> {
192
+ const table = String(input.table);
193
+ const tableClient = getClient().from(table);
194
+ const select = requireBuilderMethod<(columns: string) => SupabaseBuilder<Schema[Table][]>>(
195
+ tableClient,
196
+ 'select',
197
+ table,
198
+ );
199
+ let builder = applyQueryOptions(select(selectColumns(input.select)), input, table);
200
+ if (input.single) {
201
+ const single =
202
+ typeof builder.maybeSingle === 'function'
203
+ ? (builder.maybeSingle.bind(builder) as () => Promise<SupabaseResult<Schema[Table]>>)
204
+ : typeof builder.single === 'function'
205
+ ? (builder.single.bind(builder) as () => Promise<SupabaseResult<Schema[Table]>>)
206
+ : null;
207
+ if (!single) {
208
+ throw new DatabaseUnsupportedError(`Supabase builder does not support single queries for ${table}.`, {
209
+ table,
210
+ });
211
+ }
212
+ return rowsFromResult<Schema[Table]>(throwIfError(await single(), table));
213
+ }
214
+ return rowsFromResult<Schema[Table]>(throwIfError(await builder, table));
215
+ },
216
+
217
+ async mutate<Table extends DatabaseTableName<Schema>>(
218
+ input: DatabaseMutationInput<Schema, Table>,
219
+ ): Promise<DatabaseMutationResult<Schema[Table]>> {
220
+ const table = String(input.table);
221
+ const tableClient = getClient().from(table);
222
+ let builder: SupabaseBuilder<Schema[Table][]>;
223
+
224
+ if (input.type === 'insert') {
225
+ if (!input.values) {
226
+ throw new DatabaseValidationError(`Insert into ${table} requires values.`, { table });
227
+ }
228
+ builder = requireBuilderMethod<(values: unknown) => SupabaseBuilder<Schema[Table][]>>(
229
+ tableClient,
230
+ 'insert',
231
+ table,
232
+ )(input.values);
233
+ } else if (input.type === 'update') {
234
+ if (!input.values) {
235
+ throw new DatabaseValidationError(`Update ${table} requires values.`, { table });
236
+ }
237
+ builder = requireBuilderMethod<(values: unknown) => SupabaseBuilder<Schema[Table][]>>(
238
+ tableClient,
239
+ 'update',
240
+ table,
241
+ )(input.values);
242
+ builder = applyFilters(builder, input.filters, table);
243
+ } else if (input.type === 'upsert') {
244
+ if (!input.values) {
245
+ throw new DatabaseValidationError(`Upsert into ${table} requires values.`, { table });
246
+ }
247
+ builder = requireBuilderMethod<(values: unknown) => SupabaseBuilder<Schema[Table][]>>(
248
+ tableClient,
249
+ 'upsert',
250
+ table,
251
+ )(input.values);
252
+ } else {
253
+ builder = requireBuilderMethod<() => SupabaseBuilder<Schema[Table][]>>(
254
+ tableClient,
255
+ 'delete',
256
+ table,
257
+ )();
258
+ builder = applyFilters(builder, input.filters, table);
259
+ }
260
+
261
+ if (typeof builder.select === 'function') {
262
+ builder = (builder.select as (columns: string) => SupabaseBuilder<Schema[Table][]>)(
263
+ selectColumns(input.select),
264
+ );
265
+ }
266
+
267
+ const result = await builder;
268
+ const rows = rowsFromResult<Schema[Table]>(throwIfError(result, table));
269
+ return {
270
+ rows,
271
+ count: result.count ?? rows.length,
272
+ };
273
+ },
274
+
275
+ async transaction<Result>(fn: (client: DatabaseAdapter<Schema>) => Promise<Result>): Promise<Result> {
276
+ if (options.transactionMode === 'unsupported') {
277
+ throw new DatabaseUnsupportedError(
278
+ 'Supabase client-side transactions are not available through the generated adapter. Use a Postgres function or server route for atomic multi-step writes.',
279
+ );
280
+ }
281
+ return fn(adapter);
282
+ },
283
+
284
+ subscribe<Table extends DatabaseTableName<Schema>>(
285
+ input: DatabaseSubscribeInput<Schema, Table>,
286
+ onChange: (event: DatabaseChangeEvent<Schema[Table]>) => void,
287
+ onError?: (error: DatabaseAdapterError) => void,
288
+ ) {
289
+ const table = String(input.table);
290
+ const client = getClient();
291
+ if (!client.channel) {
292
+ throw new DatabaseUnsupportedError('Supabase subscriptions require a realtime-capable client.', {
293
+ table,
294
+ });
295
+ }
296
+ if (input.filters?.length) {
297
+ onError?.(
298
+ new DatabaseUnsupportedError(
299
+ 'Supabase realtime filters are intentionally not generated yet. Filter rows in the callback or add a provider-specific adapter extension.',
300
+ { table },
301
+ ),
302
+ );
303
+ }
304
+
305
+ const channel = client
306
+ .channel(`mds-db-${table}`)
307
+ .on(
308
+ 'postgres_changes',
309
+ {
310
+ event: toPostgresEvent(input.event),
311
+ schema: options.schema ?? 'public',
312
+ table,
313
+ },
314
+ (payload) => {
315
+ onChange({
316
+ type: fromPostgresEvent(payload.eventType),
317
+ table,
318
+ row: (payload.new as Schema[Table] | null | undefined) ?? null,
319
+ oldRow: (payload.old as Partial<Schema[Table]> | null | undefined) ?? null,
320
+ });
321
+ },
322
+ )
323
+ .subscribe();
324
+
325
+ return () => {
326
+ if (channel && typeof (channel as { unsubscribe?: unknown }).unsubscribe === 'function') {
327
+ void (channel as { unsubscribe: () => unknown }).unsubscribe();
328
+ return;
329
+ }
330
+ void client.removeChannel?.(channel);
331
+ };
332
+ },
333
+ };
334
+
335
+ return adapter;
336
+ }
337
+
@@ -0,0 +1,82 @@
1
+ import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
2
+
3
+ import type { AuthActionInput, AuthActionResult, AuthAdapter, AuthSession } from './auth-types';
4
+
5
+ let activeSession: AuthSession | null = null;
6
+ const listeners = new Set<(session: AuthSession | null) => void>();
7
+
8
+ function emit(nextSession: AuthSession | null) {
9
+ activeSession = nextSession;
10
+ for (const listener of listeners) listener(nextSession);
11
+ }
12
+
13
+ function sessionFromEmail(email: string): AuthSession {
14
+ return {
15
+ user: {
16
+ id: `base-${email.trim().toLowerCase()}`,
17
+ email: email.trim(),
18
+ provider: 'base',
19
+ },
20
+ };
21
+ }
22
+
23
+ export function AuthAdapterProvider({ children }: { children: ReactNode }) {
24
+ return <>{children}</>;
25
+ }
26
+
27
+ export function useAuthAdapter(): AuthAdapter {
28
+ const [session, setSession] = useState<AuthSession | null>(activeSession);
29
+
30
+ useEffect(() => {
31
+ listeners.add(setSession);
32
+ return () => {
33
+ listeners.delete(setSession);
34
+ };
35
+ }, []);
36
+
37
+ const signInWithEmailPassword = useCallback(async ({ email }: AuthActionInput) => {
38
+ emit(sessionFromEmail(email));
39
+ return { ok: true } satisfies AuthActionResult;
40
+ }, []);
41
+
42
+ const signUpWithEmailPassword = useCallback(async ({ email }: AuthActionInput) => {
43
+ emit(sessionFromEmail(email));
44
+ return { ok: true } satisfies AuthActionResult;
45
+ }, []);
46
+
47
+ const requestPasswordReset = useCallback(async () => {
48
+ return {
49
+ ok: true,
50
+ message: 'Base auth has no backend. Wire this adapter to your provider before release.',
51
+ } satisfies AuthActionResult;
52
+ }, []);
53
+
54
+ const signOut = useCallback(async () => {
55
+ emit(null);
56
+ return { ok: true } satisfies AuthActionResult;
57
+ }, []);
58
+
59
+ const refreshSession = useCallback(async () => {
60
+ emit(activeSession);
61
+ }, []);
62
+
63
+ return useMemo<AuthAdapter>(
64
+ () => ({
65
+ provider: 'base',
66
+ state: { isLoading: false, session },
67
+ refreshSession,
68
+ signInWithEmailPassword,
69
+ signUpWithEmailPassword,
70
+ requestPasswordReset,
71
+ signOut,
72
+ }),
73
+ [
74
+ refreshSession,
75
+ requestPasswordReset,
76
+ session,
77
+ signInWithEmailPassword,
78
+ signOut,
79
+ signUpWithEmailPassword,
80
+ ],
81
+ );
82
+ }
@@ -0,0 +1,156 @@
1
+ import { ConvexAuthProvider, useAuthActions, useConvexAuth } from '@convex-dev/auth/react';
2
+ import { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react';
3
+
4
+ import { convexAuthStorage, getConvexClient, isConvexConfigured } from '../../services/convex';
5
+
6
+ import type { AuthActionInput, AuthActionResult, AuthAdapter, AuthSession } from './auth-types';
7
+
8
+ const configurationError =
9
+ 'Set EXPO_PUBLIC_CONVEX_URL and initialize Convex Auth before using this generated adapter.';
10
+
11
+ const ConvexAuthAdapterContext = createContext<AuthAdapter | null>(null);
12
+
13
+ function authError(error: unknown): AuthActionResult {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ return { ok: false, error: message };
16
+ }
17
+
18
+ function useMissingConvexAuthAdapter(): AuthAdapter {
19
+ const unavailable = useCallback(async () => {
20
+ return { ok: false, error: configurationError } satisfies AuthActionResult;
21
+ }, []);
22
+
23
+ const refreshSession = useCallback(async () => {}, []);
24
+
25
+ return useMemo<AuthAdapter>(
26
+ () => ({
27
+ provider: 'convex',
28
+ state: { isLoading: false, session: null, error: configurationError },
29
+ refreshSession,
30
+ signInWithEmailPassword: unavailable,
31
+ signUpWithEmailPassword: unavailable,
32
+ requestPasswordReset: unavailable,
33
+ signOut: unavailable,
34
+ }),
35
+ [refreshSession, unavailable],
36
+ );
37
+ }
38
+
39
+ function MissingConvexAuthAdapterProvider({ children }: { children: ReactNode }) {
40
+ const adapter = useMissingConvexAuthAdapter();
41
+ return <ConvexAuthAdapterContext.Provider value={adapter}>{children}</ConvexAuthAdapterContext.Provider>;
42
+ }
43
+
44
+ function ConfiguredConvexAuthAdapterProvider({ children }: { children: ReactNode }) {
45
+ return (
46
+ <ConvexAuthProvider client={getConvexClient()} storage={convexAuthStorage}>
47
+ <ConfiguredConvexAuthAdapterState>{children}</ConfiguredConvexAuthAdapterState>
48
+ </ConvexAuthProvider>
49
+ );
50
+ }
51
+
52
+ function ConfiguredConvexAuthAdapterState({ children }: { children: ReactNode }) {
53
+ const adapter = useConfiguredConvexAuthAdapter();
54
+ return <ConvexAuthAdapterContext.Provider value={adapter}>{children}</ConvexAuthAdapterContext.Provider>;
55
+ }
56
+
57
+ export function AuthAdapterProvider({ children }: { children: ReactNode }) {
58
+ if (!isConvexConfigured) {
59
+ return <MissingConvexAuthAdapterProvider>{children}</MissingConvexAuthAdapterProvider>;
60
+ }
61
+
62
+ return <ConfiguredConvexAuthAdapterProvider>{children}</ConfiguredConvexAuthAdapterProvider>;
63
+ }
64
+
65
+ function useConfiguredConvexAuthAdapter(): AuthAdapter {
66
+ const { isAuthenticated, isLoading } = useConvexAuth();
67
+ const { signIn, signOut: convexSignOut } = useAuthActions();
68
+ const session = useMemo<AuthSession | null>(
69
+ () =>
70
+ isAuthenticated
71
+ ? {
72
+ user: {
73
+ id: 'convex-auth-user',
74
+ provider: 'convex',
75
+ },
76
+ }
77
+ : null,
78
+ [isAuthenticated],
79
+ );
80
+
81
+ const signInWithEmailPassword = useCallback(
82
+ async ({ email, password }: AuthActionInput) => {
83
+ try {
84
+ await signIn('password', { email: email.trim(), password, flow: 'signIn' });
85
+ return { ok: true } satisfies AuthActionResult;
86
+ } catch (error) {
87
+ return authError(error);
88
+ }
89
+ },
90
+ [signIn],
91
+ );
92
+
93
+ const signUpWithEmailPassword = useCallback(
94
+ async ({ email, password }: AuthActionInput) => {
95
+ try {
96
+ await signIn('password', { email: email.trim(), password, flow: 'signUp' });
97
+ return { ok: true } satisfies AuthActionResult;
98
+ } catch (error) {
99
+ return authError(error);
100
+ }
101
+ },
102
+ [signIn],
103
+ );
104
+
105
+ const requestPasswordReset = useCallback(
106
+ async (email: string) => {
107
+ try {
108
+ await signIn('password', { email: email.trim(), flow: 'reset' });
109
+ return { ok: true, message: 'Password reset request sent.' } satisfies AuthActionResult;
110
+ } catch (error) {
111
+ return authError(error);
112
+ }
113
+ },
114
+ [signIn],
115
+ );
116
+
117
+ const signOut = useCallback(async () => {
118
+ try {
119
+ await convexSignOut();
120
+ return { ok: true } satisfies AuthActionResult;
121
+ } catch (error) {
122
+ return authError(error);
123
+ }
124
+ }, [convexSignOut]);
125
+
126
+ const refreshSession = useCallback(async () => {}, []);
127
+
128
+ return useMemo<AuthAdapter>(
129
+ () => ({
130
+ provider: 'convex',
131
+ state: { isLoading, session },
132
+ refreshSession,
133
+ signInWithEmailPassword,
134
+ signUpWithEmailPassword,
135
+ requestPasswordReset,
136
+ signOut,
137
+ }),
138
+ [
139
+ isLoading,
140
+ refreshSession,
141
+ requestPasswordReset,
142
+ session,
143
+ signInWithEmailPassword,
144
+ signOut,
145
+ signUpWithEmailPassword,
146
+ ],
147
+ );
148
+ }
149
+
150
+ export function useAuthAdapter(): AuthAdapter {
151
+ const adapter = useContext(ConvexAuthAdapterContext);
152
+ if (!adapter) {
153
+ throw new Error('useAuthAdapter must be used inside AuthAdapterProvider.');
154
+ }
155
+ return adapter;
156
+ }