create-croissant 0.1.44 → 0.1.45

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 (38) hide show
  1. package/package.json +1 -1
  2. package/template/.expo/README.md +13 -0
  3. package/template/.expo/devices.json +3 -0
  4. package/template/apps/desktop/.eslintcache +1 -0
  5. package/template/apps/mobile/app/(tabs)/_layout.tsx +21 -14
  6. package/template/apps/mobile/app/(tabs)/account.tsx +147 -0
  7. package/template/apps/mobile/app/(tabs)/explore.tsx +334 -104
  8. package/template/apps/mobile/app/(tabs)/index.tsx +99 -97
  9. package/template/apps/mobile/app/_layout.tsx +26 -7
  10. package/template/apps/mobile/app/index.tsx +136 -0
  11. package/template/apps/mobile/app/login.tsx +135 -0
  12. package/template/apps/mobile/app/signup.tsx +144 -0
  13. package/template/apps/mobile/app.json +3 -3
  14. package/template/apps/mobile/components/ui/button.tsx +86 -0
  15. package/template/apps/mobile/components/ui/input.tsx +56 -0
  16. package/template/apps/mobile/lib/orpc.ts +23 -0
  17. package/template/apps/mobile/package.json +13 -1
  18. package/template/apps/mobile/tsconfig.json +4 -1
  19. package/template/apps/platform/package.json +2 -1
  20. package/template/apps/platform/src/components/login-form.tsx +5 -4
  21. package/template/apps/platform/src/components/signup-form.tsx +12 -16
  22. package/template/apps/platform/src/routes/__root.tsx +6 -2
  23. package/template/apps/platform/src/routes/_auth/account.tsx +13 -17
  24. package/template/apps/platform/src/routes/_auth/examples/client-orpc-auth.tsx +2 -6
  25. package/template/apps/platform/src/routes/_public/examples/client-orpc.tsx +16 -29
  26. package/template/apps/platform/src/routes/_public/examples/ssr-orpc.tsx +10 -14
  27. package/template/apps/platform/src/routes/api/auth/$.ts +23 -2
  28. package/template/apps/platform/src/routes/api/rpc.$.ts +18 -0
  29. package/template/package.json +2 -2
  30. package/template/packages/orpc/package.json +7 -1
  31. package/template/packages/orpc/src/lib/planets.ts +18 -18
  32. package/template/packages/orpc/src/lib/router.ts +3 -3
  33. package/template/packages/orpc/src/react/context.tsx +23 -0
  34. package/template/packages/orpc/src/react/general.ts +29 -0
  35. package/template/packages/orpc/src/react/index.ts +3 -0
  36. package/template/packages/orpc/src/react/planets.ts +90 -0
  37. package/template/tsconfig.json +2 -1
  38. package/template/apps/mobile/app/modal.tsx +0 -29
@@ -2,9 +2,22 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native
2
2
  import { Stack } from "expo-router";
3
3
  import { StatusBar } from "expo-status-bar";
4
4
  import "react-native-reanimated";
5
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6
+ import { ORPCProvider } from "@workspace/orpc/react";
7
+ import { orpc } from "@/lib/orpc";
5
8
 
6
9
  import { useColorScheme } from "@/hooks/use-color-scheme";
7
10
 
11
+ const queryClient = new QueryClient({
12
+ defaultOptions: {
13
+ mutations: {
14
+ onError: (error) => {
15
+ console.error("Global Mutation Error:", error);
16
+ },
17
+ },
18
+ },
19
+ });
20
+
8
21
  export const unstable_settings = {
9
22
  anchor: "(tabs)",
10
23
  };
@@ -13,12 +26,18 @@ export default function RootLayout() {
13
26
  const colorScheme = useColorScheme();
14
27
 
15
28
  return (
16
- <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
17
- <Stack>
18
- <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
19
- <Stack.Screen name="modal" options={{ presentation: "modal", title: "Modal" }} />
20
- </Stack>
21
- <StatusBar style="auto" />
22
- </ThemeProvider>
29
+ <QueryClientProvider client={queryClient}>
30
+ <ORPCProvider client={orpc}>
31
+ <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
32
+ <Stack>
33
+ <Stack.Screen name="index" options={{ headerShown: false }} />
34
+ <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
35
+ <Stack.Screen name="login" options={{ title: "Login" }} />
36
+ <Stack.Screen name="signup" options={{ title: "Sign Up" }} />
37
+ </Stack>
38
+ <StatusBar style="auto" />
39
+ </ThemeProvider>
40
+ </ORPCProvider>
41
+ </QueryClientProvider>
23
42
  );
24
43
  }
