@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,2253 @@
|
|
|
1
|
+
# Amplitude React Native Example Project
|
|
2
|
+
|
|
3
|
+
Repository: https://github.com/amplitude/context-hub
|
|
4
|
+
Path: basics/react-native
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## README.md
|
|
9
|
+
|
|
10
|
+
# Amplitude React Native example
|
|
11
|
+
|
|
12
|
+
This is a bare [React Native](https://reactnative.dev/) example (no Expo) demonstrating Amplitude integration with product analytics and user identification.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Product analytics**: Track user events and behaviors
|
|
17
|
+
- **User authentication**: Demo login system with Amplitude user identification
|
|
18
|
+
- **Session persistence**: AsyncStorage for maintaining user sessions across app restarts
|
|
19
|
+
- **Native navigation**: React Navigation v7 with native stack navigator
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
### For iOS Development
|
|
24
|
+
|
|
25
|
+
You need a Mac with the following installed:
|
|
26
|
+
|
|
27
|
+
1. **Xcode** (from the Mac App Store)
|
|
28
|
+
- Open App Store and search for "Xcode"
|
|
29
|
+
- Install it (~12GB download)
|
|
30
|
+
- After installing, open Xcode once to accept the license agreement
|
|
31
|
+
|
|
32
|
+
2. **Xcode Command Line Tools**
|
|
33
|
+
```bash
|
|
34
|
+
xcode-select --install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **CocoaPods** (iOS dependency manager)
|
|
38
|
+
```bash
|
|
39
|
+
brew install cocoapods
|
|
40
|
+
```
|
|
41
|
+
Or without Homebrew:
|
|
42
|
+
```bash
|
|
43
|
+
sudo gem install cocoapods
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### For Android Development
|
|
47
|
+
|
|
48
|
+
1. **Android Studio** (the Android IDE)
|
|
49
|
+
```bash
|
|
50
|
+
brew install --cask android-studio
|
|
51
|
+
```
|
|
52
|
+
Or download from: https://developer.android.com/studio
|
|
53
|
+
|
|
54
|
+
2. **First-time Android Studio Setup**
|
|
55
|
+
- Open Android Studio
|
|
56
|
+
- Complete the setup wizard (downloads Android SDK automatically)
|
|
57
|
+
- Go to **Settings → Languages & Frameworks → Android SDK**
|
|
58
|
+
- Ensure "Android SDK Platform 34" (or latest) is installed
|
|
59
|
+
|
|
60
|
+
3. **Create an Android Emulator**
|
|
61
|
+
- In Android Studio: **Tools → Device Manager**
|
|
62
|
+
- Click **Create Device**
|
|
63
|
+
- Select a phone (e.g., "Pixel 7")
|
|
64
|
+
- Download a system image (e.g., API 34)
|
|
65
|
+
- Finish and click the **Play** button to launch
|
|
66
|
+
|
|
67
|
+
4. **Environment Variables** (add to `~/.zshrc` or `~/.bashrc`)
|
|
68
|
+
```bash
|
|
69
|
+
# Android SDK
|
|
70
|
+
export ANDROID_HOME=$HOME/Library/Android/sdk
|
|
71
|
+
export PATH=$PATH:$ANDROID_HOME/emulator
|
|
72
|
+
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
|
73
|
+
|
|
74
|
+
# Java from Android Studio (required for Gradle)
|
|
75
|
+
export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
|
76
|
+
export PATH=$JAVA_HOME/bin:$PATH
|
|
77
|
+
```
|
|
78
|
+
Then run `source ~/.zshrc` to apply.
|
|
79
|
+
|
|
80
|
+
5. **Create local.properties file** (if SDK location is not detected)
|
|
81
|
+
Create `android/local.properties` with:
|
|
82
|
+
```
|
|
83
|
+
sdk.dir=$HOME/Library/Android/sdk
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
6. **Clear Gradle cache** (required when jumping between different versions of Gradle)
|
|
87
|
+
```bash
|
|
88
|
+
rm -rf ~/.gradle/caches/modules-2/files-2.1/org.gradle.toolchains/foojay-resolver
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Getting started
|
|
92
|
+
|
|
93
|
+
### 1. Install dependencies
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Configure environment variables
|
|
100
|
+
|
|
101
|
+
Create a `.env` file:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cp .env.example .env
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Edit `.env` and add your Amplitude API key:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
AMPLITUDE_API_KEY=your_amplitude_api_key_here
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Get your Amplitude API key from your [Amplitude project settings](https://app.amplitude.com).
|
|
114
|
+
|
|
115
|
+
> **Note:** The app will still run without an Amplitude API key - analytics will simply be disabled.
|
|
116
|
+
|
|
117
|
+
### 3. Run on iOS
|
|
118
|
+
|
|
119
|
+
Install iOS dependencies (first time only):
|
|
120
|
+
```bash
|
|
121
|
+
cd ios && pod install && cd ..
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Run the app:
|
|
125
|
+
```bash
|
|
126
|
+
npm run ios
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Note:** First build takes 5-10 minutes. Subsequent builds are much faster.
|
|
130
|
+
|
|
131
|
+
### 4. Run on Android
|
|
132
|
+
|
|
133
|
+
Make sure an Android emulator is running (from Android Studio Device Manager), then:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm run android
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> **Note:** First build takes 3-5 minutes.
|
|
140
|
+
|
|
141
|
+
## Troubleshooting
|
|
142
|
+
|
|
143
|
+
### iOS Issues
|
|
144
|
+
|
|
145
|
+
**"No `Podfile' found"**
|
|
146
|
+
- Make sure you're in the `ios` directory: `cd ios && pod install`
|
|
147
|
+
|
|
148
|
+
**Build fails with signing errors**
|
|
149
|
+
- Open `ios/BurritoApp.xcworkspace` in Xcode
|
|
150
|
+
- Select the project → Signing & Capabilities
|
|
151
|
+
- Select your development team
|
|
152
|
+
|
|
153
|
+
**Simulator not launching**
|
|
154
|
+
- Open Xcode → Open Developer Tool → Simulator
|
|
155
|
+
- Or run: `open -a Simulator`
|
|
156
|
+
|
|
157
|
+
### Android Issues
|
|
158
|
+
|
|
159
|
+
**"SDK location not found"**
|
|
160
|
+
- Ensure `ANDROID_HOME` is set in your shell profile
|
|
161
|
+
- Run `source ~/.zshrc` after adding it
|
|
162
|
+
|
|
163
|
+
**"No connected devices"**
|
|
164
|
+
- Launch an emulator from Android Studio Device Manager
|
|
165
|
+
- Or connect a physical device with USB debugging enabled
|
|
166
|
+
|
|
167
|
+
**Gradle build fails**
|
|
168
|
+
- Try: `cd android && ./gradlew clean && cd ..`
|
|
169
|
+
- Then: `npm run android`
|
|
170
|
+
|
|
171
|
+
## Project structure
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
src/
|
|
175
|
+
├── config/
|
|
176
|
+
│ └── amplitude.ts # Amplitude client configuration
|
|
177
|
+
├── contexts/
|
|
178
|
+
│ └── AuthContext.tsx # Authentication context with Amplitude integration
|
|
179
|
+
├── navigation/
|
|
180
|
+
│ └── RootNavigator.tsx # React Navigation stack navigator
|
|
181
|
+
├── screens/
|
|
182
|
+
│ ├── HomeScreen.tsx # Home/login screen
|
|
183
|
+
│ ├── BurritoScreen.tsx # Demo feature screen with event tracking
|
|
184
|
+
│ └── ProfileScreen.tsx # User profile screen
|
|
185
|
+
├── services/
|
|
186
|
+
│ └── storage.ts # AsyncStorage wrapper for persistence
|
|
187
|
+
├── styles/
|
|
188
|
+
│ └── theme.ts # Shared style constants
|
|
189
|
+
└── types/
|
|
190
|
+
└── env.d.ts # Type declarations for environment variables
|
|
191
|
+
|
|
192
|
+
App.tsx # Root component
|
|
193
|
+
index.js # App entry point
|
|
194
|
+
.env # Environment variables (create from .env.example)
|
|
195
|
+
ios/ # Native iOS project (Xcode)
|
|
196
|
+
android/ # Native Android project (Android Studio)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Key integration points
|
|
200
|
+
|
|
201
|
+
### Amplitude client setup (config/amplitude.ts)
|
|
202
|
+
|
|
203
|
+
The Amplitude client is initialized with the API key from environment variables:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import * as amplitude from '@amplitude/analytics-react-native'
|
|
207
|
+
import Config from 'react-native-config'
|
|
208
|
+
|
|
209
|
+
const apiKey = Config.AMPLITUDE_API_KEY
|
|
210
|
+
const isAmplitudeConfigured = apiKey && apiKey !== 'your_amplitude_api_key_here'
|
|
211
|
+
|
|
212
|
+
if (isAmplitudeConfigured && apiKey) {
|
|
213
|
+
amplitude.init(apiKey)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { amplitude }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Screen tracking (App.tsx)
|
|
220
|
+
|
|
221
|
+
Screen views are tracked manually via React Navigation's `onStateChange`:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { amplitude } from './src/config/amplitude'
|
|
225
|
+
|
|
226
|
+
onStateChange={() => {
|
|
227
|
+
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name
|
|
228
|
+
if (previousRouteName !== currentRouteName && currentRouteName) {
|
|
229
|
+
amplitude.track('Screen Viewed', {
|
|
230
|
+
screen_name: currentRouteName,
|
|
231
|
+
previous_screen: previousRouteName,
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### User identification (contexts/AuthContext.tsx)
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { Identify } from '@amplitude/analytics-react-native'
|
|
241
|
+
import { amplitude } from '../config/amplitude'
|
|
242
|
+
|
|
243
|
+
// On login - identify user with properties
|
|
244
|
+
amplitude.setUserId(username)
|
|
245
|
+
const identifyObj = new Identify()
|
|
246
|
+
identifyObj.set('username', username)
|
|
247
|
+
amplitude.identify(identifyObj)
|
|
248
|
+
|
|
249
|
+
// Track login event
|
|
250
|
+
amplitude.track('User Logged In', {
|
|
251
|
+
username,
|
|
252
|
+
is_new_user: isNewUser,
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// On logout - reset clears identity
|
|
256
|
+
amplitude.track('User Logged Out')
|
|
257
|
+
amplitude.reset()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Event tracking (screens/BurritoScreen.tsx)
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { amplitude } from '../config/amplitude'
|
|
264
|
+
|
|
265
|
+
amplitude.track('Burrito Considered', {
|
|
266
|
+
total_considerations: newCount,
|
|
267
|
+
username: user.username,
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Session persistence (services/storage.ts)
|
|
272
|
+
|
|
273
|
+
AsyncStorage replaces localStorage for persisting user sessions:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
277
|
+
|
|
278
|
+
export const storage = {
|
|
279
|
+
getCurrentUser: async (): Promise<string | null> => {
|
|
280
|
+
return await AsyncStorage.getItem('currentUser')
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
setCurrentUser: async (username: string): Promise<void> => {
|
|
284
|
+
await AsyncStorage.setItem('currentUser', username)
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
saveUser: async (user: User): Promise<void> => {
|
|
288
|
+
const users = await storage.getUsers()
|
|
289
|
+
users[user.username] = user
|
|
290
|
+
await AsyncStorage.setItem('users', JSON.stringify(users))
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Learn more
|
|
296
|
+
|
|
297
|
+
- [Amplitude documentation](https://amplitude.com/docs)
|
|
298
|
+
- [Amplitude React Native SDK](https://amplitude.com/docs/sdks/analytics/react-native)
|
|
299
|
+
- [React Native documentation](https://reactnative.dev/docs/getting-started)
|
|
300
|
+
- [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
|
|
301
|
+
- [React Navigation documentation](https://reactnavigation.org/docs/getting-started)
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## __tests__/App.test.tsx
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
/**
|
|
309
|
+
* @format
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
import React from 'react';
|
|
313
|
+
import ReactTestRenderer from 'react-test-renderer';
|
|
314
|
+
import App from '../App';
|
|
315
|
+
|
|
316
|
+
test('renders correctly', async () => {
|
|
317
|
+
await ReactTestRenderer.act(() => {
|
|
318
|
+
ReactTestRenderer.create(<App />);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## .env.example
|
|
327
|
+
|
|
328
|
+
```example
|
|
329
|
+
AMPLITUDE_API_KEY=your_amplitude_api_key_here
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## .prettierrc.js
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
module.exports = {
|
|
339
|
+
arrowParens: 'avoid',
|
|
340
|
+
singleQuote: true,
|
|
341
|
+
trailingComma: 'all',
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## android/app/debug.keystore
|
|
349
|
+
|
|
350
|
+
```keystore
|
|
351
|
+
����androiddebugkeyCJ���0��0
|
|
352
|
+
+*��N���O�J�`>���X'����_���ȏ�R9���,xJ'K~\j��:ز��}�U)Q]"���ա!� Ȉ��Ù��F�5�H�x�A�\�P�)��@�I�s�d��>�2#m�י�j/8p��","ȁ��|\����1e�7�!����h(����.�Fi5��o��ung�*�!�����vF��֪�Sb!��T��4�
|
|
353
|
+
˺��s����Y�qX.5090�{0�c�#.�b0
|
|
354
|
+
Unknown10UAndroid10U
|
|
355
|
+
Unknown10UAndroid10U
|
|
356
|
+
���n &�l�Dx�%�
|
|
357
|
+
�EIr�M�%�����a�!00U��8�Ҋ�X��
|
|
358
|
+
�C(�#� 0
|
|
359
|
+
m~<���vֶ�'.��h%�h�w�0E^{�ù�/Oz��u�R��$_1�I�~����s��ܖ��ӓ���֭�\�JX��j��;RC�՜�n<q�f�$��Z1N�������7����ڵk[Ųm��5^B����]W��e���i4V��*:�D~]z�ޝ����<��l.e���Ɖ��#�L�:c�~0�?���q�I�@���x�٨�*I�ӗ���Y`G'@^w�
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## android/app/proguard-rules.pro
|
|
365
|
+
|
|
366
|
+
```pro
|
|
367
|
+
# Add project specific ProGuard rules here.
|
|
368
|
+
# By default, the flags in this file are appended to flags specified
|
|
369
|
+
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
|
370
|
+
# You can edit the include path and order by changing the proguardFiles
|
|
371
|
+
# directive in build.gradle.
|
|
372
|
+
#
|
|
373
|
+
# For more details, see
|
|
374
|
+
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
375
|
+
|
|
376
|
+
# Add any project specific keep options here:
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## android/app/src/main/AndroidManifest.xml
|
|
383
|
+
|
|
384
|
+
```xml
|
|
385
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
386
|
+
|
|
387
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
388
|
+
|
|
389
|
+
<application
|
|
390
|
+
android:name=".MainApplication"
|
|
391
|
+
android:label="@string/app_name"
|
|
392
|
+
android:icon="@mipmap/ic_launcher"
|
|
393
|
+
android:roundIcon="@mipmap/ic_launcher_round"
|
|
394
|
+
android:allowBackup="false"
|
|
395
|
+
android:theme="@style/AppTheme"
|
|
396
|
+
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
|
397
|
+
android:supportsRtl="true">
|
|
398
|
+
<activity
|
|
399
|
+
android:name=".MainActivity"
|
|
400
|
+
android:label="@string/app_name"
|
|
401
|
+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
|
402
|
+
android:launchMode="singleTask"
|
|
403
|
+
android:windowSoftInputMode="adjustResize"
|
|
404
|
+
android:exported="true">
|
|
405
|
+
<intent-filter>
|
|
406
|
+
<action android:name="android.intent.action.MAIN" />
|
|
407
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
408
|
+
</intent-filter>
|
|
409
|
+
</activity>
|
|
410
|
+
</application>
|
|
411
|
+
</manifest>
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## android/app/src/main/java/com/burritoapp/MainActivity.kt
|
|
418
|
+
|
|
419
|
+
```kt
|
|
420
|
+
package com.burritoapp
|
|
421
|
+
|
|
422
|
+
import com.facebook.react.ReactActivity
|
|
423
|
+
import com.facebook.react.ReactActivityDelegate
|
|
424
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
425
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
426
|
+
|
|
427
|
+
class MainActivity : ReactActivity() {
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
|
431
|
+
* rendering of the component.
|
|
432
|
+
*/
|
|
433
|
+
override fun getMainComponentName(): String = "BurritoApp"
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
|
|
437
|
+
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
|
438
|
+
*/
|
|
439
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
|
440
|
+
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## android/app/src/main/java/com/burritoapp/MainApplication.kt
|
|
448
|
+
|
|
449
|
+
```kt
|
|
450
|
+
package com.burritoapp
|
|
451
|
+
|
|
452
|
+
import android.app.Application
|
|
453
|
+
import com.facebook.react.PackageList
|
|
454
|
+
import com.facebook.react.ReactApplication
|
|
455
|
+
import com.facebook.react.ReactHost
|
|
456
|
+
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
|
457
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
458
|
+
|
|
459
|
+
class MainApplication : Application(), ReactApplication {
|
|
460
|
+
|
|
461
|
+
override val reactHost: ReactHost by lazy {
|
|
462
|
+
getDefaultReactHost(
|
|
463
|
+
context = applicationContext,
|
|
464
|
+
packageList =
|
|
465
|
+
PackageList(this).packages.apply {
|
|
466
|
+
// Packages that cannot be autolinked yet can be added manually here, for example:
|
|
467
|
+
// add(MyReactNativePackage())
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
override fun onCreate() {
|
|
473
|
+
super.onCreate()
|
|
474
|
+
loadReactNative(this)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## android/app/src/main/res/drawable/rn_edit_text_material.xml
|
|
483
|
+
|
|
484
|
+
```xml
|
|
485
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
486
|
+
<!-- Copyright (C) 2014 The Android Open Source Project
|
|
487
|
+
|
|
488
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
489
|
+
you may not use this file except in compliance with the License.
|
|
490
|
+
You may obtain a copy of the License at
|
|
491
|
+
|
|
492
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
493
|
+
|
|
494
|
+
Unless required by applicable law or agreed to in writing, software
|
|
495
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
496
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
497
|
+
See the License for the specific language governing permissions and
|
|
498
|
+
limitations under the License.
|
|
499
|
+
-->
|
|
500
|
+
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
|
501
|
+
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
|
|
502
|
+
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
|
|
503
|
+
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
|
504
|
+
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"
|
|
505
|
+
>
|
|
506
|
+
|
|
507
|
+
<selector>
|
|
508
|
+
<!--
|
|
509
|
+
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
|
510
|
+
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
|
511
|
+
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
|
512
|
+
|
|
513
|
+
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
|
514
|
+
|
|
515
|
+
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
|
516
|
+
-->
|
|
517
|
+
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
|
518
|
+
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
|
519
|
+
</selector>
|
|
520
|
+
|
|
521
|
+
</inset>
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## android/app/src/main/res/values/strings.xml
|
|
528
|
+
|
|
529
|
+
```xml
|
|
530
|
+
<resources>
|
|
531
|
+
<string name="app_name">BurritoApp</string>
|
|
532
|
+
</resources>
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## android/app/src/main/res/values/styles.xml
|
|
539
|
+
|
|
540
|
+
```xml
|
|
541
|
+
<resources>
|
|
542
|
+
|
|
543
|
+
<!-- Base application theme. -->
|
|
544
|
+
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
|
545
|
+
<!-- Customize your theme here. -->
|
|
546
|
+
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
|
547
|
+
</style>
|
|
548
|
+
|
|
549
|
+
</resources>
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## android/gradle.properties
|
|
556
|
+
|
|
557
|
+
```properties
|
|
558
|
+
# Project-wide Gradle settings.
|
|
559
|
+
|
|
560
|
+
# IDE (e.g. Android Studio) users:
|
|
561
|
+
# Gradle settings configured through the IDE *will override*
|
|
562
|
+
# any settings specified in this file.
|
|
563
|
+
|
|
564
|
+
# For more details on how to configure your build environment visit
|
|
565
|
+
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
566
|
+
|
|
567
|
+
# Specifies the JVM arguments used for the daemon process.
|
|
568
|
+
# The setting is particularly useful for tweaking memory settings.
|
|
569
|
+
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
|
570
|
+
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
|
571
|
+
|
|
572
|
+
# When configured, Gradle will run in incubating parallel mode.
|
|
573
|
+
# This option should only be used with decoupled projects. More details, visit
|
|
574
|
+
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
575
|
+
# org.gradle.parallel=true
|
|
576
|
+
|
|
577
|
+
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
578
|
+
# Android operating system, and which are packaged with your app's APK
|
|
579
|
+
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
580
|
+
android.useAndroidX=true
|
|
581
|
+
|
|
582
|
+
# Use this property to specify which architecture you want to build.
|
|
583
|
+
# You can also override it from the CLI using
|
|
584
|
+
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
|
585
|
+
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
|
586
|
+
|
|
587
|
+
# Use this property to enable support to the new architecture.
|
|
588
|
+
# This will allow you to use TurboModules and the Fabric render in
|
|
589
|
+
# your application. You should enable this flag either if you want
|
|
590
|
+
# to write custom TurboModules/Fabric components OR use libraries that
|
|
591
|
+
# are providing them.
|
|
592
|
+
newArchEnabled=true
|
|
593
|
+
|
|
594
|
+
# Use this property to enable or disable the Hermes JS engine.
|
|
595
|
+
# If set to false, you will be using JSC instead.
|
|
596
|
+
hermesEnabled=true
|
|
597
|
+
|
|
598
|
+
# Use this property to enable edge-to-edge display support.
|
|
599
|
+
# This allows your app to draw behind system bars for an immersive UI.
|
|
600
|
+
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
|
601
|
+
edgeToEdgeEnabled=false
|
|
602
|
+
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## App.tsx
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
import React, { useRef } from 'react'
|
|
611
|
+
import { StatusBar } from 'react-native'
|
|
612
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
613
|
+
import {
|
|
614
|
+
NavigationContainer,
|
|
615
|
+
NavigationContainerRef,
|
|
616
|
+
} from '@react-navigation/native'
|
|
617
|
+
|
|
618
|
+
import { AuthProvider } from './src/contexts/AuthContext'
|
|
619
|
+
import { RootNavigator, RootStackParamList } from './src/navigation/RootNavigator'
|
|
620
|
+
import { amplitude } from './src/config/amplitude'
|
|
621
|
+
import { colors } from './src/styles/theme'
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Burrito Consideration App
|
|
625
|
+
*
|
|
626
|
+
* A demo React Native application showcasing Amplitude analytics integration.
|
|
627
|
+
*
|
|
628
|
+
* Features:
|
|
629
|
+
* - User authentication (demo mode - accepts any credentials)
|
|
630
|
+
* - Burrito consideration counter with event tracking
|
|
631
|
+
* - User profile with statistics
|
|
632
|
+
*/
|
|
633
|
+
export default function App() {
|
|
634
|
+
const navigationRef = useRef<NavigationContainerRef<RootStackParamList>>(null)
|
|
635
|
+
const routeNameRef = useRef<string | undefined>()
|
|
636
|
+
|
|
637
|
+
return (
|
|
638
|
+
<SafeAreaProvider>
|
|
639
|
+
<StatusBar
|
|
640
|
+
barStyle="light-content"
|
|
641
|
+
backgroundColor={colors.headerBackground}
|
|
642
|
+
/>
|
|
643
|
+
<NavigationContainer
|
|
644
|
+
ref={navigationRef}
|
|
645
|
+
onReady={() => {
|
|
646
|
+
// Store the initial route name
|
|
647
|
+
routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name
|
|
648
|
+
}}
|
|
649
|
+
onStateChange={() => {
|
|
650
|
+
// Track screen views manually for React Navigation v7
|
|
651
|
+
const previousRouteName = routeNameRef.current
|
|
652
|
+
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name
|
|
653
|
+
|
|
654
|
+
if (previousRouteName !== currentRouteName && currentRouteName) {
|
|
655
|
+
// Track screen view event
|
|
656
|
+
amplitude.track('Screen Viewed', {
|
|
657
|
+
screen_name: currentRouteName,
|
|
658
|
+
previous_screen: previousRouteName,
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Update the stored route name
|
|
663
|
+
routeNameRef.current = currentRouteName
|
|
664
|
+
}}
|
|
665
|
+
>
|
|
666
|
+
<AuthProvider>
|
|
667
|
+
<RootNavigator />
|
|
668
|
+
</AuthProvider>
|
|
669
|
+
</NavigationContainer>
|
|
670
|
+
</SafeAreaProvider>
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## babel.config.js
|
|
679
|
+
|
|
680
|
+
```js
|
|
681
|
+
module.exports = {
|
|
682
|
+
presets: ['module:@react-native/babel-preset'],
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
---
|
|
688
|
+
|
|
689
|
+
## Gemfile
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
source 'https://rubygems.org'
|
|
693
|
+
|
|
694
|
+
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
|
695
|
+
ruby ">= 2.6.10"
|
|
696
|
+
|
|
697
|
+
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
|
698
|
+
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
|
|
699
|
+
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
|
|
700
|
+
gem 'xcodeproj', '< 1.26.0'
|
|
701
|
+
gem 'concurrent-ruby', '< 1.3.4'
|
|
702
|
+
|
|
703
|
+
# Ruby 3.4.0 has removed some libraries from the standard library.
|
|
704
|
+
gem 'bigdecimal'
|
|
705
|
+
gem 'logger'
|
|
706
|
+
gem 'benchmark'
|
|
707
|
+
gem 'mutex_m'
|
|
708
|
+
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## index.js
|
|
714
|
+
|
|
715
|
+
```js
|
|
716
|
+
/**
|
|
717
|
+
* @format
|
|
718
|
+
*/
|
|
719
|
+
|
|
720
|
+
import { AppRegistry } from 'react-native';
|
|
721
|
+
import App from './App';
|
|
722
|
+
import { name as appName } from './app.json';
|
|
723
|
+
|
|
724
|
+
AppRegistry.registerComponent(appName, () => App);
|
|
725
|
+
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## ios/.xcode.env
|
|
731
|
+
|
|
732
|
+
```env
|
|
733
|
+
# This `.xcode.env` file is versioned and is used to source the environment
|
|
734
|
+
# used when running script phases inside Xcode.
|
|
735
|
+
# To customize your local environment, you can create an `.xcode.env.local`
|
|
736
|
+
# file that is not versioned.
|
|
737
|
+
|
|
738
|
+
# NODE_BINARY variable contains the PATH to the node executable.
|
|
739
|
+
#
|
|
740
|
+
# Customize the NODE_BINARY variable here.
|
|
741
|
+
# For example, to use nvm with brew, add the following line
|
|
742
|
+
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
|
743
|
+
export NODE_BINARY=$(command -v node)
|
|
744
|
+
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## ios/BurritoApp.xcodeproj/xcshareddata/xcschemes/BurritoApp.xcscheme
|
|
750
|
+
|
|
751
|
+
```xcscheme
|
|
752
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
753
|
+
<Scheme
|
|
754
|
+
LastUpgradeVersion = "1210"
|
|
755
|
+
version = "1.3">
|
|
756
|
+
<BuildAction
|
|
757
|
+
parallelizeBuildables = "YES"
|
|
758
|
+
buildImplicitDependencies = "YES">
|
|
759
|
+
<BuildActionEntries>
|
|
760
|
+
<BuildActionEntry
|
|
761
|
+
buildForTesting = "YES"
|
|
762
|
+
buildForRunning = "YES"
|
|
763
|
+
buildForProfiling = "YES"
|
|
764
|
+
buildForArchiving = "YES"
|
|
765
|
+
buildForAnalyzing = "YES">
|
|
766
|
+
<BuildableReference
|
|
767
|
+
BuildableIdentifier = "primary"
|
|
768
|
+
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
769
|
+
BuildableName = "BurritoApp.app"
|
|
770
|
+
BlueprintName = "BurritoApp"
|
|
771
|
+
ReferencedContainer = "container:BurritoApp.xcodeproj">
|
|
772
|
+
</BuildableReference>
|
|
773
|
+
</BuildActionEntry>
|
|
774
|
+
</BuildActionEntries>
|
|
775
|
+
</BuildAction>
|
|
776
|
+
<TestAction
|
|
777
|
+
buildConfiguration = "Debug"
|
|
778
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
779
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
780
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
781
|
+
<Testables>
|
|
782
|
+
<TestableReference
|
|
783
|
+
skipped = "NO">
|
|
784
|
+
<BuildableReference
|
|
785
|
+
BuildableIdentifier = "primary"
|
|
786
|
+
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
|
787
|
+
BuildableName = "BurritoAppTests.xctest"
|
|
788
|
+
BlueprintName = "BurritoAppTests"
|
|
789
|
+
ReferencedContainer = "container:BurritoApp.xcodeproj">
|
|
790
|
+
</BuildableReference>
|
|
791
|
+
</TestableReference>
|
|
792
|
+
</Testables>
|
|
793
|
+
</TestAction>
|
|
794
|
+
<LaunchAction
|
|
795
|
+
buildConfiguration = "Debug"
|
|
796
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
797
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
798
|
+
launchStyle = "0"
|
|
799
|
+
useCustomWorkingDirectory = "NO"
|
|
800
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
801
|
+
debugDocumentVersioning = "YES"
|
|
802
|
+
debugServiceExtension = "internal"
|
|
803
|
+
allowLocationSimulation = "YES">
|
|
804
|
+
<BuildableProductRunnable
|
|
805
|
+
runnableDebuggingMode = "0">
|
|
806
|
+
<BuildableReference
|
|
807
|
+
BuildableIdentifier = "primary"
|
|
808
|
+
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
809
|
+
BuildableName = "BurritoApp.app"
|
|
810
|
+
BlueprintName = "BurritoApp"
|
|
811
|
+
ReferencedContainer = "container:BurritoApp.xcodeproj">
|
|
812
|
+
</BuildableReference>
|
|
813
|
+
</BuildableProductRunnable>
|
|
814
|
+
</LaunchAction>
|
|
815
|
+
<ProfileAction
|
|
816
|
+
buildConfiguration = "Release"
|
|
817
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
818
|
+
savedToolIdentifier = ""
|
|
819
|
+
useCustomWorkingDirectory = "NO"
|
|
820
|
+
debugDocumentVersioning = "YES">
|
|
821
|
+
<BuildableProductRunnable
|
|
822
|
+
runnableDebuggingMode = "0">
|
|
823
|
+
<BuildableReference
|
|
824
|
+
BuildableIdentifier = "primary"
|
|
825
|
+
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
826
|
+
BuildableName = "BurritoApp.app"
|
|
827
|
+
BlueprintName = "BurritoApp"
|
|
828
|
+
ReferencedContainer = "container:BurritoApp.xcodeproj">
|
|
829
|
+
</BuildableReference>
|
|
830
|
+
</BuildableProductRunnable>
|
|
831
|
+
</ProfileAction>
|
|
832
|
+
<AnalyzeAction
|
|
833
|
+
buildConfiguration = "Debug">
|
|
834
|
+
</AnalyzeAction>
|
|
835
|
+
<ArchiveAction
|
|
836
|
+
buildConfiguration = "Release"
|
|
837
|
+
revealArchiveInOrganizer = "YES">
|
|
838
|
+
</ArchiveAction>
|
|
839
|
+
</Scheme>
|
|
840
|
+
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## ios/BurritoApp.xcworkspace/contents.xcworkspacedata
|
|
846
|
+
|
|
847
|
+
```xcworkspacedata
|
|
848
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
849
|
+
<Workspace
|
|
850
|
+
version = "1.0">
|
|
851
|
+
<FileRef
|
|
852
|
+
location = "group:BurritoApp.xcodeproj">
|
|
853
|
+
</FileRef>
|
|
854
|
+
<FileRef
|
|
855
|
+
location = "group:Pods/Pods.xcodeproj">
|
|
856
|
+
</FileRef>
|
|
857
|
+
</Workspace>
|
|
858
|
+
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
## ios/BurritoApp/AppDelegate.swift
|
|
864
|
+
|
|
865
|
+
```swift
|
|
866
|
+
import UIKit
|
|
867
|
+
import React
|
|
868
|
+
import React_RCTAppDelegate
|
|
869
|
+
import ReactAppDependencyProvider
|
|
870
|
+
|
|
871
|
+
@main
|
|
872
|
+
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
873
|
+
var window: UIWindow?
|
|
874
|
+
|
|
875
|
+
var reactNativeDelegate: ReactNativeDelegate?
|
|
876
|
+
var reactNativeFactory: RCTReactNativeFactory?
|
|
877
|
+
|
|
878
|
+
func application(
|
|
879
|
+
_ application: UIApplication,
|
|
880
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
|
881
|
+
) -> Bool {
|
|
882
|
+
let delegate = ReactNativeDelegate()
|
|
883
|
+
let factory = RCTReactNativeFactory(delegate: delegate)
|
|
884
|
+
delegate.dependencyProvider = RCTAppDependencyProvider()
|
|
885
|
+
|
|
886
|
+
reactNativeDelegate = delegate
|
|
887
|
+
reactNativeFactory = factory
|
|
888
|
+
|
|
889
|
+
window = UIWindow(frame: UIScreen.main.bounds)
|
|
890
|
+
|
|
891
|
+
factory.startReactNative(
|
|
892
|
+
withModuleName: "BurritoApp",
|
|
893
|
+
in: window,
|
|
894
|
+
launchOptions: launchOptions
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
return true
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
902
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
|
903
|
+
self.bundleURL()
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
override func bundleURL() -> URL? {
|
|
907
|
+
#if DEBUG
|
|
908
|
+
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
909
|
+
#else
|
|
910
|
+
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
911
|
+
#endif
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
## ios/BurritoApp/Info.plist
|
|
920
|
+
|
|
921
|
+
```plist
|
|
922
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
923
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
924
|
+
<plist version="1.0">
|
|
925
|
+
<dict>
|
|
926
|
+
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
927
|
+
<true/>
|
|
928
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
929
|
+
<string>en</string>
|
|
930
|
+
<key>CFBundleDisplayName</key>
|
|
931
|
+
<string>BurritoApp</string>
|
|
932
|
+
<key>CFBundleExecutable</key>
|
|
933
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
934
|
+
<key>CFBundleIdentifier</key>
|
|
935
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
936
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
937
|
+
<string>6.0</string>
|
|
938
|
+
<key>CFBundleName</key>
|
|
939
|
+
<string>$(PRODUCT_NAME)</string>
|
|
940
|
+
<key>CFBundlePackageType</key>
|
|
941
|
+
<string>APPL</string>
|
|
942
|
+
<key>CFBundleShortVersionString</key>
|
|
943
|
+
<string>$(MARKETING_VERSION)</string>
|
|
944
|
+
<key>CFBundleSignature</key>
|
|
945
|
+
<string>????</string>
|
|
946
|
+
<key>CFBundleVersion</key>
|
|
947
|
+
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
948
|
+
<key>LSRequiresIPhoneOS</key>
|
|
949
|
+
<true/>
|
|
950
|
+
<key>NSAppTransportSecurity</key>
|
|
951
|
+
<dict>
|
|
952
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
953
|
+
<false/>
|
|
954
|
+
<key>NSAllowsLocalNetworking</key>
|
|
955
|
+
<true/>
|
|
956
|
+
</dict>
|
|
957
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
958
|
+
<string></string>
|
|
959
|
+
<key>RCTNewArchEnabled</key>
|
|
960
|
+
<true/>
|
|
961
|
+
<key>UILaunchStoryboardName</key>
|
|
962
|
+
<string>LaunchScreen</string>
|
|
963
|
+
<key>UIRequiredDeviceCapabilities</key>
|
|
964
|
+
<array>
|
|
965
|
+
<string>arm64</string>
|
|
966
|
+
</array>
|
|
967
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
968
|
+
<array>
|
|
969
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
970
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
971
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
972
|
+
</array>
|
|
973
|
+
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
974
|
+
<false/>
|
|
975
|
+
</dict>
|
|
976
|
+
</plist>
|
|
977
|
+
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
---
|
|
981
|
+
|
|
982
|
+
## ios/BurritoApp/LaunchScreen.storyboard
|
|
983
|
+
|
|
984
|
+
```storyboard
|
|
985
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
986
|
+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
|
987
|
+
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
|
988
|
+
<dependencies>
|
|
989
|
+
<deployment identifier="iOS"/>
|
|
990
|
+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
|
991
|
+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
992
|
+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
993
|
+
</dependencies>
|
|
994
|
+
<scenes>
|
|
995
|
+
<!--View Controller-->
|
|
996
|
+
<scene sceneID="EHf-IW-A2E">
|
|
997
|
+
<objects>
|
|
998
|
+
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
|
999
|
+
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
|
1000
|
+
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
1001
|
+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
1002
|
+
<subviews>
|
|
1003
|
+
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="BurritoApp" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
|
|
1004
|
+
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
|
|
1005
|
+
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
|
1006
|
+
<nil key="highlightedColor"/>
|
|
1007
|
+
</label>
|
|
1008
|
+
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
|
|
1009
|
+
<rect key="frame" x="0.0" y="626" width="375" height="21"/>
|
|
1010
|
+
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
|
1011
|
+
<nil key="highlightedColor"/>
|
|
1012
|
+
</label>
|
|
1013
|
+
</subviews>
|
|
1014
|
+
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
1015
|
+
<constraints>
|
|
1016
|
+
<constraint firstItem="Bcu-3y-fUS" firstAttribute="bottom" secondItem="MN2-I3-ftu" secondAttribute="bottom" constant="20" id="OZV-Vh-mqD"/>
|
|
1017
|
+
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
|
|
1018
|
+
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="akx-eg-2ui"/>
|
|
1019
|
+
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" id="i1E-0Y-4RG"/>
|
|
1020
|
+
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
|
|
1021
|
+
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
|
|
1022
|
+
</constraints>
|
|
1023
|
+
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
|
1024
|
+
</view>
|
|
1025
|
+
</viewController>
|
|
1026
|
+
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
1027
|
+
</objects>
|
|
1028
|
+
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
|
1029
|
+
</scene>
|
|
1030
|
+
</scenes>
|
|
1031
|
+
</document>
|
|
1032
|
+
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
## ios/BurritoApp/PrivacyInfo.xcprivacy
|
|
1038
|
+
|
|
1039
|
+
```xcprivacy
|
|
1040
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1041
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1042
|
+
<plist version="1.0">
|
|
1043
|
+
<dict>
|
|
1044
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
1045
|
+
<array>
|
|
1046
|
+
<dict>
|
|
1047
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
1048
|
+
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
1049
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
1050
|
+
<array>
|
|
1051
|
+
<string>C617.1</string>
|
|
1052
|
+
</array>
|
|
1053
|
+
</dict>
|
|
1054
|
+
<dict>
|
|
1055
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
1056
|
+
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
|
1057
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
1058
|
+
<array>
|
|
1059
|
+
<string>35F9.1</string>
|
|
1060
|
+
</array>
|
|
1061
|
+
</dict>
|
|
1062
|
+
<dict>
|
|
1063
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
1064
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
1065
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
1066
|
+
<array>
|
|
1067
|
+
<string>CA92.1</string>
|
|
1068
|
+
</array>
|
|
1069
|
+
</dict>
|
|
1070
|
+
<dict>
|
|
1071
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
1072
|
+
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
|
1073
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
1074
|
+
<array>
|
|
1075
|
+
<string>85F4.1</string>
|
|
1076
|
+
</array>
|
|
1077
|
+
</dict>
|
|
1078
|
+
</array>
|
|
1079
|
+
<key>NSPrivacyCollectedDataTypes</key>
|
|
1080
|
+
<array/>
|
|
1081
|
+
<key>NSPrivacyTracking</key>
|
|
1082
|
+
<false/>
|
|
1083
|
+
</dict>
|
|
1084
|
+
</plist>
|
|
1085
|
+
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
## ios/Podfile
|
|
1091
|
+
|
|
1092
|
+
```
|
|
1093
|
+
# Resolve react_native_pods.rb with node to allow for hoisting
|
|
1094
|
+
require Pod::Executable.execute_command('node', ['-p',
|
|
1095
|
+
'require.resolve(
|
|
1096
|
+
"react-native/scripts/react_native_pods.rb",
|
|
1097
|
+
{paths: [process.argv[1]]},
|
|
1098
|
+
)', __dir__]).strip
|
|
1099
|
+
|
|
1100
|
+
platform :ios, min_ios_version_supported
|
|
1101
|
+
prepare_react_native_project!
|
|
1102
|
+
|
|
1103
|
+
linkage = ENV['USE_FRAMEWORKS']
|
|
1104
|
+
if linkage != nil
|
|
1105
|
+
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
|
|
1106
|
+
use_frameworks! :linkage => linkage.to_sym
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
target 'BurritoApp' do
|
|
1110
|
+
config = use_native_modules!
|
|
1111
|
+
|
|
1112
|
+
use_react_native!(
|
|
1113
|
+
:path => config[:reactNativePath],
|
|
1114
|
+
# An absolute path to your application root.
|
|
1115
|
+
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
post_install do |installer|
|
|
1119
|
+
react_native_post_install(
|
|
1120
|
+
installer,
|
|
1121
|
+
config[:reactNativePath],
|
|
1122
|
+
:mac_catalyst_enabled => false,
|
|
1123
|
+
# :ccache_enabled => true
|
|
1124
|
+
)
|
|
1125
|
+
end
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
---
|
|
1131
|
+
|
|
1132
|
+
## jest.config.js
|
|
1133
|
+
|
|
1134
|
+
```js
|
|
1135
|
+
module.exports = {
|
|
1136
|
+
preset: 'react-native',
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
---
|
|
1142
|
+
|
|
1143
|
+
## metro.config.js
|
|
1144
|
+
|
|
1145
|
+
```js
|
|
1146
|
+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Metro configuration
|
|
1150
|
+
* https://reactnative.dev/docs/metro
|
|
1151
|
+
*
|
|
1152
|
+
* @type {import('@react-native/metro-config').MetroConfig}
|
|
1153
|
+
*/
|
|
1154
|
+
const config = {};
|
|
1155
|
+
|
|
1156
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
1157
|
+
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
## src/config/amplitude.ts
|
|
1163
|
+
|
|
1164
|
+
```ts
|
|
1165
|
+
import * as amplitude from '@amplitude/analytics-react-native'
|
|
1166
|
+
import Config from 'react-native-config'
|
|
1167
|
+
|
|
1168
|
+
// Environment variables are embedded at build time via react-native-config
|
|
1169
|
+
// Ensure .env file exists with AMPLITUDE_API_KEY
|
|
1170
|
+
const apiKey = Config.AMPLITUDE_API_KEY
|
|
1171
|
+
const isAmplitudeConfigured = apiKey && apiKey !== 'your_amplitude_api_key_here'
|
|
1172
|
+
|
|
1173
|
+
if (!isAmplitudeConfigured) {
|
|
1174
|
+
console.warn(
|
|
1175
|
+
'Amplitude API key not configured. Analytics will be disabled. ' +
|
|
1176
|
+
'Set AMPLITUDE_API_KEY in your .env file to enable analytics.'
|
|
1177
|
+
)
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (isAmplitudeConfigured && apiKey) {
|
|
1181
|
+
amplitude.init(apiKey)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export { amplitude }
|
|
1185
|
+
export const isAmplitudeEnabled = isAmplitudeConfigured
|
|
1186
|
+
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
## src/contexts/AuthContext.tsx
|
|
1192
|
+
|
|
1193
|
+
```tsx
|
|
1194
|
+
import React, {
|
|
1195
|
+
createContext,
|
|
1196
|
+
useContext,
|
|
1197
|
+
useState,
|
|
1198
|
+
useEffect,
|
|
1199
|
+
ReactNode,
|
|
1200
|
+
useCallback,
|
|
1201
|
+
} from 'react'
|
|
1202
|
+
import { Identify } from '@amplitude/analytics-react-native'
|
|
1203
|
+
import { amplitude } from '../config/amplitude'
|
|
1204
|
+
import { storage, User } from '../services/storage'
|
|
1205
|
+
|
|
1206
|
+
interface AuthContextType {
|
|
1207
|
+
user: User | null
|
|
1208
|
+
isLoading: boolean
|
|
1209
|
+
login: (username: string, password: string) => Promise<boolean>
|
|
1210
|
+
logout: () => Promise<void>
|
|
1211
|
+
incrementBurritoConsiderations: () => Promise<void>
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
|
1215
|
+
|
|
1216
|
+
interface AuthProviderProps {
|
|
1217
|
+
children: ReactNode
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Authentication Provider with Amplitude integration
|
|
1222
|
+
*
|
|
1223
|
+
* Manages user authentication state and integrates with Amplitude for:
|
|
1224
|
+
* - User identification (amplitude.setUserId + amplitude.identify)
|
|
1225
|
+
* - Login/logout event tracking
|
|
1226
|
+
* - Session reset on logout
|
|
1227
|
+
*/
|
|
1228
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
1229
|
+
const [user, setUser] = useState<User | null>(null)
|
|
1230
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
1231
|
+
|
|
1232
|
+
// Restore session on app launch
|
|
1233
|
+
useEffect(() => {
|
|
1234
|
+
restoreSession()
|
|
1235
|
+
}, [])
|
|
1236
|
+
|
|
1237
|
+
const restoreSession = async () => {
|
|
1238
|
+
try {
|
|
1239
|
+
const storedUsername = await storage.getCurrentUser()
|
|
1240
|
+
if (storedUsername) {
|
|
1241
|
+
const existingUser = await storage.getUser(storedUsername)
|
|
1242
|
+
if (existingUser) {
|
|
1243
|
+
setUser(existingUser)
|
|
1244
|
+
|
|
1245
|
+
// Re-identify user in Amplitude on session restore
|
|
1246
|
+
amplitude.setUserId(storedUsername)
|
|
1247
|
+
const identifyObj = new Identify()
|
|
1248
|
+
identifyObj.set('username', storedUsername)
|
|
1249
|
+
amplitude.identify(identifyObj)
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
console.error('Failed to restore session:', error)
|
|
1254
|
+
} finally {
|
|
1255
|
+
setIsLoading(false)
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
const login = useCallback(
|
|
1260
|
+
async (username: string, password: string): Promise<boolean> => {
|
|
1261
|
+
// Simple validation (demo app accepts any username/password)
|
|
1262
|
+
if (!username.trim() || !password.trim()) {
|
|
1263
|
+
return false
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
try {
|
|
1267
|
+
// Check if user exists or create new
|
|
1268
|
+
const existingUser = await storage.getUser(username)
|
|
1269
|
+
const isNewUser = !existingUser
|
|
1270
|
+
|
|
1271
|
+
const userData: User = existingUser || {
|
|
1272
|
+
username,
|
|
1273
|
+
burritoConsiderations: 0,
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Save user data
|
|
1277
|
+
await storage.saveUser(userData)
|
|
1278
|
+
await storage.setCurrentUser(username)
|
|
1279
|
+
setUser(userData)
|
|
1280
|
+
|
|
1281
|
+
// Identify user in Amplitude using username as user ID
|
|
1282
|
+
amplitude.setUserId(username)
|
|
1283
|
+
const identifyObj = new Identify()
|
|
1284
|
+
identifyObj.set('username', username)
|
|
1285
|
+
amplitude.identify(identifyObj)
|
|
1286
|
+
|
|
1287
|
+
// Track login event with properties
|
|
1288
|
+
amplitude.track('User Logged In', {
|
|
1289
|
+
username,
|
|
1290
|
+
is_new_user: isNewUser,
|
|
1291
|
+
})
|
|
1292
|
+
|
|
1293
|
+
return true
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
console.error('Login error:', error)
|
|
1296
|
+
return false
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
[],
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
const logout = useCallback(async () => {
|
|
1303
|
+
// Track logout event before reset
|
|
1304
|
+
amplitude.track('User Logged Out')
|
|
1305
|
+
|
|
1306
|
+
// Reset Amplitude - clears the current user's identity
|
|
1307
|
+
amplitude.reset()
|
|
1308
|
+
|
|
1309
|
+
await storage.removeCurrentUser()
|
|
1310
|
+
setUser(null)
|
|
1311
|
+
}, [])
|
|
1312
|
+
|
|
1313
|
+
const incrementBurritoConsiderations = useCallback(async () => {
|
|
1314
|
+
if (user) {
|
|
1315
|
+
const updatedUser: User = {
|
|
1316
|
+
...user,
|
|
1317
|
+
burritoConsiderations: user.burritoConsiderations + 1,
|
|
1318
|
+
}
|
|
1319
|
+
setUser(updatedUser)
|
|
1320
|
+
await storage.saveUser(updatedUser)
|
|
1321
|
+
}
|
|
1322
|
+
}, [user])
|
|
1323
|
+
|
|
1324
|
+
return (
|
|
1325
|
+
<AuthContext.Provider
|
|
1326
|
+
value={{
|
|
1327
|
+
user,
|
|
1328
|
+
isLoading,
|
|
1329
|
+
login,
|
|
1330
|
+
logout,
|
|
1331
|
+
incrementBurritoConsiderations,
|
|
1332
|
+
}}
|
|
1333
|
+
>
|
|
1334
|
+
{children}
|
|
1335
|
+
</AuthContext.Provider>
|
|
1336
|
+
)
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
export function useAuth() {
|
|
1340
|
+
const context = useContext(AuthContext)
|
|
1341
|
+
if (context === undefined) {
|
|
1342
|
+
throw new Error('useAuth must be used within an AuthProvider')
|
|
1343
|
+
}
|
|
1344
|
+
return context
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
---
|
|
1350
|
+
|
|
1351
|
+
## src/navigation/RootNavigator.tsx
|
|
1352
|
+
|
|
1353
|
+
```tsx
|
|
1354
|
+
import React from 'react'
|
|
1355
|
+
import { ActivityIndicator, View, StyleSheet } from 'react-native'
|
|
1356
|
+
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
|
1357
|
+
import { useAuth } from '../contexts/AuthContext'
|
|
1358
|
+
import { colors } from '../styles/theme'
|
|
1359
|
+
|
|
1360
|
+
import HomeScreen from '../screens/HomeScreen'
|
|
1361
|
+
import BurritoScreen from '../screens/BurritoScreen'
|
|
1362
|
+
import ProfileScreen from '../screens/ProfileScreen'
|
|
1363
|
+
|
|
1364
|
+
// Type definitions for navigation
|
|
1365
|
+
export type RootStackParamList = {
|
|
1366
|
+
Home: undefined
|
|
1367
|
+
Burrito: undefined
|
|
1368
|
+
Profile: undefined
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const Stack = createNativeStackNavigator<RootStackParamList>()
|
|
1372
|
+
|
|
1373
|
+
export function RootNavigator() {
|
|
1374
|
+
const { isLoading } = useAuth()
|
|
1375
|
+
|
|
1376
|
+
// Show loading indicator while restoring session
|
|
1377
|
+
if (isLoading) {
|
|
1378
|
+
return (
|
|
1379
|
+
<View style={styles.loadingContainer}>
|
|
1380
|
+
<ActivityIndicator size="large" color={colors.primary} />
|
|
1381
|
+
</View>
|
|
1382
|
+
)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
return (
|
|
1386
|
+
<Stack.Navigator
|
|
1387
|
+
screenOptions={{
|
|
1388
|
+
headerStyle: {
|
|
1389
|
+
backgroundColor: colors.headerBackground,
|
|
1390
|
+
},
|
|
1391
|
+
headerTintColor: colors.headerText,
|
|
1392
|
+
headerTitleStyle: {
|
|
1393
|
+
fontWeight: 'bold',
|
|
1394
|
+
},
|
|
1395
|
+
headerBackTitleVisible: false,
|
|
1396
|
+
animation: 'slide_from_right',
|
|
1397
|
+
}}
|
|
1398
|
+
>
|
|
1399
|
+
<Stack.Screen
|
|
1400
|
+
name="Home"
|
|
1401
|
+
component={HomeScreen}
|
|
1402
|
+
options={{
|
|
1403
|
+
title: 'Burrito App',
|
|
1404
|
+
}}
|
|
1405
|
+
/>
|
|
1406
|
+
<Stack.Screen
|
|
1407
|
+
name="Burrito"
|
|
1408
|
+
component={BurritoScreen}
|
|
1409
|
+
options={{
|
|
1410
|
+
title: 'Burrito Consideration',
|
|
1411
|
+
}}
|
|
1412
|
+
/>
|
|
1413
|
+
<Stack.Screen
|
|
1414
|
+
name="Profile"
|
|
1415
|
+
component={ProfileScreen}
|
|
1416
|
+
options={{
|
|
1417
|
+
title: 'Profile',
|
|
1418
|
+
}}
|
|
1419
|
+
/>
|
|
1420
|
+
</Stack.Navigator>
|
|
1421
|
+
)
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const styles = StyleSheet.create({
|
|
1425
|
+
loadingContainer: {
|
|
1426
|
+
flex: 1,
|
|
1427
|
+
justifyContent: 'center',
|
|
1428
|
+
alignItems: 'center',
|
|
1429
|
+
backgroundColor: colors.background,
|
|
1430
|
+
},
|
|
1431
|
+
})
|
|
1432
|
+
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
## src/screens/BurritoScreen.tsx
|
|
1438
|
+
|
|
1439
|
+
```tsx
|
|
1440
|
+
import React, { useState, useEffect } from 'react'
|
|
1441
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
|
1442
|
+
import { useNavigation } from '@react-navigation/native'
|
|
1443
|
+
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
1444
|
+
import { amplitude } from '../config/amplitude'
|
|
1445
|
+
import { useAuth } from '../contexts/AuthContext'
|
|
1446
|
+
import { RootStackParamList } from '../navigation/RootNavigator'
|
|
1447
|
+
import {
|
|
1448
|
+
colors,
|
|
1449
|
+
spacing,
|
|
1450
|
+
typography,
|
|
1451
|
+
borderRadius,
|
|
1452
|
+
shadows,
|
|
1453
|
+
} from '../styles/theme'
|
|
1454
|
+
|
|
1455
|
+
type BurritoScreenNavigationProp = NativeStackNavigationProp<
|
|
1456
|
+
RootStackParamList,
|
|
1457
|
+
'Burrito'
|
|
1458
|
+
>
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Burrito Consideration Screen
|
|
1462
|
+
*
|
|
1463
|
+
* Demonstrates Amplitude event tracking with custom properties.
|
|
1464
|
+
* Each time the user considers a burrito, an event is tracked.
|
|
1465
|
+
*/
|
|
1466
|
+
export default function BurritoScreen() {
|
|
1467
|
+
const { user, incrementBurritoConsiderations } = useAuth()
|
|
1468
|
+
const navigation = useNavigation<BurritoScreenNavigationProp>()
|
|
1469
|
+
const [hasConsidered, setHasConsidered] = useState(false)
|
|
1470
|
+
|
|
1471
|
+
// Redirect to home if not logged in
|
|
1472
|
+
useEffect(() => {
|
|
1473
|
+
if (!user) {
|
|
1474
|
+
navigation.navigate('Home')
|
|
1475
|
+
}
|
|
1476
|
+
}, [user, navigation])
|
|
1477
|
+
|
|
1478
|
+
if (!user) {
|
|
1479
|
+
return null
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const handleConsideration = async () => {
|
|
1483
|
+
const newCount = user.burritoConsiderations + 1
|
|
1484
|
+
|
|
1485
|
+
// Update state first for immediate feedback
|
|
1486
|
+
await incrementBurritoConsiderations()
|
|
1487
|
+
setHasConsidered(true)
|
|
1488
|
+
|
|
1489
|
+
// Hide success message after 2 seconds
|
|
1490
|
+
setTimeout(() => setHasConsidered(false), 2000)
|
|
1491
|
+
|
|
1492
|
+
// Track custom event in Amplitude with properties
|
|
1493
|
+
amplitude.track('Burrito Considered', {
|
|
1494
|
+
total_considerations: newCount,
|
|
1495
|
+
username: user.username,
|
|
1496
|
+
})
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
return (
|
|
1500
|
+
<View style={styles.container}>
|
|
1501
|
+
<View style={styles.card}>
|
|
1502
|
+
<Text style={styles.title}>Burrito Consideration Zone</Text>
|
|
1503
|
+
<Text style={styles.text}>
|
|
1504
|
+
Take a moment to truly consider the potential of burritos.
|
|
1505
|
+
</Text>
|
|
1506
|
+
|
|
1507
|
+
<TouchableOpacity
|
|
1508
|
+
style={styles.burritoButton}
|
|
1509
|
+
onPress={handleConsideration}
|
|
1510
|
+
activeOpacity={0.8}
|
|
1511
|
+
testID="consider-burrito-button"
|
|
1512
|
+
>
|
|
1513
|
+
<Text style={styles.burritoButtonText}>Consider Burrito</Text>
|
|
1514
|
+
</TouchableOpacity>
|
|
1515
|
+
|
|
1516
|
+
{hasConsidered && (
|
|
1517
|
+
<View style={styles.successContainer}>
|
|
1518
|
+
<Text style={styles.success}>
|
|
1519
|
+
Thank you for your consideration!
|
|
1520
|
+
</Text>
|
|
1521
|
+
<Text style={styles.successCount}>
|
|
1522
|
+
Count: {user.burritoConsiderations}
|
|
1523
|
+
</Text>
|
|
1524
|
+
</View>
|
|
1525
|
+
)}
|
|
1526
|
+
|
|
1527
|
+
<View style={styles.stats}>
|
|
1528
|
+
<Text style={styles.statsTitle}>Consideration Stats</Text>
|
|
1529
|
+
<Text style={styles.statsText}>
|
|
1530
|
+
Total considerations: {user.burritoConsiderations}
|
|
1531
|
+
</Text>
|
|
1532
|
+
</View>
|
|
1533
|
+
</View>
|
|
1534
|
+
</View>
|
|
1535
|
+
)
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const styles = StyleSheet.create({
|
|
1539
|
+
container: {
|
|
1540
|
+
flex: 1,
|
|
1541
|
+
backgroundColor: colors.background,
|
|
1542
|
+
padding: spacing.md,
|
|
1543
|
+
},
|
|
1544
|
+
card: {
|
|
1545
|
+
backgroundColor: colors.cardBackground,
|
|
1546
|
+
borderRadius: borderRadius.md,
|
|
1547
|
+
padding: spacing.lg,
|
|
1548
|
+
...shadows.md,
|
|
1549
|
+
},
|
|
1550
|
+
title: {
|
|
1551
|
+
fontSize: typography.sizes.xl,
|
|
1552
|
+
fontWeight: typography.weights.bold,
|
|
1553
|
+
color: colors.text,
|
|
1554
|
+
marginBottom: spacing.sm,
|
|
1555
|
+
},
|
|
1556
|
+
text: {
|
|
1557
|
+
fontSize: typography.sizes.md,
|
|
1558
|
+
color: colors.text,
|
|
1559
|
+
marginBottom: spacing.lg,
|
|
1560
|
+
lineHeight: 24,
|
|
1561
|
+
},
|
|
1562
|
+
burritoButton: {
|
|
1563
|
+
backgroundColor: colors.burrito,
|
|
1564
|
+
borderRadius: borderRadius.sm,
|
|
1565
|
+
padding: spacing.lg,
|
|
1566
|
+
alignItems: 'center',
|
|
1567
|
+
marginVertical: spacing.md,
|
|
1568
|
+
...shadows.sm,
|
|
1569
|
+
},
|
|
1570
|
+
burritoButtonText: {
|
|
1571
|
+
color: colors.white,
|
|
1572
|
+
fontSize: typography.sizes.lg,
|
|
1573
|
+
fontWeight: typography.weights.bold,
|
|
1574
|
+
},
|
|
1575
|
+
successContainer: {
|
|
1576
|
+
alignItems: 'center',
|
|
1577
|
+
marginVertical: spacing.sm,
|
|
1578
|
+
},
|
|
1579
|
+
success: {
|
|
1580
|
+
color: colors.success,
|
|
1581
|
+
fontSize: typography.sizes.md,
|
|
1582
|
+
fontWeight: typography.weights.medium,
|
|
1583
|
+
},
|
|
1584
|
+
successCount: {
|
|
1585
|
+
color: colors.success,
|
|
1586
|
+
fontSize: typography.sizes.lg,
|
|
1587
|
+
fontWeight: typography.weights.bold,
|
|
1588
|
+
marginTop: spacing.xs,
|
|
1589
|
+
},
|
|
1590
|
+
stats: {
|
|
1591
|
+
backgroundColor: colors.statsBackground,
|
|
1592
|
+
padding: spacing.md,
|
|
1593
|
+
borderRadius: borderRadius.sm,
|
|
1594
|
+
marginTop: spacing.lg,
|
|
1595
|
+
},
|
|
1596
|
+
statsTitle: {
|
|
1597
|
+
fontSize: typography.sizes.lg,
|
|
1598
|
+
fontWeight: typography.weights.semibold,
|
|
1599
|
+
color: colors.text,
|
|
1600
|
+
marginBottom: spacing.xs,
|
|
1601
|
+
},
|
|
1602
|
+
statsText: {
|
|
1603
|
+
fontSize: typography.sizes.md,
|
|
1604
|
+
color: colors.text,
|
|
1605
|
+
},
|
|
1606
|
+
})
|
|
1607
|
+
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
---
|
|
1611
|
+
|
|
1612
|
+
## src/screens/HomeScreen.tsx
|
|
1613
|
+
|
|
1614
|
+
```tsx
|
|
1615
|
+
import React, { useState } from 'react'
|
|
1616
|
+
import {
|
|
1617
|
+
View,
|
|
1618
|
+
Text,
|
|
1619
|
+
TextInput,
|
|
1620
|
+
TouchableOpacity,
|
|
1621
|
+
StyleSheet,
|
|
1622
|
+
ScrollView,
|
|
1623
|
+
KeyboardAvoidingView,
|
|
1624
|
+
Platform,
|
|
1625
|
+
} from 'react-native'
|
|
1626
|
+
import { useNavigation } from '@react-navigation/native'
|
|
1627
|
+
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
1628
|
+
import { useAuth } from '../contexts/AuthContext'
|
|
1629
|
+
import { RootStackParamList } from '../navigation/RootNavigator'
|
|
1630
|
+
import {
|
|
1631
|
+
colors,
|
|
1632
|
+
spacing,
|
|
1633
|
+
typography,
|
|
1634
|
+
borderRadius,
|
|
1635
|
+
shadows,
|
|
1636
|
+
} from '../styles/theme'
|
|
1637
|
+
|
|
1638
|
+
type HomeScreenNavigationProp = NativeStackNavigationProp<
|
|
1639
|
+
RootStackParamList,
|
|
1640
|
+
'Home'
|
|
1641
|
+
>
|
|
1642
|
+
|
|
1643
|
+
export default function HomeScreen() {
|
|
1644
|
+
const { user, login, logout } = useAuth()
|
|
1645
|
+
const navigation = useNavigation<HomeScreenNavigationProp>()
|
|
1646
|
+
const [username, setUsername] = useState('')
|
|
1647
|
+
const [password, setPassword] = useState('')
|
|
1648
|
+
const [error, setError] = useState('')
|
|
1649
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
1650
|
+
|
|
1651
|
+
const handleSubmit = async () => {
|
|
1652
|
+
setError('')
|
|
1653
|
+
|
|
1654
|
+
if (!username.trim() || !password.trim()) {
|
|
1655
|
+
setError('Please provide both username and password')
|
|
1656
|
+
return
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
setIsSubmitting(true)
|
|
1660
|
+
try {
|
|
1661
|
+
const success = await login(username, password)
|
|
1662
|
+
if (success) {
|
|
1663
|
+
setUsername('')
|
|
1664
|
+
setPassword('')
|
|
1665
|
+
} else {
|
|
1666
|
+
setError('An error occurred during login')
|
|
1667
|
+
}
|
|
1668
|
+
} catch {
|
|
1669
|
+
setError('An error occurred during login')
|
|
1670
|
+
} finally {
|
|
1671
|
+
setIsSubmitting(false)
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
// Logged in view
|
|
1676
|
+
if (user) {
|
|
1677
|
+
return (
|
|
1678
|
+
<ScrollView
|
|
1679
|
+
style={styles.scrollView}
|
|
1680
|
+
contentContainerStyle={styles.scrollContent}
|
|
1681
|
+
>
|
|
1682
|
+
<View style={styles.card}>
|
|
1683
|
+
<Text style={styles.title}>Welcome back, {user.username}!</Text>
|
|
1684
|
+
<Text style={styles.text}>
|
|
1685
|
+
You are now logged in. Feel free to explore:
|
|
1686
|
+
</Text>
|
|
1687
|
+
|
|
1688
|
+
<View style={styles.buttonGroup}>
|
|
1689
|
+
<TouchableOpacity
|
|
1690
|
+
style={[styles.button, styles.burritoButton]}
|
|
1691
|
+
onPress={() => navigation.navigate('Burrito')}
|
|
1692
|
+
activeOpacity={0.8}
|
|
1693
|
+
>
|
|
1694
|
+
<Text style={styles.buttonText}>Consider Burritos</Text>
|
|
1695
|
+
</TouchableOpacity>
|
|
1696
|
+
|
|
1697
|
+
<TouchableOpacity
|
|
1698
|
+
style={[styles.button, styles.primaryButton]}
|
|
1699
|
+
onPress={() => navigation.navigate('Profile')}
|
|
1700
|
+
activeOpacity={0.8}
|
|
1701
|
+
>
|
|
1702
|
+
<Text style={styles.buttonText}>View Profile</Text>
|
|
1703
|
+
</TouchableOpacity>
|
|
1704
|
+
|
|
1705
|
+
<TouchableOpacity
|
|
1706
|
+
style={[styles.button, styles.logoutButton]}
|
|
1707
|
+
onPress={logout}
|
|
1708
|
+
activeOpacity={0.8}
|
|
1709
|
+
>
|
|
1710
|
+
<Text style={styles.buttonText}>Logout</Text>
|
|
1711
|
+
</TouchableOpacity>
|
|
1712
|
+
</View>
|
|
1713
|
+
</View>
|
|
1714
|
+
</ScrollView>
|
|
1715
|
+
)
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Login view
|
|
1719
|
+
return (
|
|
1720
|
+
<KeyboardAvoidingView
|
|
1721
|
+
style={styles.container}
|
|
1722
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
1723
|
+
>
|
|
1724
|
+
<ScrollView
|
|
1725
|
+
style={styles.scrollView}
|
|
1726
|
+
contentContainerStyle={styles.scrollContent}
|
|
1727
|
+
keyboardShouldPersistTaps="handled"
|
|
1728
|
+
>
|
|
1729
|
+
<View style={styles.card}>
|
|
1730
|
+
<Text style={styles.title}>Welcome to Burrito Consideration App</Text>
|
|
1731
|
+
<Text style={styles.text}>
|
|
1732
|
+
Please sign in to begin your burrito journey
|
|
1733
|
+
</Text>
|
|
1734
|
+
|
|
1735
|
+
<View style={styles.form}>
|
|
1736
|
+
<Text style={styles.label}>Username:</Text>
|
|
1737
|
+
<TextInput
|
|
1738
|
+
style={styles.input}
|
|
1739
|
+
value={username}
|
|
1740
|
+
onChangeText={setUsername}
|
|
1741
|
+
placeholder="Enter any username"
|
|
1742
|
+
placeholderTextColor={colors.textLight}
|
|
1743
|
+
autoCapitalize="none"
|
|
1744
|
+
autoCorrect={false}
|
|
1745
|
+
autoComplete="username"
|
|
1746
|
+
editable={!isSubmitting}
|
|
1747
|
+
/>
|
|
1748
|
+
|
|
1749
|
+
<Text style={styles.label}>Password:</Text>
|
|
1750
|
+
<TextInput
|
|
1751
|
+
style={styles.input}
|
|
1752
|
+
value={password}
|
|
1753
|
+
onChangeText={setPassword}
|
|
1754
|
+
placeholder="Enter any password"
|
|
1755
|
+
placeholderTextColor={colors.textLight}
|
|
1756
|
+
secureTextEntry
|
|
1757
|
+
autoComplete="password"
|
|
1758
|
+
editable={!isSubmitting}
|
|
1759
|
+
/>
|
|
1760
|
+
|
|
1761
|
+
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
1762
|
+
|
|
1763
|
+
<TouchableOpacity
|
|
1764
|
+
style={[
|
|
1765
|
+
styles.button,
|
|
1766
|
+
styles.primaryButton,
|
|
1767
|
+
isSubmitting && styles.buttonDisabled,
|
|
1768
|
+
]}
|
|
1769
|
+
onPress={handleSubmit}
|
|
1770
|
+
disabled={isSubmitting}
|
|
1771
|
+
activeOpacity={0.8}
|
|
1772
|
+
>
|
|
1773
|
+
<Text style={styles.buttonText}>
|
|
1774
|
+
{isSubmitting ? 'Signing In...' : 'Sign In'}
|
|
1775
|
+
</Text>
|
|
1776
|
+
</TouchableOpacity>
|
|
1777
|
+
</View>
|
|
1778
|
+
|
|
1779
|
+
<Text style={styles.note}>
|
|
1780
|
+
Note: This is a demo app. Use any username and password to sign in.
|
|
1781
|
+
</Text>
|
|
1782
|
+
</View>
|
|
1783
|
+
</ScrollView>
|
|
1784
|
+
</KeyboardAvoidingView>
|
|
1785
|
+
)
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
const styles = StyleSheet.create({
|
|
1789
|
+
container: {
|
|
1790
|
+
flex: 1,
|
|
1791
|
+
backgroundColor: colors.background,
|
|
1792
|
+
},
|
|
1793
|
+
scrollView: {
|
|
1794
|
+
flex: 1,
|
|
1795
|
+
backgroundColor: colors.background,
|
|
1796
|
+
},
|
|
1797
|
+
scrollContent: {
|
|
1798
|
+
flexGrow: 1,
|
|
1799
|
+
padding: spacing.md,
|
|
1800
|
+
justifyContent: 'center',
|
|
1801
|
+
},
|
|
1802
|
+
card: {
|
|
1803
|
+
backgroundColor: colors.cardBackground,
|
|
1804
|
+
borderRadius: borderRadius.md,
|
|
1805
|
+
padding: spacing.lg,
|
|
1806
|
+
...shadows.md,
|
|
1807
|
+
},
|
|
1808
|
+
title: {
|
|
1809
|
+
fontSize: typography.sizes.xl,
|
|
1810
|
+
fontWeight: typography.weights.bold,
|
|
1811
|
+
color: colors.text,
|
|
1812
|
+
marginBottom: spacing.sm,
|
|
1813
|
+
},
|
|
1814
|
+
text: {
|
|
1815
|
+
fontSize: typography.sizes.md,
|
|
1816
|
+
color: colors.text,
|
|
1817
|
+
marginBottom: spacing.md,
|
|
1818
|
+
lineHeight: 24,
|
|
1819
|
+
},
|
|
1820
|
+
form: {
|
|
1821
|
+
marginTop: spacing.md,
|
|
1822
|
+
},
|
|
1823
|
+
label: {
|
|
1824
|
+
fontSize: typography.sizes.md,
|
|
1825
|
+
fontWeight: typography.weights.medium,
|
|
1826
|
+
color: colors.text,
|
|
1827
|
+
marginBottom: spacing.xs,
|
|
1828
|
+
},
|
|
1829
|
+
input: {
|
|
1830
|
+
backgroundColor: colors.inputBackground,
|
|
1831
|
+
borderWidth: 1,
|
|
1832
|
+
borderColor: colors.border,
|
|
1833
|
+
borderRadius: borderRadius.sm,
|
|
1834
|
+
padding: spacing.sm,
|
|
1835
|
+
fontSize: typography.sizes.md,
|
|
1836
|
+
color: colors.text,
|
|
1837
|
+
marginBottom: spacing.md,
|
|
1838
|
+
},
|
|
1839
|
+
buttonGroup: {
|
|
1840
|
+
marginTop: spacing.md,
|
|
1841
|
+
gap: spacing.sm,
|
|
1842
|
+
},
|
|
1843
|
+
button: {
|
|
1844
|
+
borderRadius: borderRadius.sm,
|
|
1845
|
+
padding: spacing.md,
|
|
1846
|
+
alignItems: 'center',
|
|
1847
|
+
marginTop: spacing.sm,
|
|
1848
|
+
},
|
|
1849
|
+
primaryButton: {
|
|
1850
|
+
backgroundColor: colors.primary,
|
|
1851
|
+
},
|
|
1852
|
+
burritoButton: {
|
|
1853
|
+
backgroundColor: colors.burrito,
|
|
1854
|
+
},
|
|
1855
|
+
logoutButton: {
|
|
1856
|
+
backgroundColor: colors.danger,
|
|
1857
|
+
},
|
|
1858
|
+
buttonDisabled: {
|
|
1859
|
+
opacity: 0.6,
|
|
1860
|
+
},
|
|
1861
|
+
buttonText: {
|
|
1862
|
+
color: colors.white,
|
|
1863
|
+
fontSize: typography.sizes.md,
|
|
1864
|
+
fontWeight: typography.weights.semibold,
|
|
1865
|
+
},
|
|
1866
|
+
error: {
|
|
1867
|
+
color: colors.danger,
|
|
1868
|
+
marginBottom: spacing.sm,
|
|
1869
|
+
fontSize: typography.sizes.sm,
|
|
1870
|
+
},
|
|
1871
|
+
note: {
|
|
1872
|
+
marginTop: spacing.lg,
|
|
1873
|
+
color: colors.textSecondary,
|
|
1874
|
+
fontSize: typography.sizes.sm,
|
|
1875
|
+
textAlign: 'center',
|
|
1876
|
+
lineHeight: 20,
|
|
1877
|
+
},
|
|
1878
|
+
})
|
|
1879
|
+
|
|
1880
|
+
```
|
|
1881
|
+
|
|
1882
|
+
---
|
|
1883
|
+
|
|
1884
|
+
## src/screens/ProfileScreen.tsx
|
|
1885
|
+
|
|
1886
|
+
```tsx
|
|
1887
|
+
import React, { useEffect } from 'react'
|
|
1888
|
+
import { View, Text, StyleSheet } from 'react-native'
|
|
1889
|
+
import { useNavigation } from '@react-navigation/native'
|
|
1890
|
+
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
|
1891
|
+
import { useAuth } from '../contexts/AuthContext'
|
|
1892
|
+
import { RootStackParamList } from '../navigation/RootNavigator'
|
|
1893
|
+
import {
|
|
1894
|
+
colors,
|
|
1895
|
+
spacing,
|
|
1896
|
+
typography,
|
|
1897
|
+
borderRadius,
|
|
1898
|
+
shadows,
|
|
1899
|
+
} from '../styles/theme'
|
|
1900
|
+
|
|
1901
|
+
type ProfileScreenNavigationProp = NativeStackNavigationProp<
|
|
1902
|
+
RootStackParamList,
|
|
1903
|
+
'Profile'
|
|
1904
|
+
>
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* Profile Screen
|
|
1908
|
+
*
|
|
1909
|
+
* Displays user information and burrito journey stats.
|
|
1910
|
+
*/
|
|
1911
|
+
export default function ProfileScreen() {
|
|
1912
|
+
const { user } = useAuth()
|
|
1913
|
+
const navigation = useNavigation<ProfileScreenNavigationProp>()
|
|
1914
|
+
|
|
1915
|
+
// Redirect to home if not logged in
|
|
1916
|
+
useEffect(() => {
|
|
1917
|
+
if (!user) {
|
|
1918
|
+
navigation.navigate('Home')
|
|
1919
|
+
}
|
|
1920
|
+
}, [user, navigation])
|
|
1921
|
+
|
|
1922
|
+
if (!user) {
|
|
1923
|
+
return null
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
const getJourneyMessage = () => {
|
|
1927
|
+
const count = user.burritoConsiderations
|
|
1928
|
+
if (count === 0) {
|
|
1929
|
+
return "You haven't considered any burritos yet. Visit the Burrito Consideration page to start!"
|
|
1930
|
+
} else if (count === 1) {
|
|
1931
|
+
return "You've considered the burrito potential once. Keep going!"
|
|
1932
|
+
} else if (count < 5) {
|
|
1933
|
+
return "You're getting the hang of burrito consideration!"
|
|
1934
|
+
} else if (count < 10) {
|
|
1935
|
+
return "You're becoming a burrito consideration expert!"
|
|
1936
|
+
} else {
|
|
1937
|
+
return 'You are a true burrito consideration master!'
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
return (
|
|
1942
|
+
<View style={styles.container}>
|
|
1943
|
+
<View style={styles.card}>
|
|
1944
|
+
<Text style={styles.title}>User Profile</Text>
|
|
1945
|
+
|
|
1946
|
+
<View style={styles.stats}>
|
|
1947
|
+
<Text style={styles.statsTitle}>Your Information</Text>
|
|
1948
|
+
<View style={styles.infoRow}>
|
|
1949
|
+
<Text style={styles.infoLabel}>Username:</Text>
|
|
1950
|
+
<Text style={styles.infoValue}>{user.username}</Text>
|
|
1951
|
+
</View>
|
|
1952
|
+
<View style={styles.infoRow}>
|
|
1953
|
+
<Text style={styles.infoLabel}>Burrito Considerations:</Text>
|
|
1954
|
+
<Text style={styles.infoValue}>{user.burritoConsiderations}</Text>
|
|
1955
|
+
</View>
|
|
1956
|
+
</View>
|
|
1957
|
+
|
|
1958
|
+
<View style={styles.journey}>
|
|
1959
|
+
<Text style={styles.journeyTitle}>Your Burrito Journey</Text>
|
|
1960
|
+
<Text style={styles.journeyText}>{getJourneyMessage()}</Text>
|
|
1961
|
+
</View>
|
|
1962
|
+
</View>
|
|
1963
|
+
</View>
|
|
1964
|
+
)
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
const styles = StyleSheet.create({
|
|
1968
|
+
container: {
|
|
1969
|
+
flex: 1,
|
|
1970
|
+
backgroundColor: colors.background,
|
|
1971
|
+
padding: spacing.md,
|
|
1972
|
+
},
|
|
1973
|
+
card: {
|
|
1974
|
+
backgroundColor: colors.cardBackground,
|
|
1975
|
+
borderRadius: borderRadius.md,
|
|
1976
|
+
padding: spacing.lg,
|
|
1977
|
+
...shadows.md,
|
|
1978
|
+
},
|
|
1979
|
+
title: {
|
|
1980
|
+
fontSize: typography.sizes.xl,
|
|
1981
|
+
fontWeight: typography.weights.bold,
|
|
1982
|
+
color: colors.text,
|
|
1983
|
+
marginBottom: spacing.md,
|
|
1984
|
+
},
|
|
1985
|
+
stats: {
|
|
1986
|
+
backgroundColor: colors.statsBackground,
|
|
1987
|
+
padding: spacing.md,
|
|
1988
|
+
borderRadius: borderRadius.sm,
|
|
1989
|
+
},
|
|
1990
|
+
statsTitle: {
|
|
1991
|
+
fontSize: typography.sizes.lg,
|
|
1992
|
+
fontWeight: typography.weights.semibold,
|
|
1993
|
+
color: colors.text,
|
|
1994
|
+
marginBottom: spacing.sm,
|
|
1995
|
+
},
|
|
1996
|
+
infoRow: {
|
|
1997
|
+
flexDirection: 'row',
|
|
1998
|
+
marginBottom: spacing.xs,
|
|
1999
|
+
},
|
|
2000
|
+
infoLabel: {
|
|
2001
|
+
fontSize: typography.sizes.md,
|
|
2002
|
+
fontWeight: typography.weights.bold,
|
|
2003
|
+
color: colors.text,
|
|
2004
|
+
marginRight: spacing.xs,
|
|
2005
|
+
},
|
|
2006
|
+
infoValue: {
|
|
2007
|
+
fontSize: typography.sizes.md,
|
|
2008
|
+
color: colors.text,
|
|
2009
|
+
},
|
|
2010
|
+
journey: {
|
|
2011
|
+
marginTop: spacing.lg,
|
|
2012
|
+
},
|
|
2013
|
+
journeyTitle: {
|
|
2014
|
+
fontSize: typography.sizes.lg,
|
|
2015
|
+
fontWeight: typography.weights.semibold,
|
|
2016
|
+
color: colors.text,
|
|
2017
|
+
marginBottom: spacing.sm,
|
|
2018
|
+
},
|
|
2019
|
+
journeyText: {
|
|
2020
|
+
fontSize: typography.sizes.md,
|
|
2021
|
+
color: colors.text,
|
|
2022
|
+
lineHeight: 24,
|
|
2023
|
+
},
|
|
2024
|
+
})
|
|
2025
|
+
|
|
2026
|
+
```
|
|
2027
|
+
|
|
2028
|
+
---
|
|
2029
|
+
|
|
2030
|
+
## src/services/storage.ts
|
|
2031
|
+
|
|
2032
|
+
```ts
|
|
2033
|
+
import AsyncStorage from '@react-native-async-storage/async-storage'
|
|
2034
|
+
|
|
2035
|
+
const CURRENT_USER_KEY = 'currentUser'
|
|
2036
|
+
const USERS_KEY = 'users'
|
|
2037
|
+
|
|
2038
|
+
export interface User {
|
|
2039
|
+
username: string
|
|
2040
|
+
burritoConsiderations: number
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/**
|
|
2044
|
+
* Storage service for persisting user data
|
|
2045
|
+
* Uses AsyncStorage (React Native's async key-value storage)
|
|
2046
|
+
*/
|
|
2047
|
+
export const storage = {
|
|
2048
|
+
/**
|
|
2049
|
+
* Get the currently logged in user's username
|
|
2050
|
+
*/
|
|
2051
|
+
getCurrentUser: async (): Promise<string | null> => {
|
|
2052
|
+
try {
|
|
2053
|
+
return await AsyncStorage.getItem(CURRENT_USER_KEY)
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
console.error('Error getting current user:', error)
|
|
2056
|
+
return null
|
|
2057
|
+
}
|
|
2058
|
+
},
|
|
2059
|
+
|
|
2060
|
+
/**
|
|
2061
|
+
* Set the currently logged in user's username
|
|
2062
|
+
*/
|
|
2063
|
+
setCurrentUser: async (username: string): Promise<void> => {
|
|
2064
|
+
try {
|
|
2065
|
+
await AsyncStorage.setItem(CURRENT_USER_KEY, username)
|
|
2066
|
+
} catch (error) {
|
|
2067
|
+
console.error('Error setting current user:', error)
|
|
2068
|
+
}
|
|
2069
|
+
},
|
|
2070
|
+
|
|
2071
|
+
/**
|
|
2072
|
+
* Remove the current user (logout)
|
|
2073
|
+
*/
|
|
2074
|
+
removeCurrentUser: async (): Promise<void> => {
|
|
2075
|
+
try {
|
|
2076
|
+
await AsyncStorage.removeItem(CURRENT_USER_KEY)
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
console.error('Error removing current user:', error)
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
|
|
2082
|
+
/**
|
|
2083
|
+
* Get all stored users
|
|
2084
|
+
*/
|
|
2085
|
+
getUsers: async (): Promise<Record<string, User>> => {
|
|
2086
|
+
try {
|
|
2087
|
+
const data = await AsyncStorage.getItem(USERS_KEY)
|
|
2088
|
+
return data ? JSON.parse(data) : {}
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
console.error('Error getting users:', error)
|
|
2091
|
+
return {}
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Get a specific user by username
|
|
2097
|
+
*/
|
|
2098
|
+
getUser: async (username: string): Promise<User | null> => {
|
|
2099
|
+
try {
|
|
2100
|
+
const users = await storage.getUsers()
|
|
2101
|
+
return users[username] || null
|
|
2102
|
+
} catch (error) {
|
|
2103
|
+
console.error('Error getting user:', error)
|
|
2104
|
+
return null
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
|
|
2108
|
+
/**
|
|
2109
|
+
* Save a user to storage
|
|
2110
|
+
*/
|
|
2111
|
+
saveUser: async (user: User): Promise<void> => {
|
|
2112
|
+
try {
|
|
2113
|
+
const users = await storage.getUsers()
|
|
2114
|
+
users[user.username] = user
|
|
2115
|
+
await AsyncStorage.setItem(USERS_KEY, JSON.stringify(users))
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
console.error('Error saving user:', error)
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
|
|
2121
|
+
/**
|
|
2122
|
+
* Clear all stored data (for testing/debugging)
|
|
2123
|
+
*/
|
|
2124
|
+
clearAll: async (): Promise<void> => {
|
|
2125
|
+
try {
|
|
2126
|
+
await AsyncStorage.multiRemove([CURRENT_USER_KEY, USERS_KEY])
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
console.error('Error clearing storage:', error)
|
|
2129
|
+
}
|
|
2130
|
+
},
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
```
|
|
2134
|
+
|
|
2135
|
+
---
|
|
2136
|
+
|
|
2137
|
+
## src/styles/theme.ts
|
|
2138
|
+
|
|
2139
|
+
```ts
|
|
2140
|
+
/**
|
|
2141
|
+
* Theme constants for consistent styling across the app
|
|
2142
|
+
* Matches the color scheme from the TanStack Start web version
|
|
2143
|
+
*/
|
|
2144
|
+
|
|
2145
|
+
export const colors = {
|
|
2146
|
+
// Primary colors
|
|
2147
|
+
primary: '#0070f3',
|
|
2148
|
+
primaryDark: '#0051cc',
|
|
2149
|
+
|
|
2150
|
+
// Status colors
|
|
2151
|
+
success: '#28a745',
|
|
2152
|
+
successDark: '#218838',
|
|
2153
|
+
danger: '#dc3545',
|
|
2154
|
+
dangerDark: '#c82333',
|
|
2155
|
+
|
|
2156
|
+
// Feature colors
|
|
2157
|
+
burrito: '#e07c24',
|
|
2158
|
+
burritoDark: '#c96a1a',
|
|
2159
|
+
|
|
2160
|
+
// Neutral colors
|
|
2161
|
+
background: '#f5f5f5',
|
|
2162
|
+
white: '#ffffff',
|
|
2163
|
+
text: '#333333',
|
|
2164
|
+
textSecondary: '#666666',
|
|
2165
|
+
textLight: '#999999',
|
|
2166
|
+
border: '#dddddd',
|
|
2167
|
+
borderLight: '#eeeeee',
|
|
2168
|
+
|
|
2169
|
+
// Component-specific
|
|
2170
|
+
statsBackground: '#f8f9fa',
|
|
2171
|
+
headerBackground: '#333333',
|
|
2172
|
+
headerText: '#ffffff',
|
|
2173
|
+
inputBackground: '#ffffff',
|
|
2174
|
+
cardBackground: '#ffffff',
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
export const spacing = {
|
|
2178
|
+
xs: 4,
|
|
2179
|
+
sm: 8,
|
|
2180
|
+
md: 16,
|
|
2181
|
+
lg: 24,
|
|
2182
|
+
xl: 32,
|
|
2183
|
+
xxl: 48,
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
export const typography = {
|
|
2187
|
+
sizes: {
|
|
2188
|
+
xs: 12,
|
|
2189
|
+
sm: 14,
|
|
2190
|
+
md: 16,
|
|
2191
|
+
lg: 18,
|
|
2192
|
+
xl: 24,
|
|
2193
|
+
xxl: 32,
|
|
2194
|
+
},
|
|
2195
|
+
weights: {
|
|
2196
|
+
normal: '400' as const,
|
|
2197
|
+
medium: '500' as const,
|
|
2198
|
+
semibold: '600' as const,
|
|
2199
|
+
bold: '700' as const,
|
|
2200
|
+
},
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
export const borderRadius = {
|
|
2204
|
+
sm: 4,
|
|
2205
|
+
md: 8,
|
|
2206
|
+
lg: 12,
|
|
2207
|
+
full: 9999,
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
export const shadows = {
|
|
2211
|
+
sm: {
|
|
2212
|
+
shadowColor: '#000',
|
|
2213
|
+
shadowOffset: { width: 0, height: 1 },
|
|
2214
|
+
shadowOpacity: 0.05,
|
|
2215
|
+
shadowRadius: 2,
|
|
2216
|
+
elevation: 1,
|
|
2217
|
+
},
|
|
2218
|
+
md: {
|
|
2219
|
+
shadowColor: '#000',
|
|
2220
|
+
shadowOffset: { width: 0, height: 2 },
|
|
2221
|
+
shadowOpacity: 0.1,
|
|
2222
|
+
shadowRadius: 4,
|
|
2223
|
+
elevation: 3,
|
|
2224
|
+
},
|
|
2225
|
+
lg: {
|
|
2226
|
+
shadowColor: '#000',
|
|
2227
|
+
shadowOffset: { width: 0, height: 4 },
|
|
2228
|
+
shadowOpacity: 0.15,
|
|
2229
|
+
shadowRadius: 8,
|
|
2230
|
+
elevation: 5,
|
|
2231
|
+
},
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
```
|
|
2235
|
+
|
|
2236
|
+
---
|
|
2237
|
+
|
|
2238
|
+
## src/types/env.d.ts
|
|
2239
|
+
|
|
2240
|
+
```ts
|
|
2241
|
+
declare module 'react-native-config' {
|
|
2242
|
+
export interface NativeConfig {
|
|
2243
|
+
AMPLITUDE_API_KEY?: string
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
export const Config: NativeConfig
|
|
2247
|
+
export default Config
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
```
|
|
2251
|
+
|
|
2252
|
+
---
|
|
2253
|
+
|