create-m5kdev 0.32.8 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/src/catalog.cjs +76 -11
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +13 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs +13 -7
  7. package/dist/src/doctor.cjs.map +1 -1
  8. package/dist/src/fs.cjs +10 -5
  9. package/dist/src/fs.cjs.map +1 -1
  10. package/dist/src/prompts.cjs +32 -0
  11. package/dist/src/prompts.cjs.map +1 -1
  12. package/dist/src/prompts.d.cts +7 -1
  13. package/dist/src/template.cjs +23 -4
  14. package/dist/src/template.cjs.map +1 -1
  15. package/dist/src/template.d.cts +17 -3
  16. package/dist/src/types.d.cts +11 -4
  17. package/package.json +4 -1
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.dockerignore.tpl +31 -0
  23. package/templates/minimal-app/.gitignore.tpl +1 -0
  24. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  25. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  26. package/templates/minimal-app/README.md.tpl +50 -19
  27. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  28. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  29. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  30. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  31. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  32. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  33. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  34. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  35. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  36. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  37. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  38. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  39. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  40. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  41. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  42. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  43. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  44. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  45. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  46. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  47. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  48. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  49. package/templates/minimal-app/apps/expo/package.json.tpl +59 -59
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  75. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  76. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  77. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  78. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  79. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  80. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  81. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  82. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  83. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  84. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  85. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  86. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +3 -0
  87. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  88. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  89. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  90. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -0
  91. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  92. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +15 -1
  93. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  94. package/templates/minimal-app/apps/server/package.json.tpl +57 -56
  95. package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
  96. package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
  97. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  98. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  99. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  100. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -30
  103. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  104. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  105. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -115
  106. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  107. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  108. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  109. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -1
  110. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  111. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  112. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  113. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  114. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  115. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  116. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  117. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  118. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  119. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  120. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  121. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  122. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  123. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  124. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  125. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  126. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  127. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  128. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  129. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  130. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  131. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  132. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  133. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  134. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  135. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  136. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  137. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  138. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  139. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  140. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  141. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  142. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  143. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  144. package/templates/minimal-app/biome.json.tpl +17 -12
  145. package/templates/minimal-app/package.json.tpl +9 -5
  146. package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
  147. package/templates/minimal-app/template.manifest.json +92 -2
  148. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  149. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  150. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  151. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
  152. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
