@usequark/quark-create-app 1.23.11

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 (237) hide show
  1. package/README.md +210 -0
  2. package/package.json +56 -0
  3. package/src/dependabot-config.test.js +128 -0
  4. package/src/deploy/adapters/iac.js +203 -0
  5. package/src/deploy/adapters/index.js +27 -0
  6. package/src/deploy/adapters/railway.js +508 -0
  7. package/src/deploy/adapters/railway.test.js +318 -0
  8. package/src/deploy/contract.js +59 -0
  9. package/src/deploy/deploy.integration.test.js +183 -0
  10. package/src/deploy/deploy.js +430 -0
  11. package/src/deploy/deploy.test.js +105 -0
  12. package/src/deploy/discovery.js +115 -0
  13. package/src/deploy/discovery.test.js +141 -0
  14. package/src/deploy/index.js +38 -0
  15. package/src/deploy/inspect.js +130 -0
  16. package/src/index.js +2920 -0
  17. package/src/mobile-template.test.js +232 -0
  18. package/src/prepare-hooks.test.js +535 -0
  19. package/src/scaffold-guards.test.js +140 -0
  20. package/src/scaffold-output.test.js +228 -0
  21. package/src/template-scripts.test.js +160 -0
  22. package/src/utils.js +77 -0
  23. package/src/utils.test.js +95 -0
  24. package/src/worker-dockerfile.test.js +85 -0
  25. package/templates/base-project/.cursor/rules/quark.mdc +172 -0
  26. package/templates/base-project/.dockerignore +14 -0
  27. package/templates/base-project/.env.railway.example +87 -0
  28. package/templates/base-project/.github/copilot-instructions.md +65 -0
  29. package/templates/base-project/.github/dependabot.yml +31 -0
  30. package/templates/base-project/.github/skills/project-context/SKILL.md +109 -0
  31. package/templates/base-project/.github/workflows/ci.yml +116 -0
  32. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  33. package/templates/base-project/.github/workflows/install-windows.yml +27 -0
  34. package/templates/base-project/.github/workflows/release.yml +67 -0
  35. package/templates/base-project/.railway/railway.ts +40 -0
  36. package/templates/base-project/CLAUDE.md +163 -0
  37. package/templates/base-project/MAIN.md +21 -0
  38. package/templates/base-project/README.md +164 -0
  39. package/templates/base-project/apps/web/.env.example +99 -0
  40. package/templates/base-project/apps/web/Dockerfile +38 -0
  41. package/templates/base-project/apps/web/biome.json +8 -0
  42. package/templates/base-project/apps/web/jsconfig.json +9 -0
  43. package/templates/base-project/apps/web/next.config.js +154 -0
  44. package/templates/base-project/apps/web/package.json +42 -0
  45. package/templates/base-project/apps/web/postcss.config.mjs +7 -0
  46. package/templates/base-project/apps/web/public/.well-known/security.txt +3 -0
  47. package/templates/base-project/apps/web/public/quark.svg +47 -0
  48. package/templates/base-project/apps/web/scripts/prepare-standalone.mjs +71 -0
  49. package/templates/base-project/apps/web/src/app/_components/AsciiPanel.js +103 -0
  50. package/templates/base-project/apps/web/src/app/_components/HealthIndicator.js +85 -0
  51. package/templates/base-project/apps/web/src/app/_components/HomeThemeToggle.js +63 -0
  52. package/templates/base-project/apps/web/src/app/_components/QuarkAnimation.js +168 -0
  53. package/templates/base-project/apps/web/src/app/_components/SyncedPanels.js +212 -0
  54. package/templates/base-project/apps/web/src/app/_components/UmamiReplayRecorder.js +198 -0
  55. package/templates/base-project/apps/web/src/app/_components/UmamiWebVitals.js +39 -0
  56. package/templates/base-project/apps/web/src/app/api/auth/[...nextauth]/route.js +7 -0
  57. package/templates/base-project/apps/web/src/app/api/auth/apple/route.js +82 -0
  58. package/templates/base-project/apps/web/src/app/api/auth/google/route.js +59 -0
  59. package/templates/base-project/apps/web/src/app/api/auth/refresh/route.js +48 -0
  60. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +63 -0
  61. package/templates/base-project/apps/web/src/app/api/auth/token/route.js +44 -0
  62. package/templates/base-project/apps/web/src/app/api/csrf/route.js +40 -0
  63. package/templates/base-project/apps/web/src/app/api/device/register/route.js +57 -0
  64. package/templates/base-project/apps/web/src/app/api/error-handler.js +23 -0
  65. package/templates/base-project/apps/web/src/app/api/files/[id]/route.js +89 -0
  66. package/templates/base-project/apps/web/src/app/api/files/route.js +120 -0
  67. package/templates/base-project/apps/web/src/app/api/health/route.js +160 -0
  68. package/templates/base-project/apps/web/src/app/api/users/[id]/route.js +54 -0
  69. package/templates/base-project/apps/web/src/app/api/users/me/route.js +67 -0
  70. package/templates/base-project/apps/web/src/app/api/users/route.js +45 -0
  71. package/templates/base-project/apps/web/src/app/auth/error/page.js +83 -0
  72. package/templates/base-project/apps/web/src/app/auth/forgot-password/page.js +58 -0
  73. package/templates/base-project/apps/web/src/app/auth/provider-loading.js +25 -0
  74. package/templates/base-project/apps/web/src/app/auth/register/RegisterPageClient.js +340 -0
  75. package/templates/base-project/apps/web/src/app/auth/register/page.js +17 -0
  76. package/templates/base-project/apps/web/src/app/auth/signin/SignInPageClient.js +230 -0
  77. package/templates/base-project/apps/web/src/app/auth/signin/page.js +10 -0
  78. package/templates/base-project/apps/web/src/app/favicon.ico +0 -0
  79. package/templates/base-project/apps/web/src/app/global-error.js +58 -0
  80. package/templates/base-project/apps/web/src/app/globals.css +1166 -0
  81. package/templates/base-project/apps/web/src/app/icon.svg +47 -0
  82. package/templates/base-project/apps/web/src/app/layout.js +57 -0
  83. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  84. package/templates/base-project/apps/web/src/app/not-found.js +39 -0
  85. package/templates/base-project/apps/web/src/app/page.js +262 -0
  86. package/templates/base-project/apps/web/src/app/robots.js +23 -0
  87. package/templates/base-project/apps/web/src/app/sitemap.js +14 -0
  88. package/templates/base-project/apps/web/src/lib/analytics/umami-before-send.js +29 -0
  89. package/templates/base-project/apps/web/src/lib/analytics/umami-config.js +95 -0
  90. package/templates/base-project/apps/web/src/lib/analytics/umami-csp.js +24 -0
  91. package/templates/base-project/apps/web/src/lib/analytics/umami-marketing.js +131 -0
  92. package/templates/base-project/apps/web/src/lib/analytics/umami-replay.js +204 -0
  93. package/templates/base-project/apps/web/src/lib/analytics/umami-web-vitals.js +237 -0
  94. package/templates/base-project/apps/web/src/lib/analytics/umami.js +57 -0
  95. package/templates/base-project/apps/web/src/lib/auth-middleware.js +82 -0
  96. package/templates/base-project/apps/web/src/lib/auth-signup.js +5 -0
  97. package/templates/base-project/apps/web/src/lib/auth.js +191 -0
  98. package/templates/base-project/apps/web/src/lib/csrf-cookie.js +11 -0
  99. package/templates/base-project/apps/web/src/lib/jwt.js +84 -0
  100. package/templates/base-project/apps/web/src/lib/load-crm-config.js +35 -0
  101. package/templates/base-project/apps/web/src/lib/normalize-auth-request.js +33 -0
  102. package/templates/base-project/apps/web/src/lib/proxy-auth.js +80 -0
  103. package/templates/base-project/apps/web/src/lib/seo/indexing.js +25 -0
  104. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +74 -0
  105. package/templates/base-project/apps/web/src/lib/sitemap-entries.js +22 -0
  106. package/templates/base-project/apps/web/src/preflight.js +55 -0
  107. package/templates/base-project/apps/web/src/proxy.js +328 -0
  108. package/templates/base-project/biome.json +52 -0
  109. package/templates/base-project/docker-compose.override.yml +17 -0
  110. package/templates/base-project/docker-compose.yml +37 -0
  111. package/templates/base-project/docs/FIRST_FEATURE.md +74 -0
  112. package/templates/base-project/docs/START_HERE.md +70 -0
  113. package/templates/base-project/docs/adr/README.md +42 -0
  114. package/templates/base-project/package.json +58 -0
  115. package/templates/base-project/packages/db/package.json +38 -0
  116. package/templates/base-project/packages/db/prisma/migrations/20260202061128_initial/migration.sql +202 -0
  117. package/templates/base-project/packages/db/prisma/migrations/migration_lock.toml +3 -0
  118. package/templates/base-project/packages/db/prisma/schema.prisma +161 -0
  119. package/templates/base-project/packages/db/prisma/seed.js +152 -0
  120. package/templates/base-project/packages/db/prisma.config.js +27 -0
  121. package/templates/base-project/packages/db/src/client.js +75 -0
  122. package/templates/base-project/packages/db/src/connection.js +44 -0
  123. package/templates/base-project/packages/db/src/context.js +275 -0
  124. package/templates/base-project/packages/db/src/index.js +5 -0
  125. package/templates/base-project/packages/db/src/ping.js +66 -0
  126. package/templates/base-project/packages/db/src/queries.js +292 -0
  127. package/templates/base-project/packages/db/src/schemas.js +30 -0
  128. package/templates/base-project/packages/db/src/umami.js +299 -0
  129. package/templates/base-project/pnpm-workspace.yaml +20 -0
  130. package/templates/base-project/scripts/check-loading.mjs +49 -0
  131. package/templates/base-project/scripts/check-standards.mjs +186 -0
  132. package/templates/base-project/scripts/clean-workspace.mjs +479 -0
  133. package/templates/base-project/scripts/dev-preflight.mjs +48 -0
  134. package/templates/base-project/scripts/doctor.js +408 -0
  135. package/templates/base-project/scripts/prepare.js +490 -0
  136. package/templates/base-project/scripts/run-tests.mjs +71 -0
  137. package/templates/base-project/skills/add-dashboard/SKILL.md +28 -0
  138. package/templates/base-project/skills/add-endpoint/SKILL.md +29 -0
  139. package/templates/base-project/skills/add-model/SKILL.md +28 -0
  140. package/templates/base-project/skills/ecommerce/SKILL.md +224 -0
  141. package/templates/base-project/skills/ecommerce-cart/SKILL.md +193 -0
  142. package/templates/base-project/skills/ecommerce-catalog/SKILL.md +179 -0
  143. package/templates/base-project/skills/ecommerce-checkout/SKILL.md +195 -0
  144. package/templates/base-project/skills/i18n/SKILL.md +211 -0
  145. package/templates/base-project/skills/payment/SKILL.md +280 -0
  146. package/templates/base-project/skills/quark-skills/SKILL.md +37 -0
  147. package/templates/base-project/turbo.json +143 -0
  148. package/templates/config/package.json +20 -0
  149. package/templates/config/src/app-url.js +134 -0
  150. package/templates/config/src/environment.js +284 -0
  151. package/templates/config/src/index.js +16 -0
  152. package/templates/config/src/load-config.js +157 -0
  153. package/templates/config/src/rate-limit.js +136 -0
  154. package/templates/config/src/redis.js +43 -0
  155. package/templates/config/src/validate-env.js +532 -0
  156. package/templates/jobs/README.md +52 -0
  157. package/templates/jobs/package.json +9 -0
  158. package/templates/jobs/src/definitions.js +13 -0
  159. package/templates/jobs/src/index.js +1 -0
  160. package/templates/mobile/.env.example +16 -0
  161. package/templates/mobile/CHECKLIST.md +40 -0
  162. package/templates/mobile/README.md +130 -0
  163. package/templates/mobile/app/(app)/_layout.tsx +83 -0
  164. package/templates/mobile/app/(app)/index.tsx +147 -0
  165. package/templates/mobile/app/(app)/profile.tsx +204 -0
  166. package/templates/mobile/app/(app)/settings.tsx +80 -0
  167. package/templates/mobile/app/(auth)/_layout.tsx +10 -0
  168. package/templates/mobile/app/(auth)/sign-in.tsx +140 -0
  169. package/templates/mobile/app/(auth)/sign-up.tsx +178 -0
  170. package/templates/mobile/app/+not-found.tsx +32 -0
  171. package/templates/mobile/app/_layout.tsx +18 -0
  172. package/templates/mobile/app.json +47 -0
  173. package/templates/mobile/assets/adaptive-icon.png +0 -0
  174. package/templates/mobile/assets/icon.png +0 -0
  175. package/templates/mobile/assets/notification-icon.png +0 -0
  176. package/templates/mobile/assets/splash.png +0 -0
  177. package/templates/mobile/biome.json +27 -0
  178. package/templates/mobile/components/AuthGate.tsx +19 -0
  179. package/templates/mobile/components/ThemeProvider.tsx +23 -0
  180. package/templates/mobile/eas.json +29 -0
  181. package/templates/mobile/hooks/use-auth.ts +127 -0
  182. package/templates/mobile/lib/api-client.ts +138 -0
  183. package/templates/mobile/lib/api.ts +83 -0
  184. package/templates/mobile/lib/auth-providers-google.ts +74 -0
  185. package/templates/mobile/lib/auth-providers.ts +74 -0
  186. package/templates/mobile/lib/auth.ts +100 -0
  187. package/templates/mobile/lib/config.ts +14 -0
  188. package/templates/mobile/lib/errors.ts +46 -0
  189. package/templates/mobile/lib/notifications.ts +77 -0
  190. package/templates/mobile/lib/storage.ts +25 -0
  191. package/templates/mobile/lib/theme-tokens.ts +16 -0
  192. package/templates/mobile/package.json +37 -0
  193. package/templates/mobile/tsconfig.json +10 -0
  194. package/templates/pwa/app/_components/PWARegister.js +15 -0
  195. package/templates/pwa/app/manifest.js +32 -0
  196. package/templates/pwa/public/sw.js +72 -0
  197. package/templates/ui/README.md +44 -0
  198. package/templates/ui/package.json +21 -0
  199. package/templates/ui/src/badge.js +23 -0
  200. package/templates/ui/src/button.js +111 -0
  201. package/templates/ui/src/card.js +128 -0
  202. package/templates/ui/src/checkbox.js +20 -0
  203. package/templates/ui/src/container.js +13 -0
  204. package/templates/ui/src/dialog.js +137 -0
  205. package/templates/ui/src/error-banner.js +15 -0
  206. package/templates/ui/src/footer.js +239 -0
  207. package/templates/ui/src/form-field.integration-test.js +142 -0
  208. package/templates/ui/src/form-field.js +50 -0
  209. package/templates/ui/src/index.js +24 -0
  210. package/templates/ui/src/input.js +11 -0
  211. package/templates/ui/src/label.js +14 -0
  212. package/templates/ui/src/lightbox.integration-test.js +167 -0
  213. package/templates/ui/src/lightbox.js +243 -0
  214. package/templates/ui/src/logo.js +52 -0
  215. package/templates/ui/src/navbar.js +602 -0
  216. package/templates/ui/src/password-input.js +31 -0
  217. package/templates/ui/src/rich-text.js +514 -0
  218. package/templates/ui/src/select.js +317 -0
  219. package/templates/ui/src/skeleton.js +10 -0
  220. package/templates/ui/src/spinner.js +31 -0
  221. package/templates/ui/src/table.js +169 -0
  222. package/templates/ui/src/textarea.js +11 -0
  223. package/templates/ui/src/theme-constants.js +24 -0
  224. package/templates/ui/src/theme.js +196 -0
  225. package/templates/ui/src/toast.js +206 -0
  226. package/templates/worker/Dockerfile +64 -0
  227. package/templates/worker/README.md +56 -0
  228. package/templates/worker/package.json +36 -0
  229. package/templates/worker/src/config/pricing.js +46 -0
  230. package/templates/worker/src/handlers/email-job-validation.js +36 -0
  231. package/templates/worker/src/handlers/email.js +98 -0
  232. package/templates/worker/src/handlers/files.js +59 -0
  233. package/templates/worker/src/handlers/index.js +20 -0
  234. package/templates/worker/src/handlers/push-notification.js +234 -0
  235. package/templates/worker/src/index.js +505 -0
  236. package/templates/worker/src/lib/railway.js +438 -0
  237. package/templates/worker/src/lib/tokens.js +81 -0
