create-100x-mobile 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const prompts_1 = require("@clack/prompts");
5
+ const new_1 = require("./commands/new");
6
+ function printUsage() {
7
+ console.log(`create-100x-mobile
8
+
9
+ Usage:
10
+ bunx create-100x-mobile [name]
11
+ bun create 100x-mobile [name]
12
+ npx create-100x-mobile [name]
13
+
14
+ Scaffolds a full-stack mobile app with Expo + Convex + Clerk.
15
+ `);
16
+ }
17
+ async function main() {
18
+ const args = process.argv.slice(2);
19
+ const command = args[0];
20
+ if (command === "-h" || command === "--help" || command === "help") {
21
+ printUsage();
22
+ return;
23
+ }
24
+ if (command === "-v" || command === "--version" || command === "version") {
25
+ const pkg = require("../package.json");
26
+ console.log(pkg.version ?? "0.0.0");
27
+ return;
28
+ }
29
+ // Default action: scaffold a new app
30
+ // If first arg looks like a flag, show help; otherwise treat as app name
31
+ if (command && command.startsWith("-")) {
32
+ prompts_1.log.error(`Unknown flag: ${command}`);
33
+ printUsage();
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+ await (0, new_1.cmdNew)(args);
38
+ }
39
+ main().catch((err) => {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ prompts_1.log.error(message);
42
+ process.exitCode = 1;
43
+ });
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cmdNew = cmdNew;
7
+ const node_path_1 = require("node:path");
8
+ const prompts_1 = require("@clack/prompts");
9
+ const picocolors_1 = __importDefault(require("picocolors"));
10
+ const fs_1 = require("../lib/fs");
11
+ const projectName_1 = require("../lib/projectName");
12
+ const run_1 = require("../lib/run");
13
+ const dotenv_1 = require("../lib/dotenv");
14
+ const fs_2 = require("../lib/fs");
15
+ // Config templates
16
+ const packageJson_1 = require("../templates/config/packageJson");
17
+ const appJson_1 = require("../templates/config/appJson");
18
+ const tsconfig_1 = require("../templates/config/tsconfig");
19
+ const gitignore_1 = require("../templates/config/gitignore");
20
+ const envExample_1 = require("../templates/config/envExample");
21
+ // Convex templates
22
+ const schema_1 = require("../templates/convex/schema");
23
+ const todos_1 = require("../templates/convex/todos");
24
+ const users_1 = require("../templates/convex/users");
25
+ const authConfig_1 = require("../templates/convex/authConfig");
26
+ // App templates
27
+ const rootLayout_1 = require("../templates/app/rootLayout");
28
+ const notFound_1 = require("../templates/app/notFound");
29
+ const authProvider_1 = require("../templates/app/authProvider");
30
+ const authLayout_1 = require("../templates/app/authLayout");
31
+ const signIn_1 = require("../templates/app/signIn");
32
+ const signUp_1 = require("../templates/app/signUp");
33
+ const tabsLayout_1 = require("../templates/app/tabsLayout");
34
+ const todosScreen_1 = require("../templates/app/todosScreen");
35
+ const settingsScreen_1 = require("../templates/app/settingsScreen");
36
+ // Component templates
37
+ const addTodoForm_1 = require("../templates/components/addTodoForm");
38
+ const todoItem_1 = require("../templates/components/todoItem");
39
+ const filterTabs_1 = require("../templates/components/filterTabs");
40
+ const emptyState_1 = require("../templates/components/emptyState");
41
+ // Hook templates
42
+ const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
43
+ async function cmdNew(args) {
44
+ (0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
45
+ // Step 1: Parse & validate project name
46
+ let projectName = args[0];
47
+ if (!projectName) {
48
+ const nameInput = await (0, prompts_1.text)({
49
+ message: "What is your project name?",
50
+ placeholder: "my-app",
51
+ defaultValue: "my-app",
52
+ });
53
+ if ((0, prompts_1.isCancel)(nameInput) || !nameInput) {
54
+ (0, prompts_1.cancel)("Operation cancelled.");
55
+ return;
56
+ }
57
+ projectName = nameInput;
58
+ }
59
+ const sanitizedName = (0, projectName_1.sanitizeProjectName)(projectName);
60
+ if (sanitizedName !== projectName) {
61
+ prompts_1.log.warn(`Project name sanitized: "${projectName}" → "${sanitizedName}"`);
62
+ projectName = sanitizedName;
63
+ }
64
+ if (!projectName) {
65
+ prompts_1.log.error("Invalid project name.");
66
+ process.exitCode = 1;
67
+ return;
68
+ }
69
+ const projectDir = (0, node_path_1.resolve)(process.cwd(), projectName);
70
+ // Check if directory exists
71
+ if (await (0, fs_1.pathExists)(projectDir)) {
72
+ prompts_1.log.warn(`Directory "${projectName}" already exists.`);
73
+ const choice = await (0, prompts_1.select)({
74
+ message: "What would you like to do?",
75
+ options: [
76
+ { value: "overwrite", label: "Overwrite the existing directory" },
77
+ { value: "cancel", label: "Cancel" },
78
+ ],
79
+ initialValue: "cancel",
80
+ });
81
+ if ((0, prompts_1.isCancel)(choice) || choice === "cancel") {
82
+ (0, prompts_1.cancel)("Operation cancelled.");
83
+ return;
84
+ }
85
+ await (0, fs_1.removeDir)(projectDir);
86
+ }
87
+ // Step 2: Create directories
88
+ const s = (0, prompts_1.spinner)();
89
+ s.start("Creating project structure");
90
+ const dirs = [
91
+ "app/(auth)",
92
+ "app/(tabs)",
93
+ "app/providers",
94
+ "components",
95
+ "convex",
96
+ "hooks",
97
+ "assets/images",
98
+ ];
99
+ for (const dir of dirs) {
100
+ await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
101
+ }
102
+ // Step 3: Write all template files
103
+ const files = [
104
+ // Config
105
+ ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName)],
106
+ ["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
107
+ ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
108
+ [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
109
+ [".env.example", (0, envExample_1.envExampleTemplate)()],
110
+ ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
111
+ // Convex
112
+ ["convex/schema.ts", (0, schema_1.schemaTemplate)()],
113
+ ["convex/todos.ts", (0, todos_1.todosTemplate)()],
114
+ ["convex/users.ts", (0, users_1.usersTemplate)()],
115
+ ["convex/auth.config.ts", (0, authConfig_1.authConfigTemplate)()],
116
+ ["convex/tsconfig.json", (0, authConfig_1.convexTsconfigTemplate)()],
117
+ ["convex/http.ts", (0, authConfig_1.httpTemplate)()],
118
+ // App
119
+ ["app/_layout.tsx", (0, rootLayout_1.rootLayoutTemplate)()],
120
+ ["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
121
+ ["app/providers/AuthProvider.tsx", (0, authProvider_1.authProviderTemplate)()],
122
+ ["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
123
+ ["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
124
+ ["app/(auth)/sign-up.tsx", (0, signUp_1.signUpTemplate)()],
125
+ ["app/(tabs)/_layout.tsx", (0, tabsLayout_1.tabsLayoutTemplate)()],
126
+ ["app/(tabs)/index.tsx", (0, todosScreen_1.todosScreenTemplate)()],
127
+ ["app/(tabs)/settings.tsx", (0, settingsScreen_1.settingsScreenTemplate)()],
128
+ // Components
129
+ ["components/AddTodoForm.tsx", (0, addTodoForm_1.addTodoFormTemplate)()],
130
+ ["components/TodoItem.tsx", (0, todoItem_1.todoItemTemplate)()],
131
+ ["components/FilterTabs.tsx", (0, filterTabs_1.filterTabsTemplate)()],
132
+ ["components/EmptyState.tsx", (0, emptyState_1.emptyStateTemplate)()],
133
+ // Hooks
134
+ ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
135
+ ];
136
+ for (const [filePath, content] of files) {
137
+ await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, filePath), content);
138
+ }
139
+ s.stop("Project files created.");
140
+ // Step 4: Install dependencies
141
+ s.start("Installing dependencies");
142
+ try {
143
+ await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
144
+ }
145
+ catch {
146
+ prompts_1.log.warn("bun install failed, trying npm install...");
147
+ try {
148
+ await (0, run_1.run)("npm", ["install"], { cwd: projectDir });
149
+ }
150
+ catch {
151
+ prompts_1.log.error("Failed to install dependencies. Run `bun install` or `npm install` manually.");
152
+ }
153
+ }
154
+ s.stop("Dependencies installed.");
155
+ // Step 5: Initialize Convex
156
+ s.start("Setting up Convex");
157
+ try {
158
+ await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
159
+ s.stop("Convex initialized.");
160
+ }
161
+ catch {
162
+ s.stop("Convex setup needs manual configuration.");
163
+ prompts_1.log.warn("Convex initialization failed. Run `bunx convex dev --once` in your project directory.");
164
+ }
165
+ // Step 6: Prompt for Clerk keys
166
+ prompts_1.log.info("");
167
+ prompts_1.log.info(picocolors_1.default.bold("Clerk Authentication Setup"));
168
+ prompts_1.log.info(picocolors_1.default.dim("Get your keys from https://dashboard.clerk.com → Your App → API Keys"));
169
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
170
+ const clerkKey = await (0, prompts_1.text)({
171
+ message: "Clerk Publishable Key",
172
+ placeholder: "pk_test_...",
173
+ defaultValue: "",
174
+ });
175
+ const clerkKeyValue = (0, prompts_1.isCancel)(clerkKey) || !clerkKey ? "" : clerkKey.trim();
176
+ let clerkDomain = "";
177
+ if (clerkKeyValue) {
178
+ const domainInput = await (0, prompts_1.text)({
179
+ message: "Clerk JWT Issuer Domain",
180
+ placeholder: "https://your-app.clerk.accounts.dev",
181
+ defaultValue: "",
182
+ });
183
+ clerkDomain =
184
+ (0, prompts_1.isCancel)(domainInput) || !domainInput ? "" : domainInput.trim();
185
+ }
186
+ // Step 7: Write env vars
187
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
188
+ let envContents = "";
189
+ // Read existing .env.local (Convex may have written EXPO_PUBLIC_CONVEX_URL)
190
+ try {
191
+ envContents = await (0, fs_2.readTextFile)(envLocalPath);
192
+ }
193
+ catch {
194
+ envContents = "";
195
+ }
196
+ if (clerkKeyValue) {
197
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", clerkKeyValue);
198
+ }
199
+ if (clerkDomain) {
200
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "CLERK_JWT_ISSUER_DOMAIN", clerkDomain);
201
+ }
202
+ if (envContents) {
203
+ await (0, fs_1.writeTextFile)(envLocalPath, envContents);
204
+ }
205
+ // Step 8: Set Convex env var for Clerk
206
+ if (clerkDomain) {
207
+ s.start("Setting Convex environment variable");
208
+ try {
209
+ await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
210
+ s.stop("Convex environment variable set.");
211
+ }
212
+ catch {
213
+ s.stop("Could not set Convex env var.");
214
+ prompts_1.log.warn(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`);
215
+ }
216
+ }
217
+ // Step 9: Push Convex functions (if Clerk was configured)
218
+ if (clerkKeyValue && clerkDomain) {
219
+ s.start("Deploying Convex functions");
220
+ try {
221
+ await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
222
+ s.stop("Convex functions deployed.");
223
+ }
224
+ catch {
225
+ s.stop("Convex deployment needs retry.");
226
+ prompts_1.log.warn("Run `bunx convex dev --once` in your project directory.");
227
+ }
228
+ }
229
+ // Step 10: Success message
230
+ prompts_1.log.info("");
231
+ if (clerkKeyValue && clerkDomain) {
232
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
233
+ prompts_1.log.info("");
234
+ prompts_1.log.info(" Next steps:");
235
+ prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
236
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
237
+ prompts_1.log.info("");
238
+ prompts_1.log.info(picocolors_1.default.dim('Important: In your Clerk dashboard, create a JWT template named "convex"'));
239
+ prompts_1.log.info(picocolors_1.default.dim("with issuer set to your Convex deployment URL."));
240
+ }
241
+ else {
242
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded!")));
243
+ prompts_1.log.info("");
244
+ prompts_1.log.info(" To finish setup:");
245
+ prompts_1.log.info("");
246
+ prompts_1.log.info(` 1. Get your Clerk keys from ${picocolors_1.default.cyan("https://dashboard.clerk.com")}`);
247
+ prompts_1.log.info(` 2. Add them to ${picocolors_1.default.cyan(".env.local")}:`);
248
+ prompts_1.log.info(` ${picocolors_1.default.dim("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...")}`);
249
+ prompts_1.log.info(` ${picocolors_1.default.dim("CLERK_JWT_ISSUER_DOMAIN=https://your-app.clerk.accounts.dev")}`);
250
+ prompts_1.log.info(` 3. Set the Convex env var:`);
251
+ prompts_1.log.info(` ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
252
+ prompts_1.log.info(` 4. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
253
+ prompts_1.log.info(` 5. Deploy Convex: ${picocolors_1.default.dim("bunx convex dev --once")}`);
254
+ prompts_1.log.info("");
255
+ prompts_1.log.info(" Then start your app:");
256
+ prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
257
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
258
+ }
259
+ (0, prompts_1.outro)(picocolors_1.default.dim("Happy building!"));
260
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseDotenv = parseDotenv;
4
+ exports.upsertDotenvVar = upsertDotenvVar;
5
+ const LINE_SPLIT_REGEX = /\r?\n/;
6
+ const TRAILING_NEWLINES_REGEX = /\n+$/;
7
+ function parseDotenv(contents) {
8
+ const out = {};
9
+ for (const line of contents.split(LINE_SPLIT_REGEX)) {
10
+ const trimmed = line.trim();
11
+ if (!trimmed || trimmed.startsWith("#")) {
12
+ continue;
13
+ }
14
+ const idx = trimmed.indexOf("=");
15
+ if (idx === -1) {
16
+ continue;
17
+ }
18
+ const key = trimmed.slice(0, idx).trim();
19
+ let value = trimmed.slice(idx + 1).trim();
20
+ if ((value.startsWith('"') && value.endsWith('"')) ||
21
+ (value.startsWith("'") && value.endsWith("'"))) {
22
+ value = value.slice(1, -1);
23
+ }
24
+ if (key) {
25
+ out[key] = value;
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function upsertDotenvVar(contents, key, value) {
31
+ const lines = contents ? contents.split(LINE_SPLIT_REGEX) : [];
32
+ const newLine = `${key}=${value}`;
33
+ let found = false;
34
+ for (let i = 0; i < lines.length; i++) {
35
+ if (lines[i].startsWith(`${key}=`)) {
36
+ lines[i] = newLine;
37
+ found = true;
38
+ break;
39
+ }
40
+ }
41
+ if (!found) {
42
+ if (lines.length && lines.at(-1) !== "") {
43
+ lines.push("");
44
+ }
45
+ lines.push(newLine);
46
+ }
47
+ return lines.join("\n").replace(TRAILING_NEWLINES_REGEX, "\n");
48
+ }
package/dist/lib/fs.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathExists = pathExists;
4
+ exports.ensureDir = ensureDir;
5
+ exports.removeDir = removeDir;
6
+ exports.readTextFile = readTextFile;
7
+ exports.writeTextFile = writeTextFile;
8
+ exports.writeTextFileIfChanged = writeTextFileIfChanged;
9
+ const promises_1 = require("node:fs/promises");
10
+ const node_path_1 = require("node:path");
11
+ async function pathExists(p) {
12
+ try {
13
+ await (0, promises_1.stat)(p);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ async function ensureDir(dirPath) {
21
+ await (0, promises_1.mkdir)(dirPath, { recursive: true });
22
+ }
23
+ async function removeDir(dirPath) {
24
+ await (0, promises_1.rm)(dirPath, { recursive: true, force: true });
25
+ }
26
+ async function readTextFile(filePath) {
27
+ return await (0, promises_1.readFile)(filePath, "utf8");
28
+ }
29
+ async function writeTextFile(filePath, contents) {
30
+ await ensureDir((0, node_path_1.dirname)(filePath));
31
+ await (0, promises_1.writeFile)(filePath, contents);
32
+ }
33
+ async function writeTextFileIfChanged(filePath, contents) {
34
+ const exists = await pathExists(filePath);
35
+ if (exists) {
36
+ const current = await readTextFile(filePath);
37
+ if (current === contents) {
38
+ return;
39
+ }
40
+ }
41
+ await writeTextFile(filePath, contents);
42
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeProjectName = sanitizeProjectName;
4
+ function sanitizeProjectName(name) {
5
+ return name
6
+ .replace(/[^a-z0-9-]/gi, "-")
7
+ .replace(/-+/g, "-")
8
+ .replace(/^-|-$/g, "")
9
+ .toLowerCase();
10
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = run;
4
+ const node_child_process_1 = require("node:child_process");
5
+ async function run(cmd, args, options = {}) {
6
+ await new Promise((resolve, reject) => {
7
+ const child = (0, node_child_process_1.spawn)(cmd, args, {
8
+ cwd: options.cwd,
9
+ env: options.env ?? process.env,
10
+ stdio: "inherit",
11
+ shell: process.platform === "win32",
12
+ });
13
+ child.on("error", reject);
14
+ child.on("exit", (code, signal) => {
15
+ if (code === 0) {
16
+ return resolve();
17
+ }
18
+ reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code ?? "null"} signal ${signal ?? "null"}`));
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authLayoutTemplate = authLayoutTemplate;
4
+ function authLayoutTemplate() {
5
+ return `import { Stack } from "expo-router";
6
+
7
+ export default function AuthLayout() {
8
+ return (
9
+ <Stack>
10
+ <Stack.Screen
11
+ name="sign-in"
12
+ options={{
13
+ title: "Sign In",
14
+ headerBackTitle: "Back",
15
+ }}
16
+ />
17
+ <Stack.Screen
18
+ name="sign-up"
19
+ options={{
20
+ title: "Sign Up",
21
+ headerBackTitle: "Back",
22
+ }}
23
+ />
24
+ </Stack>
25
+ );
26
+ }
27
+ `;
28
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authProviderTemplate = authProviderTemplate;
4
+ function authProviderTemplate() {
5
+ return `import React from "react";
6
+ import { ClerkProvider, ClerkLoaded, useAuth } from "@clerk/clerk-expo";
7
+ import { ConvexReactClient } from "convex/react";
8
+ import { ConvexProviderWithClerk } from "convex/react-clerk";
9
+ import * as SecureStore from "expo-secure-store";
10
+ import { View, Text, StyleSheet } from "react-native";
11
+
12
+ const convexUrl = process.env.EXPO_PUBLIC_CONVEX_URL;
13
+ const isConvexConfigured = convexUrl && convexUrl.startsWith("https://");
14
+
15
+ const convex = isConvexConfigured ? new ConvexReactClient(convexUrl) : null;
16
+
17
+ const tokenCache = {
18
+ async getToken(key: string) {
19
+ try {
20
+ return await SecureStore.getItemAsync(key);
21
+ } catch (error) {
22
+ console.error("SecureStore getToken error:", error);
23
+ return null;
24
+ }
25
+ },
26
+ async saveToken(key: string, value: string) {
27
+ try {
28
+ await SecureStore.setItemAsync(key, value);
29
+ } catch (error) {
30
+ console.error("SecureStore saveToken error:", error);
31
+ }
32
+ },
33
+ async deleteToken(key: string) {
34
+ try {
35
+ await SecureStore.deleteItemAsync(key);
36
+ } catch (error) {
37
+ console.error("SecureStore deleteToken error:", error);
38
+ }
39
+ },
40
+ };
41
+
42
+ function ConvexProviderWithAuth({ children }: { children: React.ReactNode }) {
43
+ if (!convex) {
44
+ return (
45
+ <View style={styles.setupContainer}>
46
+ <View style={styles.setupCard}>
47
+ <Text style={styles.setupTitle}>Setup Required</Text>
48
+ <Text style={styles.setupText}>
49
+ Configure Convex and Clerk to use this app.
50
+ </Text>
51
+ <Text style={styles.setupStep}>
52
+ 1. Set up your Convex project{"\\n"}
53
+ 2. Set up your Clerk application{"\\n"}
54
+ 3. Add environment variables to .env.local
55
+ </Text>
56
+ </View>
57
+ </View>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
63
+ {children}
64
+ </ConvexProviderWithClerk>
65
+ );
66
+ }
67
+
68
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
69
+ const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
70
+
71
+ if (!publishableKey) {
72
+ return (
73
+ <View style={styles.setupContainer}>
74
+ <View style={styles.setupCard}>
75
+ <Text style={styles.setupTitle}>Setup Required</Text>
76
+ <Text style={styles.setupText}>
77
+ Configure Convex and Clerk to use this app.
78
+ </Text>
79
+ <Text style={styles.setupStep}>
80
+ 1. Set up your Convex project{"\\n"}
81
+ 2. Set up your Clerk application{"\\n"}
82
+ 3. Add environment variables to .env.local
83
+ </Text>
84
+ </View>
85
+ </View>
86
+ );
87
+ }
88
+
89
+ return (
90
+ <ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
91
+ <ClerkLoaded>
92
+ <ConvexProviderWithAuth>{children}</ConvexProviderWithAuth>
93
+ </ClerkLoaded>
94
+ </ClerkProvider>
95
+ );
96
+ }
97
+
98
+ export default AuthProvider;
99
+
100
+ const styles = StyleSheet.create({
101
+ setupContainer: {
102
+ flex: 1,
103
+ backgroundColor: "#f5f5f5",
104
+ justifyContent: "center",
105
+ alignItems: "center",
106
+ padding: 20,
107
+ },
108
+ setupCard: {
109
+ backgroundColor: "#fff",
110
+ borderRadius: 12,
111
+ padding: 24,
112
+ maxWidth: 400,
113
+ width: "100%",
114
+ shadowColor: "#000",
115
+ shadowOffset: { width: 0, height: 2 },
116
+ shadowOpacity: 0.1,
117
+ shadowRadius: 8,
118
+ elevation: 3,
119
+ },
120
+ setupTitle: {
121
+ fontSize: 24,
122
+ fontWeight: "bold",
123
+ color: "#333",
124
+ marginBottom: 16,
125
+ textAlign: "center",
126
+ },
127
+ setupText: {
128
+ fontSize: 16,
129
+ color: "#666",
130
+ marginBottom: 12,
131
+ lineHeight: 22,
132
+ textAlign: "center",
133
+ },
134
+ setupStep: {
135
+ fontSize: 14,
136
+ color: "#888",
137
+ marginTop: 16,
138
+ padding: 16,
139
+ backgroundColor: "#f8f9fa",
140
+ borderRadius: 8,
141
+ lineHeight: 20,
142
+ },
143
+ });
144
+ `;
145
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.notFoundTemplate = notFoundTemplate;
4
+ function notFoundTemplate() {
5
+ return `import { Link, Stack } from "expo-router";
6
+ import { StyleSheet, Text, View } from "react-native";
7
+
8
+ export default function NotFoundScreen() {
9
+ return (
10
+ <>
11
+ <Stack.Screen options={{ title: "Oops!" }} />
12
+ <View style={styles.container}>
13
+ <Text style={styles.text}>This screen doesn't exist.</Text>
14
+ <Link href="/" style={styles.link}>
15
+ <Text>Go to home screen!</Text>
16
+ </Link>
17
+ </View>
18
+ </>
19
+ );
20
+ }
21
+
22
+ const styles = StyleSheet.create({
23
+ container: {
24
+ flex: 1,
25
+ alignItems: "center",
26
+ justifyContent: "center",
27
+ padding: 20,
28
+ },
29
+ text: {
30
+ fontSize: 20,
31
+ fontWeight: "600",
32
+ },
33
+ link: {
34
+ marginTop: 15,
35
+ paddingVertical: 15,
36
+ },
37
+ });
38
+ `;
39
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rootLayoutTemplate = rootLayoutTemplate;
4
+ function rootLayoutTemplate() {
5
+ return `import { Stack } from "expo-router";
6
+ import { StatusBar } from "expo-status-bar";
7
+ import { useFrameworkReady } from "@/hooks/useFrameworkReady";
8
+ import { AuthProvider } from "./providers/AuthProvider";
9
+
10
+ export default function RootLayout() {
11
+ useFrameworkReady();
12
+
13
+ return (
14
+ <AuthProvider>
15
+ <Stack screenOptions={{ headerShown: false }}>
16
+ <Stack.Screen name="+not-found" />
17
+ </Stack>
18
+ <StatusBar style="auto" />
19
+ </AuthProvider>
20
+ );
21
+ }
22
+ `;
23
+ }