@@ -1,16 +1,16 @@
1
- const { getDefaultConfig } = require("expo/metro-config");
2
- const { withUniwindConfig } = require("uniwind/metro");
3
- const path = require("node:path");
4
-
5
- const config = getDefaultConfig(__dirname);
6
- const workspaceRoot = path.resolve(__dirname, "../../..");
7
-
8
- config.watchFolders = [workspaceRoot];
9
- config.resolver.nodeModulesPaths = [
10
- path.resolve(__dirname, "node_modules"),
11
- path.resolve(workspaceRoot, "node_modules"),
12
- ];
13
-
14
- module.exports = withUniwindConfig(config, {
15
- cssEntryFile: "./src/global.css",
16
- });
1
+ const { getDefaultConfig } = require("expo/metro-config");
2
+ const { withUniwindConfig } = require("uniwind/metro");
3
+ const path = require("node:path");
4
+
5
+ const config = getDefaultConfig(__dirname);
6
+ const workspaceRoot = path.resolve(__dirname, "../../..");
7
+
8
+ config.watchFolders = [workspaceRoot];
9
+ config.resolver.nodeModulesPaths = [
10
+ path.resolve(__dirname, "node_modules"),
11
+ path.resolve(workspaceRoot, "node_modules"),
12
+ ];
13
+
14
+ module.exports = withUniwindConfig(config, {
15
+ cssEntryFile: "./src/global.css",
16
+ });
@@ -1,59 +1,59 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/expo",
3
- "version": "1.5.3",
4
- "private": true,
5
- "main": "expo-router/entry",
6
- "scripts": {
7
- "build": "expo export --platform web --output-dir dist",
8
- "start": "expo start",
9
- "dev:standard": "EXPO_PUBLIC_ENABLE_WAITLIST=false EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18182 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15183 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15183 --host localhost",
10
- "dev:waitlist": "EXPO_PUBLIC_ENABLE_WAITLIST=true EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18183 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15184 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15184 --host localhost",
11
- "android": "expo start --android",
12
- "ios": "expo start --ios",
13
- "lint": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts",
14
- "lint:fix": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts --write",
15
- "format": "prettier --write .",
16
- "format:check": "prettier --check .",
17
- "check-types": "tsc --noEmit",
18
- "typecheck": "pnpm check-types"
19
- },
20
- "dependencies": {
21
- "@expo/metro-runtime": "~56.0.13",
22
- "@gorhom/bottom-sheet": "^5.2.14",
23
- "@m5kdev/commons": "catalog:",
24
- "@m5kdev/frontend": "catalog:",
25
- "@trpc/client": "catalog:",
26
- "expo": "~56.0.5",
27
- "expo-constants": "~56.0.16",
28
- "expo-linking": "~56.0.12",
29
- "expo-router": "~56.2.7",
30
- "expo-splash-screen": "~56.0.10",
31
- "expo-status-bar": "~56.0.4",
32
- "heroui-native": "^1.0.2",
33
- "{{PACKAGE_SCOPE}}/shared": "workspace:*",
34
- "react": "19.2.3",
35
- "react-dom": "19.2.3",
36
- "react-native": "0.85.3",
37
- "react-native-gesture-handler": "~2.31.2",
38
- "react-native-reanimated": "~4.3.1",
39
- "react-native-safe-area-context": "~5.7.0",
40
- "react-native-screens": "~4.25.2",
41
- "react-native-svg": "15.15.4",
42
- "react-native-web": "catalog:",
43
- "react-native-worklets": "0.8.3",
44
- "tailwind-merge": "^3.4.0",
45
- "tailwind-variants": "^3.2.2",
46
- "tailwindcss": "^4.1.17",
47
- "uniwind": "^1.5.0"
48
- },
49
- "devDependencies": {
50
- "@biomejs/biome": "catalog:",
51
- "@m5kdev/backend": "catalog:",
52
- "@m5kdev/config": "catalog:",
53
- "@types/react": "~19.2.15",
54
- "eslint": "^9.39.4",
55
- "eslint-config-expo": "^56.0.4",
56
- "prettier": "^3.8.3",
57
- "typescript": "~6.0.3"
58
- }
59
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/expo",
3
+ "version": "1.5.3",
4
+ "private": true,
5
+ "main": "expo-router/entry",
6
+ "scripts": {
7
+ "build": "expo export --platform web --output-dir dist",
8
+ "start": "expo start",
9
+ "dev:standard": "EXPO_PUBLIC_ENABLE_WAITLIST=false EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18182 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15183 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15183 --host localhost",
10
+ "dev:waitlist": "EXPO_PUBLIC_ENABLE_WAITLIST=true EXPO_PUBLIC_SERVER_URL=http://127.0.0.1:18183 EXPO_PUBLIC_APP_URL=http://127.0.0.1:15184 EXPO_NO_TELEMETRY=1 BROWSER=none expo start --web --port 15184 --host localhost",
11
+ "android": "expo start --android",
12
+ "ios": "expo start --ios",
13
+ "lint": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts",
14
+ "lint:fix": "biome check app.json package.json metro.config.js eslint.config.js src tsconfig.json uniwind-env.d.ts --write",
15
+ "format": "prettier --write .",
16
+ "format:check": "prettier --check .",
17
+ "check-types": "tsc --noEmit",
18
+ "typecheck": "pnpm check-types"
19
+ },
20
+ "dependencies": {
21
+ "@expo/metro-runtime": "~56.0.13",
22
+ "@gorhom/bottom-sheet": "^5.2.14",
23
+ "@m5kdev/commons": "catalog:m5kdev",
24
+ "@m5kdev/frontend": "catalog:m5kdev",
25
+ "@trpc/client": "catalog:m5kdev",
26
+ "expo": "~56.0.5",
27
+ "expo-constants": "~56.0.16",
28
+ "expo-linking": "~56.0.12",
29
+ "expo-router": "~56.2.7",
30
+ "expo-splash-screen": "~56.0.10",
31
+ "expo-status-bar": "~56.0.4",
32
+ "heroui-native": "^1.0.2",
33
+ "{{PACKAGE_SCOPE}}/shared": "workspace:*",
34
+ "react": "catalog:m5kdev",
35
+ "react-dom": "catalog:m5kdev",
36
+ "react-native": "0.85.3",
37
+ "react-native-gesture-handler": "~2.31.2",
38
+ "react-native-reanimated": "~4.3.1",
39
+ "react-native-safe-area-context": "~5.7.0",
40
+ "react-native-screens": "~4.25.2",
41
+ "react-native-svg": "15.15.4",
42
+ "react-native-web": "catalog:m5kdev",
43
+ "react-native-worklets": "0.8.3",
44
+ "tailwind-merge": "catalog:m5kdev",
45
+ "tailwind-variants": "^3.2.2",
46
+ "tailwindcss": "catalog:m5kdev",
47
+ "uniwind": "^1.5.0"
48
+ },
49
+ "devDependencies": {
50
+ "@biomejs/biome": "catalog:m5kdev",
51
+ "@m5kdev/backend": "catalog:m5kdev",
52
+ "@m5kdev/config": "catalog:m5kdev",
53
+ "@types/react": "catalog:m5kdev",
54
+ "eslint": "^9.39.4",
55
+ "eslint-config-expo": "^56.0.4",
56
+ "prettier": "^3.8.3",
57
+ "typescript": "catalog:m5kdev"
58
+ }
59
+ }
@@ -1,31 +1,31 @@
1
- import { AppConfigProvider } from "@m5kdev/frontend/modules/app/components/AppConfigProvider";
2
- import { AuthProvider } from "@m5kdev/frontend/modules/auth/components/AuthProvider";
3
- import { Stack } from "expo-router";
4
- import { HeroUINativeProvider } from "heroui-native";
5
- import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
6
- import type { JSX } from "react";
7
- import { GestureHandlerRootView } from "react-native-gesture-handler";
8
- import { LoadingScreen } from "../components/LoadingScreen";
9
- import { appUrl, serverUrl } from "../config";
10
-
11
- import "../global.css";
12
-
13
- export default function RootLayout(): JSX.Element {
14
- return (
15
- <GestureHandlerRootView style={{ flex: 1 }}>
16
- <HeroUINativeProvider>
17
- <AppConfigProvider
18
- config={{
19
- appName: APP_NAME,
20
- appUrl,
21
- serverUrl,
22
- }}
23
- >
24
- <AuthProvider loader={<LoadingScreen />}>
25
- <Stack screenOptions={{ headerShown: false }} />
26
- </AuthProvider>
27
- </AppConfigProvider>
28
- </HeroUINativeProvider>
29
- </GestureHandlerRootView>
30
- );
31
- }
1
+ import { AppConfigProvider } from "@m5kdev/frontend/modules/app/components/AppConfigProvider";
2
+ import { AuthProvider } from "@m5kdev/frontend/modules/auth/components/AuthProvider";
3
+ import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
4
+ import { Stack } from "expo-router";
5
+ import { HeroUINativeProvider } from "heroui-native";
6
+ import type { JSX } from "react";
7
+ import { GestureHandlerRootView } from "react-native-gesture-handler";
8
+ import { LoadingScreen } from "../components/LoadingScreen";
9
+ import { appUrl, serverUrl } from "../config";
10
+
11
+ import "../global.css";
12
+
13
+ export default function RootLayout(): JSX.Element {
14
+ return (
15
+ <GestureHandlerRootView style={{ flex: 1 }}>
16
+ <HeroUINativeProvider>
17
+ <AppConfigProvider
18
+ config={{
19
+ appName: APP_NAME,
20
+ appUrl,
21
+ serverUrl,
22
+ }}
23
+ >
24
+ <AuthProvider loader={<LoadingScreen />}>
25
+ <Stack screenOptions={{ headerShown: false }} />
26
+ </AuthProvider>
27
+ </AppConfigProvider>
28
+ </HeroUINativeProvider>
29
+ </GestureHandlerRootView>
30
+ );
31
+ }
@@ -1,151 +1,151 @@
1
- import { useAppConfig } from "@m5kdev/frontend/modules/app/hooks/useAppConfig";
2
- import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
3
- import { Button } from "heroui-native";
4
- import { useEffect, useState } from "react";
5
- import { View } from "react-native";
6
- import { AuthScaffold } from "../components/AuthScaffold";
7
- import { FormTextField } from "../components/FormTextField";
8
- import { StatusText } from "../components/StatusText";
9
- import { trpcClient } from "../lib/trpc";
10
-
11
- export default function ClaimAccountScreen() {
12
- const { serverUrl } = useAppConfig();
13
- const { data: session, registerSession } = useSession();
14
- const [email, setEmail] = useState("");
15
- const [password, setPassword] = useState("");
16
- const [status, setStatus] = useState<string | null>("Loading claim status...");
17
- const [statusTone, setStatusTone] = useState<"default" | "error" | "success">("default");
18
- const [hasClaim, setHasClaim] = useState(false);
19
- const [isClaimed, setIsClaimed] = useState(false);
20
- const [isBusy, setIsBusy] = useState(false);
21
-
22
- useEffect(() => {
23
- if (session?.user.email) {
24
- setEmail(session.user.email);
25
- }
26
- }, [session?.user.email]);
27
-
28
- useEffect(() => {
29
- let cancelled = false;
30
-
31
- if (isClaimed) {
32
- return;
33
- }
34
-
35
- if (!session?.user) {
36
- setStatusTone("error");
37
- setStatus("You need to sign in with your magic link before claiming this account.");
38
- setHasClaim(false);
39
- return;
40
- }
41
-
42
- trpcClient.auth.getMyAccountClaimStatus
43
- .query()
44
- .then((claim) => {
45
- if (cancelled) return;
46
- setHasClaim(Boolean(claim));
47
- setStatusTone(claim ? "success" : "error");
48
- setStatus(claim ? "Claim your account" : "No pending account claim was found.");
49
- if (claim?.claimedEmail) {
50
- setEmail(claim.claimedEmail);
51
- }
52
- })
53
- .catch((error) => {
54
- if (cancelled) return;
55
- setHasClaim(false);
56
- setStatusTone("error");
57
- setStatus(error instanceof Error ? error.message : "Unable to load claim status");
58
- });
59
-
60
- return () => {
61
- cancelled = true;
62
- };
63
- }, [isClaimed, session?.user]);
64
-
65
- const saveEmail = async () => {
66
- setIsBusy(true);
67
- setStatus(null);
68
- try {
69
- await trpcClient.auth.setMyAccountClaimEmail.mutate({ email });
70
- registerSession();
71
- setStatusTone("success");
72
- setStatus("Email updated");
73
- } catch (error) {
74
- setStatusTone("error");
75
- setStatus(error instanceof Error ? error.message : "Unable to update email");
76
- } finally {
77
- setIsBusy(false);
78
- }
79
- };
80
-
81
- const setPasswordAndClaim = async () => {
82
- setIsBusy(true);
83
- setStatus(null);
84
- try {
85
- const response = await fetch(`${serverUrl}/api/auth/set-password`, {
86
- method: "POST",
87
- credentials: "include",
88
- headers: {
89
- "Content-Type": "application/json",
90
- },
91
- body: JSON.stringify({
92
- newPassword: password,
93
- }),
94
- });
95
- const payload = (await response.json().catch(() => ({}))) as { message?: string };
96
- if (!response.ok) {
97
- throw new Error(payload.message ?? "Unable to set password");
98
- }
99
-
100
- await trpcClient.auth.acceptMyAccountClaim.mutate();
101
- setIsClaimed(true);
102
- registerSession();
103
- setStatusTone("success");
104
- setStatus("Password set. Account claimed.");
105
- } catch (error) {
106
- setStatusTone("error");
107
- setStatus(error instanceof Error ? error.message : "Unable to claim account");
108
- } finally {
109
- setIsBusy(false);
110
- }
111
- };
112
-
113
- return (
114
- <AuthScaffold
115
- title="Claim your account"
116
- description="Set permanent credentials for a provisioned account."
117
- >
118
- <View className="gap-4">
119
- <FormTextField
120
- label="Email"
121
- testID="claim-email"
122
- value={email}
123
- onChangeText={setEmail}
124
- keyboardType="email-address"
125
- editable={hasClaim && !isBusy}
126
- />
127
- <Button testID="claim-email-submit" onPress={saveEmail} isDisabled={!hasClaim || isBusy}>
128
- Save Email
129
- </Button>
130
- <FormTextField
131
- label="Set password"
132
- testID="claim-password"
133
- value={password}
134
- onChangeText={setPassword}
135
- secureTextEntry
136
- editable={hasClaim && !isBusy}
137
- />
138
- <Button
139
- testID="claim-password-submit"
140
- onPress={setPasswordAndClaim}
141
- isDisabled={!hasClaim || !password || isBusy}
142
- >
143
- Set Password and Claim
144
- </Button>
145
- <StatusText testID="claim-status" tone={statusTone}>
146
- {status}
147
- </StatusText>
148
- </View>
149
- </AuthScaffold>
150
- );
151
- }
1
+ import { useAppConfig } from "@m5kdev/frontend/modules/app/hooks/useAppConfig";
2
+ import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
3
+ import { Button } from "heroui-native";
4
+ import { useEffect, useState } from "react";
5
+ import { View } from "react-native";
6
+ import { AuthScaffold } from "../components/AuthScaffold";
7
+ import { FormTextField } from "../components/FormTextField";
8
+ import { StatusText } from "../components/StatusText";
9
+ import { trpcClient } from "../lib/trpc";
10
+
11
+ export default function ClaimAccountScreen() {
12
+ const { serverUrl } = useAppConfig();
13
+ const { data: session, registerSession } = useSession();
14
+ const [email, setEmail] = useState("");
15
+ const [password, setPassword] = useState("");
16
+ const [status, setStatus] = useState<string | null>("Loading claim status...");
17
+ const [statusTone, setStatusTone] = useState<"default" | "error" | "success">("default");
18
+ const [hasClaim, setHasClaim] = useState(false);
19
+ const [isClaimed, setIsClaimed] = useState(false);
20
+ const [isBusy, setIsBusy] = useState(false);
21
+
22
+ useEffect(() => {
23
+ if (session?.user.email) {
24
+ setEmail(session.user.email);
25
+ }
26
+ }, [session?.user.email]);
27
+
28
+ useEffect(() => {
29
+ let cancelled = false;
30
+
31
+ if (isClaimed) {
32
+ return;
33
+ }
34
+
35
+ if (!session?.user) {
36
+ setStatusTone("error");
37
+ setStatus("You need to sign in with your magic link before claiming this account.");
38
+ setHasClaim(false);
39
+ return;
40
+ }
41
+
42
+ trpcClient.auth.getMyAccountClaimStatus
43
+ .query()
44
+ .then((claim) => {
45
+ if (cancelled) return;
46
+ setHasClaim(Boolean(claim));
47
+ setStatusTone(claim ? "success" : "error");
48
+ setStatus(claim ? "Claim your account" : "No pending account claim was found.");
49
+ if (claim?.claimedEmail) {
50
+ setEmail(claim.claimedEmail);
51
+ }
52
+ })
53
+ .catch((error) => {
54
+ if (cancelled) return;
55
+ setHasClaim(false);
56
+ setStatusTone("error");
57
+ setStatus(error instanceof Error ? error.message : "Unable to load claim status");
58
+ });
59
+
60
+ return () => {
61
+ cancelled = true;
62
+ };
63
+ }, [isClaimed, session?.user]);
64
+
65
+ const saveEmail = async () => {
66
+ setIsBusy(true);
67
+ setStatus(null);
68
+ try {
69
+ await trpcClient.auth.setMyAccountClaimEmail.mutate({ email });
70
+ registerSession();
71
+ setStatusTone("success");
72
+ setStatus("Email updated");
73
+ } catch (error) {
74
+ setStatusTone("error");
75
+ setStatus(error instanceof Error ? error.message : "Unable to update email");
76
+ } finally {
77
+ setIsBusy(false);
78
+ }
79
+ };
80
+
81
+ const setPasswordAndClaim = async () => {
82
+ setIsBusy(true);
83
+ setStatus(null);
84
+ try {
85
+ const response = await fetch(`${serverUrl}/api/auth/set-password`, {
86
+ method: "POST",
87
+ credentials: "include",
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ },
91
+ body: JSON.stringify({
92
+ newPassword: password,
93
+ }),
94
+ });
95
+ const payload = (await response.json().catch(() => ({}))) as { message?: string };
96
+ if (!response.ok) {
97
+ throw new Error(payload.message ?? "Unable to set password");
98
+ }
99
+
100
+ await trpcClient.auth.acceptMyAccountClaim.mutate();
101
+ setIsClaimed(true);
102
+ registerSession();
103
+ setStatusTone("success");
104
+ setStatus("Password set. Account claimed.");
105
+ } catch (error) {
106
+ setStatusTone("error");
107
+ setStatus(error instanceof Error ? error.message : "Unable to claim account");
108
+ } finally {
109
+ setIsBusy(false);
110
+ }
111
+ };
112
+
113
+ return (
114
+ <AuthScaffold
115
+ title="Claim your account"
116
+ description="Set permanent credentials for a provisioned account."
117
+ >
118
+ <View className="gap-4">
119
+ <FormTextField
120
+ label="Email"
121
+ testID="claim-email"
122
+ value={email}
123
+ onChangeText={setEmail}
124
+ keyboardType="email-address"
125
+ editable={hasClaim && !isBusy}
126
+ />
127
+ <Button testID="claim-email-submit" onPress={saveEmail} isDisabled={!hasClaim || isBusy}>
128
+ Save Email
129
+ </Button>
130
+ <FormTextField
131
+ label="Set password"
132
+ testID="claim-password"
133
+ value={password}
134
+ onChangeText={setPassword}
135
+ secureTextEntry
136
+ editable={hasClaim && !isBusy}
137
+ />
138
+ <Button
139
+ testID="claim-password-submit"
140
+ onPress={setPasswordAndClaim}
141
+ isDisabled={!hasClaim || !password || isBusy}
142
+ >
143
+ Set Password and Claim
144
+ </Button>
145
+ <StatusText testID="claim-status" tone={statusTone}>
146
+ {status}
147
+ </StatusText>
148
+ </View>
149
+ </AuthScaffold>
150
+ );
151
+ }
@@ -1,53 +1,53 @@
1
- import { useAuthClient } from "@m5kdev/frontend/modules/auth/hooks/useAuthClient";
2
- import { Button } from "heroui-native";
3
- import { useState } from "react";
4
- import { View } from "react-native";
5
- import { AuthScaffold } from "../components/AuthScaffold";
6
- import { FormTextField } from "../components/FormTextField";
7
- import { StatusText } from "../components/StatusText";
8
-
9
- export default function ForgotPasswordScreen() {
10
- const authClient = useAuthClient();
11
- const [email, setEmail] = useState("");
12
- const [status, setStatus] = useState<string | null>(null);
13
- const [tone, setTone] = useState<"error" | "success">("success");
14
- const [isBusy, setIsBusy] = useState(false);
15
-
16
- const submit = async () => {
17
- setIsBusy(true);
18
- setStatus(null);
19
- try {
20
- await authClient.requestPasswordReset({
21
- email,
22
- redirectTo: "/reset-password",
23
- });
24
- setTone("success");
25
- setStatus("Password reset email sent");
26
- } catch (error) {
27
- setTone("error");
28
- setStatus(error instanceof Error ? error.message : "Failed to request password reset");
29
- } finally {
30
- setIsBusy(false);
31
- }
32
- };
33
-
34
- return (
35
- <AuthScaffold title="Reset password">
36
- <View className="gap-4">
37
- <FormTextField
38
- label="Email"
39
- testID="forgot-password-email"
40
- value={email}
41
- onChangeText={setEmail}
42
- keyboardType="email-address"
43
- />
44
- <StatusText testID="auth-status" tone={tone}>
45
- {status}
46
- </StatusText>
47
- <Button testID="forgot-password-submit" onPress={submit} isDisabled={isBusy}>
48
- Reset password
49
- </Button>
50
- </View>
51
- </AuthScaffold>
52
- );
53
- }
1
+ import { useAuthClient } from "@m5kdev/frontend/modules/auth/hooks/useAuthClient";
2
+ import { Button } from "heroui-native";
3
+ import { useState } from "react";
4
+ import { View } from "react-native";
5
+ import { AuthScaffold } from "../components/AuthScaffold";
6
+ import { FormTextField } from "../components/FormTextField";
7
+ import { StatusText } from "../components/StatusText";
8
+
9
+ export default function ForgotPasswordScreen() {
10
+ const authClient = useAuthClient();
11
+ const [email, setEmail] = useState("");
12
+ const [status, setStatus] = useState<string | null>(null);
13
+ const [tone, setTone] = useState<"error" | "success">("success");
14
+ const [isBusy, setIsBusy] = useState(false);
15
+
16
+ const submit = async () => {
17
+ setIsBusy(true);
18
+ setStatus(null);
19
+ try {
20
+ await authClient.requestPasswordReset({
21
+ email,
22
+ redirectTo: "/reset-password",
23
+ });
24
+ setTone("success");
25
+ setStatus("Password reset email sent");
26
+ } catch (error) {
27
+ setTone("error");
28
+ setStatus(error instanceof Error ? error.message : "Failed to request password reset");
29
+ } finally {
30
+ setIsBusy(false);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <AuthScaffold title="Reset password">
36
+ <View className="gap-4">
37
+ <FormTextField
38
+ label="Email"
39
+ testID="forgot-password-email"
40
+ value={email}
41
+ onChangeText={setEmail}
42
+ keyboardType="email-address"
43
+ />
44
+ <StatusText testID="auth-status" tone={tone}>
45
+ {status}
46
+ </StatusText>
47
+ <Button testID="forgot-password-submit" onPress={submit} isDisabled={isBusy}>
48
+ Reset password
49
+ </Button>
50
+ </View>
51
+ </AuthScaffold>
52
+ );
53
+ }
@@ -1,8 +1,8 @@
1
- import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
2
- import { Redirect } from "expo-router";
3
-
4
- export default function IndexScreen() {
5
- const { data: session } = useSession();
6
-
7
- return <Redirect href={session ? "/posts" : "/login"} />;
8
- }
1
+ import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
2
+ import { Redirect } from "expo-router";
3
+
4
+ export default function IndexScreen() {
5
+ const { data: session } = useSession();
6
+
7
+ return <Redirect href={session ? "/posts" : "/login"} />;
8
+ }