create-100x-mobile 0.4.3 → 0.4.5

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.
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantLibTemplate = instantLibTemplate;
4
+ function instantLibTemplate() {
5
+ return `import "react-native-get-random-values";
6
+ import { init, i, id, type InstaQLEntity } from "@instantdb/react-native";
7
+
8
+ const appId = process.env.EXPO_PUBLIC_INSTANT_APP_ID;
9
+
10
+ export const schema = i.schema({
11
+ entities: {
12
+ todos: i.entity({
13
+ text: i.string(),
14
+ completed: i.boolean(),
15
+ createdAt: i.number().indexed(),
16
+ }),
17
+ },
18
+ });
19
+
20
+ export type InstantTodo = InstaQLEntity<typeof schema, "todos">;
21
+ export const createId = id;
22
+
23
+ export const isInstantConfigured = Boolean(appId);
24
+ export const instantDb = isInstantConfigured
25
+ ? init({ appId: appId as string, schema })
26
+ : null;
27
+ `;
28
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantRootLayoutTemplate = instantRootLayoutTemplate;
4
+ function instantRootLayoutTemplate() {
5
+ return `import { Stack } from "expo-router";
6
+ import { StatusBar } from "expo-status-bar";
7
+ import { useFrameworkReady } from "@/hooks/useFrameworkReady";
8
+
9
+ export default function RootLayout() {
10
+ useFrameworkReady();
11
+
12
+ return (
13
+ <>
14
+ <Stack screenOptions={{ headerShown: false }}>
15
+ <Stack.Screen name="(tabs)" />
16
+ <Stack.Screen name="+not-found" />
17
+ </Stack>
18
+ <StatusBar style="auto" />
19
+ </>
20
+ );
21
+ }
22
+ `;
23
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantSettingsScreenTemplate = instantSettingsScreenTemplate;
4
+ function instantSettingsScreenTemplate() {
5
+ return `import React from "react";
6
+ import { View, Text, StyleSheet, StatusBar } from "react-native";
7
+ import { SafeAreaView } from "react-native-safe-area-context";
8
+
9
+ export default function SettingsScreen() {
10
+ const instantAppId = process.env.EXPO_PUBLIC_INSTANT_APP_ID;
11
+
12
+ return (
13
+ <SafeAreaView style={styles.container}>
14
+ <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
15
+ <View style={styles.header}>
16
+ <Text style={styles.title}>Settings</Text>
17
+ </View>
18
+
19
+ <View style={styles.content}>
20
+ <View style={styles.card}>
21
+ <Text style={styles.sectionTitle}>Backend</Text>
22
+ <Text style={styles.sectionText}>InstantDB</Text>
23
+ </View>
24
+
25
+ <View style={styles.card}>
26
+ <Text style={styles.sectionTitle}>Instant App ID</Text>
27
+ <Text style={styles.sectionText}>
28
+ {instantAppId || "Not configured (set EXPO_PUBLIC_INSTANT_APP_ID)"}
29
+ </Text>
30
+ </View>
31
+
32
+ <View style={styles.card}>
33
+ <Text style={styles.sectionTitle}>About</Text>
34
+ <Text style={styles.sectionText}>Version 1.0.0</Text>
35
+ <Text style={styles.sectionText}>Built with Expo + InstantDB</Text>
36
+ </View>
37
+ </View>
38
+ </SafeAreaView>
39
+ );
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ container: {
44
+ flex: 1,
45
+ backgroundColor: "#FFFFFF",
46
+ },
47
+ header: {
48
+ paddingHorizontal: 16,
49
+ paddingTop: 24,
50
+ paddingBottom: 16,
51
+ },
52
+ title: {
53
+ fontSize: 28,
54
+ fontWeight: "700",
55
+ color: "#1F2937",
56
+ },
57
+ content: {
58
+ paddingHorizontal: 16,
59
+ paddingBottom: 24,
60
+ gap: 12,
61
+ },
62
+ card: {
63
+ backgroundColor: "#F8F9FA",
64
+ borderRadius: 14,
65
+ padding: 16,
66
+ },
67
+ sectionTitle: {
68
+ fontSize: 14,
69
+ fontWeight: "600",
70
+ color: "#374151",
71
+ marginBottom: 8,
72
+ textTransform: "uppercase",
73
+ letterSpacing: 0.4,
74
+ },
75
+ sectionText: {
76
+ fontSize: 15,
77
+ color: "#1F2937",
78
+ marginBottom: 4,
79
+ lineHeight: 22,
80
+ },
81
+ });
82
+ `;
83
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantTabsLayoutTemplate = instantTabsLayoutTemplate;
4
+ function instantTabsLayoutTemplate() {
5
+ return `import { Tabs } from "expo-router";
6
+ import { SquareCheck as CheckSquare, Settings } from "lucide-react-native";
7
+ import { Platform } from "react-native";
8
+
9
+ export default function TabLayout() {
10
+ return (
11
+ <Tabs
12
+ screenOptions={{
13
+ headerShown: false,
14
+ tabBarStyle: {
15
+ backgroundColor: "#FFFFFF",
16
+ borderTopWidth: 0,
17
+ elevation: 0,
18
+ shadowOpacity: 0,
19
+ height: Platform.OS === "ios" ? 88 : 72,
20
+ paddingBottom: Platform.OS === "ios" ? 32 : 12,
21
+ paddingTop: 12,
22
+ },
23
+ tabBarActiveTintColor: "#1F2937",
24
+ tabBarInactiveTintColor: "#9CA3AF",
25
+ tabBarLabelStyle: {
26
+ fontSize: 13,
27
+ fontWeight: "500",
28
+ marginTop: 4,
29
+ },
30
+ }}
31
+ >
32
+ <Tabs.Screen
33
+ name="index"
34
+ options={{
35
+ title: "Todos",
36
+ tabBarIcon: ({ size, color }) => (
37
+ <CheckSquare size={size} color={color} strokeWidth={2} />
38
+ ),
39
+ }}
40
+ />
41
+ <Tabs.Screen
42
+ name="settings"
43
+ options={{
44
+ title: "Settings",
45
+ tabBarIcon: ({ size, color }) => (
46
+ <Settings size={size} color={color} strokeWidth={2} />
47
+ ),
48
+ }}
49
+ />
50
+ </Tabs>
51
+ );
52
+ }
53
+ `;
54
+ }
@@ -0,0 +1,408 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.instantTodosScreenTemplate = instantTodosScreenTemplate;
4
+ function instantTodosScreenTemplate() {
5
+ return `import React, { useMemo, useState } from "react";
6
+ import {
7
+ Alert,
8
+ FlatList,
9
+ KeyboardAvoidingView,
10
+ Platform,
11
+ Pressable,
12
+ StatusBar,
13
+ StyleSheet,
14
+ Text,
15
+ TextInput,
16
+ View,
17
+ } from "react-native";
18
+ import { SafeAreaView } from "react-native-safe-area-context";
19
+ import { Check, Plus, Trash2 } from "lucide-react-native";
20
+ import { createId, instantDb, type InstantTodo } from "@/lib/instant";
21
+
22
+ type FilterType = "all" | "active" | "completed";
23
+
24
+ function SetupRequired() {
25
+ return (
26
+ <View style={styles.setupContainer}>
27
+ <Text style={styles.setupTitle}>InstantDB Setup Required</Text>
28
+ <Text style={styles.setupText}>
29
+ Add EXPO_PUBLIC_INSTANT_APP_ID to your .env.local file.
30
+ </Text>
31
+ <Text style={styles.setupHint}>
32
+ You can create an app id with:\\n
33
+ npx instant-cli init-without-files --title my-app
34
+ </Text>
35
+ </View>
36
+ );
37
+ }
38
+
39
+ function InstantTodosScreen() {
40
+ const db = instantDb!;
41
+ const [text, setText] = useState("");
42
+ const [filter, setFilter] = useState<FilterType>("all");
43
+
44
+ const { isLoading, error, data } = db.useQuery({
45
+ todos: {
46
+ $: {
47
+ order: { createdAt: "desc" },
48
+ },
49
+ },
50
+ });
51
+
52
+ const todos = data?.todos ?? [];
53
+
54
+ const filteredTodos = useMemo(() => {
55
+ switch (filter) {
56
+ case "active":
57
+ return todos.filter((todo) => !todo.completed);
58
+ case "completed":
59
+ return todos.filter((todo) => todo.completed);
60
+ default:
61
+ return todos;
62
+ }
63
+ }, [filter, todos]);
64
+
65
+ const activeTodosCount = todos.filter((todo) => !todo.completed).length;
66
+ const completedTodosCount = todos.filter((todo) => todo.completed).length;
67
+
68
+ const addTodo = async () => {
69
+ const trimmed = text.trim();
70
+ if (!trimmed) return;
71
+
72
+ try {
73
+ await db.transact(
74
+ db.tx.todos[createId()].create({
75
+ text: trimmed,
76
+ completed: false,
77
+ createdAt: Date.now(),
78
+ })
79
+ );
80
+ setText("");
81
+ } catch (txnError) {
82
+ console.error("Failed to add todo:", txnError);
83
+ Alert.alert("Error", "Could not add todo.");
84
+ }
85
+ };
86
+
87
+ const toggleTodo = async (todo: InstantTodo) => {
88
+ try {
89
+ await db.transact(
90
+ db.tx.todos[todo.id].update({ completed: !todo.completed })
91
+ );
92
+ } catch (txnError) {
93
+ console.error("Failed to toggle todo:", txnError);
94
+ Alert.alert("Error", "Could not update todo.");
95
+ }
96
+ };
97
+
98
+ const removeTodo = async (todo: InstantTodo) => {
99
+ try {
100
+ await db.transact(db.tx.todos[todo.id].delete());
101
+ } catch (txnError) {
102
+ console.error("Failed to delete todo:", txnError);
103
+ Alert.alert("Error", "Could not delete todo.");
104
+ }
105
+ };
106
+
107
+ if (isLoading) {
108
+ return (
109
+ <View style={styles.loadingContainer}>
110
+ <Text style={styles.loadingText}>Loading todos...</Text>
111
+ </View>
112
+ );
113
+ }
114
+
115
+ if (error) {
116
+ return (
117
+ <View style={styles.loadingContainer}>
118
+ <Text style={styles.errorText}>Error: {error.message}</Text>
119
+ </View>
120
+ );
121
+ }
122
+
123
+ return (
124
+ <SafeAreaView style={styles.container}>
125
+ <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
126
+ <KeyboardAvoidingView
127
+ style={styles.keyboardAvoidingView}
128
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
129
+ >
130
+ <View style={styles.header}>
131
+ <Text style={styles.title}>My Tasks</Text>
132
+ <Text style={styles.subtitle}>
133
+ {activeTodosCount} active, {completedTodosCount} completed
134
+ </Text>
135
+ </View>
136
+
137
+ <View style={styles.addRow}>
138
+ <TextInput
139
+ style={styles.input}
140
+ placeholder="Add a new task..."
141
+ placeholderTextColor="#9CA3AF"
142
+ value={text}
143
+ onChangeText={setText}
144
+ onSubmitEditing={addTodo}
145
+ returnKeyType="done"
146
+ />
147
+ <Pressable
148
+ style={[styles.addButton, !text.trim() && styles.addButtonDisabled]}
149
+ onPress={addTodo}
150
+ disabled={!text.trim()}
151
+ >
152
+ <Plus size={18} color={text.trim() ? "#FFFFFF" : "#9CA3AF"} />
153
+ </Pressable>
154
+ </View>
155
+
156
+ <View style={styles.filters}>
157
+ {(["all", "active", "completed"] as const).map((key) => (
158
+ <Pressable
159
+ key={key}
160
+ style={[styles.filterPill, filter === key && styles.filterPillActive]}
161
+ onPress={() => setFilter(key)}
162
+ >
163
+ <Text
164
+ style={[
165
+ styles.filterText,
166
+ filter === key && styles.filterTextActive,
167
+ ]}
168
+ >
169
+ {key === "completed" ? "done" : key}
170
+ </Text>
171
+ </Pressable>
172
+ ))}
173
+ </View>
174
+
175
+ <FlatList
176
+ data={filteredTodos}
177
+ keyExtractor={(item) => item.id}
178
+ contentContainerStyle={styles.listContent}
179
+ ListEmptyComponent={
180
+ <View style={styles.emptyState}>
181
+ <Text style={styles.emptyTitle}>No tasks yet</Text>
182
+ <Text style={styles.emptySubtitle}>
183
+ Add your first task above to get started.
184
+ </Text>
185
+ </View>
186
+ }
187
+ renderItem={({ item }) => (
188
+ <View style={styles.todoRow}>
189
+ <Pressable
190
+ style={[
191
+ styles.checkbox,
192
+ item.completed && styles.checkboxCompleted,
193
+ ]}
194
+ onPress={() => toggleTodo(item)}
195
+ >
196
+ {item.completed ? (
197
+ <Check size={14} color="#FFFFFF" strokeWidth={3} />
198
+ ) : null}
199
+ </Pressable>
200
+ <Text
201
+ style={[
202
+ styles.todoText,
203
+ item.completed && styles.todoTextCompleted,
204
+ ]}
205
+ >
206
+ {item.text}
207
+ </Text>
208
+ <Pressable style={styles.deleteButton} onPress={() => removeTodo(item)}>
209
+ <Trash2 size={18} color="#9CA3AF" />
210
+ </Pressable>
211
+ </View>
212
+ )}
213
+ />
214
+ </KeyboardAvoidingView>
215
+ </SafeAreaView>
216
+ );
217
+ }
218
+
219
+ export default function TodosScreen() {
220
+ if (!instantDb) {
221
+ return (
222
+ <SafeAreaView style={styles.container}>
223
+ <SetupRequired />
224
+ </SafeAreaView>
225
+ );
226
+ }
227
+ return <InstantTodosScreen />;
228
+ }
229
+
230
+ const styles = StyleSheet.create({
231
+ container: {
232
+ flex: 1,
233
+ backgroundColor: "#FFFFFF",
234
+ },
235
+ keyboardAvoidingView: {
236
+ flex: 1,
237
+ },
238
+ header: {
239
+ paddingHorizontal: 16,
240
+ paddingTop: 24,
241
+ paddingBottom: 16,
242
+ },
243
+ title: {
244
+ fontSize: 28,
245
+ fontWeight: "700",
246
+ color: "#1F2937",
247
+ marginBottom: 4,
248
+ },
249
+ subtitle: {
250
+ fontSize: 14,
251
+ color: "#6B7280",
252
+ fontWeight: "500",
253
+ },
254
+ addRow: {
255
+ paddingHorizontal: 16,
256
+ flexDirection: "row",
257
+ alignItems: "center",
258
+ gap: 8,
259
+ },
260
+ input: {
261
+ flex: 1,
262
+ height: 52,
263
+ borderWidth: 1,
264
+ borderColor: "#E5E7EB",
265
+ borderRadius: 12,
266
+ paddingHorizontal: 14,
267
+ fontSize: 16,
268
+ color: "#1F2937",
269
+ },
270
+ addButton: {
271
+ width: 44,
272
+ height: 44,
273
+ borderRadius: 12,
274
+ backgroundColor: "#1F2937",
275
+ alignItems: "center",
276
+ justifyContent: "center",
277
+ },
278
+ addButtonDisabled: {
279
+ backgroundColor: "#E5E7EB",
280
+ },
281
+ filters: {
282
+ flexDirection: "row",
283
+ paddingHorizontal: 16,
284
+ paddingTop: 14,
285
+ paddingBottom: 10,
286
+ gap: 8,
287
+ },
288
+ filterPill: {
289
+ paddingHorizontal: 12,
290
+ paddingVertical: 8,
291
+ borderRadius: 999,
292
+ backgroundColor: "#F3F4F6",
293
+ },
294
+ filterPillActive: {
295
+ backgroundColor: "#111827",
296
+ },
297
+ filterText: {
298
+ color: "#6B7280",
299
+ fontWeight: "600",
300
+ textTransform: "capitalize",
301
+ },
302
+ filterTextActive: {
303
+ color: "#FFFFFF",
304
+ },
305
+ listContent: {
306
+ paddingHorizontal: 16,
307
+ paddingBottom: 96,
308
+ flexGrow: 1,
309
+ },
310
+ todoRow: {
311
+ flexDirection: "row",
312
+ alignItems: "center",
313
+ minHeight: 56,
314
+ borderRadius: 12,
315
+ borderWidth: 1,
316
+ borderColor: "#E5E7EB",
317
+ marginBottom: 8,
318
+ paddingHorizontal: 8,
319
+ backgroundColor: "#FFFFFF",
320
+ },
321
+ checkbox: {
322
+ width: 24,
323
+ height: 24,
324
+ borderRadius: 6,
325
+ borderWidth: 2,
326
+ borderColor: "#D1D5DB",
327
+ alignItems: "center",
328
+ justifyContent: "center",
329
+ marginRight: 10,
330
+ },
331
+ checkboxCompleted: {
332
+ backgroundColor: "#10B981",
333
+ borderColor: "#10B981",
334
+ },
335
+ todoText: {
336
+ flex: 1,
337
+ color: "#1F2937",
338
+ fontSize: 16,
339
+ },
340
+ todoTextCompleted: {
341
+ color: "#9CA3AF",
342
+ textDecorationLine: "line-through",
343
+ },
344
+ deleteButton: {
345
+ width: 40,
346
+ height: 40,
347
+ borderRadius: 10,
348
+ alignItems: "center",
349
+ justifyContent: "center",
350
+ },
351
+ setupContainer: {
352
+ flex: 1,
353
+ justifyContent: "center",
354
+ paddingHorizontal: 24,
355
+ },
356
+ setupTitle: {
357
+ fontSize: 24,
358
+ fontWeight: "700",
359
+ color: "#111827",
360
+ marginBottom: 12,
361
+ textAlign: "center",
362
+ },
363
+ setupText: {
364
+ fontSize: 15,
365
+ color: "#4B5563",
366
+ textAlign: "center",
367
+ lineHeight: 22,
368
+ },
369
+ setupHint: {
370
+ marginTop: 16,
371
+ fontSize: 13,
372
+ color: "#6B7280",
373
+ textAlign: "center",
374
+ lineHeight: 20,
375
+ },
376
+ loadingContainer: {
377
+ flex: 1,
378
+ justifyContent: "center",
379
+ alignItems: "center",
380
+ paddingHorizontal: 24,
381
+ },
382
+ loadingText: {
383
+ color: "#4B5563",
384
+ fontSize: 16,
385
+ },
386
+ errorText: {
387
+ color: "#DC2626",
388
+ fontSize: 15,
389
+ textAlign: "center",
390
+ },
391
+ emptyState: {
392
+ marginTop: 48,
393
+ alignItems: "center",
394
+ },
395
+ emptyTitle: {
396
+ fontSize: 18,
397
+ fontWeight: "600",
398
+ color: "#1F2937",
399
+ },
400
+ emptySubtitle: {
401
+ marginTop: 8,
402
+ color: "#6B7280",
403
+ fontSize: 14,
404
+ textAlign: "center",
405
+ },
406
+ });
407
+ `;
408
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-100x-mobile",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Scaffold a full-stack mobile app with Expo + Convex + Clerk in seconds",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {