create-100x-mobile 0.4.11 → 0.5.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.
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.magicSettingsScreenTemplate = magicSettingsScreenTemplate;
4
+ function magicSettingsScreenTemplate(stackLabel) {
5
+ return `import React from "react";
6
+ import {
7
+ Alert,
8
+ ActivityIndicator,
9
+ Image,
10
+ Platform,
11
+ StatusBar,
12
+ StyleSheet,
13
+ Text,
14
+ TouchableOpacity,
15
+ View,
16
+ } from "react-native";
17
+ import { SafeAreaView } from "react-native-safe-area-context";
18
+ import { LogOut, Mail, User } from "lucide-react-native";
19
+ import { instantDb } from "@/lib/instant";
20
+
21
+ function SetupRequired() {
22
+ return (
23
+ <SafeAreaView style={styles.container}>
24
+ <View style={styles.loadingContainer}>
25
+ <View style={styles.card}>
26
+ <Text style={styles.sectionTitle}>Setup Required</Text>
27
+ <Text style={styles.sectionText}>
28
+ Add EXPO_PUBLIC_INSTANT_APP_ID to your .env.local file.
29
+ </Text>
30
+ <Text style={styles.sectionText}>
31
+ Create one with: npx instant-cli init-without-files --title my-app
32
+ </Text>
33
+ </View>
34
+ </View>
35
+ </SafeAreaView>
36
+ );
37
+ }
38
+
39
+ function MagicSettingsContent({
40
+ db,
41
+ }: {
42
+ db: NonNullable<typeof instantDb>;
43
+ }) {
44
+ const { isLoading, error } = db.useAuth();
45
+ const user = db.useUser();
46
+ const [isSigningOut, setIsSigningOut] = React.useState(false);
47
+ const [imageLoadError, setImageLoadError] = React.useState(false);
48
+ const instantAppId = process.env.EXPO_PUBLIC_INSTANT_APP_ID;
49
+
50
+ React.useEffect(() => {
51
+ setImageLoadError(false);
52
+ }, [user?.imageUrl]);
53
+
54
+ const performSignOut = async () => {
55
+ setIsSigningOut(true);
56
+ try {
57
+ await db.auth.signOut();
58
+ } catch (signOutError) {
59
+ console.warn("Instant signOut warning:", signOutError);
60
+ } finally {
61
+ setIsSigningOut(false);
62
+ }
63
+ };
64
+
65
+ const handleSignOut = async () => {
66
+ if (Platform.OS === "web") {
67
+ const confirmed = window.confirm("Are you sure you want to sign out?");
68
+ if (confirmed) await performSignOut();
69
+ return;
70
+ }
71
+
72
+ Alert.alert("Sign Out", "Are you sure you want to sign out?", [
73
+ { text: "Cancel", style: "cancel" },
74
+ { text: "Sign Out", style: "destructive", onPress: performSignOut },
75
+ ]);
76
+ };
77
+
78
+ if (isLoading) {
79
+ return (
80
+ <SafeAreaView style={styles.container}>
81
+ <View style={styles.loadingContainer}>
82
+ <ActivityIndicator size="large" color="#4B5563" />
83
+ </View>
84
+ </SafeAreaView>
85
+ );
86
+ }
87
+
88
+ if (error) {
89
+ return (
90
+ <SafeAreaView style={styles.container}>
91
+ <View style={styles.content}>
92
+ <View style={styles.card}>
93
+ <Text style={styles.sectionTitle}>Authentication Error</Text>
94
+ <Text style={styles.sectionText}>{error.message}</Text>
95
+ </View>
96
+ </View>
97
+ </SafeAreaView>
98
+ );
99
+ }
100
+
101
+ return (
102
+ <SafeAreaView style={styles.container}>
103
+ <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
104
+ <View style={styles.header}>
105
+ <Text style={styles.title}>Settings</Text>
106
+ </View>
107
+
108
+ <View style={styles.content}>
109
+ <View style={styles.card}>
110
+ <Text style={styles.sectionTitle}>Profile</Text>
111
+ <View style={styles.profileRow}>
112
+ <View style={styles.avatarWrap}>
113
+ {user?.imageUrl && !imageLoadError ? (
114
+ <Image
115
+ source={{ uri: user.imageUrl }}
116
+ style={styles.avatar}
117
+ onError={() => setImageLoadError(true)}
118
+ />
119
+ ) : (
120
+ <User size={28} color="#6B7280" />
121
+ )}
122
+ </View>
123
+ <View style={styles.profileTextWrap}>
124
+ <Text style={styles.profileName}>
125
+ {user?.email || "User"}
126
+ </Text>
127
+ <View style={styles.emailRow}>
128
+ <Mail size={14} color="#9CA3AF" />
129
+ <Text style={styles.profileEmail}>{user?.email || "No email"}</Text>
130
+ </View>
131
+ </View>
132
+ </View>
133
+ </View>
134
+
135
+ <View style={styles.card}>
136
+ <Text style={styles.sectionTitle}>Backend</Text>
137
+ <Text style={styles.sectionText}>${stackLabel}</Text>
138
+ </View>
139
+
140
+ <View style={styles.card}>
141
+ <Text style={styles.sectionTitle}>Instant App ID</Text>
142
+ <Text style={styles.sectionText}>
143
+ {instantAppId || "Not configured (set EXPO_PUBLIC_INSTANT_APP_ID)"}
144
+ </Text>
145
+ </View>
146
+
147
+ <View style={styles.card}>
148
+ <Text style={styles.sectionTitle}>About</Text>
149
+ <Text style={styles.sectionText}>Version 1.0.0</Text>
150
+ <Text style={styles.sectionText}>Built with Expo + ${stackLabel}</Text>
151
+ </View>
152
+
153
+ <TouchableOpacity
154
+ style={[styles.signOutButton, isSigningOut && styles.signOutButtonDisabled]}
155
+ onPress={() => {
156
+ if (!isSigningOut) handleSignOut();
157
+ }}
158
+ activeOpacity={0.75}
159
+ >
160
+ {isSigningOut ? (
161
+ <ActivityIndicator size={18} color="#EF4444" />
162
+ ) : (
163
+ <LogOut size={18} color="#EF4444" />
164
+ )}
165
+ <Text style={styles.signOutText}>
166
+ {isSigningOut ? "Signing Out..." : "Sign Out"}
167
+ </Text>
168
+ </TouchableOpacity>
169
+ </View>
170
+ </SafeAreaView>
171
+ );
172
+ }
173
+
174
+ export default function SettingsScreen() {
175
+ if (!instantDb) {
176
+ return <SetupRequired />;
177
+ }
178
+
179
+ return <MagicSettingsContent db={instantDb} />;
180
+ }
181
+
182
+ const styles = StyleSheet.create({
183
+ container: {
184
+ flex: 1,
185
+ backgroundColor: "#FFFFFF",
186
+ },
187
+ loadingContainer: {
188
+ flex: 1,
189
+ justifyContent: "center",
190
+ alignItems: "center",
191
+ },
192
+ header: {
193
+ paddingHorizontal: 16,
194
+ paddingTop: 24,
195
+ paddingBottom: 16,
196
+ },
197
+ title: {
198
+ fontSize: 28,
199
+ fontWeight: "700",
200
+ color: "#1F2937",
201
+ },
202
+ content: {
203
+ paddingHorizontal: 16,
204
+ paddingBottom: 24,
205
+ gap: 12,
206
+ },
207
+ card: {
208
+ backgroundColor: "#F8F9FA",
209
+ borderRadius: 14,
210
+ padding: 16,
211
+ },
212
+ sectionTitle: {
213
+ fontSize: 14,
214
+ fontWeight: "600",
215
+ color: "#374151",
216
+ marginBottom: 8,
217
+ textTransform: "uppercase",
218
+ letterSpacing: 0.4,
219
+ },
220
+ sectionText: {
221
+ fontSize: 15,
222
+ color: "#1F2937",
223
+ marginBottom: 4,
224
+ lineHeight: 22,
225
+ },
226
+ profileRow: {
227
+ flexDirection: "row",
228
+ alignItems: "center",
229
+ },
230
+ avatarWrap: {
231
+ width: 54,
232
+ height: 54,
233
+ borderRadius: 27,
234
+ backgroundColor: "#E5E7EB",
235
+ alignItems: "center",
236
+ justifyContent: "center",
237
+ marginRight: 12,
238
+ },
239
+ avatar: {
240
+ width: 54,
241
+ height: 54,
242
+ borderRadius: 27,
243
+ },
244
+ profileTextWrap: {
245
+ flex: 1,
246
+ },
247
+ profileName: {
248
+ fontSize: 17,
249
+ fontWeight: "600",
250
+ color: "#111827",
251
+ marginBottom: 4,
252
+ },
253
+ emailRow: {
254
+ flexDirection: "row",
255
+ alignItems: "center",
256
+ gap: 6,
257
+ },
258
+ profileEmail: {
259
+ fontSize: 14,
260
+ color: "#6B7280",
261
+ },
262
+ signOutButton: {
263
+ flexDirection: "row",
264
+ alignItems: "center",
265
+ justifyContent: "center",
266
+ gap: 8,
267
+ backgroundColor: "#F3F4F6",
268
+ borderRadius: 14,
269
+ paddingVertical: 14,
270
+ marginTop: 8,
271
+ },
272
+ signOutButtonDisabled: {
273
+ opacity: 0.7,
274
+ },
275
+ signOutText: {
276
+ fontSize: 16,
277
+ fontWeight: "600",
278
+ color: "#EF4444",
279
+ },
280
+ });
281
+ `;
282
+ }
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.magicSignInTemplate = magicSignInTemplate;
4
+ function magicSignInTemplate() {
5
+ return `import React from "react";
6
+ import {
7
+ ActivityIndicator,
8
+ Alert,
9
+ KeyboardAvoidingView,
10
+ Platform,
11
+ StyleSheet,
12
+ Text,
13
+ TextInput,
14
+ TouchableOpacity,
15
+ View,
16
+ } from "react-native";
17
+ import { SafeAreaView } from "react-native-safe-area-context";
18
+ import { CheckSquare, Mail, ShieldCheck } from "lucide-react-native";
19
+ import { instantDb } from "@/lib/instant";
20
+
21
+ function SetupRequired() {
22
+ return null;
23
+ }
24
+
25
+ function MagicCodeSignIn() {
26
+ const db = instantDb!;
27
+ const [email, setEmail] = React.useState("");
28
+ const [code, setCode] = React.useState("");
29
+ const [sentEmail, setSentEmail] = React.useState("");
30
+ const [isSending, setIsSending] = React.useState(false);
31
+ const [isVerifying, setIsVerifying] = React.useState(false);
32
+
33
+ const handleSendCode = async () => {
34
+ const trimmedEmail = email.trim().toLowerCase();
35
+ if (!trimmedEmail) {
36
+ Alert.alert("Enter your email", "We need an email address to send a code.");
37
+ return;
38
+ }
39
+
40
+ setIsSending(true);
41
+ try {
42
+ await db.auth.sendMagicCode({ email: trimmedEmail });
43
+ setSentEmail(trimmedEmail);
44
+ setCode("");
45
+ } catch (error) {
46
+ console.error("Magic code send error:", error);
47
+ Alert.alert(
48
+ "Could not send code",
49
+ error instanceof Error ? error.message : "Please try again."
50
+ );
51
+ } finally {
52
+ setIsSending(false);
53
+ }
54
+ };
55
+
56
+ const handleVerifyCode = async () => {
57
+ const trimmedEmail = sentEmail.trim().toLowerCase();
58
+ const trimmedCode = code.trim();
59
+ if (!trimmedEmail || !trimmedCode) {
60
+ Alert.alert("Enter your code", "Paste the code from your email to continue.");
61
+ return;
62
+ }
63
+
64
+ setIsVerifying(true);
65
+ try {
66
+ await db.auth.signInWithMagicCode({ email: trimmedEmail, code: trimmedCode });
67
+ } catch (error) {
68
+ console.error("Magic code sign-in error:", error);
69
+ Alert.alert(
70
+ "Could not verify code",
71
+ error instanceof Error ? error.message : "Please try again."
72
+ );
73
+ } finally {
74
+ setIsVerifying(false);
75
+ }
76
+ };
77
+
78
+ return (
79
+ <SafeAreaView style={styles.container}>
80
+ <KeyboardAvoidingView
81
+ style={styles.content}
82
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
83
+ >
84
+ <View style={styles.branding}>
85
+ <View style={styles.logoContainer}>
86
+ <CheckSquare size={40} color="#1F2937" strokeWidth={2} />
87
+ </View>
88
+ <Text style={styles.title}>TaskSync</Text>
89
+ <Text style={styles.subtitle}>
90
+ Sign in with a one-time code from InstantDB
91
+ </Text>
92
+ </View>
93
+
94
+ <View style={styles.card}>
95
+ {!sentEmail ? (
96
+ <>
97
+ <Text style={styles.sectionTitle}>Enter your email</Text>
98
+ <View style={styles.inputRow}>
99
+ <Mail size={18} color="#6B7280" />
100
+ <TextInput
101
+ style={styles.input}
102
+ placeholder="you@example.com"
103
+ placeholderTextColor="#9CA3AF"
104
+ keyboardType="email-address"
105
+ autoCapitalize="none"
106
+ autoCorrect={false}
107
+ value={email}
108
+ onChangeText={setEmail}
109
+ editable={!isSending}
110
+ returnKeyType="send"
111
+ onSubmitEditing={handleSendCode}
112
+ />
113
+ </View>
114
+
115
+ <TouchableOpacity
116
+ style={[styles.button, isSending && styles.buttonDisabled]}
117
+ onPress={handleSendCode}
118
+ activeOpacity={0.8}
119
+ disabled={isSending}
120
+ >
121
+ {isSending ? (
122
+ <ActivityIndicator color="#FFFFFF" />
123
+ ) : (
124
+ <Text style={styles.buttonText}>Send code</Text>
125
+ )}
126
+ </TouchableOpacity>
127
+ </>
128
+ ) : (
129
+ <>
130
+ <View style={styles.successRow}>
131
+ <ShieldCheck size={20} color="#059669" />
132
+ <Text style={styles.sectionTitle}>Code sent</Text>
133
+ </View>
134
+ <Text style={styles.helperText}>
135
+ We sent a login code to <Text style={styles.boldText}>{sentEmail}</Text>.
136
+ </Text>
137
+
138
+ <TextInput
139
+ style={styles.codeInput}
140
+ placeholder="123456"
141
+ placeholderTextColor="#9CA3AF"
142
+ keyboardType="number-pad"
143
+ value={code}
144
+ onChangeText={setCode}
145
+ autoCapitalize="none"
146
+ autoCorrect={false}
147
+ returnKeyType="done"
148
+ onSubmitEditing={handleVerifyCode}
149
+ />
150
+
151
+ <TouchableOpacity
152
+ style={[styles.button, isVerifying && styles.buttonDisabled]}
153
+ onPress={handleVerifyCode}
154
+ activeOpacity={0.8}
155
+ disabled={isVerifying}
156
+ >
157
+ {isVerifying ? (
158
+ <ActivityIndicator color="#FFFFFF" />
159
+ ) : (
160
+ <Text style={styles.buttonText}>Verify code</Text>
161
+ )}
162
+ </TouchableOpacity>
163
+
164
+ <TouchableOpacity
165
+ style={styles.secondaryButton}
166
+ onPress={() => {
167
+ setSentEmail("");
168
+ setCode("");
169
+ }}
170
+ activeOpacity={0.75}
171
+ disabled={isVerifying}
172
+ >
173
+ <Text style={styles.secondaryButtonText}>Use a different email</Text>
174
+ </TouchableOpacity>
175
+ </>
176
+ )}
177
+ </View>
178
+ </KeyboardAvoidingView>
179
+ </SafeAreaView>
180
+ );
181
+ }
182
+
183
+ export default function SignInScreen() {
184
+ if (!instantDb) {
185
+ return <SetupRequired />;
186
+ }
187
+
188
+ return <MagicCodeSignIn />;
189
+ }
190
+
191
+ const styles = StyleSheet.create({
192
+ container: {
193
+ flex: 1,
194
+ backgroundColor: "#FFFFFF",
195
+ },
196
+ content: {
197
+ flex: 1,
198
+ justifyContent: "center",
199
+ paddingHorizontal: 24,
200
+ },
201
+ branding: {
202
+ alignItems: "center",
203
+ marginBottom: 48,
204
+ },
205
+ logoContainer: {
206
+ width: 80,
207
+ height: 80,
208
+ borderRadius: 20,
209
+ backgroundColor: "#F3F4F6",
210
+ alignItems: "center",
211
+ justifyContent: "center",
212
+ marginBottom: 24,
213
+ },
214
+ title: {
215
+ fontSize: 32,
216
+ fontWeight: "700",
217
+ color: "#1F2937",
218
+ marginBottom: 8,
219
+ },
220
+ subtitle: {
221
+ fontSize: 16,
222
+ color: "#6B7280",
223
+ textAlign: "center",
224
+ lineHeight: 24,
225
+ },
226
+ card: {
227
+ backgroundColor: "#F9FAFB",
228
+ borderRadius: 16,
229
+ padding: 20,
230
+ gap: 14,
231
+ },
232
+ sectionTitle: {
233
+ fontSize: 18,
234
+ fontWeight: "600",
235
+ color: "#111827",
236
+ },
237
+ inputRow: {
238
+ flexDirection: "row",
239
+ alignItems: "center",
240
+ gap: 12,
241
+ backgroundColor: "#FFFFFF",
242
+ borderColor: "#E5E7EB",
243
+ borderWidth: 1,
244
+ borderRadius: 12,
245
+ paddingHorizontal: 14,
246
+ height: 52,
247
+ },
248
+ input: {
249
+ flex: 1,
250
+ fontSize: 16,
251
+ color: "#111827",
252
+ },
253
+ codeInput: {
254
+ backgroundColor: "#FFFFFF",
255
+ borderColor: "#E5E7EB",
256
+ borderWidth: 1,
257
+ borderRadius: 12,
258
+ paddingHorizontal: 14,
259
+ height: 52,
260
+ fontSize: 16,
261
+ color: "#111827",
262
+ },
263
+ button: {
264
+ height: 52,
265
+ borderRadius: 12,
266
+ alignItems: "center",
267
+ justifyContent: "center",
268
+ backgroundColor: "#111827",
269
+ },
270
+ buttonDisabled: {
271
+ opacity: 0.6,
272
+ },
273
+ buttonText: {
274
+ color: "#FFFFFF",
275
+ fontSize: 16,
276
+ fontWeight: "600",
277
+ },
278
+ helperText: {
279
+ fontSize: 14,
280
+ lineHeight: 22,
281
+ color: "#4B5563",
282
+ },
283
+ boldText: {
284
+ fontWeight: "600",
285
+ color: "#111827",
286
+ },
287
+ secondaryButton: {
288
+ alignItems: "center",
289
+ justifyContent: "center",
290
+ paddingVertical: 4,
291
+ },
292
+ secondaryButtonText: {
293
+ fontSize: 14,
294
+ fontWeight: "600",
295
+ color: "#6B7280",
296
+ },
297
+ successRow: {
298
+ flexDirection: "row",
299
+ alignItems: "center",
300
+ gap: 8,
301
+ },
302
+ setupContainer: {
303
+ flex: 1,
304
+ backgroundColor: "#f5f5f5",
305
+ justifyContent: "center",
306
+ alignItems: "center",
307
+ padding: 20,
308
+ },
309
+ setupCard: {
310
+ backgroundColor: "#fff",
311
+ borderRadius: 12,
312
+ padding: 24,
313
+ maxWidth: 420,
314
+ width: "100%",
315
+ shadowColor: "#000",
316
+ shadowOffset: { width: 0, height: 2 },
317
+ shadowOpacity: 0.1,
318
+ shadowRadius: 8,
319
+ elevation: 3,
320
+ },
321
+ setupTitle: {
322
+ fontSize: 24,
323
+ fontWeight: "700",
324
+ color: "#333",
325
+ marginBottom: 16,
326
+ textAlign: "center",
327
+ },
328
+ setupText: {
329
+ fontSize: 16,
330
+ color: "#666",
331
+ marginBottom: 12,
332
+ lineHeight: 22,
333
+ textAlign: "center",
334
+ },
335
+ setupStep: {
336
+ fontSize: 14,
337
+ color: "#888",
338
+ marginTop: 16,
339
+ padding: 16,
340
+ backgroundColor: "#f8f9fa",
341
+ borderRadius: 8,
342
+ lineHeight: 20,
343
+ },
344
+ });
345
+ `;
346
+ }