create-better-t-stack 2.14.3 → 2.15.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/index.js +19 -15
- package/package.json +1 -1
- package/templates/examples/ai/native/nativewind/app/(drawer)/ai.tsx.hbs +155 -0
- package/templates/examples/ai/native/nativewind/polyfills.js +25 -0
- package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +279 -0
- package/templates/examples/ai/native/unistyles/polyfills.js +25 -0
- package/templates/examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs +340 -0
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx +24 -7
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx +15 -13
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx +15 -13
- package/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs +25 -9
- package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +43 -30
- package/templates/frontend/native/nativewind/app/+not-found.tsx +20 -9
- package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +3 -0
- package/templates/frontend/native/nativewind/app/modal.tsx +9 -7
- package/templates/frontend/native/nativewind/components/container.tsx +2 -3
- package/templates/frontend/native/nativewind/components/header-button.tsx +24 -29
- package/templates/frontend/native/nativewind/components/tabbar-icon.tsx +3 -10
- package/templates/frontend/native/nativewind/global.css +36 -11
- package/templates/frontend/native/nativewind/lib/constants.ts +8 -8
- package/templates/frontend/native/nativewind/{package.json → package.json.hbs} +4 -0
- package/templates/frontend/native/nativewind/tailwind.config.js +27 -0
- package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx +9 -4
- package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx +25 -17
- package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx +26 -18
- package/templates/frontend/native/unistyles/app/(drawer)/{_layout.tsx → _layout.tsx.hbs} +35 -7
- package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +121 -58
- package/templates/frontend/native/unistyles/app/+not-found.tsx +45 -14
- package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +9 -6
- package/templates/frontend/native/unistyles/app/modal.tsx +18 -14
- package/templates/frontend/native/unistyles/components/container.tsx +3 -8
- package/templates/frontend/native/unistyles/components/header-button.tsx +33 -28
- package/templates/frontend/native/unistyles/components/tabbar-icon.tsx +3 -10
- package/templates/frontend/native/unistyles/{package.json → package.json.hbs} +5 -1
- package/templates/frontend/native/unistyles/theme.ts +82 -19
- package/templates/frontend/native/unistyles/unistyles.ts +4 -4
- /package/templates/examples/todo/native/nativewind/app/{todo.tsx.hbs → (drawer)/todos.tsx.hbs} +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
ScrollView,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
Alert,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
12
|
+
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
13
|
+
|
|
14
|
+
{{#if (eq backend "convex")}}
|
|
15
|
+
import { useMutation, useQuery } from "convex/react";
|
|
16
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
17
|
+
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
18
|
+
{{else}}
|
|
19
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
20
|
+
{{/if}}
|
|
21
|
+
|
|
22
|
+
import { Container } from "@/components/container";
|
|
23
|
+
{{#unless (eq backend "convex")}}
|
|
24
|
+
{{#if (eq api "orpc")}}
|
|
25
|
+
import { orpc } from "@/utils/orpc";
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{#if (eq api "trpc")}}
|
|
28
|
+
import { trpc } from "@/utils/trpc";
|
|
29
|
+
{{/if}}
|
|
30
|
+
{{/unless}}
|
|
31
|
+
|
|
32
|
+
export default function TodosScreen() {
|
|
33
|
+
const [newTodoText, setNewTodoText] = useState("");
|
|
34
|
+
const { theme } = useUnistyles();
|
|
35
|
+
|
|
36
|
+
{{#if (eq backend "convex")}}
|
|
37
|
+
const todos = useQuery(api.todos.getAll);
|
|
38
|
+
const createTodoMutation = useMutation(api.todos.create);
|
|
39
|
+
const toggleTodoMutation = useMutation(api.todos.toggle);
|
|
40
|
+
const deleteTodoMutation = useMutation(api.todos.deleteTodo);
|
|
41
|
+
|
|
42
|
+
const handleAddTodo = async () => {
|
|
43
|
+
const text = newTodoText.trim();
|
|
44
|
+
if (!text) return;
|
|
45
|
+
await createTodoMutation({ text });
|
|
46
|
+
setNewTodoText("");
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
|
|
50
|
+
toggleTodoMutation({ id, completed: !currentCompleted });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleDeleteTodo = (id: Id<"todos">) => {
|
|
54
|
+
Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [
|
|
55
|
+
{ text: "Cancel", style: "cancel" },
|
|
56
|
+
{
|
|
57
|
+
text: "Delete",
|
|
58
|
+
style: "destructive",
|
|
59
|
+
onPress: () => deleteTodoMutation({ id }),
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
};
|
|
63
|
+
{{else}}
|
|
64
|
+
{{#if (eq api "orpc")}}
|
|
65
|
+
const todos = useQuery(orpc.todo.getAll.queryOptions());
|
|
66
|
+
const createMutation = useMutation(
|
|
67
|
+
orpc.todo.create.mutationOptions({
|
|
68
|
+
onSuccess: () => {
|
|
69
|
+
todos.refetch();
|
|
70
|
+
setNewTodoText("");
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
const toggleMutation = useMutation(
|
|
75
|
+
orpc.todo.toggle.mutationOptions({
|
|
76
|
+
onSuccess: () => todos.refetch(),
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
const deleteMutation = useMutation(
|
|
80
|
+
orpc.todo.delete.mutationOptions({
|
|
81
|
+
onSuccess: () => todos.refetch(),
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{#if (eq api "trpc")}}
|
|
86
|
+
const todos = useQuery(trpc.todo.getAll.queryOptions());
|
|
87
|
+
const createMutation = useMutation(
|
|
88
|
+
trpc.todo.create.mutationOptions({
|
|
89
|
+
onSuccess: () => {
|
|
90
|
+
todos.refetch();
|
|
91
|
+
setNewTodoText("");
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
const toggleMutation = useMutation(
|
|
96
|
+
trpc.todo.toggle.mutationOptions({
|
|
97
|
+
onSuccess: () => todos.refetch(),
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
const deleteMutation = useMutation(
|
|
101
|
+
trpc.todo.delete.mutationOptions({
|
|
102
|
+
onSuccess: () => todos.refetch(),
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
{{/if}}
|
|
106
|
+
|
|
107
|
+
const handleAddTodo = () => {
|
|
108
|
+
if (newTodoText.trim()) {
|
|
109
|
+
createMutation.mutate({ text: newTodoText });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleToggleTodo = (id: number, completed: boolean) => {
|
|
114
|
+
toggleMutation.mutate({ id, completed: !completed });
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleDeleteTodo = (id: number) => {
|
|
118
|
+
Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [
|
|
119
|
+
{ text: "Cancel", style: "cancel" },
|
|
120
|
+
{
|
|
121
|
+
text: "Delete",
|
|
122
|
+
style: "destructive",
|
|
123
|
+
onPress: () => deleteMutation.mutate({ id }),
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
};
|
|
127
|
+
{{/if}}
|
|
128
|
+
|
|
129
|
+
const isLoading = {{#if (eq backend "convex")}}!todos{{else}}todos.isLoading{{/if}};
|
|
130
|
+
const isCreating = {{#if (eq backend "convex")}}false{{else}}createMutation.isPending{{/if}};
|
|
131
|
+
const primaryButtonTextColor = theme.colors.background;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<Container>
|
|
135
|
+
<ScrollView style={styles.scrollView}>
|
|
136
|
+
<View style={styles.headerContainer}>
|
|
137
|
+
<Text style={styles.headerTitle}>Todo List</Text>
|
|
138
|
+
<Text style={styles.headerSubtitle}>
|
|
139
|
+
Manage your tasks efficiently
|
|
140
|
+
</Text>
|
|
141
|
+
|
|
142
|
+
<View style={styles.inputContainer}>
|
|
143
|
+
<TextInput
|
|
144
|
+
value={newTodoText}
|
|
145
|
+
onChangeText={setNewTodoText}
|
|
146
|
+
placeholder="Add a new task..."
|
|
147
|
+
placeholderTextColor={theme.colors.border}
|
|
148
|
+
editable={!isCreating}
|
|
149
|
+
style={styles.textInput}
|
|
150
|
+
onSubmitEditing={handleAddTodo}
|
|
151
|
+
returnKeyType="done"
|
|
152
|
+
/>
|
|
153
|
+
<TouchableOpacity
|
|
154
|
+
onPress={handleAddTodo}
|
|
155
|
+
disabled={isCreating || !newTodoText.trim()}
|
|
156
|
+
style={[
|
|
157
|
+
styles.addButton,
|
|
158
|
+
(isCreating || !newTodoText.trim()) && styles.addButtonDisabled,
|
|
159
|
+
]}
|
|
160
|
+
>
|
|
161
|
+
{isCreating ? (
|
|
162
|
+
<ActivityIndicator size="small" color={primaryButtonTextColor} />
|
|
163
|
+
) : (
|
|
164
|
+
<Ionicons
|
|
165
|
+
name="add"
|
|
166
|
+
size={24}
|
|
167
|
+
color={primaryButtonTextColor}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
</TouchableOpacity>
|
|
171
|
+
</View>
|
|
172
|
+
</View>
|
|
173
|
+
|
|
174
|
+
{isLoading && (
|
|
175
|
+
<View style={styles.loadingContainer}>
|
|
176
|
+
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
177
|
+
<Text style={styles.loadingText}>Loading todos...</Text>
|
|
178
|
+
</View>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{{#if (eq backend "convex")}}
|
|
182
|
+
{todos && todos.length === 0 && !isLoading && (
|
|
183
|
+
<Text style={styles.emptyText}>No todos yet. Add one!</Text>
|
|
184
|
+
)}
|
|
185
|
+
{todos?.map((todo) => (
|
|
186
|
+
<View key={todo._id} style={styles.todoItem}>
|
|
187
|
+
<TouchableOpacity
|
|
188
|
+
onPress={() => handleToggleTodo(todo._id, todo.completed)}
|
|
189
|
+
style={styles.todoContent}
|
|
190
|
+
>
|
|
191
|
+
<Ionicons
|
|
192
|
+
name={todo.completed ? "checkbox" : "square-outline"}
|
|
193
|
+
size={24}
|
|
194
|
+
color={todo.completed ? theme.colors.primary : theme.colors.typography}
|
|
195
|
+
style={styles.checkbox}
|
|
196
|
+
/>
|
|
197
|
+
<Text
|
|
198
|
+
style={[
|
|
199
|
+
styles.todoText,
|
|
200
|
+
todo.completed && styles.todoTextCompleted,
|
|
201
|
+
]}
|
|
202
|
+
>
|
|
203
|
+
{todo.text}
|
|
204
|
+
</Text>
|
|
205
|
+
</TouchableOpacity>
|
|
206
|
+
<TouchableOpacity onPress={() => handleDeleteTodo(todo._id)}>
|
|
207
|
+
<Ionicons name="trash-outline" size={24} color={theme.colors.destructive} />
|
|
208
|
+
</TouchableOpacity>
|
|
209
|
+
</View>
|
|
210
|
+
))}
|
|
211
|
+
{{else}}
|
|
212
|
+
{todos.data && todos.data.length === 0 && !isLoading && (
|
|
213
|
+
<Text style={styles.emptyText}>No todos yet. Add one!</Text>
|
|
214
|
+
)}
|
|
215
|
+
{todos.data?.map((todo: { id: number; text: string; completed: boolean }) => (
|
|
216
|
+
<View key={todo.id} style={styles.todoItem}>
|
|
217
|
+
<TouchableOpacity
|
|
218
|
+
onPress={() => handleToggleTodo(todo.id, todo.completed)}
|
|
219
|
+
style={styles.todoContent}
|
|
220
|
+
>
|
|
221
|
+
<Ionicons
|
|
222
|
+
name={todo.completed ? "checkbox" : "square-outline"}
|
|
223
|
+
size={24}
|
|
224
|
+
color={todo.completed ? theme.colors.primary : theme.colors.typography}
|
|
225
|
+
style={styles.checkbox}
|
|
226
|
+
/>
|
|
227
|
+
<Text
|
|
228
|
+
style={[
|
|
229
|
+
styles.todoText,
|
|
230
|
+
todo.completed && styles.todoTextCompleted,
|
|
231
|
+
]}
|
|
232
|
+
>
|
|
233
|
+
{todo.text}
|
|
234
|
+
</Text>
|
|
235
|
+
</TouchableOpacity>
|
|
236
|
+
<TouchableOpacity onPress={() => handleDeleteTodo(todo.id)}>
|
|
237
|
+
<Ionicons name="trash-outline" size={24} color={theme.colors.destructive} />
|
|
238
|
+
</TouchableOpacity>
|
|
239
|
+
</View>
|
|
240
|
+
))}
|
|
241
|
+
{{/if}}
|
|
242
|
+
</ScrollView>
|
|
243
|
+
</Container>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const styles = StyleSheet.create((theme) => ({
|
|
248
|
+
scrollView: {
|
|
249
|
+
flex: 1,
|
|
250
|
+
},
|
|
251
|
+
headerContainer: {
|
|
252
|
+
paddingHorizontal: theme.spacing.md,
|
|
253
|
+
paddingVertical: theme.spacing.lg,
|
|
254
|
+
borderBottomWidth: 1,
|
|
255
|
+
borderBottomColor: theme.colors.border,
|
|
256
|
+
backgroundColor: theme.colors.background,
|
|
257
|
+
},
|
|
258
|
+
headerTitle: {
|
|
259
|
+
fontSize: 28,
|
|
260
|
+
fontWeight: "bold",
|
|
261
|
+
color: theme.colors.typography,
|
|
262
|
+
marginBottom: theme.spacing.sm,
|
|
263
|
+
},
|
|
264
|
+
headerSubtitle: {
|
|
265
|
+
fontSize: 16,
|
|
266
|
+
color: theme.colors.typography,
|
|
267
|
+
marginBottom: theme.spacing.md,
|
|
268
|
+
},
|
|
269
|
+
inputContainer: {
|
|
270
|
+
flexDirection: "row",
|
|
271
|
+
alignItems: "center",
|
|
272
|
+
marginBottom: theme.spacing.md,
|
|
273
|
+
},
|
|
274
|
+
textInput: {
|
|
275
|
+
flex: 1,
|
|
276
|
+
borderWidth: 1,
|
|
277
|
+
borderColor: theme.colors.border,
|
|
278
|
+
borderRadius: 8,
|
|
279
|
+
paddingHorizontal: theme.spacing.md,
|
|
280
|
+
paddingVertical: theme.spacing.sm,
|
|
281
|
+
color: theme.colors.typography,
|
|
282
|
+
backgroundColor: theme.colors.background,
|
|
283
|
+
marginRight: theme.spacing.sm,
|
|
284
|
+
fontSize: 16,
|
|
285
|
+
},
|
|
286
|
+
addButton: {
|
|
287
|
+
backgroundColor: theme.colors.primary,
|
|
288
|
+
padding: theme.spacing.sm,
|
|
289
|
+
borderRadius: 8,
|
|
290
|
+
justifyContent: "center",
|
|
291
|
+
alignItems: "center",
|
|
292
|
+
},
|
|
293
|
+
addButtonDisabled: {
|
|
294
|
+
backgroundColor: theme.colors.border,
|
|
295
|
+
},
|
|
296
|
+
loadingContainer: {
|
|
297
|
+
flex: 1,
|
|
298
|
+
justifyContent: "center",
|
|
299
|
+
alignItems: "center",
|
|
300
|
+
padding: theme.spacing.lg,
|
|
301
|
+
},
|
|
302
|
+
loadingText: {
|
|
303
|
+
marginTop: theme.spacing.sm,
|
|
304
|
+
fontSize: 16,
|
|
305
|
+
color: theme.colors.typography,
|
|
306
|
+
},
|
|
307
|
+
emptyText: {
|
|
308
|
+
textAlign: "center",
|
|
309
|
+
marginTop: theme.spacing.xl,
|
|
310
|
+
fontSize: 16,
|
|
311
|
+
color: theme.colors.typography,
|
|
312
|
+
},
|
|
313
|
+
todoItem: {
|
|
314
|
+
flexDirection: "row",
|
|
315
|
+
justifyContent: "space-between",
|
|
316
|
+
alignItems: "center",
|
|
317
|
+
paddingVertical: theme.spacing.md,
|
|
318
|
+
paddingHorizontal: theme.spacing.md,
|
|
319
|
+
borderBottomWidth: 1,
|
|
320
|
+
borderBottomColor: theme.colors.border,
|
|
321
|
+
backgroundColor: theme.colors.background,
|
|
322
|
+
},
|
|
323
|
+
todoContent: {
|
|
324
|
+
flexDirection: "row",
|
|
325
|
+
alignItems: "center",
|
|
326
|
+
flex: 1,
|
|
327
|
+
},
|
|
328
|
+
checkbox: {
|
|
329
|
+
marginRight: theme.spacing.md,
|
|
330
|
+
},
|
|
331
|
+
todoText: {
|
|
332
|
+
fontSize: 16,
|
|
333
|
+
color: theme.colors.typography,
|
|
334
|
+
flex: 1,
|
|
335
|
+
},
|
|
336
|
+
todoTextCompleted: {
|
|
337
|
+
textDecorationLine: "line-through",
|
|
338
|
+
color: theme.colors.border,
|
|
339
|
+
},
|
|
340
|
+
}));
|
|
@@ -1,27 +1,44 @@
|
|
|
1
|
-
import { Tabs } from "expo-router";
|
|
2
|
-
|
|
3
1
|
import { TabBarIcon } from "@/components/tabbar-icon";
|
|
2
|
+
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
3
|
+
import { Tabs } from "expo-router";
|
|
4
4
|
|
|
5
5
|
export default function TabLayout() {
|
|
6
|
+
const { isDarkColorScheme } = useColorScheme();
|
|
7
|
+
|
|
6
8
|
return (
|
|
7
9
|
<Tabs
|
|
8
10
|
screenOptions={{
|
|
9
11
|
headerShown: false,
|
|
10
|
-
tabBarActiveTintColor:
|
|
12
|
+
tabBarActiveTintColor: isDarkColorScheme
|
|
13
|
+
? "hsl(217.2 91.2% 59.8%)"
|
|
14
|
+
: "hsl(221.2 83.2% 53.3%)",
|
|
15
|
+
tabBarInactiveTintColor: isDarkColorScheme
|
|
16
|
+
? "hsl(215 20.2% 65.1%)"
|
|
17
|
+
: "hsl(215.4 16.3% 46.9%)",
|
|
18
|
+
tabBarStyle: {
|
|
19
|
+
backgroundColor: isDarkColorScheme
|
|
20
|
+
? "hsl(222.2 84% 4.9%)"
|
|
21
|
+
: "hsl(0 0% 100%)",
|
|
22
|
+
borderTopColor: isDarkColorScheme
|
|
23
|
+
? "hsl(217.2 32.6% 17.5%)"
|
|
24
|
+
: "hsl(214.3 31.8% 91.4%)",
|
|
25
|
+
},
|
|
11
26
|
}}
|
|
12
27
|
>
|
|
13
28
|
<Tabs.Screen
|
|
14
29
|
name="index"
|
|
15
30
|
options={{
|
|
16
|
-
title: "
|
|
17
|
-
tabBarIcon: ({ color }) => <TabBarIcon name="
|
|
31
|
+
title: "Home",
|
|
32
|
+
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
|
18
33
|
}}
|
|
19
34
|
/>
|
|
20
35
|
<Tabs.Screen
|
|
21
36
|
name="two"
|
|
22
37
|
options={{
|
|
23
|
-
title: "
|
|
24
|
-
tabBarIcon: ({ color }) =>
|
|
38
|
+
title: "Explore",
|
|
39
|
+
tabBarIcon: ({ color }) => (
|
|
40
|
+
<TabBarIcon name="compass" color={color} />
|
|
41
|
+
),
|
|
25
42
|
}}
|
|
26
43
|
/>
|
|
27
44
|
</Tabs>
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Container } from "@/components/container";
|
|
2
|
-
import { Text, View } from "react-native";
|
|
2
|
+
import { ScrollView, Text, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
export default function TabOne() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
return (
|
|
6
|
+
<Container>
|
|
7
|
+
<ScrollView className="flex-1 p-6">
|
|
8
|
+
<View className="py-8">
|
|
9
|
+
<Text className="text-3xl font-bold text-foreground mb-2">
|
|
10
|
+
Tab One
|
|
11
|
+
</Text>
|
|
12
|
+
<Text className="text-lg text-muted-foreground">
|
|
13
|
+
Explore the first section of your app
|
|
14
|
+
</Text>
|
|
15
|
+
</View>
|
|
16
|
+
</ScrollView>
|
|
17
|
+
</Container>
|
|
18
|
+
);
|
|
17
19
|
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Container } from "@/components/container";
|
|
2
|
-
import { Text, View } from "react-native";
|
|
2
|
+
import { ScrollView, Text, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
export default function TabTwo() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
return (
|
|
6
|
+
<Container>
|
|
7
|
+
<ScrollView className="flex-1 p-6">
|
|
8
|
+
<View className="py-8">
|
|
9
|
+
<Text className="text-3xl font-bold text-foreground mb-2">
|
|
10
|
+
Tab Two
|
|
11
|
+
</Text>
|
|
12
|
+
<Text className="text-lg text-muted-foreground">
|
|
13
|
+
Discover more features and content
|
|
14
|
+
</Text>
|
|
15
|
+
</View>
|
|
16
|
+
</ScrollView>
|
|
17
|
+
</Container>
|
|
18
|
+
);
|
|
17
19
|
}
|
|
@@ -17,6 +17,21 @@ const DrawerLayout = () => {
|
|
|
17
17
|
),
|
|
18
18
|
}}
|
|
19
19
|
/>
|
|
20
|
+
<Drawer.Screen
|
|
21
|
+
name="(tabs)"
|
|
22
|
+
options=\{{
|
|
23
|
+
headerTitle: "Tabs",
|
|
24
|
+
drawerLabel: "Tabs",
|
|
25
|
+
drawerIcon: ({ size, color }) => (
|
|
26
|
+
<MaterialIcons name="border-bottom" size={size} color={color} />
|
|
27
|
+
),
|
|
28
|
+
headerRight: () => (
|
|
29
|
+
<Link href="/modal" asChild>
|
|
30
|
+
<HeaderButton />
|
|
31
|
+
</Link>
|
|
32
|
+
),
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
20
35
|
{{#if (includes examples "todo")}}
|
|
21
36
|
<Drawer.Screen
|
|
22
37
|
name="todos"
|
|
@@ -29,21 +44,22 @@ const DrawerLayout = () => {
|
|
|
29
44
|
}}
|
|
30
45
|
/>
|
|
31
46
|
{{/if}}
|
|
47
|
+
{{#if (includes examples "ai")}}
|
|
32
48
|
<Drawer.Screen
|
|
33
|
-
name="
|
|
49
|
+
name="ai"
|
|
34
50
|
options=\{{
|
|
35
|
-
headerTitle: "
|
|
36
|
-
drawerLabel: "
|
|
51
|
+
headerTitle: "AI",
|
|
52
|
+
drawerLabel: "AI",
|
|
37
53
|
drawerIcon: ({ size, color }) => (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</Link>
|
|
54
|
+
<Ionicons
|
|
55
|
+
name="chatbubble-ellipses-outline"
|
|
56
|
+
size={size}
|
|
57
|
+
color={color}
|
|
58
|
+
/>
|
|
44
59
|
),
|
|
45
60
|
}}
|
|
46
61
|
/>
|
|
62
|
+
{{/if}}
|
|
47
63
|
</Drawer>
|
|
48
64
|
);
|
|
49
65
|
};
|
|
@@ -26,52 +26,65 @@ export default function Home() {
|
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
28
|
<Container>
|
|
29
|
-
<ScrollView className="
|
|
30
|
-
<Text className="font-mono text-foreground text-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<View className="rounded-lg border border-foreground p-4">
|
|
35
|
-
<Text className="mb-2 font-medium text-foreground">API Status</Text>
|
|
29
|
+
<ScrollView showsVerticalScrollIndicator={false} className="flex-1">
|
|
30
|
+
<Text className="font-mono text-foreground text-3xl font-bold mb-4">
|
|
31
|
+
BETTER T STACK
|
|
32
|
+
</Text>
|
|
33
|
+
<View className="bg-card border border-border rounded-xl p-6 mb-6 shadow-sm">
|
|
36
34
|
{{#if (eq backend "convex")}}
|
|
37
|
-
<View className="flex-row items-center gap-
|
|
35
|
+
<View className="flex-row items-center gap-3">
|
|
38
36
|
<View
|
|
39
|
-
className={`h-
|
|
40
|
-
healthCheck ? "bg-green-500" : "bg-
|
|
37
|
+
className={`h-3 w-3 rounded-full ${
|
|
38
|
+
healthCheck ? "bg-green-500" : "bg-orange-500"
|
|
41
39
|
}`}
|
|
42
40
|
/>
|
|
43
|
-
<
|
|
41
|
+
<View className="flex-1">
|
|
42
|
+
<Text className="text-sm font-medium text-card-foreground">
|
|
43
|
+
Convex
|
|
44
|
+
</Text>
|
|
45
|
+
<Text className="text-xs text-muted-foreground">
|
|
44
46
|
{healthCheck === undefined
|
|
45
|
-
? "Checking..."
|
|
47
|
+
? "Checking connection..."
|
|
46
48
|
: healthCheck === "OK"
|
|
47
|
-
? "
|
|
48
|
-
: "
|
|
49
|
-
|
|
49
|
+
? "All systems operational"
|
|
50
|
+
: "Service unavailable"}
|
|
51
|
+
</Text>
|
|
52
|
+
</View>
|
|
50
53
|
</View>
|
|
51
54
|
{{else}}
|
|
52
55
|
{{#unless (eq api "none")}}
|
|
53
|
-
<View className="flex-row items-center gap-
|
|
56
|
+
<View className="flex-row items-center gap-3">
|
|
54
57
|
<View
|
|
55
|
-
className={`h-
|
|
56
|
-
healthCheck.data ? "bg-green-500" : "bg-
|
|
58
|
+
className={`h-3 w-3 rounded-full ${
|
|
59
|
+
healthCheck.data ? "bg-green-500" : "bg-orange-500"
|
|
57
60
|
}`}
|
|
58
61
|
/>
|
|
59
|
-
<
|
|
60
|
-
|
|
62
|
+
<View className="flex-1">
|
|
63
|
+
<Text className="text-sm font-medium text-card-foreground">
|
|
64
|
+
{{#if (eq api "orpc")}}
|
|
65
|
+
ORPC
|
|
66
|
+
{{/if}}
|
|
67
|
+
{{#if (eq api "trpc")}}
|
|
68
|
+
TRPC
|
|
69
|
+
{{/if}}
|
|
70
|
+
</Text>
|
|
71
|
+
<Text className="text-xs text-muted-foreground">
|
|
72
|
+
{{#if (eq api "orpc")}}
|
|
61
73
|
{healthCheck.isLoading
|
|
62
|
-
? "Checking..."
|
|
74
|
+
? "Checking connection..."
|
|
63
75
|
: healthCheck.data
|
|
64
|
-
? "
|
|
65
|
-
: "
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
? "All systems operational"
|
|
77
|
+
: "Service unavailable"}
|
|
78
|
+
{{/if}}
|
|
79
|
+
{{#if (eq api "trpc")}}
|
|
68
80
|
{healthCheck.isLoading
|
|
69
|
-
? "Checking..."
|
|
81
|
+
? "Checking connection..."
|
|
70
82
|
: healthCheck.data
|
|
71
|
-
? "
|
|
72
|
-
: "
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
? "All systems operational"
|
|
84
|
+
: "Service unavailable"}
|
|
85
|
+
{{/if}}
|
|
86
|
+
</Text>
|
|
87
|
+
</View>
|
|
75
88
|
</View>
|
|
76
89
|
{{/unless}}
|
|
77
90
|
{{/if}}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { Container } from '@/components/container';
|
|
1
|
+
import { Container } from "@/components/container";
|
|
2
|
+
import { Link, Stack } from "expo-router";
|
|
3
|
+
import { Text, View } from "react-native";
|
|
5
4
|
|
|
6
5
|
export default function NotFoundScreen() {
|
|
7
6
|
return (
|
|
8
7
|
<>
|
|
9
|
-
<Stack.Screen options={{ title:
|
|
8
|
+
<Stack.Screen options={{ title: "Oops!" }} />
|
|
10
9
|
<Container>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
<View className="flex-1 justify-center items-center p-6">
|
|
11
|
+
<View className="items-center">
|
|
12
|
+
<Text className="text-6xl mb-4">🤔</Text>
|
|
13
|
+
<Text className="text-2xl font-bold text-foreground mb-2 text-center">
|
|
14
|
+
Page Not Found
|
|
15
|
+
</Text>
|
|
16
|
+
<Text className="text-muted-foreground text-center mb-8 max-w-sm">
|
|
17
|
+
Sorry, the page you're looking for doesn't exist.
|
|
18
|
+
</Text>
|
|
19
|
+
<Link href="/" asChild>
|
|
20
|
+
<Text className="text-primary font-medium bg-primary/10 px-6 py-3 rounded-lg">
|
|
21
|
+
Go to Home
|
|
22
|
+
</Text>
|
|
23
|
+
</Link>
|
|
24
|
+
</View>
|
|
25
|
+
</View>
|
|
15
26
|
</Container>
|
|
16
27
|
</>
|
|
17
28
|
);
|
|
@@ -2,11 +2,13 @@ import { Container } from "@/components/container";
|
|
|
2
2
|
import { Text, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
export default function Modal() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
return (
|
|
6
|
+
<Container>
|
|
7
|
+
<View className="flex-1 p-6">
|
|
8
|
+
<View className="flex-row items-center justify-between mb-8">
|
|
9
|
+
<Text className="text-2xl font-bold text-foreground">Modal</Text>
|
|
10
|
+
</View>
|
|
11
|
+
</View>
|
|
12
|
+
</Container>
|
|
13
|
+
);
|
|
12
14
|
}
|