@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,242 @@
1
+ import { useRouter } from 'expo-router';
2
+ import { useState } from 'react';
3
+ import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
4
+
5
+ import { LegalDocumentModal } from '../legal/legal-document-modal';
6
+ import { getLegalDocument, type LegalDocumentId } from '../legal/legal-documents';
7
+ import {
8
+ getRequiredMaterialLegalDocuments,
9
+ useLegalUpdateGateSnapshot,
10
+ } from '../legal/legal-acceptance-adapter';
11
+ import { getReadableTextColor } from '../../theme/color-utils';
12
+ import { useAppTheme } from '../../theme/provider';
13
+ import { markOnboardingComplete } from '../onboarding-state/onboarding-state';
14
+ import { onboardingConfig } from './onboarding-config';
15
+
16
+ const requiredDocuments = getRequiredMaterialLegalDocuments();
17
+
18
+ function LegalRow({
19
+ documentId,
20
+ accepted,
21
+ onOpen,
22
+ }: {
23
+ documentId: LegalDocumentId;
24
+ accepted: boolean;
25
+ onOpen: () => void;
26
+ }) {
27
+ const theme = useAppTheme();
28
+ const colors = theme.activeColors;
29
+ const document = getLegalDocument(documentId);
30
+
31
+ return (
32
+ <Pressable
33
+ accessibilityRole="button"
34
+ onPress={onOpen}
35
+ style={[
36
+ styles.documentCard,
37
+ {
38
+ backgroundColor: colors.surface,
39
+ borderColor: accepted ? colors.success : colors.primary,
40
+ borderRadius: theme.layout.radius,
41
+ },
42
+ ]}
43
+ >
44
+ <View style={styles.documentText}>
45
+ <Text style={[styles.documentTitle, { color: colors.text }]}>{document.title}</Text>
46
+ <Text style={[styles.documentMeta, { color: colors.text }]}>
47
+ Last updated {document.lastUpdated}
48
+ </Text>
49
+ </View>
50
+ <Text style={[styles.documentAction, { color: accepted ? colors.success : colors.primary }]}>
51
+ {accepted ? 'Accepted' : 'Review'}
52
+ </Text>
53
+ </Pressable>
54
+ );
55
+ }
56
+
57
+ export default function OnboardingLegalReviewScreen() {
58
+ const router = useRouter();
59
+ const theme = useAppTheme();
60
+ const colors = theme.activeColors;
61
+ const primaryForeground = getReadableTextColor(colors.primary, theme.colors.light.text);
62
+ const [activeDocument, setActiveDocument] = useState<LegalDocumentId | null>(null);
63
+ const [completionError, setCompletionError] = useState<string | null>(null);
64
+ const [isCompleting, setIsCompleting] = useState(false);
65
+ const { snapshot, acceptDocument, savingDocumentId } = useLegalUpdateGateSnapshot();
66
+ const missingDocumentIds = new Set(
67
+ snapshot.requiredDocuments.map((document) => document.documentId)
68
+ );
69
+ const hasAcceptedRequiredDocuments = snapshot.status === 'complete';
70
+ const canContinue = hasAcceptedRequiredDocuments && !savingDocumentId && !isCompleting;
71
+
72
+ const closeModal = () => setActiveDocument(null);
73
+ const acceptActiveDocument = async () => {
74
+ if (activeDocument) {
75
+ const document = requiredDocuments.find((item) => item.documentId === activeDocument);
76
+ if (document) {
77
+ await acceptDocument(document);
78
+ }
79
+ }
80
+ closeModal();
81
+ };
82
+ const finishOnboarding = async () => {
83
+ if (!canContinue) {
84
+ return;
85
+ }
86
+ setIsCompleting(true);
87
+ setCompletionError(null);
88
+ try {
89
+ await markOnboardingComplete();
90
+ setTimeout(() => router.replace(onboardingConfig.completion.route), 0);
91
+ } catch (error) {
92
+ setCompletionError(
93
+ error instanceof Error ? error.message : 'Unable to finish onboarding. Please try again.'
94
+ );
95
+ } finally {
96
+ setIsCompleting(false);
97
+ }
98
+ };
99
+
100
+ return (
101
+ <ScrollView
102
+ contentInsetAdjustmentBehavior="automatic"
103
+ contentContainerStyle={styles.content}
104
+ style={[styles.screen, { backgroundColor: colors.background }]}
105
+ >
106
+ <View style={styles.header}>
107
+ <Text style={[styles.kicker, { color: colors.primary }]}>Legal</Text>
108
+ <Text
109
+ style={[
110
+ styles.title,
111
+ {
112
+ color: colors.text,
113
+ fontFamily: theme.typography.fontTitle,
114
+ },
115
+ ]}
116
+ >
117
+ {onboardingConfig.legal.title}
118
+ </Text>
119
+ <Text style={[styles.body, { color: colors.text }]}>{onboardingConfig.legal.body}</Text>
120
+ </View>
121
+
122
+ <View style={styles.stack}>
123
+ {requiredDocuments.map((document) => (
124
+ <LegalRow
125
+ accepted={!missingDocumentIds.has(document.documentId)}
126
+ documentId={document.documentId}
127
+ key={document.documentId}
128
+ onOpen={() => setActiveDocument(document.documentId)}
129
+ />
130
+ ))}
131
+ </View>
132
+
133
+ <Pressable
134
+ accessibilityRole="button"
135
+ disabled={!canContinue}
136
+ onPress={() => void finishOnboarding()}
137
+ style={[
138
+ styles.primaryButton,
139
+ {
140
+ backgroundColor: canContinue ? colors.primary : colors.surface,
141
+ borderRadius: theme.layout.radius,
142
+ },
143
+ ]}
144
+ >
145
+ <Text
146
+ style={[
147
+ styles.primaryButtonText,
148
+ {
149
+ color: canContinue ? primaryForeground : colors.text,
150
+ },
151
+ ]}
152
+ >
153
+ {isCompleting ? 'Completing...' : onboardingConfig.completion.label}
154
+ </Text>
155
+ </Pressable>
156
+ {completionError ? (
157
+ <Text style={[styles.errorText, { color: colors.warning }]}>{completionError}</Text>
158
+ ) : null}
159
+
160
+ {activeDocument ? (
161
+ <LegalDocumentModal
162
+ documentId={activeDocument}
163
+ onClose={closeModal}
164
+ onPrimaryAction={acceptActiveDocument}
165
+ primaryActionLabel={`Accept ${getLegalDocument(activeDocument).title}`}
166
+ visible
167
+ />
168
+ ) : null}
169
+ </ScrollView>
170
+ );
171
+ }
172
+
173
+ const styles = StyleSheet.create({
174
+ screen: {
175
+ flex: 1,
176
+ },
177
+ content: {
178
+ flexGrow: 1,
179
+ gap: 18,
180
+ justifyContent: 'center',
181
+ padding: 20,
182
+ paddingTop: Platform.OS === 'web' ? 84 : 28,
183
+ },
184
+ header: {
185
+ gap: 8,
186
+ },
187
+ kicker: {
188
+ fontSize: 13,
189
+ fontWeight: '900',
190
+ textTransform: 'uppercase',
191
+ },
192
+ title: {
193
+ fontSize: 28,
194
+ fontWeight: '900',
195
+ lineHeight: 34,
196
+ },
197
+ body: {
198
+ fontSize: 15,
199
+ lineHeight: 22,
200
+ maxWidth: 720,
201
+ },
202
+ stack: {
203
+ gap: 12,
204
+ },
205
+ documentCard: {
206
+ alignItems: 'center',
207
+ borderWidth: 1,
208
+ flexDirection: 'row',
209
+ gap: 12,
210
+ padding: 16,
211
+ },
212
+ documentText: {
213
+ flex: 1,
214
+ gap: 4,
215
+ },
216
+ documentTitle: {
217
+ fontSize: 17,
218
+ fontWeight: '900',
219
+ },
220
+ documentMeta: {
221
+ fontSize: 13,
222
+ lineHeight: 18,
223
+ },
224
+ documentAction: {
225
+ fontSize: 13,
226
+ fontWeight: '900',
227
+ },
228
+ primaryButton: {
229
+ alignItems: 'center',
230
+ marginTop: 4,
231
+ paddingVertical: 15,
232
+ },
233
+ primaryButtonText: {
234
+ fontSize: 15,
235
+ fontWeight: '900',
236
+ },
237
+ errorText: {
238
+ fontSize: 13,
239
+ fontWeight: '800',
240
+ lineHeight: 19,
241
+ },
242
+ });
@@ -0,0 +1,104 @@
1
+ import type { Href } from 'expo-router';
2
+
3
+ export type OnboardingCompletionMode = 'enter-app' | 'auth' | 'account-setup' | 'custom';
4
+
5
+ export interface OnboardingValueProp {
6
+ title: string;
7
+ body: string;
8
+ }
9
+
10
+ export interface OnboardingFeatureHighlight {
11
+ id: string;
12
+ title: string;
13
+ body: string;
14
+ badge?: string;
15
+ }
16
+
17
+ export interface OnboardingCompletionConfig {
18
+ mode: OnboardingCompletionMode;
19
+ route: Href;
20
+ label: string;
21
+ helperText: string;
22
+ }
23
+
24
+ export interface OnboardingConfig {
25
+ appName: string;
26
+ welcomeEyebrow: string;
27
+ welcomeTitle: string;
28
+ welcomeBody: string;
29
+ valueProps: OnboardingValueProp[];
30
+ nextRouteAfterWelcome: Href;
31
+ featuresEyebrow: string;
32
+ featuresTitle: string;
33
+ featuresBody: string;
34
+ featureHighlights: OnboardingFeatureHighlight[];
35
+ nextRouteAfterFeatures: Href;
36
+ legal: {
37
+ title: string;
38
+ body: string;
39
+ };
40
+ completeTitle: string;
41
+ completeBody: string;
42
+ completion: OnboardingCompletionConfig;
43
+ }
44
+
45
+ export const onboardingConfig: OnboardingConfig = {
46
+ appName: '__MDS_APP_NAME__',
47
+ welcomeEyebrow: 'Welcome',
48
+ welcomeTitle: 'A quick tour before you begin',
49
+ welcomeBody:
50
+ 'See the core workflow, review what the app can do, and continue when you are ready.',
51
+ valueProps: [
52
+ {
53
+ title: 'Start with the main job',
54
+ body: 'The first screen should explain the app in plain customer-facing language.',
55
+ },
56
+ {
57
+ title: 'Show real value',
58
+ body: 'Use the second screen to highlight features, integrations, or product moments that matter.',
59
+ },
60
+ {
61
+ title: 'Hand off cleanly',
62
+ body: 'The final action is editable for app entry, auth, account setup, or a custom route.',
63
+ },
64
+ ],
65
+ nextRouteAfterWelcome: '/onboarding/features' as Href,
66
+ featuresEyebrow: 'Features',
67
+ featuresTitle: 'What you can do next',
68
+ featuresBody:
69
+ 'These highlights are informational by default, so users are not asked to make choices before those choices affect the app.',
70
+ featureHighlights: [
71
+ {
72
+ id: 'workflow',
73
+ title: 'Follow the core workflow',
74
+ body: 'Explain the shortest path from the user arriving to getting meaningful work done.',
75
+ badge: 'Core',
76
+ },
77
+ {
78
+ id: 'integrations',
79
+ title: 'Introduce optional integrations',
80
+ body: 'Mention connected services only when they expand the product value without blocking setup.',
81
+ badge: 'Optional',
82
+ },
83
+ {
84
+ id: 'control',
85
+ title: 'Keep the user in control',
86
+ body: 'Avoid collecting preferences until the app stores them or changes behavior from them.',
87
+ badge: 'Simple',
88
+ },
89
+ ],
90
+ nextRouteAfterFeatures: '/onboarding/legal' as Href,
91
+ legal: {
92
+ title: 'Review legal documents',
93
+ body: 'Review the Terms of Service and Privacy Policy before continuing.',
94
+ },
95
+ completeTitle: 'You are ready to begin',
96
+ completeBody:
97
+ 'Continue into the next step of the app. Wire this final action to auth, account setup, or the main product route.',
98
+ completion: {
99
+ mode: 'enter-app',
100
+ route: '/' as Href,
101
+ label: "Let's begin",
102
+ helperText: 'Persist or replace this choice when real onboarding state is ready.',
103
+ },
104
+ };
@@ -0,0 +1,104 @@
1
+ import type { Href } from 'expo-router';
2
+
3
+ export type OnboardingCompletionMode = 'enter-app' | 'auth' | 'account-setup' | 'custom';
4
+
5
+ export interface OnboardingValueProp {
6
+ title: string;
7
+ body: string;
8
+ }
9
+
10
+ export interface OnboardingFeatureHighlight {
11
+ id: string;
12
+ title: string;
13
+ body: string;
14
+ badge?: string;
15
+ }
16
+
17
+ export interface OnboardingCompletionConfig {
18
+ mode: OnboardingCompletionMode;
19
+ route: Href;
20
+ label: string;
21
+ helperText: string;
22
+ }
23
+
24
+ export interface OnboardingConfig {
25
+ appName: string;
26
+ welcomeEyebrow: string;
27
+ welcomeTitle: string;
28
+ welcomeBody: string;
29
+ valueProps: OnboardingValueProp[];
30
+ nextRouteAfterWelcome: Href;
31
+ featuresEyebrow: string;
32
+ featuresTitle: string;
33
+ featuresBody: string;
34
+ featureHighlights: OnboardingFeatureHighlight[];
35
+ nextRouteAfterFeatures: Href;
36
+ legal: {
37
+ title: string;
38
+ body: string;
39
+ };
40
+ completeTitle: string;
41
+ completeBody: string;
42
+ completion: OnboardingCompletionConfig;
43
+ }
44
+
45
+ export const onboardingConfig: OnboardingConfig = {
46
+ appName: '__MDS_APP_NAME__',
47
+ welcomeEyebrow: 'Welcome',
48
+ welcomeTitle: 'A quick tour before you begin',
49
+ welcomeBody:
50
+ 'See the core workflow, review what the app can do, and continue when you are ready.',
51
+ valueProps: [
52
+ {
53
+ title: 'Start with the main job',
54
+ body: 'The first screen should explain the app in plain customer-facing language.',
55
+ },
56
+ {
57
+ title: 'Show real value',
58
+ body: 'Use the second screen to highlight features, integrations, or product moments that matter.',
59
+ },
60
+ {
61
+ title: 'Hand off cleanly',
62
+ body: 'The final action is editable for app entry, auth, account setup, or a custom route.',
63
+ },
64
+ ],
65
+ nextRouteAfterWelcome: '/onboarding/features' as Href,
66
+ featuresEyebrow: 'Features',
67
+ featuresTitle: 'What you can do next',
68
+ featuresBody:
69
+ 'These highlights are informational by default, so users are not asked to make choices before those choices affect the app.',
70
+ featureHighlights: [
71
+ {
72
+ id: 'workflow',
73
+ title: 'Follow the core workflow',
74
+ body: 'Explain the shortest path from the user arriving to getting meaningful work done.',
75
+ badge: 'Core',
76
+ },
77
+ {
78
+ id: 'integrations',
79
+ title: 'Introduce optional integrations',
80
+ body: 'Mention connected services only when they expand the product value without blocking setup.',
81
+ badge: 'Optional',
82
+ },
83
+ {
84
+ id: 'control',
85
+ title: 'Keep the user in control',
86
+ body: 'Avoid collecting preferences until the app stores them or changes behavior from them.',
87
+ badge: 'Simple',
88
+ },
89
+ ],
90
+ nextRouteAfterFeatures: '/onboarding/complete' as Href,
91
+ legal: {
92
+ title: 'Review legal documents',
93
+ body: 'Review the Terms of Service and Privacy Policy before continuing.',
94
+ },
95
+ completeTitle: 'You are ready to begin',
96
+ completeBody:
97
+ 'Continue into the next step of the app. Wire this final action to auth, account setup, or the main product route.',
98
+ completion: {
99
+ mode: 'enter-app',
100
+ route: '/' as Href,
101
+ label: "Let's begin",
102
+ helperText: 'Persist or replace this choice when real onboarding state is ready.',
103
+ },
104
+ };
@@ -0,0 +1 @@
1
+ export { OnboardingPersistenceSync } from '../onboarding-state/onboarding-state-adapter';
@@ -0,0 +1,130 @@
1
+ import { Link } from 'expo-router';
2
+ import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
3
+
4
+ import { getReadableTextColor } from '../../theme/color-utils';
5
+ import { useAppTheme } from '../../theme/provider';
6
+ import { onboardingConfig } from './onboarding-config';
7
+
8
+ export default function OnboardingWelcomeScreen() {
9
+ const theme = useAppTheme();
10
+ const colors = theme.activeColors;
11
+ const primaryForeground = getReadableTextColor(colors.primary, theme.colors.light.text);
12
+
13
+ return (
14
+ <ScrollView
15
+ contentInsetAdjustmentBehavior="automatic"
16
+ contentContainerStyle={styles.content}
17
+ style={[styles.screen, { backgroundColor: colors.background }]}>
18
+ <View style={styles.header}>
19
+ <Text style={[styles.eyebrow, { color: colors.primary }]}>
20
+ {onboardingConfig.welcomeEyebrow}
21
+ </Text>
22
+ <Text
23
+ style={[
24
+ styles.title,
25
+ {
26
+ color: colors.text,
27
+ fontFamily: theme.typography.fontTitle,
28
+ fontSize: Math.max(28, theme.typography.displaySize),
29
+ },
30
+ ]}>
31
+ {onboardingConfig.appName}
32
+ </Text>
33
+ <Text style={[styles.subtitle, { color: colors.text }]}>
34
+ {onboardingConfig.welcomeTitle}
35
+ </Text>
36
+ <Text style={[styles.body, { color: colors.text }]}>{onboardingConfig.welcomeBody}</Text>
37
+ </View>
38
+
39
+ <View style={styles.stack}>
40
+ {onboardingConfig.valueProps.map((item) => (
41
+ <View
42
+ key={item.title}
43
+ style={[
44
+ styles.card,
45
+ {
46
+ backgroundColor: colors.surface,
47
+ borderColor: colors.primary,
48
+ borderRadius: theme.layout.radius,
49
+ },
50
+ ]}>
51
+ <Text style={[styles.cardTitle, { color: colors.text }]}>{item.title}</Text>
52
+ <Text style={[styles.cardBody, { color: colors.text }]}>{item.body}</Text>
53
+ </View>
54
+ ))}
55
+ </View>
56
+
57
+ <Link href={onboardingConfig.nextRouteAfterWelcome} asChild>
58
+ <Pressable
59
+ accessibilityRole="button"
60
+ style={StyleSheet.flatten([
61
+ styles.primaryButton,
62
+ { backgroundColor: colors.primary, borderRadius: theme.layout.radius },
63
+ ])}>
64
+ <Text style={[styles.primaryButtonText, { color: primaryForeground }]}>Get started</Text>
65
+ </Pressable>
66
+ </Link>
67
+ </ScrollView>
68
+ );
69
+ }
70
+
71
+ const styles = StyleSheet.create({
72
+ screen: {
73
+ flex: 1,
74
+ },
75
+ content: {
76
+ flexGrow: 1,
77
+ gap: 18,
78
+ justifyContent: 'center',
79
+ padding: 20,
80
+ paddingTop: Platform.OS === 'web' ? 84 : 28,
81
+ },
82
+ header: {
83
+ gap: 8,
84
+ },
85
+ eyebrow: {
86
+ fontSize: 13,
87
+ fontWeight: '900',
88
+ textTransform: 'uppercase',
89
+ },
90
+ title: {
91
+ fontSize: 32,
92
+ fontWeight: '900',
93
+ lineHeight: 38,
94
+ },
95
+ subtitle: {
96
+ fontSize: 20,
97
+ fontWeight: '800',
98
+ lineHeight: 26,
99
+ },
100
+ body: {
101
+ fontSize: 15,
102
+ lineHeight: 22,
103
+ maxWidth: 680,
104
+ },
105
+ stack: {
106
+ gap: 12,
107
+ },
108
+ card: {
109
+ borderWidth: 1,
110
+ gap: 6,
111
+ padding: 16,
112
+ },
113
+ cardTitle: {
114
+ fontSize: 17,
115
+ fontWeight: '900',
116
+ },
117
+ cardBody: {
118
+ fontSize: 14,
119
+ lineHeight: 20,
120
+ },
121
+ primaryButton: {
122
+ alignItems: 'center',
123
+ marginTop: 4,
124
+ paddingVertical: 15,
125
+ },
126
+ primaryButtonText: {
127
+ fontSize: 15,
128
+ fontWeight: '900',
129
+ },
130
+ });
@@ -0,0 +1,15 @@
1
+ import { configureLegalAcceptanceAdapter } from '../legal/legal-acceptance-config';
2
+ import { configureOnboardingStateAdapter } from './onboarding-state-core';
3
+ import { createMemoryOnboardingPersistence } from './onboarding-state-memory';
4
+
5
+ const persistence = createMemoryOnboardingPersistence();
6
+
7
+ configureOnboardingStateAdapter(persistence.onboarding);
8
+ configureLegalAcceptanceAdapter(persistence.legal);
9
+
10
+ export const onboardingStateAdapter = persistence.onboarding;
11
+ export const legalAcceptanceAdapter = persistence.legal;
12
+
13
+ export function OnboardingPersistenceSync() {
14
+ return null;
15
+ }
@@ -0,0 +1,45 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { useAuth } from '../auth/auth-provider';
4
+ import {
5
+ configureLegalAcceptanceAdapter,
6
+ setLegalAcceptanceUserId,
7
+ } from '../legal/legal-acceptance-config';
8
+ import { getSupabaseClient } from '../../services/supabase';
9
+ import {
10
+ configureOnboardingStateAdapter,
11
+ getOnboardingStateAdapter,
12
+ setOnboardingStateUserId,
13
+ } from './onboarding-state-core';
14
+ import {
15
+ createSupabaseLegalAcceptanceAdapter,
16
+ createSupabaseOnboardingStateAdapter,
17
+ type SupabaseOnboardingClient,
18
+ type SupabaseClientFactory,
19
+ } from './onboarding-state-supabase';
20
+
21
+ const getClient: SupabaseClientFactory = () =>
22
+ getSupabaseClient() as unknown as SupabaseOnboardingClient;
23
+
24
+ export const onboardingStateAdapter = createSupabaseOnboardingStateAdapter(getClient);
25
+ export const legalAcceptanceAdapter = createSupabaseLegalAcceptanceAdapter(getClient);
26
+
27
+ configureOnboardingStateAdapter(onboardingStateAdapter);
28
+ configureLegalAcceptanceAdapter(legalAcceptanceAdapter);
29
+
30
+ export function OnboardingPersistenceSync() {
31
+ const auth = useAuth();
32
+
33
+ useEffect(() => {
34
+ const userId = auth.user?.id;
35
+ setOnboardingStateUserId(userId);
36
+ setLegalAcceptanceUserId(userId);
37
+
38
+ if (!userId) {
39
+ return;
40
+ }
41
+ void getOnboardingStateAdapter().syncPending?.(userId);
42
+ }, [auth.user?.id]);
43
+
44
+ return null;
45
+ }
@@ -0,0 +1,16 @@
1
+ import { configureLegalAcceptanceAdapter } from '../legal/legal-acceptance-config';
2
+ import { configureOnboardingStateAdapter } from './onboarding-state-core';
3
+ import {
4
+ createZustandLegalAcceptanceAdapter,
5
+ createZustandOnboardingStateAdapter,
6
+ } from './onboarding-state-zustand';
7
+
8
+ export const onboardingStateAdapter = createZustandOnboardingStateAdapter();
9
+ export const legalAcceptanceAdapter = createZustandLegalAcceptanceAdapter();
10
+
11
+ configureOnboardingStateAdapter(onboardingStateAdapter);
12
+ configureLegalAcceptanceAdapter(legalAcceptanceAdapter);
13
+
14
+ export function OnboardingPersistenceSync() {
15
+ return null;
16
+ }