@@ -0,0 +1,136 @@
1
+ import { View, Text, StyleSheet, ScrollView, FlatList } from "react-native";
2
+ import { Link, useRouter } from "expo-router";
3
+ import { useQueryClient } from "@tanstack/react-query";
4
+ import { Button } from "@/components/ui/button";
5
+ import { usePlanets } from "@workspace/orpc/react";
6
+ import { orpc } from "@/lib/orpc";
7
+ import { useEffect, useState } from "react";
8
+
9
+ export default function LandingScreen() {
10
+ const router = useRouter();
11
+ const [helloMessage, setHelloMessage] = useState("");
12
+ const { data: planets = [], isLoading } = usePlanets();
13
+
14
+ useEffect(() => {
15
+ orpc.hello({ name: "Croissant Stack Mobile" }).then((res) => {
16
+ setHelloMessage(res.message);
17
+ });
18
+ }, []);
19
+
20
+ return (
21
+ <ScrollView style={styles.container} contentContainerStyle={styles.content}>
22
+ <View style={styles.header}>
23
+ <Text style={styles.title}>Project ready!</Text>
24
+ <Text style={styles.subtitle}>
25
+ oRPC integration: <Text style={styles.bold}>{helloMessage || "Loading..."}</Text>
26
+ </Text>
27
+ </View>
28
+
29
+ <View style={styles.section}>
30
+ <Text style={styles.sectionTitle}>Planets from Database:</Text>
31
+ {isLoading ? (
32
+ <Text style={styles.loading}>Loading planets...</Text>
33
+ ) : planets.length === 0 ? (
34
+ <Text style={styles.empty}>
35
+ No planets found in the database. Run `db:push` and seed data if needed.
36
+ </Text>
37
+ ) : (
38
+ planets.map((planet: any) => (
39
+ <View key={planet.id} style={styles.planetCard}>
40
+ <Text style={styles.planetName}>{planet.name}</Text>
41
+ <Text style={styles.planetDesc}>{planet.description}</Text>
42
+ </View>
43
+ ))
44
+ )}
45
+ </View>
46
+
47
+ <View style={styles.footer}>
48
+ <Button onPress={() => router.push("/login")} style={styles.button}>
49
+ Go to Login
50
+ </Button>
51
+ <Button
52
+ onPress={() => router.push("/(tabs)")}
53
+ variant="outline"
54
+ style={styles.button}
55
+ >
56
+ Go to Dashboard
57
+ </Button>
58
+ <Text style={styles.footerText}>
59
+ You may now add components and start building.
60
+ </Text>
61
+ </View>
62
+ </ScrollView>
63
+ );
64
+ }
65
+
66
+ const styles = StyleSheet.create({
67
+ container: {
68
+ flex: 1,
69
+ backgroundColor: "#fff",
70
+ },
71
+ content: {
72
+ padding: 24,
73
+ paddingTop: 60,
74
+ },
75
+ header: {
76
+ marginBottom: 32,
77
+ },
78
+ title: {
79
+ fontSize: 28,
80
+ fontWeight: "bold",
81
+ marginBottom: 8,
82
+ },
83
+ subtitle: {
84
+ fontSize: 16,
85
+ color: "#666",
86
+ },
87
+ bold: {
88
+ fontWeight: "bold",
89
+ color: "#000",
90
+ },
91
+ section: {
92
+ marginBottom: 32,
93
+ },
94
+ sectionTitle: {
95
+ fontSize: 20,
96
+ fontWeight: "600",
97
+ marginBottom: 16,
98
+ },
99
+ planetCard: {
100
+ padding: 16,
101
+ borderRadius: 8,
102
+ borderWidth: 1,
103
+ borderColor: "#eee",
104
+ marginBottom: 12,
105
+ backgroundColor: "#fafafa",
106
+ },
107
+ planetName: {
108
+ fontSize: 16,
109
+ fontWeight: "bold",
110
+ marginBottom: 4,
111
+ },
112
+ planetDesc: {
113
+ fontSize: 14,
114
+ color: "#666",
115
+ },
116
+ loading: {
117
+ fontStyle: "italic",
118
+ color: "#999",
119
+ },
120
+ empty: {
121
+ fontStyle: "italic",
122
+ color: "#999",
123
+ },
124
+ footer: {
125
+ gap: 12,
126
+ },
127
+ button: {
128
+ width: "100%",
129
+ },
130
+ footerText: {
131
+ marginTop: 16,
132
+ textAlign: "center",
133
+ color: "#999",
134
+ fontSize: 14,
135
+ },
136
+ });
@@ -0,0 +1,135 @@
1
+ import { useState } from "react";
2
+ import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Text } from "react-native";
3
+ import { useRouter } from "expo-router";
4
+ import { authClient } from "@/lib/auth-client";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+
8
+ export default function LoginScreen() {
9
+ const router = useRouter();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [loading, setLoading] = useState(false);
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ const handleLogin = async () => {
16
+ if (!email || !password) {
17
+ setError("Please fill in all fields");
18
+ return;
19
+ }
20
+
21
+ setLoading(true);
22
+ setError(null);
23
+ try {
24
+ const { error } = await authClient.signIn.email({
25
+ email,
26
+ password,
27
+ });
28
+
29
+ if (error) {
30
+ setError(error.message || "Invalid credentials");
31
+ } else {
32
+ router.replace("/(tabs)");
33
+ }
34
+ } catch (err) {
35
+ setError(err instanceof Error ? err.message : "An error occurred during login");
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ };
40
+
41
+ return (
42
+ <KeyboardAvoidingView
43
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
44
+ style={styles.container}
45
+ >
46
+ <ScrollView contentContainerStyle={styles.scrollContent}>
47
+ <View style={styles.header}>
48
+ <Text style={styles.title}>Welcome Back</Text>
49
+ <Text style={styles.subtitle}>Sign in to your account</Text>
50
+ </View>
51
+
52
+ <View style={styles.form}>
53
+ <Input
54
+ label="Email"
55
+ placeholder="email@example.com"
56
+ value={email}
57
+ onChangeText={setEmail}
58
+ autoCapitalize="none"
59
+ keyboardType="email-address"
60
+ />
61
+
62
+ <Input
63
+ label="Password"
64
+ placeholder="••••••••"
65
+ value={password}
66
+ onChangeText={setPassword}
67
+ secureTextEntry
68
+ />
69
+
70
+ {error && <Text style={styles.errorText}>{error}</Text>}
71
+
72
+ <Button
73
+ onPress={handleLogin}
74
+ loading={loading}
75
+ >
76
+ Sign In
77
+ </Button>
78
+
79
+ <View style={styles.footer}>
80
+ <Text style={styles.footerText}>Don't have an account? </Text>
81
+ <Text
82
+ style={styles.link}
83
+ onPress={() => router.push("/signup")}
84
+ >
85
+ Sign Up
86
+ </Text>
87
+ </View>
88
+ </View>
89
+ </ScrollView>
90
+ </KeyboardAvoidingView>
91
+ );
92
+ }
93
+
94
+ const styles = StyleSheet.create({
95
+ container: {
96
+ flex: 1,
97
+ backgroundColor: "#fff",
98
+ },
99
+ scrollContent: {
100
+ flexGrow: 1,
101
+ padding: 24,
102
+ justifyContent: "center",
103
+ },
104
+ header: {
105
+ marginBottom: 40,
106
+ },
107
+ title: {
108
+ fontSize: 32,
109
+ fontWeight: "bold",
110
+ marginBottom: 8,
111
+ },
112
+ subtitle: {
113
+ fontSize: 16,
114
+ color: "#666",
115
+ },
116
+ form: {
117
+ gap: 20,
118
+ },
119
+ errorText: {
120
+ color: "#ef4444",
121
+ fontSize: 14,
122
+ },
123
+ footer: {
124
+ flexDirection: "row",
125
+ justifyContent: "center",
126
+ marginTop: 20,
127
+ },
128
+ footerText: {
129
+ color: "#666",
130
+ },
131
+ link: {
132
+ color: "#000",
133
+ fontWeight: "bold",
134
+ },
135
+ });
@@ -0,0 +1,144 @@
1
+ import { useState } from "react";
2
+ import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Text } from "react-native";
3
+ import { useRouter } from "expo-router";
4
+ import { authClient } from "@/lib/auth-client";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+
8
+ export default function SignupScreen() {
9
+ const router = useRouter();
10
+ const [name, setName] = useState("");
11
+ const [email, setEmail] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const [loading, setLoading] = useState(false);
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ const handleSignup = async () => {
17
+ if (!name || !email || !password) {
18
+ setError("Please fill in all fields");
19
+ return;
20
+ }
21
+
22
+ setLoading(true);
23
+ setError(null);
24
+ try {
25
+ const { error } = await authClient.signUp.email({
26
+ email,
27
+ password,
28
+ name,
29
+ });
30
+
31
+ if (error) {
32
+ setError(error.message || "An error occurred during signup");
33
+ } else {
34
+ router.replace("/(tabs)");
35
+ }
36
+ } catch (err) {
37
+ setError(err instanceof Error ? err.message : "An error occurred during signup");
38
+ } finally {
39
+ setLoading(false);
40
+ }
41
+ };
42
+
43
+ return (
44
+ <KeyboardAvoidingView
45
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
46
+ style={styles.container}
47
+ >
48
+ <ScrollView contentContainerStyle={styles.scrollContent}>
49
+ <View style={styles.header}>
50
+ <Text style={styles.title}>Create Account</Text>
51
+ <Text style={styles.subtitle}>Join the Croissant Stack</Text>
52
+ </View>
53
+
54
+ <View style={styles.form}>
55
+ <Input
56
+ label="Name"
57
+ placeholder="John Doe"
58
+ value={name}
59
+ onChangeText={setName}
60
+ />
61
+
62
+ <Input
63
+ label="Email"
64
+ placeholder="email@example.com"
65
+ value={email}
66
+ onChangeText={setEmail}
67
+ autoCapitalize="none"
68
+ keyboardType="email-address"
69
+ />
70
+
71
+ <Input
72
+ label="Password"
73
+ placeholder="••••••••"
74
+ value={password}
75
+ onChangeText={setPassword}
76
+ secureTextEntry
77
+ />
78
+
79
+ {error && <Text style={styles.errorText}>{error}</Text>}
80
+
81
+ <Button
82
+ onPress={handleSignup}
83
+ loading={loading}
84
+ >
85
+ Sign Up
86
+ </Button>
87
+
88
+ <View style={styles.footer}>
89
+ <Text style={styles.footerText}>Already have an account? </Text>
90
+ <Text
91
+ style={styles.link}
92
+ onPress={() => router.push("/login")}
93
+ >
94
+ Sign In
95
+ </Text>
96
+ </View>
97
+ </View>
98
+ </ScrollView>
99
+ </KeyboardAvoidingView>
100
+ );
101
+ }
102
+
103
+ const styles = StyleSheet.create({
104
+ container: {
105
+ flex: 1,
106
+ backgroundColor: "#fff",
107
+ },
108
+ scrollContent: {
109
+ flexGrow: 1,
110
+ padding: 24,
111
+ justifyContent: "center",
112
+ },
113
+ header: {
114
+ marginBottom: 40,
115
+ },
116
+ title: {
117
+ fontSize: 32,
118
+ fontWeight: "bold",
119
+ marginBottom: 8,
120
+ },
121
+ subtitle: {
122
+ fontSize: 16,
123
+ color: "#666",
124
+ },
125
+ form: {
126
+ gap: 20,
127
+ },
128
+ errorText: {
129
+ color: "#ef4444",
130
+ fontSize: 14,
131
+ },
132
+ footer: {
133
+ flexDirection: "row",
134
+ justifyContent: "center",
135
+ marginTop: 20,
136
+ },
137
+ footerText: {
138
+ color: "#666",
139
+ },
140
+ link: {
141
+ color: "#000",
142
+ fontWeight: "bold",
143
+ },
144
+ });
@@ -23,7 +23,8 @@
23
23
  },