@@ -0,0 +1,140 @@
1
+ import { useRouter } from "expo-router";
2
+ import { useState } from "react";
3
+ import { Alert, Pressable, Text, TextInput, View } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { useAuth } from "../../hooks/use-auth";
6
+ import { useThemeTokens } from "../../lib/theme-tokens";
7
+
8
+ export default function SignInScreen() {
9
+ const router = useRouter();
10
+ const { signIn, isLoading } = useAuth();
11
+ const { bgColor, textColor, mutedColor, borderColor, inputBg, isDark } =
12
+ useThemeTokens();
13
+ const insets = useSafeAreaInsets();
14
+ const [email, setEmail] = useState("");
15
+ const [password, setPassword] = useState("");
16
+
17
+ async function handleSignIn() {
18
+ if (!email || !password) {
19
+ Alert.alert("Error", "Please enter email and password");
20
+ return;
21
+ }
22
+
23
+ try {
24
+ await signIn(email, password);
25
+ router.replace("/(app)");
26
+ } catch (error: unknown) {
27
+ Alert.alert(
28
+ "Sign In Failed",
29
+ error instanceof Error ? error.message : "Invalid credentials",
30
+ );
31
+ }
32
+ }
33
+
34
+ return (
35
+ <View
36
+ style={{
37
+ flex: 1,
38
+ justifyContent: "center",
39
+ padding: 20,
40
+ paddingTop: insets.top + 20,
41
+ backgroundColor: bgColor,
42
+ }}
43
+ accessibilityLabel="Sign in screen"
44
+ >
45
+ <Text
46
+ style={{
47
+ fontSize: 28,
48
+ fontWeight: "bold",
49
+ color: textColor,
50
+ marginBottom: 32,
51
+ textAlign: "center",
52
+ }}
53
+ accessibilityRole="header"
54
+ >
55
+ Welcome Back
56
+ </Text>
57
+
58
+ <TextInput
59
+ placeholder="Email"
60
+ placeholderTextColor={mutedColor}
61
+ value={email}
62
+ onChangeText={setEmail}
63
+ autoCapitalize="none"
64
+ keyboardType="email-address"
65
+ autoComplete="email"
66
+ style={{
67
+ backgroundColor: inputBg,
68
+ borderWidth: 1,
69
+ borderColor,
70
+ borderRadius: 8,
71
+ padding: 12,
72
+ marginBottom: 12,
73
+ fontSize: 16,
74
+ color: textColor,
75
+ }}
76
+ accessibilityLabel="Email address"
77
+ accessibilityHint="Enter your email address to sign in"
78
+ />
79
+
80
+ <TextInput
81
+ placeholder="Password"
82
+ placeholderTextColor={mutedColor}
83
+ value={password}
84
+ onChangeText={setPassword}
85
+ secureTextEntry
86
+ autoComplete="password"
87
+ style={{
88
+ backgroundColor: inputBg,
89
+ borderWidth: 1,
90
+ borderColor,
91
+ borderRadius: 8,
92
+ padding: 12,
93
+ marginBottom: 20,
94
+ fontSize: 16,
95
+ color: textColor,
96
+ }}
97
+ accessibilityLabel="Password"
98
+ accessibilityHint="Enter your password to sign in"
99
+ />
100
+
101
+ <Pressable
102
+ onPress={handleSignIn}
103
+ disabled={isLoading}
104
+ style={{
105
+ backgroundColor: isDark ? "#fff" : "#000",
106
+ padding: 14,
107
+ borderRadius: 8,
108
+ alignItems: "center",
109
+ opacity: isLoading ? 0.6 : 1,
110
+ }}
111
+ accessibilityLabel="Sign in"
112
+ accessibilityRole="button"
113
+ accessibilityState={{ disabled: isLoading }}
114
+ >
115
+ <Text
116
+ style={{
117
+ color: isDark ? "#000" : "#fff",
118
+ fontSize: 16,
119
+ fontWeight: "600",
120
+ }}
121
+ >
122
+ {isLoading ? "Signing in..." : "Sign In"}
123
+ </Text>
124
+ </Pressable>
125
+
126
+ <Pressable
127
+ onPress={() => router.push("/(auth)/sign-up")}
128
+ style={{ marginTop: 16, alignItems: "center" }}
129
+ accessibilityLabel="Go to sign up screen"
130
+ accessibilityRole="button"
131
+ accessibilityHint="Opens the account creation screen"
132
+ >
133
+ <Text style={{ color: mutedColor }}>
134
+ Don't have an account?{" "}
135
+ <Text style={{ fontWeight: "600", color: textColor }}>Sign Up</Text>
136
+ </Text>
137
+ </Pressable>
138
+ </View>
139
+ );
140
+ }
@@ -0,0 +1,178 @@
1
+ import { useRouter } from "expo-router";
2
+ import { useState } from "react";
3
+ import { Alert, Pressable, Text, TextInput, View } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { useThemeTokens } from "../../lib/theme-tokens";
6
+
7
+ export default function SignUpScreen() {
8
+ const router = useRouter();
9
+ const { bgColor, textColor, mutedColor, inputBg, borderColor, isDark } =
10
+ useThemeTokens();
11
+ const insets = useSafeAreaInsets();
12
+ const [name, setName] = useState("");
13
+ const [email, setEmail] = useState("");
14
+ const [password, setPassword] = useState("");
15
+ const [loading, setLoading] = useState(false);
16
+
17
+ async function handleSignUp() {
18
+ if (!name || !email || !password) {
19
+ Alert.alert("Error", "Please fill in all fields");
20
+ return;
21
+ }
22
+
23
+ setLoading(true);
24
+ try {
25
+ const { getConfig } = await import("../../lib/config");
26
+ const config = getConfig();
27
+
28
+ const response = await fetch(`${config.apiUrl}/api/auth/register`, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ name, email, password }),
32
+ });
33
+
34
+ if (!response.ok) {
35
+ const error = await response.json();
36
+ throw new Error(error.message || "Registration failed");
37
+ }
38
+
39
+ Alert.alert("Success", "Account created! Please sign in.", [
40
+ { text: "OK", onPress: () => router.replace("/(auth)/sign-in") },
41
+ ]);
42
+ } catch (error: unknown) {
43
+ Alert.alert(
44
+ "Sign Up Failed",
45
+ error instanceof Error ? error.message : "Registration failed",
46
+ );
47
+ } finally {
48
+ setLoading(false);
49
+ }
50
+ }
51
+
52
+ return (
53
+ <View
54
+ style={{
55
+ flex: 1,
56
+ justifyContent: "center",
57
+ padding: 20,
58
+ paddingTop: insets.top + 20,
59
+ backgroundColor: bgColor,
60
+ }}
61
+ accessibilityLabel="Create account screen"
62
+ >
63
+ <Text
64
+ style={{
65
+ fontSize: 28,
66
+ fontWeight: "bold",
67
+ color: textColor,
68
+ marginBottom: 32,
69
+ textAlign: "center",
70
+ }}
71
+ accessibilityRole="header"
72
+ >
73
+ Create Account
74
+ </Text>
75
+
76
+ <TextInput
77
+ placeholder="Name"
78
+ placeholderTextColor={mutedColor}
79
+ value={name}
80
+ onChangeText={setName}
81
+ autoComplete="name"
82
+ style={{
83
+ backgroundColor: inputBg,
84
+ borderWidth: 1,
85
+ borderColor,
86
+ borderRadius: 8,
87
+ padding: 12,
88
+ marginBottom: 12,
89
+ fontSize: 16,
90
+ color: textColor,
91
+ }}
92
+ accessibilityLabel="Full name"
93
+ accessibilityHint="Enter your full name"
94
+ />
95
+
96
+ <TextInput
97
+ placeholder="Email"
98
+ placeholderTextColor={mutedColor}
99
+ value={email}
100
+ onChangeText={setEmail}
101
+ autoCapitalize="none"
102
+ keyboardType="email-address"
103
+ autoComplete="email"
104
+ style={{
105
+ backgroundColor: inputBg,
106
+ borderWidth: 1,
107
+ borderColor,
108
+ borderRadius: 8,
109
+ padding: 12,
110
+ marginBottom: 12,
111
+ fontSize: 16,
112
+ color: textColor,
113
+ }}
114
+ accessibilityLabel="Email address"
115
+ accessibilityHint="Enter your email address"
116
+ />
117
+
118
+ <TextInput
119
+ placeholder="Password"
120
+ placeholderTextColor={mutedColor}
121
+ value={password}
122
+ onChangeText={setPassword}
123
+ secureTextEntry
124
+ autoComplete="password-new"
125
+ style={{
126
+ backgroundColor: inputBg,
127
+ borderWidth: 1,
128
+ borderColor,
129
+ borderRadius: 8,
130
+ padding: 12,
131
+ marginBottom: 20,
132
+ fontSize: 16,
133
+ color: textColor,
134
+ }}
135
+ accessibilityLabel="Password"
136
+ accessibilityHint="Choose a password for your account"
137
+ />
138
+
139
+ <Pressable
140
+ onPress={handleSignUp}
141
+ disabled={loading}
142
+ style={{
143
+ backgroundColor: isDark ? "#fff" : "#000",
144
+ padding: 14,
145
+ borderRadius: 8,
146
+ alignItems: "center",
147
+ opacity: loading ? 0.6 : 1,
148
+ }}
149
+ accessibilityLabel="Create account"
150
+ accessibilityRole="button"
151
+ accessibilityState={{ disabled: loading }}
152
+ >
153
+ <Text
154
+ style={{
155
+ color: isDark ? "#000" : "#fff",
156
+ fontSize: 16,
157
+ fontWeight: "600",
158
+ }}
159
+ >
160
+ {loading ? "Creating account..." : "Sign Up"}
161
+ </Text>
162
+ </Pressable>
163
+
164
+ <Pressable
165
+ onPress={() => router.back()}
166
+ style={{ marginTop: 16, alignItems: "center" }}
167
+ accessibilityLabel="Go to sign in screen"
168
+ accessibilityRole="button"
169
+ accessibilityHint="Opens the sign in screen"
170
+ >
171
+ <Text style={{ color: mutedColor }}>
172
+ Already have an account?{" "}
173
+ <Text style={{ fontWeight: "600", color: textColor }}>Sign In</Text>
174
+ </Text>
175
+ </Pressable>
176
+ </View>
177
+ );
178
+ }
@@ -0,0 +1,32 @@
1
+ import { Link, Stack } from "expo-router";
2
+ import { Text, View } from "react-native";
3
+ import { useThemeTokens } from "../lib/theme-tokens";
4
+
5
+ export default function NotFoundScreen() {
6
+ const { bgColor, textColor, mutedColor } = useThemeTokens();
7
+
8
+ return (
9
+ <>
10
+ <Stack.Screen options={{ title: "Oops!" }} />
11
+ <View
12
+ style={{
13
+ flex: 1,
14
+ justifyContent: "center",
15
+ alignItems: "center",
16
+ padding: 20,
17
+ backgroundColor: bgColor,
18
+ }}
19
+ >
20
+ <Text style={{ fontSize: 20, fontWeight: "bold", color: textColor }}>
21
+ Page not found
22
+ </Text>
23
+ <Text style={{ marginTop: 8, color: mutedColor }}>
24
+ This screen doesn't exist.
25
+ </Text>
26
+ <Link href="/" style={{ marginTop: 16 }}>
27
+ <Text style={{ color: "#007AFF" }}>Go to home screen</Text>
28
+ </Link>
29
+ </View>
30
+ </>
31
+ );
32
+ }
@@ -0,0 +1,18 @@
1
+ import { Slot } from "expo-router";
2
+ import { StatusBar } from "expo-status-bar";
3
+ import { SafeAreaProvider } from "react-native-safe-area-context";
4
+ import { ThemeProvider } from "../components/ThemeProvider";
5
+ import { useAuthInit } from "../hooks/use-auth";
6
+
7
+ export default function RootLayout() {
8
+ useAuthInit();
9
+
10
+ return (
11
+ <SafeAreaProvider>
12
+ <ThemeProvider>
13
+ <StatusBar style="auto" />
14
+ <Slot />
15
+ </ThemeProvider>
16
+ </SafeAreaProvider>
17
+ );
18
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "expo": {
3
+ "name": "__QUARK_APP_NAME__",
4
+ "slug": "__QUARK_PROJECT_NAME__",
5
+ "scheme": "__QUARK_PROJECT_NAME__",
6
+ "version": "1.0.0",
7
+ "orientation": "portrait",
8
+ "icon": "./assets/icon.png",
9
+ "userInterfaceStyle": "automatic",
10
+ "newArchEnabled": true,
11
+ "splash": {
12
+ "image": "./assets/splash.png",
13
+ "resizeMode": "contain",
14
+ "backgroundColor": "#ffffff"
15
+ },
16
+ "ios": {
17
+ "supportsTablet": true,
18
+ "bundleIdentifier": "com.__QUARK_PROJECT_NAME__.app",
19
+ "config": {
20
+ "usesNonExemptEncryption": false
21
+ }
22
+ },
23
+ "android": {
24
+ "adaptiveIcon": {
25
+ "foregroundImage": "./assets/adaptive-icon.png",
26
+ "backgroundColor": "#ffffff"
27
+ },
28
+ "package": "com.__QUARK_PROJECT_NAME__.app"
29
+ },
30
+ "plugins": [
31
+ "expo-router",
32
+ "expo-secure-store"
33
+ ],
34
+ "updates": {
35
+ "url": "https://u.expo.dev/__QUARK_PROJECT_NAME__",
36
+ "fallbackToCacheTimeout": 0
37
+ },
38
+ "runtimeVersion": {
39
+ "policy": "appVersion"
40
+ },
41
+ "extra": {
42
+ "eas": {
43
+ "projectId": "__QUARK_EAS_PROJECT_ID__"
44
+ }
45
+ }
46
+ }
47
+ }
Binary file
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": ["**", "!**/dist", "!**/.expo"]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "tab"
15
+ },
16
+ "linter": {
17
+ "enabled": true,
18
+ "rules": {
19
+ "recommended": true
20
+ }
21
+ },
22
+ "javascript": {
23
+ "formatter": {
24
+ "quoteStyle": "double"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ import { type ReactNode, useEffect } from "react";
2
+ import { useAuth } from "../hooks/use-auth";
3
+
4
+ interface AuthGateProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ /**
9
+ * Wraps authenticated screens and ensures push token is registered.
10
+ */
11
+ export function AuthGate({ children }: AuthGateProps) {
12
+ const { registerPushToken } = useAuth();
13
+
14
+ useEffect(() => {
15
+ registerPushToken();
16
+ }, [registerPushToken]);
17
+
18
+ return <>{children}</>;
19
+ }
@@ -0,0 +1,23 @@
1
+ import { createContext, type ReactNode, useContext } from "react";
2
+ import { useColorScheme } from "react-native";
3
+
4
+ type Theme = "light" | "dark";
5
+
6
+ const ThemeContext = createContext<Theme>("light");
7
+
8
+ export function useTheme(): Theme {
9
+ return useContext(ThemeContext);
10
+ }
11
+
12
+ interface ThemeProviderProps {
13
+ children: ReactNode;
14
+ }
15
+
16
+ export function ThemeProvider({ children }: ThemeProviderProps) {
17
+ const colorScheme = useColorScheme();
18
+ const theme: Theme = colorScheme === "dark" ? "dark" : "light";
19
+
20
+ return (
21
+ <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
22
+ );
23
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "cli": {
3
+ "version": ">= 15.0.0"
4
+ },
5
+ "build": {
6
+ "development": {
7
+ "developmentClient": true,
8
+ "distribution": "internal",
9
+ "android": {
10
+ "buildType": "apk"
11
+ }
12
+ },
13
+ "preview": {
14
+ "distribution": "internal",
15
+ "android": {
16
+ "buildType": "apk"
17
+ }
18
+ },
19
+ "production": {
20
+ "autoIncrement": true,
21
+ "android": {
22
+ "buildType": "app-bundle"
23
+ }
24
+ }
25
+ },
26
+ "submit": {
27
+ "production": {}
28
+ }
29
+ }
@@ -0,0 +1,127 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import {
3
+ signIn as authSignIn,
4
+ signOut as authSignOut,
5
+ isAuthenticated as checkAuth,
6
+ } from "../lib/auth";
7
+
8
+ interface AuthState {
9
+ isAuthenticated: boolean;
10
+ isLoading: boolean;
11
+ signIn: (email: string, password: string) => Promise<void>;
12
+ signOut: () => Promise<void>;
13
+ registerPushToken: () => Promise<void>;
14
+ }
15
+
16
+ let authStateListeners: Array<() => void> = [];
17
+ let globalAuthState: AuthState = {
18
+ isAuthenticated: false,
19
+ isLoading: true,
20
+ signIn: async () => {},
21
+ signOut: async () => {},
22
+ registerPushToken: async () => {},
23
+ };
24
+
25
+ function notifyListeners() {
26
+ for (const listener of authStateListeners) {
27
+ listener();
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Initialize auth state on app launch.
33
+ */
34
+ export function useAuthInit() {
35
+ const [, setState] = useState<AuthState>(globalAuthState);
36
+
37
+ useEffect(() => {
38
+ import("../lib/notifications")
39
+ .then((m) => m.configureNotificationHandler())
40
+ .catch(() => {});
41
+
42
+ (async () => {
43
+ try {
44
+ const authenticated = await checkAuth();
45
+ globalAuthState = {
46
+ ...globalAuthState,
47
+ isAuthenticated: authenticated,
48
+ isLoading: false,
49
+ };
50
+ notifyListeners();
51
+ } catch {
52
+ globalAuthState = {
53
+ ...globalAuthState,
54
+ isAuthenticated: false,
55
+ isLoading: false,
56
+ };
57
+ notifyListeners();
58
+ }
59
+ })();
60
+
61
+ const listener = () => {
62
+ setState({ ...globalAuthState });
63
+ };
64
+ authStateListeners.push(listener);
65
+
66
+ return () => {
67
+ authStateListeners = authStateListeners.filter((l) => l !== listener);
68
+ };
69
+ }, []);
70
+ }
71
+
72
+ /**
73
+ * Hook to access auth state and actions.
74
+ */
75
+ export function useAuth(): AuthState {
76
+ const [state, setState] = useState<AuthState>(globalAuthState);
77
+
78
+ useEffect(() => {
79
+ setState({ ...globalAuthState });
80
+ const listener = () => setState({ ...globalAuthState });
81
+ authStateListeners.push(listener);
82
+ return () => {
83
+ authStateListeners = authStateListeners.filter((l) => l !== listener);
84
+ };
85
+ }, []);
86
+
87
+ const signIn = useCallback(async (email: string, password: string) => {
88
+ await authSignIn(email, password);
89
+ globalAuthState = {
90
+ ...globalAuthState,
91
+ isAuthenticated: true,
92
+ isLoading: false,
93
+ };
94
+ notifyListeners();
95
+ }, []);
96
+
97
+ const signOut = useCallback(async () => {
98
+ await authSignOut();
99
+ globalAuthState = {
100
+ ...globalAuthState,
101
+ isAuthenticated: false,
102
+ isLoading: false,
103
+ };
104
+ notifyListeners();
105
+ }, []);
106
+
107
+ const registerPushToken = useCallback(async () => {
108
+ try {
109
+ const { registerForPushNotifications, registerDevice } = await import(
110
+ "../lib/notifications"
111
+ );
112
+ const token = await registerForPushNotifications();
113
+ if (token) {
114
+ await registerDevice(token);
115
+ }
116
+ } catch {
117
+ // Non-critical - don't block auth flow
118
+ }
119
+ }, []);
120
+
121
+ return {
122
+ ...state,
123
+ signIn,
124
+ signOut,
125
+ registerPushToken,
126
+ };
127
+ }