@amplitude/wizard 1.0.0-beta.2 → 1.0.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.
- package/README.md +171 -74
- package/dist/bin.js +338 -222
- package/dist/src/lib/agent-interface.js +64 -9
- package/dist/src/lib/agent-runner.js +1 -10
- package/dist/src/lib/api.d.ts +22 -4
- package/dist/src/lib/api.js +114 -12
- package/dist/src/lib/commandments.js +14 -1
- package/dist/src/lib/constants.d.ts +6 -5
- package/dist/src/lib/constants.js +13 -13
- package/dist/src/lib/credential-resolution.d.ts +45 -0
- package/dist/src/lib/credential-resolution.js +311 -0
- package/dist/src/lib/exit-codes.d.ts +10 -0
- package/dist/src/lib/exit-codes.js +12 -0
- package/dist/src/lib/health-checks/statuspage.d.ts +1 -0
- package/dist/src/lib/health-checks/statuspage.js +5 -1
- package/dist/src/lib/mode-config.d.ts +14 -0
- package/dist/src/lib/mode-config.js +14 -0
- package/dist/src/lib/session-checkpoint.d.ts +27 -0
- package/dist/src/lib/session-checkpoint.js +134 -0
- package/dist/src/lib/wizard-session.d.ts +44 -1
- package/dist/src/lib/wizard-session.js +70 -14
- package/dist/src/lib/wizard-tools.js +19 -4
- package/dist/src/steps/add-mcp-server-to-clients/clients/claude.d.ts +3 -0
- package/dist/src/steps/add-mcp-server-to-clients/clients/claude.js +6 -0
- package/dist/src/steps/add-mcp-server-to-clients/clients/cursor.js +3 -1
- package/dist/src/ui/agent-ui.d.ts +91 -0
- package/dist/src/ui/agent-ui.js +277 -0
- package/dist/src/ui/logging-ui.js +1 -1
- package/dist/src/ui/tui/App.d.ts +12 -0
- package/dist/src/ui/tui/App.js +29 -18
- package/dist/src/ui/tui/components/AmplitudeLogo.js +16 -17
- package/dist/src/ui/tui/components/AmplitudeTextLogo.d.ts +0 -2
- package/dist/src/ui/tui/components/AmplitudeTextLogo.js +53 -18
- package/dist/src/ui/tui/components/BrailleSpinner.d.ts +8 -0
- package/dist/src/ui/tui/components/BrailleSpinner.js +15 -0
- package/dist/src/ui/tui/components/ConsoleView.d.ts +8 -11
- package/dist/src/ui/tui/components/ConsoleView.js +51 -34
- package/dist/src/ui/tui/components/HeaderBar.d.ts +12 -0
- package/dist/src/ui/tui/components/HeaderBar.js +17 -0
- package/dist/src/ui/tui/components/JourneyStepper.d.ts +16 -0
- package/dist/src/ui/tui/components/JourneyStepper.js +83 -0
- package/dist/src/ui/tui/components/KeyHintBar.d.ts +19 -0
- package/dist/src/ui/tui/components/KeyHintBar.js +20 -0
- package/dist/src/ui/tui/console-commands.d.ts +1 -2
- package/dist/src/ui/tui/console-commands.js +48 -7
- package/dist/src/ui/tui/flows.d.ts +1 -1
- package/dist/src/ui/tui/flows.js +1 -1
- package/dist/src/ui/tui/hooks/useAsyncEffect.d.ts +15 -0
- package/dist/src/ui/tui/hooks/useAsyncEffect.js +35 -0
- package/dist/src/ui/tui/hooks/useWizardStore.d.ts +9 -0
- package/dist/src/ui/tui/hooks/useWizardStore.js +11 -0
- package/dist/src/ui/tui/ink-ui.js +1 -1
- package/dist/src/ui/tui/primitives/DissolveTransition.js +4 -5
- package/dist/src/ui/tui/primitives/EventPlanViewer.d.ts +3 -1
- package/dist/src/ui/tui/primitives/EventPlanViewer.js +8 -3
- package/dist/src/ui/tui/primitives/ProgressList.js +1 -1
- package/dist/src/ui/tui/primitives/SlashCommandInput.js +19 -4
- package/dist/src/ui/tui/primitives/SplitView.d.ts +2 -1
- package/dist/src/ui/tui/primitives/SplitView.js +10 -2
- package/dist/src/ui/tui/primitives/TabContainer.js +10 -2
- package/dist/src/ui/tui/primitives/index.d.ts +0 -1
- package/dist/src/ui/tui/primitives/index.js +0 -1
- package/dist/src/ui/tui/router.js +1 -1
- package/dist/src/ui/tui/screen-registry.d.ts +0 -7
- package/dist/src/ui/tui/screen-registry.js +13 -4
- package/dist/src/ui/tui/screens/ActivationOptionsScreen.d.ts +2 -2
- package/dist/src/ui/tui/screens/ActivationOptionsScreen.js +8 -8
- package/dist/src/ui/tui/screens/AuthScreen.js +57 -27
- package/dist/src/ui/tui/screens/ChecklistScreen.d.ts +2 -12
- package/dist/src/ui/tui/screens/ChecklistScreen.js +22 -33
- package/dist/src/ui/tui/screens/DataIngestionCheckScreen.d.ts +3 -12
- package/dist/src/ui/tui/screens/DataIngestionCheckScreen.js +109 -39
- package/dist/src/ui/tui/screens/DataSetupScreen.d.ts +3 -3
- package/dist/src/ui/tui/screens/DataSetupScreen.js +17 -10
- package/dist/src/ui/tui/screens/IntroScreen.d.ts +5 -3
- package/dist/src/ui/tui/screens/IntroScreen.js +132 -41
- package/dist/src/ui/tui/screens/LoginScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/LoginScreen.js +4 -4
- package/dist/src/ui/tui/screens/LogoutScreen.d.ts +4 -2
- package/dist/src/ui/tui/screens/LogoutScreen.js +17 -5
- package/dist/src/ui/tui/screens/McpScreen.d.ts +4 -4
- package/dist/src/ui/tui/screens/McpScreen.js +25 -17
- package/dist/src/ui/tui/screens/OutageScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/OutageScreen.js +5 -5
- package/dist/src/ui/tui/screens/OutroScreen.d.ts +5 -0
- package/dist/src/ui/tui/screens/OutroScreen.js +21 -14
- package/dist/src/ui/tui/screens/RegionSelectScreen.js +15 -13
- package/dist/src/ui/tui/screens/RunScreen.d.ts +7 -5
- package/dist/src/ui/tui/screens/RunScreen.js +102 -157
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/SettingsOverrideScreen.js +6 -5
- package/dist/src/ui/tui/screens/SetupScreen.d.ts +1 -1
- package/dist/src/ui/tui/screens/SetupScreen.js +7 -7
- package/dist/src/ui/tui/screens/SlackScreen.d.ts +2 -2
- package/dist/src/ui/tui/screens/SlackScreen.js +60 -35
- package/dist/src/ui/tui/session-constants.d.ts +41 -0
- package/dist/src/ui/tui/session-constants.js +38 -0
- package/dist/src/ui/tui/start-tui.d.ts +3 -1
- package/dist/src/ui/tui/start-tui.js +14 -10
- package/dist/src/ui/tui/store.d.ts +2 -1
- package/dist/src/ui/tui/store.js +33 -7
- package/dist/src/ui/tui/styles.d.ts +75 -19
- package/dist/src/ui/tui/styles.js +101 -19
- package/dist/src/ui/tui/utils/classify-error.d.ts +14 -0
- package/dist/src/ui/tui/utils/classify-error.js +90 -0
- package/dist/src/ui/tui/utils/diagnostics.d.ts +21 -0
- package/dist/src/ui/tui/utils/diagnostics.js +72 -0
- package/dist/src/ui/tui/utils/with-retry.d.ts +12 -0
- package/dist/src/ui/tui/utils/with-retry.js +32 -0
- package/dist/src/ui/tui/utils/with-timeout.d.ts +10 -0
- package/dist/src/ui/tui/utils/with-timeout.js +24 -0
- package/dist/src/utils/ampli-settings.d.ts +1 -1
- package/dist/src/utils/ampli-settings.js +15 -5
- package/dist/src/utils/api-key-store.js +5 -5
- package/dist/src/utils/atomic-write.d.ts +15 -0
- package/dist/src/utils/atomic-write.js +34 -0
- package/dist/src/utils/setup-utils.js +2 -2
- package/dist/src/utils/token-refresh.d.ts +22 -0
- package/dist/src/utils/token-refresh.js +79 -0
- package/dist/src/utils/wizard-abort.js +6 -1
- package/package.json +6 -6
- package/skills/instrumentation/add-analytics-instrumentation/SKILL.md +142 -0
- package/skills/instrumentation/diff-intake/SKILL.md +128 -0
- package/skills/instrumentation/discover-analytics-patterns/SKILL.md +185 -0
- package/skills/instrumentation/discover-event-surfaces/SKILL.md +322 -0
- package/skills/instrumentation/discover-event-surfaces/references/best-practices.md +563 -0
- package/skills/instrumentation/instrument-events/SKILL.md +169 -0
- package/skills/instrumentation/instrument-events/references/best-practices.md +563 -0
- package/skills/integration/integration-android/SKILL.md +49 -0
- package/skills/integration/integration-android/references/EXAMPLE.md +1977 -0
- package/skills/integration/integration-android/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-android/references/analytics.md +1778 -0
- package/skills/integration/integration-android/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-android/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-android/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-android/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-angular/SKILL.md +49 -0
- package/skills/integration/integration-angular/references/EXAMPLE.md +899 -0
- package/skills/integration/integration-angular/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-angular/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-angular/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-angular/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-angular/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-angular/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-hybrid/SKILL.md +56 -0
- package/skills/integration/integration-astro-hybrid/references/EXAMPLE.md +1095 -0
- package/skills/integration/integration-astro-hybrid/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-hybrid/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-hybrid/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-ssr/SKILL.md +52 -0
- package/skills/integration/integration-astro-ssr/references/EXAMPLE.md +1106 -0
- package/skills/integration/integration-astro-ssr/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-ssr/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-ssr/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-static/SKILL.md +49 -0
- package/skills/integration/integration-astro-static/references/EXAMPLE.md +910 -0
- package/skills/integration/integration-astro-static/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-static/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-static/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-astro-view-transitions/SKILL.md +51 -0
- package/skills/integration/integration-astro-view-transitions/references/EXAMPLE.md +979 -0
- package/skills/integration/integration-astro-view-transitions/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-astro-view-transitions/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-astro-view-transitions/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-django/SKILL.md +57 -0
- package/skills/integration/integration-django/references/EXAMPLE.md +1005 -0
- package/skills/integration/integration-django/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-django/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-django/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-django/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-django/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-django/references/python.md +1424 -0
- package/skills/integration/integration-expo/SKILL.md +53 -0
- package/skills/integration/integration-expo/references/EXAMPLE.md +1291 -0
- package/skills/integration/integration-expo/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-expo/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-expo/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-expo/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-expo/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-expo/references/react-native-sdk.md +2819 -0
- package/skills/integration/integration-fastapi/SKILL.md +57 -0
- package/skills/integration/integration-fastapi/references/EXAMPLE.md +1389 -0
- package/skills/integration/integration-fastapi/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-fastapi/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-fastapi/references/python.md +1424 -0
- package/skills/integration/integration-flask/SKILL.md +56 -0
- package/skills/integration/integration-flask/references/EXAMPLE.md +1130 -0
- package/skills/integration/integration-flask/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-flask/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-flask/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-flask/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-flask/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-flask/references/python.md +1424 -0
- package/skills/integration/integration-javascript_node/SKILL.md +54 -0
- package/skills/integration/integration-javascript_node/references/EXAMPLE.md +365 -0
- package/skills/integration/integration-javascript_node/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-javascript_node/references/analytics.md +1778 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-javascript_node/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-javascript_web/SKILL.md +58 -0
- package/skills/integration/integration-javascript_web/references/EXAMPLE.md +451 -0
- package/skills/integration/integration-javascript_web/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-javascript_web/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-javascript_web/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-laravel/SKILL.md +52 -0
- package/skills/integration/integration-laravel/references/EXAMPLE.md +2039 -0
- package/skills/integration/integration-laravel/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-laravel/references/analytics.md +1778 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-laravel/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-app-router/SKILL.md +54 -0
- package/skills/integration/integration-nextjs-app-router/references/EXAMPLE.md +673 -0
- package/skills/integration/integration-nextjs-app-router/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nextjs-app-router/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-app-router/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nextjs-pages-router/SKILL.md +54 -0
- package/skills/integration/integration-nextjs-pages-router/references/EXAMPLE.md +735 -0
- package/skills/integration/integration-nextjs-pages-router/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nextjs-pages-router/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nextjs-pages-router/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nuxt-3.6/SKILL.md +46 -0
- package/skills/integration/integration-nuxt-3.6/references/EXAMPLE.md +8422 -0
- package/skills/integration/integration-nuxt-3.6/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nuxt-3.6/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nuxt-3.6/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-nuxt-4/SKILL.md +46 -0
- package/skills/integration/integration-nuxt-4/references/EXAMPLE.md +8670 -0
- package/skills/integration/integration-nuxt-4/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-nuxt-4/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-nuxt-4/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-python/SKILL.md +53 -0
- package/skills/integration/integration-python/references/EXAMPLE.md +445 -0
- package/skills/integration/integration-python/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-python/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-python/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-python/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-python/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-python/references/python.md +1424 -0
- package/skills/integration/integration-react-native/SKILL.md +49 -0
- package/skills/integration/integration-react-native/references/EXAMPLE.md +2253 -0
- package/skills/integration/integration-react-native/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-native/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-native/references/react-native-sdk.md +2819 -0
- package/skills/integration/integration-react-react-router-6/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-6/references/EXAMPLE.md +570 -0
- package/skills/integration/integration-react-react-router-6/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-6/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-6/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-data/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-data/references/EXAMPLE.md +830 -0
- package/skills/integration/integration-react-react-router-7-data/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-data/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-data/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-declarative/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/EXAMPLE.md +609 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-declarative/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-react-router-7-framework/SKILL.md +53 -0
- package/skills/integration/integration-react-react-router-7-framework/references/EXAMPLE.md +1081 -0
- package/skills/integration/integration-react-react-router-7-framework/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-react-router-7-framework/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-react-router-7-framework/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-tanstack-router-code-based/SKILL.md +57 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/EXAMPLE.md +659 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-tanstack-router-code-based/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-tanstack-router-file-based/SKILL.md +57 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/EXAMPLE.md +777 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-tanstack-router-file-based/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-react-vite/SKILL.md +53 -0
- package/skills/integration/integration-react-vite/references/EXAMPLE.md +542 -0
- package/skills/integration/integration-react-vite/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-react-vite/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-react-vite/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-ruby/SKILL.md +50 -0
- package/skills/integration/integration-ruby/references/EXAMPLE.md +420 -0
- package/skills/integration/integration-ruby/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-ruby/references/analytics.md +1778 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-ruby/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-ruby-on-rails/SKILL.md +55 -0
- package/skills/integration/integration-ruby-on-rails/references/EXAMPLE.md +1013 -0
- package/skills/integration/integration-ruby-on-rails/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-ruby-on-rails/references/analytics.md +1778 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-ruby-on-rails/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-sveltekit/SKILL.md +47 -0
- package/skills/integration/integration-sveltekit/references/EXAMPLE.md +14121 -0
- package/skills/integration/integration-sveltekit/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-sveltekit/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-sveltekit/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-swift/SKILL.md +49 -0
- package/skills/integration/integration-swift/references/EXAMPLE.md +660 -0
- package/skills/integration/integration-swift/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-swift/references/analytics.md +1778 -0
- package/skills/integration/integration-swift/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-swift/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-swift/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-swift/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-tanstack-start/SKILL.md +58 -0
- package/skills/integration/integration-tanstack-start/references/EXAMPLE.md +998 -0
- package/skills/integration/integration-tanstack-start/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-tanstack-start/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-tanstack-start/references/browser-sdk-2.md +4680 -0
- package/skills/integration/integration-vue-3/SKILL.md +46 -0
- package/skills/integration/integration-vue-3/references/EXAMPLE.md +846 -0
- package/skills/integration/integration-vue-3/references/amplitude-quickstart.md +1845 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.0-begin.md +43 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.1-edit.md +35 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.2-revise.md +23 -0
- package/skills/integration/integration-vue-3/references/basic-integration-1.3-conclude.md +57 -0
- package/skills/integration/integration-vue-3/references/browser-sdk-2.md +4680 -0
- package/skills/taxonomy/amplitude-quickstart-taxonomy-agent/SKILL.md +228 -0
- package/dist/src/ui/tui/components/TitleBar.d.ts +0 -8
- package/dist/src/ui/tui/components/TitleBar.js +0 -27
- package/dist/src/ui/tui/primitives/KagiSmallWebViewer.d.ts +0 -7
- package/dist/src/ui/tui/primitives/KagiSmallWebViewer.js +0 -101
- package/dist/src/utils/anthropic-status.d.ts +0 -17
- package/dist/src/utils/anthropic-status.js +0 -51
|
@@ -0,0 +1,1291 @@
|
|
|
1
|
+
# Amplitude Expo Example Project
|
|
2
|
+
|
|
3
|
+
Repository: https://github.com/amplitude/context-hub
|
|
4
|
+
Path: basics/expo
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## README.md
|
|
9
|
+
|
|
10
|
+
# Burrito Consideration App (Expo)
|
|
11
|
+
|
|
12
|
+
A React Native Expo app demonstrating Amplitude product analytics integration with modern React Native best practices.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Product Analytics**: Full Amplitude integration with event tracking
|
|
17
|
+
- **User Authentication**: Demo login with Amplitude user identification
|
|
18
|
+
- **Session Persistence**: AsyncStorage for session management
|
|
19
|
+
- **Modern React**: React 19 with React Compiler for automatic memoization
|
|
20
|
+
- **File-based Routing**: Expo Router for navigation
|
|
21
|
+
- **New Architecture**: Enabled by default for better performance
|
|
22
|
+
|
|
23
|
+
## Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
basics/expo/
|
|
27
|
+
├── app/ # Expo Router screens (file-based routing)
|
|
28
|
+
│ ├── _layout.tsx # Root layout with AuthProvider
|
|
29
|
+
│ ├── index.tsx # Home screen (login/welcome)
|
|
30
|
+
│ ├── burrito.tsx # Burrito consideration screen
|
|
31
|
+
│ └── profile.tsx # User profile screen
|
|
32
|
+
├── src/
|
|
33
|
+
│ ├── config/
|
|
34
|
+
│ │ └── amplitude.ts # Amplitude client configuration
|
|
35
|
+
│ ├── contexts/
|
|
36
|
+
│ │ └── AuthContext.tsx # Authentication context with Amplitude
|
|
37
|
+
│ ├── services/
|
|
38
|
+
│ │ └── storage.ts # AsyncStorage wrapper
|
|
39
|
+
│ └── styles/
|
|
40
|
+
│ └── theme.ts # Shared style constants
|
|
41
|
+
├── app.config.js # Expo configuration
|
|
42
|
+
├── babel.config.js # Babel config with React Compiler
|
|
43
|
+
├── eslint.config.js # ESLint flat config
|
|
44
|
+
├── package.json # Dependencies
|
|
45
|
+
├── tsconfig.json # TypeScript strict configuration
|
|
46
|
+
└── .env.example # Environment variables template
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Getting Started
|
|
50
|
+
|
|
51
|
+
### Prerequisites
|
|
52
|
+
|
|
53
|
+
- Node.js 18+
|
|
54
|
+
- iOS: Xcode (for iOS Simulator)
|
|
55
|
+
- Android: Android Studio with emulator
|
|
56
|
+
|
|
57
|
+
**For Android builds:** Set environment variables (required):
|
|
58
|
+
|
|
59
|
+
Add to `~/.zshrc` or `~/.bashrc`:
|
|
60
|
+
```bash
|
|
61
|
+
# Java from Android Studio (required for Gradle)
|
|
62
|
+
export JAVA_HOME="<path-to-android-studio-jdk>"
|
|
63
|
+
|
|
64
|
+
# Android SDK location
|
|
65
|
+
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
- `export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"`
|
|
70
|
+
- `export ANDROID_HOME="$HOME/Library/Android/sdk"`
|
|
71
|
+
|
|
72
|
+
Then run `source ~/.zshrc` to apply.
|
|
73
|
+
|
|
74
|
+
### Installation
|
|
75
|
+
|
|
76
|
+
1. Install dependencies:
|
|
77
|
+
```bash
|
|
78
|
+
cd basics/expo
|
|
79
|
+
npm install
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
2. Configure Amplitude (optional):
|
|
83
|
+
```bash
|
|
84
|
+
cp .env.example .env
|
|
85
|
+
# Edit .env with your Amplitude API key
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
3. Start the development server:
|
|
89
|
+
```bash
|
|
90
|
+
npx expo start
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Running the App
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Start development server
|
|
97
|
+
npx expo start
|
|
98
|
+
|
|
99
|
+
# Run on iOS Simulator
|
|
100
|
+
npx expo run:ios
|
|
101
|
+
|
|
102
|
+
# Run on Android Emulator
|
|
103
|
+
npx expo run:android
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Amplitude Integration
|
|
107
|
+
|
|
108
|
+
### Configuration
|
|
109
|
+
|
|
110
|
+
Amplitude is configured in `src/config/amplitude.ts` using environment variables from `app.config.js`:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import Constants from 'expo-constants'
|
|
114
|
+
|
|
115
|
+
const apiKey = Constants.expoConfig?.extra?.amplitudeApiKey
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Event Tracking
|
|
119
|
+
|
|
120
|
+
Events are tracked with properties:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
amplitude.track('Burrito Considered', {
|
|
124
|
+
total_considerations: count,
|
|
125
|
+
username: user.username,
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### User Identification
|
|
130
|
+
|
|
131
|
+
Users are identified on login:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
amplitude.setUserId(username)
|
|
135
|
+
const identifyObj = new Identify()
|
|
136
|
+
identifyObj.set('username', username)
|
|
137
|
+
amplitude.identify(identifyObj)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Screen Tracking
|
|
141
|
+
|
|
142
|
+
Manual screen tracking with Expo Router:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
amplitude.track('Screen Viewed', {
|
|
147
|
+
screen_name: pathname,
|
|
148
|
+
previous_screen: previousPathname.current,
|
|
149
|
+
})
|
|
150
|
+
}, [pathname])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Modern React Features
|
|
154
|
+
|
|
155
|
+
### React Compiler
|
|
156
|
+
|
|
157
|
+
Automatic memoization is enabled via `babel-plugin-react-compiler`. No need for manual `useMemo`, `useCallback`, or `React.memo`.
|
|
158
|
+
|
|
159
|
+
### React 19 `use` API
|
|
160
|
+
|
|
161
|
+
The `useAuth` hook uses the new `use` API for context:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
export function useAuth() {
|
|
165
|
+
const context = use(AuthContext)
|
|
166
|
+
if (context === undefined) {
|
|
167
|
+
throw new Error('useAuth must be used within an AuthProvider')
|
|
168
|
+
}
|
|
169
|
+
return context
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### New Architecture
|
|
174
|
+
|
|
175
|
+
Enabled in `app.config.js` for better performance:
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
{
|
|
179
|
+
expo: {
|
|
180
|
+
newArchEnabled: true
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Building for Production
|
|
186
|
+
|
|
187
|
+
Use EAS Build for production builds:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Install EAS CLI
|
|
191
|
+
npm install -g eas-cli
|
|
192
|
+
|
|
193
|
+
# Configure EAS
|
|
194
|
+
eas build:configure
|
|
195
|
+
|
|
196
|
+
# Build for iOS
|
|
197
|
+
eas build --platform ios
|
|
198
|
+
|
|
199
|
+
# Build for Android
|
|
200
|
+
eas build --platform android
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Tech Stack
|
|
204
|
+
|
|
205
|
+
- **Expo SDK 54** - Managed workflow
|
|
206
|
+
- **React 19** - Latest React with Compiler support
|
|
207
|
+
- **React Native 0.81** - Latest stable
|
|
208
|
+
- **Expo Router 6** - File-based navigation
|
|
209
|
+
- **Amplitude** - Product analytics
|
|
210
|
+
- **TypeScript** - Strict mode enabled
|
|
211
|
+
- **React Native Reanimated** - Smooth animations
|
|
212
|
+
- **React Native Gesture Handler** - Native gestures
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## .env.example
|
|
221
|
+
|
|
222
|
+
```example
|
|
223
|
+
AMPLITUDE_API_KEY=your_amplitude_api_key_here
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## .npmrc
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
legacy-peer-deps=true
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## app.config.js
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
export default {
|
|
242
|
+
expo: {
|
|
243
|
+
name: 'BurritoApp',
|
|
244
|
+
slug: 'burrito-app',
|
|
245
|
+
version: '1.0.0',
|
|
246
|
+
orientation: 'portrait',
|
|
247
|
+
icon: './assets/icon.png',
|
|
248
|
+
userInterfaceStyle: 'light',
|
|
249
|
+
newArchEnabled: true,
|
|
250
|
+
experiments: {
|
|
251
|
+
reactCompiler: true,
|
|
252
|
+
},
|
|
253
|
+
splash: {
|
|
254
|
+
image: './assets/splash-icon.png',
|
|
255
|
+
resizeMode: 'contain',
|
|
256
|
+
backgroundColor: '#333333',
|
|
257
|
+
},
|
|
258
|
+
ios: {
|
|
259
|
+
supportsTablet: true,
|
|
260
|
+
bundleIdentifier: 'com.amplitude.burritoapp',
|
|
261
|
+
},
|
|
262
|
+
android: {
|
|
263
|
+
adaptiveIcon: {
|
|
264
|
+
foregroundImage: './assets/adaptive-icon.png',
|
|
265
|
+
backgroundColor: '#333333',
|
|
266
|
+
},
|
|
267
|
+
package: 'com.amplitude.burritoapp',
|
|
268
|
+
edgeToEdgeEnabled: true,
|
|
269
|
+
},
|
|
270
|
+
web: {
|
|
271
|
+
favicon: './assets/favicon.png',
|
|
272
|
+
},
|
|
273
|
+
scheme: 'burritoapp',
|
|
274
|
+
extra: {
|
|
275
|
+
amplitudeApiKey: process.env.AMPLITUDE_API_KEY,
|
|
276
|
+
},
|
|
277
|
+
plugins: ['expo-router', 'expo-localization'],
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## app/_layout.tsx
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { Stack, usePathname, useGlobalSearchParams } from 'expo-router'
|
|
289
|
+
import { useEffect, useRef } from 'react'
|
|
290
|
+
import { StatusBar } from 'expo-status-bar'
|
|
291
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
292
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
293
|
+
|
|
294
|
+
import { AuthProvider } from '../src/contexts/AuthContext'
|
|
295
|
+
import { amplitude } from '../src/config/amplitude'
|
|
296
|
+
import { colors } from '../src/styles/theme'
|
|
297
|
+
|
|
298
|
+
export default function RootLayout() {
|
|
299
|
+
const pathname = usePathname()
|
|
300
|
+
const params = useGlobalSearchParams()
|
|
301
|
+
const previousPathname = useRef<string | undefined>(undefined)
|
|
302
|
+
|
|
303
|
+
// Manual screen tracking for Expo Router
|
|
304
|
+
// @see https://docs.expo.dev/router/reference/screen-tracking/
|
|
305
|
+
// React Compiler will auto-optimize this effect
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (previousPathname.current !== pathname) {
|
|
308
|
+
amplitude.track('Screen Viewed', {
|
|
309
|
+
screen_name: pathname,
|
|
310
|
+
previous_screen: previousPathname.current ?? null,
|
|
311
|
+
// Include route params for analytics (filter sensitive data if needed)
|
|
312
|
+
...params,
|
|
313
|
+
})
|
|
314
|
+
previousPathname.current = pathname
|
|
315
|
+
}
|
|
316
|
+
}, [pathname, params])
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
320
|
+
<SafeAreaProvider>
|
|
321
|
+
<StatusBar style="light" backgroundColor={colors.headerBackground} />
|
|
322
|
+
<AuthProvider>
|
|
323
|
+
<Stack
|
|
324
|
+
screenOptions={{
|
|
325
|
+
headerStyle: { backgroundColor: colors.headerBackground },
|
|
326
|
+
headerTintColor: colors.headerText,
|
|
327
|
+
headerTitleStyle: { fontWeight: 'bold' },
|
|
328
|
+
animation: 'slide_from_right',
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
<Stack.Screen name="index" options={{ title: 'Burrito App' }} />
|
|
332
|
+
<Stack.Screen name="burrito" options={{ title: 'Burrito Consideration' }} />
|
|
333
|
+
<Stack.Screen name="profile" options={{ title: 'Profile' }} />
|
|
334
|
+
</Stack>
|
|
335
|
+
</AuthProvider>
|
|
336
|
+
</SafeAreaProvider>
|
|
337
|
+
</GestureHandlerRootView>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## app/burrito.tsx
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
import { useState, useEffect } from 'react'
|
|
349
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
|
350
|
+
import { useRouter } from 'expo-router'
|
|
351
|
+
import { useAuth } from '../src/contexts/AuthContext'
|
|
352
|
+
import { amplitude } from '../src/config/amplitude'
|
|
353
|
+
import { colors, spacing, typography, borderRadius, shadows } from '../src/styles/theme'
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Burrito Consideration Screen
|
|
357
|
+
*
|
|
358
|
+
* Demonstrates Amplitude event tracking with custom properties.
|
|
359
|
+
* Each time the user considers a burrito, an event is captured.
|
|
360
|
+
*
|
|
361
|
+
* @see https://amplitude.com/docs/sdks/analytics/react-native
|
|
362
|
+
*/
|
|
363
|
+
export default function BurritoScreen() {
|
|
364
|
+
const { user, incrementBurritoConsiderations } = useAuth()
|
|
365
|
+
const router = useRouter()
|
|
366
|
+
const [hasConsidered, setHasConsidered] = useState(false)
|
|
367
|
+
|
|
368
|
+
// Redirect to home if not logged in
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
if (!user) {
|
|
371
|
+
router.replace('/')
|
|
372
|
+
}
|
|
373
|
+
}, [user, router])
|
|
374
|
+
|
|
375
|
+
if (!user) {
|
|
376
|
+
return null
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const handleConsideration = async () => {
|
|
380
|
+
const newCount = user.burritoConsiderations + 1
|
|
381
|
+
|
|
382
|
+
// Update state first for immediate feedback
|
|
383
|
+
await incrementBurritoConsiderations()
|
|
384
|
+
setHasConsidered(true)
|
|
385
|
+
|
|
386
|
+
// Hide success message after 2 seconds
|
|
387
|
+
setTimeout(() => setHasConsidered(false), 2000)
|
|
388
|
+
|
|
389
|
+
// Capture custom event in Amplitude with properties
|
|
390
|
+
amplitude.track('Burrito Considered', {
|
|
391
|
+
total_considerations: newCount,
|
|
392
|
+
username: user.username,
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<View style={styles.container}>
|
|
398
|
+
<View style={styles.card}>
|
|
399
|
+
<Text style={styles.title}>Burrito Consideration Zone</Text>
|
|
400
|
+
<Text style={styles.text}>
|
|
401
|
+
Take a moment to truly consider the potential of burritos.
|
|
402
|
+
</Text>
|
|
403
|
+
|
|
404
|
+
<TouchableOpacity
|
|
405
|
+
style={styles.burritoButton}
|
|
406
|
+
onPress={handleConsideration}
|
|
407
|
+
activeOpacity={0.8}
|
|
408
|
+
testID="consider-burrito-button"
|
|
409
|
+
>
|
|
410
|
+
<Text style={styles.burritoButtonText}>Consider Burrito</Text>
|
|
411
|
+
</TouchableOpacity>
|
|
412
|
+
|
|
413
|
+
{hasConsidered && (
|
|
414
|
+
<View style={styles.successContainer}>
|
|
415
|
+
<Text style={styles.success}>Thank you for your consideration!</Text>
|
|
416
|
+
<Text style={styles.successCount}>Count: {user.burritoConsiderations}</Text>
|
|
417
|
+
</View>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
<View style={styles.stats}>
|
|
421
|
+
<Text style={styles.statsTitle}>Consideration Stats</Text>
|
|
422
|
+
<Text style={styles.statsText}>Total considerations: {user.burritoConsiderations}</Text>
|
|
423
|
+
</View>
|
|
424
|
+
</View>
|
|
425
|
+
</View>
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const styles = StyleSheet.create({
|
|
430
|
+
container: {
|
|
431
|
+
flex: 1,
|
|
432
|
+
backgroundColor: colors.background,
|
|
433
|
+
padding: spacing.md,
|
|
434
|
+
},
|
|
435
|
+
card: {
|
|
436
|
+
backgroundColor: colors.cardBackground,
|
|
437
|
+
borderRadius: borderRadius.md,
|
|
438
|
+
padding: spacing.lg,
|
|
439
|
+
...shadows.md,
|
|
440
|
+
},
|
|
441
|
+
title: {
|
|
442
|
+
fontSize: typography.sizes.xl,
|
|
443
|
+
fontWeight: typography.weights.bold,
|
|
444
|
+
color: colors.text,
|
|
445
|
+
marginBottom: spacing.sm,
|
|
446
|
+
},
|
|
447
|
+
text: {
|
|
448
|
+
fontSize: typography.sizes.md,
|
|
449
|
+
color: colors.text,
|
|
450
|
+
marginBottom: spacing.lg,
|
|
451
|
+
lineHeight: 24,
|
|
452
|
+
},
|
|
453
|
+
burritoButton: {
|
|
454
|
+
backgroundColor: colors.burrito,
|
|
455
|
+
borderRadius: borderRadius.sm,
|
|
456
|
+
padding: spacing.lg,
|
|
457
|
+
alignItems: 'center',
|
|
458
|
+
marginVertical: spacing.md,
|
|
459
|
+
...shadows.sm,
|
|
460
|
+
},
|
|
461
|
+
burritoButtonText: {
|
|
462
|
+
color: colors.white,
|
|
463
|
+
fontSize: typography.sizes.lg,
|
|
464
|
+
fontWeight: typography.weights.bold,
|
|
465
|
+
},
|
|
466
|
+
successContainer: {
|
|
467
|
+
alignItems: 'center',
|
|
468
|
+
marginVertical: spacing.sm,
|
|
469
|
+
},
|
|
470
|
+
success: {
|
|
471
|
+
color: colors.success,
|
|
472
|
+
fontSize: typography.sizes.md,
|
|
473
|
+
fontWeight: typography.weights.medium,
|
|
474
|
+
},
|
|
475
|
+
successCount: {
|
|
476
|
+
color: colors.success,
|
|
477
|
+
fontSize: typography.sizes.lg,
|
|
478
|
+
fontWeight: typography.weights.bold,
|
|
479
|
+
marginTop: spacing.xs,
|
|
480
|
+
},
|
|
481
|
+
stats: {
|
|
482
|
+
backgroundColor: colors.statsBackground,
|
|
483
|
+
padding: spacing.md,
|
|
484
|
+
borderRadius: borderRadius.sm,
|
|
485
|
+
marginTop: spacing.lg,
|
|
486
|
+
},
|
|
487
|
+
statsTitle: {
|
|
488
|
+
fontSize: typography.sizes.lg,
|
|
489
|
+
fontWeight: typography.weights.semibold,
|
|
490
|
+
color: colors.text,
|
|
491
|
+
marginBottom: spacing.xs,
|
|
492
|
+
},
|
|
493
|
+
statsText: {
|
|
494
|
+
fontSize: typography.sizes.md,
|
|
495
|
+
color: colors.text,
|
|
496
|
+
},
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## app/index.tsx
|
|
504
|
+
|
|
505
|
+
```tsx
|
|
506
|
+
import { useState } from 'react'
|
|
507
|
+
import {
|
|
508
|
+
View,
|
|
509
|
+
Text,
|
|
510
|
+
TextInput,
|
|
511
|
+
TouchableOpacity,
|
|
512
|
+
StyleSheet,
|
|
513
|
+
ScrollView,
|
|
514
|
+
KeyboardAvoidingView,
|
|
515
|
+
Platform,
|
|
516
|
+
} from 'react-native'
|
|
517
|
+
import { useRouter } from 'expo-router'
|
|
518
|
+
import { useAuth } from '../src/contexts/AuthContext'
|
|
519
|
+
import { colors, spacing, typography, borderRadius, shadows } from '../src/styles/theme'
|
|
520
|
+
|
|
521
|
+
export default function HomeScreen() {
|
|
522
|
+
const { user, login, logout } = useAuth()
|
|
523
|
+
const router = useRouter()
|
|
524
|
+
const [username, setUsername] = useState('')
|
|
525
|
+
const [password, setPassword] = useState('')
|
|
526
|
+
const [error, setError] = useState('')
|
|
527
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
528
|
+
|
|
529
|
+
const handleSubmit = async () => {
|
|
530
|
+
setError('')
|
|
531
|
+
|
|
532
|
+
if (!username.trim() || !password.trim()) {
|
|
533
|
+
setError('Please provide both username and password')
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
setIsSubmitting(true)
|
|
538
|
+
try {
|
|
539
|
+
const success = await login(username, password)
|
|
540
|
+
if (success) {
|
|
541
|
+
setUsername('')
|
|
542
|
+
setPassword('')
|
|
543
|
+
} else {
|
|
544
|
+
setError('An error occurred during login')
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
setError('An error occurred during login')
|
|
548
|
+
} finally {
|
|
549
|
+
setIsSubmitting(false)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Logged in view
|
|
554
|
+
if (user) {
|
|
555
|
+
return (
|
|
556
|
+
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContent}>
|
|
557
|
+
<View style={styles.card}>
|
|
558
|
+
<Text style={styles.title}>Welcome back, {user.username}!</Text>
|
|
559
|
+
<Text style={styles.text}>You are now logged in. Feel free to explore:</Text>
|
|
560
|
+
|
|
561
|
+
<View style={styles.buttonGroup}>
|
|
562
|
+
<TouchableOpacity
|
|
563
|
+
style={[styles.button, styles.burritoButton]}
|
|
564
|
+
onPress={() => router.push('/burrito')}
|
|
565
|
+
activeOpacity={0.8}
|
|
566
|
+
>
|
|
567
|
+
<Text style={styles.buttonText}>Consider Burritos</Text>
|
|
568
|
+
</TouchableOpacity>
|
|
569
|
+
|
|
570
|
+
<TouchableOpacity
|
|
571
|
+
style={[styles.button, styles.primaryButton]}
|
|
572
|
+
onPress={() => router.push('/profile')}
|
|
573
|
+
activeOpacity={0.8}
|
|
574
|
+
>
|
|
575
|
+
<Text style={styles.buttonText}>View Profile</Text>
|
|
576
|
+
</TouchableOpacity>
|
|
577
|
+
|
|
578
|
+
<TouchableOpacity
|
|
579
|
+
style={[styles.button, styles.logoutButton]}
|
|
580
|
+
onPress={logout}
|
|
581
|
+
activeOpacity={0.8}
|
|
582
|
+
>
|
|
583
|
+
<Text style={styles.buttonText}>Logout</Text>
|
|
584
|
+
</TouchableOpacity>
|
|
585
|
+
</View>
|
|
586
|
+
</View>
|
|
587
|
+
</ScrollView>
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Login view
|
|
592
|
+
return (
|
|
593
|
+
<KeyboardAvoidingView
|
|
594
|
+
style={styles.container}
|
|
595
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
596
|
+
>
|
|
597
|
+
<ScrollView
|
|
598
|
+
style={styles.scrollView}
|
|
599
|
+
contentContainerStyle={styles.scrollContent}
|
|
600
|
+
keyboardShouldPersistTaps="handled"
|
|
601
|
+
>
|
|
602
|
+
<View style={styles.card}>
|
|
603
|
+
<Text style={styles.title}>Welcome to Burrito Consideration App</Text>
|
|
604
|
+
<Text style={styles.text}>Please sign in to begin your burrito journey</Text>
|
|
605
|
+
|
|
606
|
+
<View style={styles.form}>
|
|
607
|
+
<Text style={styles.label}>Username:</Text>
|
|
608
|
+
<TextInput
|
|
609
|
+
style={styles.input}
|
|
610
|
+
value={username}
|
|
611
|
+
onChangeText={setUsername}
|
|
612
|
+
placeholder="Enter any username"
|
|
613
|
+
placeholderTextColor={colors.textLight}
|
|
614
|
+
autoCapitalize="none"
|
|
615
|
+
autoCorrect={false}
|
|
616
|
+
autoComplete="username"
|
|
617
|
+
editable={!isSubmitting}
|
|
618
|
+
/>
|
|
619
|
+
|
|
620
|
+
<Text style={styles.label}>Password:</Text>
|
|
621
|
+
<TextInput
|
|
622
|
+
style={styles.input}
|
|
623
|
+
value={password}
|
|
624
|
+
onChangeText={setPassword}
|
|
625
|
+
placeholder="Enter any password"
|
|
626
|
+
placeholderTextColor={colors.textLight}
|
|
627
|
+
secureTextEntry
|
|
628
|
+
autoComplete="password"
|
|
629
|
+
editable={!isSubmitting}
|
|
630
|
+
/>
|
|
631
|
+
|
|
632
|
+
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
633
|
+
|
|
634
|
+
<TouchableOpacity
|
|
635
|
+
style={[styles.button, styles.primaryButton, isSubmitting && styles.buttonDisabled]}
|
|
636
|
+
onPress={handleSubmit}
|
|
637
|
+
disabled={isSubmitting}
|
|
638
|
+
activeOpacity={0.8}
|
|
639
|
+
>
|
|
640
|
+
<Text style={styles.buttonText}>{isSubmitting ? 'Signing In...' : 'Sign In'}</Text>
|
|
641
|
+
</TouchableOpacity>
|
|
642
|
+
</View>
|
|
643
|
+
|
|
644
|
+
<Text style={styles.note}>
|
|
645
|
+
Note: This is a demo app. Use any username and password to sign in.
|
|
646
|
+
</Text>
|
|
647
|
+
</View>
|
|
648
|
+
</ScrollView>
|
|
649
|
+
</KeyboardAvoidingView>
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const styles = StyleSheet.create({
|
|
654
|
+
container: {
|
|
655
|
+
flex: 1,
|
|
656
|
+
backgroundColor: colors.background,
|
|
657
|
+
},
|
|
658
|
+
scrollView: {
|
|
659
|
+
flex: 1,
|
|
660
|
+
backgroundColor: colors.background,
|
|
661
|
+
},
|
|
662
|
+
scrollContent: {
|
|
663
|
+
flexGrow: 1,
|
|
664
|
+
padding: spacing.md,
|
|
665
|
+
justifyContent: 'center',
|
|
666
|
+
},
|
|
667
|
+
card: {
|
|
668
|
+
backgroundColor: colors.cardBackground,
|
|
669
|
+
borderRadius: borderRadius.md,
|
|
670
|
+
padding: spacing.lg,
|
|
671
|
+
...shadows.md,
|
|
672
|
+
},
|
|
673
|
+
title: {
|
|
674
|
+
fontSize: typography.sizes.xl,
|
|
675
|
+
fontWeight: typography.weights.bold,
|
|
676
|
+
color: colors.text,
|
|
677
|
+
marginBottom: spacing.sm,
|
|
678
|
+
},
|
|
679
|
+
text: {
|
|
680
|
+
fontSize: typography.sizes.md,
|
|
681
|
+
color: colors.text,
|
|
682
|
+
marginBottom: spacing.md,
|
|
683
|
+
lineHeight: 24,
|
|
684
|
+
},
|
|
685
|
+
form: {
|
|
686
|
+
marginTop: spacing.md,
|
|
687
|
+
},
|
|
688
|
+
label: {
|
|
689
|
+
fontSize: typography.sizes.md,
|
|
690
|
+
fontWeight: typography.weights.medium,
|
|
691
|
+
color: colors.text,
|
|
692
|
+
marginBottom: spacing.xs,
|
|
693
|
+
},
|
|
694
|
+
input: {
|
|
695
|
+
backgroundColor: colors.inputBackground,
|
|
696
|
+
borderWidth: 1,
|
|
697
|
+
borderColor: colors.border,
|
|
698
|
+
borderRadius: borderRadius.sm,
|
|
699
|
+
padding: spacing.sm,
|
|
700
|
+
fontSize: typography.sizes.md,
|
|
701
|
+
color: colors.text,
|
|
702
|
+
marginBottom: spacing.md,
|
|
703
|
+
},
|
|
704
|
+
buttonGroup: {
|
|
705
|
+
marginTop: spacing.md,
|
|
706
|
+
gap: spacing.sm,
|
|
707
|
+
},
|
|
708
|
+
button: {
|
|
709
|
+
borderRadius: borderRadius.sm,
|
|
710
|
+
padding: spacing.md,
|
|
711
|
+
alignItems: 'center',
|
|
712
|
+
marginTop: spacing.sm,
|
|
713
|
+
},
|
|
714
|
+
primaryButton: {
|
|
715
|
+
backgroundColor: colors.primary,
|
|
716
|
+
},
|
|
717
|
+
burritoButton: {
|
|
718
|
+
backgroundColor: colors.burrito,
|
|
719
|
+
},
|
|
720
|
+
logoutButton: {
|
|
721
|
+
backgroundColor: colors.danger,
|
|
722
|
+
},
|
|
723
|
+
buttonDisabled: {
|
|
724
|
+
opacity: 0.6,
|
|
725
|
+
},
|
|
726
|
+
buttonText: {
|
|
727
|
+
color: colors.white,
|
|
728
|
+
fontSize: typography.sizes.md,
|
|
729
|
+
fontWeight: typography.weights.semibold,
|
|
730
|
+
},
|
|
731
|
+
error: {
|
|
732
|
+
color: colors.danger,
|
|
733
|
+
marginBottom: spacing.sm,
|
|
734
|
+
fontSize: typography.sizes.sm,
|
|
735
|
+
},
|
|
736
|
+
note: {
|
|
737
|
+
marginTop: spacing.lg,
|
|
738
|
+
color: colors.textSecondary,
|
|
739
|
+
fontSize: typography.sizes.sm,
|
|
740
|
+
textAlign: 'center',
|
|
741
|
+
lineHeight: 20,
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## app/profile.tsx
|
|
750
|
+
|
|
751
|
+
```tsx
|
|
752
|
+
import { useEffect } from 'react'
|
|
753
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
|
754
|
+
import { useRouter } from 'expo-router'
|
|
755
|
+
import { useAuth } from '../src/contexts/AuthContext'
|
|
756
|
+
import { amplitude } from '../src/config/amplitude'
|
|
757
|
+
import { colors, spacing, typography, borderRadius, shadows } from '../src/styles/theme'
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Profile Screen
|
|
761
|
+
*
|
|
762
|
+
* Displays user information and demonstrates Amplitude event tracking.
|
|
763
|
+
*
|
|
764
|
+
* @see https://amplitude.com/docs/sdks/analytics/react-native
|
|
765
|
+
*/
|
|
766
|
+
export default function ProfileScreen() {
|
|
767
|
+
const { user } = useAuth()
|
|
768
|
+
const router = useRouter()
|
|
769
|
+
|
|
770
|
+
// Redirect to home if not logged in
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
if (!user) {
|
|
773
|
+
router.replace('/')
|
|
774
|
+
}
|
|
775
|
+
}, [user, router])
|
|
776
|
+
|
|
777
|
+
if (!user) {
|
|
778
|
+
return null
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const getJourneyMessage = () => {
|
|
782
|
+
const count = user.burritoConsiderations
|
|
783
|
+
if (count === 0) {
|
|
784
|
+
return "You haven't considered any burritos yet. Visit the Burrito Consideration page to start!"
|
|
785
|
+
} else if (count === 1) {
|
|
786
|
+
return "You've considered the burrito potential once. Keep going!"
|
|
787
|
+
} else if (count < 5) {
|
|
788
|
+
return "You're getting the hang of burrito consideration!"
|
|
789
|
+
} else if (count < 10) {
|
|
790
|
+
return "You're becoming a burrito consideration expert!"
|
|
791
|
+
} else {
|
|
792
|
+
return 'You are a true burrito consideration master!'
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return (
|
|
797
|
+
<View style={styles.container}>
|
|
798
|
+
<View style={styles.card}>
|
|
799
|
+
<Text style={styles.title}>User Profile</Text>
|
|
800
|
+
|
|
801
|
+
<View style={styles.stats}>
|
|
802
|
+
<Text style={styles.statsTitle}>Your Information</Text>
|
|
803
|
+
<View style={styles.infoRow}>
|
|
804
|
+
<Text style={styles.infoLabel}>Username:</Text>
|
|
805
|
+
<Text style={styles.infoValue}>{user.username}</Text>
|
|
806
|
+
</View>
|
|
807
|
+
<View style={styles.infoRow}>
|
|
808
|
+
<Text style={styles.infoLabel}>Burrito Considerations:</Text>
|
|
809
|
+
<Text style={styles.infoValue}>{user.burritoConsiderations}</Text>
|
|
810
|
+
</View>
|
|
811
|
+
</View>
|
|
812
|
+
|
|
813
|
+
<View style={styles.journey}>
|
|
814
|
+
<Text style={styles.journeyTitle}>Your Burrito Journey</Text>
|
|
815
|
+
<Text style={styles.journeyText}>{getJourneyMessage()}</Text>
|
|
816
|
+
</View>
|
|
817
|
+
</View>
|
|
818
|
+
</View>
|
|
819
|
+
)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const styles = StyleSheet.create({
|
|
823
|
+
container: {
|
|
824
|
+
flex: 1,
|
|
825
|
+
backgroundColor: colors.background,
|
|
826
|
+
padding: spacing.md,
|
|
827
|
+
},
|
|
828
|
+
card: {
|
|
829
|
+
backgroundColor: colors.cardBackground,
|
|
830
|
+
borderRadius: borderRadius.md,
|
|
831
|
+
padding: spacing.lg,
|
|
832
|
+
...shadows.md,
|
|
833
|
+
},
|
|
834
|
+
title: {
|
|
835
|
+
fontSize: typography.sizes.xl,
|
|
836
|
+
fontWeight: typography.weights.bold,
|
|
837
|
+
color: colors.text,
|
|
838
|
+
marginBottom: spacing.md,
|
|
839
|
+
},
|
|
840
|
+
stats: {
|
|
841
|
+
backgroundColor: colors.statsBackground,
|
|
842
|
+
padding: spacing.md,
|
|
843
|
+
borderRadius: borderRadius.sm,
|
|
844
|
+
},
|
|
845
|
+
statsTitle: {
|
|
846
|
+
fontSize: typography.sizes.lg,
|
|
847
|
+
fontWeight: typography.weights.semibold,
|
|
848
|
+
color: colors.text,
|
|
849
|
+
marginBottom: spacing.sm,
|
|
850
|
+
},
|
|
851
|
+
infoRow: {
|
|
852
|
+
flexDirection: 'row',
|
|
853
|
+
marginBottom: spacing.xs,
|
|
854
|
+
},
|
|
855
|
+
infoLabel: {
|
|
856
|
+
fontSize: typography.sizes.md,
|
|
857
|
+
fontWeight: typography.weights.bold,
|
|
858
|
+
color: colors.text,
|
|
859
|
+
marginRight: spacing.xs,
|
|
860
|
+
},
|
|
861
|
+
infoValue: {
|
|
862
|
+
fontSize: typography.sizes.md,
|
|
863
|
+
color: colors.text,
|
|
864
|
+
},
|
|
865
|
+
journey: {
|
|
866
|
+
marginTop: spacing.lg,
|
|
867
|
+
},
|
|
868
|
+
journeyTitle: {
|
|
869
|
+
fontSize: typography.sizes.lg,
|
|
870
|
+
fontWeight: typography.weights.semibold,
|
|
871
|
+
color: colors.text,
|
|
872
|
+
marginBottom: spacing.sm,
|
|
873
|
+
},
|
|
874
|
+
journeyText: {
|
|
875
|
+
fontSize: typography.sizes.md,
|
|
876
|
+
color: colors.text,
|
|
877
|
+
lineHeight: 24,
|
|
878
|
+
},
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## babel.config.js
|
|
886
|
+
|
|
887
|
+
```js
|
|
888
|
+
module.exports = function (api) {
|
|
889
|
+
api.cache(true)
|
|
890
|
+
return {
|
|
891
|
+
presets: ['babel-preset-expo'],
|
|
892
|
+
plugins: [
|
|
893
|
+
['babel-plugin-react-compiler'],
|
|
894
|
+
'react-native-reanimated/plugin', // Must be last
|
|
895
|
+
],
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## src/config/amplitude.ts
|
|
904
|
+
|
|
905
|
+
```ts
|
|
906
|
+
import * as amplitude from '@amplitude/analytics-react-native'
|
|
907
|
+
import Constants from 'expo-constants'
|
|
908
|
+
|
|
909
|
+
// Configuration loaded from app.config.js extras via expo-constants
|
|
910
|
+
// Environment variables are read at build time in app.config.js
|
|
911
|
+
const apiKey = Constants.expoConfig?.extra?.amplitudeApiKey as string | undefined
|
|
912
|
+
const isAmplitudeConfigured = !!apiKey
|
|
913
|
+
|
|
914
|
+
if (__DEV__) {
|
|
915
|
+
console.log('Amplitude config:', {
|
|
916
|
+
apiKey: apiKey ? 'SET' : 'NOT SET',
|
|
917
|
+
isConfigured: isAmplitudeConfigured,
|
|
918
|
+
})
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (!isAmplitudeConfigured) {
|
|
922
|
+
console.warn(
|
|
923
|
+
'Amplitude API key not configured. Analytics will be disabled. ' +
|
|
924
|
+
'Set AMPLITUDE_API_KEY in your .env file to enable analytics.'
|
|
925
|
+
)
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Initialize the Amplitude SDK for Expo
|
|
930
|
+
*
|
|
931
|
+
* Configuration loaded from app.config.js extras via expo-constants.
|
|
932
|
+
*
|
|
933
|
+
* @see https://amplitude.com/docs/sdks/analytics/react-native
|
|
934
|
+
*/
|
|
935
|
+
if (isAmplitudeConfigured && apiKey) {
|
|
936
|
+
amplitude.init(apiKey)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
export { amplitude }
|
|
940
|
+
export const isAmplitudeEnabled = isAmplitudeConfigured
|
|
941
|
+
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## src/contexts/AuthContext.tsx
|
|
947
|
+
|
|
948
|
+
```tsx
|
|
949
|
+
import React, { createContext, useState, useEffect, use } from 'react'
|
|
950
|
+
import type { ReactNode } from 'react'
|
|
951
|
+
import { Identify } from '@amplitude/analytics-react-native'
|
|
952
|
+
import { amplitude } from '../config/amplitude'
|
|
953
|
+
import { storage } from '../services/storage'
|
|
954
|
+
import type { User } from '../services/storage'
|
|
955
|
+
|
|
956
|
+
interface AuthContextType {
|
|
957
|
+
user: User | null
|
|
958
|
+
isLoading: boolean
|
|
959
|
+
login: (username: string, password: string) => Promise<boolean>
|
|
960
|
+
logout: () => Promise<void>
|
|
961
|
+
incrementBurritoConsiderations: () => Promise<void>
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
965
|
+
|
|
966
|
+
interface AuthProviderProps {
|
|
967
|
+
children: ReactNode
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
971
|
+
const [user, setUser] = useState<User | null>(null)
|
|
972
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
973
|
+
|
|
974
|
+
useEffect(() => {
|
|
975
|
+
const restoreSession = async () => {
|
|
976
|
+
try {
|
|
977
|
+
const storedUsername = await storage.getCurrentUser()
|
|
978
|
+
if (storedUsername) {
|
|
979
|
+
const existingUser = await storage.getUser(storedUsername)
|
|
980
|
+
if (existingUser) {
|
|
981
|
+
setUser(existingUser)
|
|
982
|
+
amplitude.setUserId(storedUsername)
|
|
983
|
+
const identifyObj = new Identify()
|
|
984
|
+
identifyObj.set('username', storedUsername)
|
|
985
|
+
amplitude.identify(identifyObj)
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
} catch (error) {
|
|
989
|
+
console.error('Failed to restore session:', error)
|
|
990
|
+
} finally {
|
|
991
|
+
setIsLoading(false)
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
restoreSession()
|
|
995
|
+
}, [])
|
|
996
|
+
|
|
997
|
+
// React Compiler auto-memoizes these callbacks - no useCallback needed!
|
|
998
|
+
const login = async (username: string, password: string): Promise<boolean> => {
|
|
999
|
+
if (!username.trim() || !password.trim()) {
|
|
1000
|
+
return false
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
try {
|
|
1004
|
+
const existingUser = await storage.getUser(username)
|
|
1005
|
+
const isNewUser = !existingUser
|
|
1006
|
+
|
|
1007
|
+
const userData: User = existingUser || {
|
|
1008
|
+
username,
|
|
1009
|
+
burritoConsiderations: 0,
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
await storage.saveUser(userData)
|
|
1013
|
+
await storage.setCurrentUser(username)
|
|
1014
|
+
setUser(userData)
|
|
1015
|
+
|
|
1016
|
+
// Amplitude: Identify user and capture login event
|
|
1017
|
+
amplitude.setUserId(username)
|
|
1018
|
+
const identifyObj = new Identify()
|
|
1019
|
+
identifyObj.set('username', username)
|
|
1020
|
+
amplitude.identify(identifyObj)
|
|
1021
|
+
|
|
1022
|
+
amplitude.track('User Logged In', {
|
|
1023
|
+
username,
|
|
1024
|
+
is_new_user: isNewUser,
|
|
1025
|
+
})
|
|
1026
|
+
|
|
1027
|
+
return true
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
console.error('Login error:', error)
|
|
1030
|
+
return false
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const logout = async () => {
|
|
1035
|
+
amplitude.track('User Logged Out')
|
|
1036
|
+
amplitude.reset()
|
|
1037
|
+
await storage.removeCurrentUser()
|
|
1038
|
+
setUser(null)
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const incrementBurritoConsiderations = async () => {
|
|
1042
|
+
if (user) {
|
|
1043
|
+
const updatedUser: User = {
|
|
1044
|
+
...user,
|
|
1045
|
+
burritoConsiderations: user.burritoConsiderations + 1,
|
|
1046
|
+
}
|
|
1047
|
+
setUser(updatedUser)
|
|
1048
|
+
await storage.saveUser(updatedUser)
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return (
|
|
1053
|
+
<AuthContext
|
|
1054
|
+
value={{
|
|
1055
|
+
user,
|
|
1056
|
+
isLoading,
|
|
1057
|
+
login,
|
|
1058
|
+
logout,
|
|
1059
|
+
incrementBurritoConsiderations,
|
|
1060
|
+
}}
|
|
1061
|
+
>
|
|
1062
|
+
{children}
|
|
1063
|
+
</AuthContext>
|
|
1064
|
+
)
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* React 19: Use the `use` API instead of useContext
|
|
1069
|
+
* - Can be called conditionally (unlike useContext)
|
|
1070
|
+
* - Enables more flexible component composition
|
|
1071
|
+
*/
|
|
1072
|
+
export function useAuth() {
|
|
1073
|
+
const context = use(AuthContext)
|
|
1074
|
+
if (context === undefined) {
|
|
1075
|
+
throw new Error('useAuth must be used within an AuthProvider')
|
|
1076
|
+
}
|
|
1077
|
+
return context
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
---
|
|
1083
|
+
|
|
1084
|
+
## src/services/storage.ts
|
|
1085
|
+
|
|
1086
|
+
```ts
|
|
1087
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
1088
|
+
|
|
1089
|
+
const CURRENT_USER_KEY = 'currentUser'
|
|
1090
|
+
const USERS_KEY = 'users'
|
|
1091
|
+
|
|
1092
|
+
export interface User {
|
|
1093
|
+
username: string
|
|
1094
|
+
burritoConsiderations: number
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Storage service for persisting user data
|
|
1099
|
+
* Uses AsyncStorage (React Native's async key-value storage)
|
|
1100
|
+
*/
|
|
1101
|
+
export const storage = {
|
|
1102
|
+
/**
|
|
1103
|
+
* Get the currently logged in user's username
|
|
1104
|
+
*/
|
|
1105
|
+
getCurrentUser: async (): Promise<string | null> => {
|
|
1106
|
+
try {
|
|
1107
|
+
return await AsyncStorage.getItem(CURRENT_USER_KEY)
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
console.error('Error getting current user:', error)
|
|
1110
|
+
return null
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Set the currently logged in user's username
|
|
1116
|
+
*/
|
|
1117
|
+
setCurrentUser: async (username: string): Promise<void> => {
|
|
1118
|
+
try {
|
|
1119
|
+
await AsyncStorage.setItem(CURRENT_USER_KEY, username)
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
console.error('Error setting current user:', error)
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Remove the current user (logout)
|
|
1127
|
+
*/
|
|
1128
|
+
removeCurrentUser: async (): Promise<void> => {
|
|
1129
|
+
try {
|
|
1130
|
+
await AsyncStorage.removeItem(CURRENT_USER_KEY)
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.error('Error removing current user:', error)
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Get all stored users
|
|
1138
|
+
*/
|
|
1139
|
+
getUsers: async (): Promise<Record<string, User>> => {
|
|
1140
|
+
try {
|
|
1141
|
+
const data = await AsyncStorage.getItem(USERS_KEY)
|
|
1142
|
+
return data ? JSON.parse(data) : {}
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
console.error('Error getting users:', error)
|
|
1145
|
+
return {}
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Get a specific user by username
|
|
1151
|
+
*/
|
|
1152
|
+
getUser: async (username: string): Promise<User | null> => {
|
|
1153
|
+
try {
|
|
1154
|
+
const users = await storage.getUsers()
|
|
1155
|
+
return users[username] || null
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
console.error('Error getting user:', error)
|
|
1158
|
+
return null
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Save a user to storage
|
|
1164
|
+
*/
|
|
1165
|
+
saveUser: async (user: User): Promise<void> => {
|
|
1166
|
+
try {
|
|
1167
|
+
const users = await storage.getUsers()
|
|
1168
|
+
users[user.username] = user
|
|
1169
|
+
await AsyncStorage.setItem(USERS_KEY, JSON.stringify(users))
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.error('Error saving user:', error)
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Clear all stored data (for testing/debugging)
|
|
1177
|
+
*/
|
|
1178
|
+
clearAll: async (): Promise<void> => {
|
|
1179
|
+
try {
|
|
1180
|
+
await AsyncStorage.multiRemove([CURRENT_USER_KEY, USERS_KEY])
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
console.error('Error clearing storage:', error)
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
## src/styles/theme.ts
|
|
1192
|
+
|
|
1193
|
+
```ts
|
|
1194
|
+
/**
|
|
1195
|
+
* Theme constants for consistent styling across the app
|
|
1196
|
+
* Matches the color scheme from the TanStack Start web version
|
|
1197
|
+
*/
|
|
1198
|
+
|
|
1199
|
+
export const colors = {
|
|
1200
|
+
// Primary colors
|
|
1201
|
+
primary: '#0070f3',
|
|
1202
|
+
primaryDark: '#0051cc',
|
|
1203
|
+
|
|
1204
|
+
// Status colors
|
|
1205
|
+
success: '#28a745',
|
|
1206
|
+
successDark: '#218838',
|
|
1207
|
+
danger: '#dc3545',
|
|
1208
|
+
dangerDark: '#c82333',
|
|
1209
|
+
|
|
1210
|
+
// Feature colors
|
|
1211
|
+
burrito: '#e07c24',
|
|
1212
|
+
burritoDark: '#c96a1a',
|
|
1213
|
+
|
|
1214
|
+
// Neutral colors
|
|
1215
|
+
background: '#f5f5f5',
|
|
1216
|
+
white: '#ffffff',
|
|
1217
|
+
text: '#333333',
|
|
1218
|
+
textSecondary: '#666666',
|
|
1219
|
+
textLight: '#999999',
|
|
1220
|
+
border: '#dddddd',
|
|
1221
|
+
borderLight: '#eeeeee',
|
|
1222
|
+
|
|
1223
|
+
// Component-specific
|
|
1224
|
+
statsBackground: '#f8f9fa',
|
|
1225
|
+
headerBackground: '#333333',
|
|
1226
|
+
headerText: '#ffffff',
|
|
1227
|
+
inputBackground: '#ffffff',
|
|
1228
|
+
cardBackground: '#ffffff',
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
export const spacing = {
|
|
1232
|
+
xs: 4,
|
|
1233
|
+
sm: 8,
|
|
1234
|
+
md: 16,
|
|
1235
|
+
lg: 24,
|
|
1236
|
+
xl: 32,
|
|
1237
|
+
xxl: 48,
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export const typography = {
|
|
1241
|
+
sizes: {
|
|
1242
|
+
xs: 12,
|
|
1243
|
+
sm: 14,
|
|
1244
|
+
md: 16,
|
|
1245
|
+
lg: 18,
|
|
1246
|
+
xl: 24,
|
|
1247
|
+
xxl: 32,
|
|
1248
|
+
},
|
|
1249
|
+
weights: {
|
|
1250
|
+
normal: '400' as const,
|
|
1251
|
+
medium: '500' as const,
|
|
1252
|
+
semibold: '600' as const,
|
|
1253
|
+
bold: '700' as const,
|
|
1254
|
+
},
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
export const borderRadius = {
|
|
1258
|
+
sm: 4,
|
|
1259
|
+
md: 8,
|
|
1260
|
+
lg: 12,
|
|
1261
|
+
full: 9999,
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export const shadows = {
|
|
1265
|
+
sm: {
|
|
1266
|
+
shadowColor: '#000',
|
|
1267
|
+
shadowOffset: { width: 0, height: 1 },
|
|
1268
|
+
shadowOpacity: 0.05,
|
|
1269
|
+
shadowRadius: 2,
|
|
1270
|
+
elevation: 1,
|
|
1271
|
+
},
|
|
1272
|
+
md: {
|
|
1273
|
+
shadowColor: '#000',
|
|
1274
|
+
shadowOffset: { width: 0, height: 2 },
|
|
1275
|
+
shadowOpacity: 0.1,
|
|
1276
|
+
shadowRadius: 4,
|
|
1277
|
+
elevation: 3,
|
|
1278
|
+
},
|
|
1279
|
+
lg: {
|
|
1280
|
+
shadowColor: '#000',
|
|
1281
|
+
shadowOffset: { width: 0, height: 4 },
|
|
1282
|
+
shadowOpacity: 0.15,
|
|
1283
|
+
shadowRadius: 8,
|
|
1284
|
+
elevation: 5,
|
|
1285
|
+
},
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
---
|
|
1291
|
+
|