24
24
  "web": {
25
25
  "output": "single",
26
- "favicon": "./assets/images/favicon.png"
26
+ "favicon": "./assets/images/favicon.png",
27
+ "bundler": "metro"
27
28
  },
28
29
  "plugins": [
29
30
  "expo-router",
@@ -44,8 +45,7 @@
44
45
  "expo-web-browser"
45
46
  ],
46
47
  "experiments": {
47
- "typedRoutes": true,
48
- "reactCompiler": true
48
+ "typedRoutes": true
49
49
  }
50
50
  }
51
51
  }
@@ -0,0 +1,86 @@
1
+ import React from "react";
2
+ import { TouchableOpacity, Text, StyleSheet, ActivityIndicator, ViewStyle, StyleProp } from "react-native";
3
+
4
+ interface ButtonProps {
5
+ onPress: () => void;
6
+ children: React.ReactNode;
7
+ disabled?: boolean;
8
+ loading?: boolean;
9
+ variant?: "primary" | "secondary" | "outline" | "destructive";
10
+ style?: StyleProp<ViewStyle>;
11
+ }
12
+
13
+ export function Button({
14
+ onPress,
15
+ children,
16
+ disabled,
17
+ loading,
18
+ variant = "primary",
19
+ style
20
+ }: ButtonProps) {
21
+ const containerStyle = [
22
+ styles.button,
23
+ styles[variant],
24
+ disabled && styles.disabled,
25
+ style,
26
+ ];
27
+
28
+ const textStyle = [
29
+ styles.text,
30
+ variant === "outline" && styles.outlineText,
31
+ disabled && styles.disabledText,
32
+ ];
33
+
34
+ return (
35
+ <TouchableOpacity
36
+ style={containerStyle}
37
+ onPress={onPress}
38
+ disabled={disabled || loading}
39
+ >
40
+ {loading ? (
41
+ <ActivityIndicator color={variant === "outline" ? "#000" : "#fff"} />
42
+ ) : (
43
+ <Text style={textStyle}>{children}</Text>
44
+ )}
45
+ </TouchableOpacity>
46
+ );
47
+ }
48
+
49
+ const styles = StyleSheet.create({
50
+ button: {
51
+ height: 48,
52
+ borderRadius: 8,
53
+ justifyContent: "center",
54
+ alignItems: "center",
55
+ paddingHorizontal: 16,
56
+ },
57
+ primary: {
58
+ backgroundColor: "#000",
59
+ },
60
+ secondary: {
61
+ backgroundColor: "#f0f0f0",
62
+ },
63
+ outline: {
64
+ backgroundColor: "transparent",
65
+ borderWidth: 1,
66
+ borderColor: "#e0e0e0",
67
+ },
68
+ destructive: {
69
+ backgroundColor: "#ef4444",
70
+ },
71
+ disabled: {
72
+ backgroundColor: "#ccc",
73
+ borderColor: "#ccc",
74
+ },
75
+ text: {
76
+ color: "#fff",
77
+ fontSize: 16,
78
+ fontWeight: "600",
79
+ },
80
+ outlineText: {
81
+ color: "#000",
82
+ },
83
+ disabledText: {
84
+ color: "#999",
85
+ },
86
+ });
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { TextInput, StyleSheet, TextInputProps, View, Text } from "react-native";
3
+
4
+ interface InputProps extends TextInputProps {
5
+ label?: string;
6
+ error?: string;
7
+ }
8
+
9
+ export function Input({ label, error, style, ...props }: InputProps) {
10
+ return (
11
+ <View style={styles.container}>
12
+ {label && <Text style={styles.label}>{label}</Text>}
13
+ <TextInput
14
+ style={[
15
+ styles.input,
16
+ error && styles.inputError,
17
+ style,
18
+ ]}
19
+ placeholderTextColor="#999"
20
+ {...props}
21
+ />
22
+ {error && <Text style={styles.errorText}>{error}</Text>}
23
+ </View>
24
+ );
25
+ }
26
+
27
+ const styles = StyleSheet.create({
28
+ container: {
29
+ width: "100%",
30
+ marginBottom: 16,
31
+ },
32
+ label: {
33
+ fontSize: 14,
34
+ fontWeight: "500",
35
+ marginBottom: 8,
36
+ color: "#333",
37
+ },
38
+ input: {
39
+ height: 48,
40
+ borderWidth: 1,
41
+ borderColor: "#e0e0e0",
42
+ borderRadius: 8,
43
+ paddingHorizontal: 12,
44
+ fontSize: 16,
45
+ color: "#000",
46
+ backgroundColor: "#fff",
47
+ },
48
+ inputError: {
49
+ borderColor: "#ff4444",
50
+ },
51
+ errorText: {
52
+ color: "#ff4444",
53
+ fontSize: 12,
54
+ marginTop: 4,
55
+ },
56
+ });
@@ -0,0 +1,23 @@
1
+ import { createORPCClient } from '@orpc/client'
2
+ import { RPCLink } from '@orpc/client/fetch'
3
+ import { RouterClient } from '@orpc/server'
4
+ import { router } from '@workspace/orpc/router'
5
+
6
+ export const link = new RPCLink({
7
+ url: `http://localhost:3000/api/rpc`,
8
+ async fetch(request, init) {
9
+ const { fetch } = await import('expo/fetch')
10
+
11
+ const resp = await fetch(request.url, {
12
+ body: await request.blob(),
13
+ headers: request.headers,
14
+ method: request.method,
15
+ signal: request.signal,
16
+ ...init,
17
+ })
18
+
19
+ return resp
20
+ },
21
+ })
22
+
23
+ export const orpc: RouterClient<typeof router> = createORPCClient(link)
@@ -15,9 +15,16 @@
15
15
  "dependencies": {
16
16
  "@better-auth/expo": "^1.6.9",
17
17
  "@expo/vector-icons": "^15.0.3",
18
+ "@orpc/client": "^1.14.0",
19
+ "@orpc/server": "^1.14.0",
20
+ "@orpc/tanstack-query": "^1.14.0",
18
21
  "@react-navigation/bottom-tabs": "^7.4.0",
19
22
  "@react-navigation/elements": "^2.6.3",
20
23
  "@react-navigation/native": "^7.1.8",
24
+ "@tanstack/react-form": "^1.29.1",
25
+ "@tanstack/react-query": "^5.100.5",
26
+ "@workspace/orpc": "^0.0.0",
27
+ "@workspace/ui": "^0.0.0",
21
28
  "better-auth": "^1.6.9",
22
29
  "expo": "^55.0.17",
23
30
  "expo-constants": "~55.0.15",
@@ -39,6 +46,11 @@
39
46
  "react-native-safe-area-context": "~5.6.0",
40
47
  "react-native-screens": "~4.23.0",
41
48
  "react-native-web": "~0.21.0",
42
- "react-native-worklets": "0.7.4"
49
+ "react-native-worklets": "0.7.4",
50
+ "zod": "4.3.6"
51
+ },
52
+ "devDependencies": {
53
+ "babel-plugin-transform-import-meta": "^2.3.3",
54
+ "metro-react-native-babel-transformer": "^0.77.0"
43
55
  }
44
56
  }
@@ -1,5 +1,8 @@
1
1
  {
2
- "extends": "expo/tsconfig.base",
2
+ "extends": [
3
+ "expo/tsconfig.base",
4
+ "@workspace/config-typescript/react.json"
5
+ ],
3
6
  "compilerOptions": {
4
7
  "strict": true,
5
8
  "paths": {
@@ -29,7 +29,8 @@
29
29
  "lucide-react": "^1.11.0",
30
30
  "nitro": "latest",
31
31
  "tailwindcss": "^4.2.4",
32
- "vite-tsconfig-paths": "^6.1.1"
32
+ "vite-tsconfig-paths": "^6.1.1",
33
+ "zod": "4.3.6"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^25.6.0",