create-better-t-stack 3.2.22 → 3.2.23-canary.b8d62867
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/README.md +2 -2
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/{src-WIwtBCHf.js → src-VoUvj8ZF.js} +106 -66
- package/package.json +1 -1
- package/templates/addons/biome/biome.json.hbs +5 -0
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +5 -5
- package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +1 -1
- package/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs +127 -0
- package/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs +138 -0
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +91 -0
- package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +102 -0
- package/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +186 -0
- package/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs +131 -0
- package/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs +150 -0
- package/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +9 -1
- package/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs +5 -0
- package/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs +5 -0
- package/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +123 -0
- package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +90 -0
- package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +116 -0
- package/templates/auth/better-auth/server/base/src/index.ts.hbs +5 -5
- package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +287 -0
- package/templates/examples/ai/native/{nativewind → bare}/polyfills.js +1 -0
- package/templates/examples/ai/native/{nativewind → uniwind}/app/(drawer)/ai.tsx.hbs +52 -51
- package/templates/examples/ai/native/uniwind/polyfills.js +26 -0
- package/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +521 -0
- package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +295 -0
- package/templates/extras/bunfig.toml.hbs +3 -3
- package/templates/frontend/native/bare/_gitignore +18 -0
- package/templates/frontend/native/{nativewind → bare}/app/(drawer)/(tabs)/_layout.tsx.hbs +7 -12
- package/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs +43 -0
- package/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs +43 -0
- package/templates/frontend/native/{nativewind → bare}/app/(drawer)/_layout.tsx.hbs +24 -1
- package/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +234 -0
- package/templates/frontend/native/bare/app/+not-found.tsx.hbs +65 -0
- package/templates/frontend/native/bare/app/_layout.tsx.hbs +163 -0
- package/templates/frontend/native/bare/app/modal.tsx.hbs +34 -0
- package/templates/frontend/native/{nativewind → bare}/app.json.hbs +1 -0
- package/templates/frontend/native/bare/components/container.tsx.hbs +25 -0
- package/templates/frontend/native/bare/components/header-button.tsx.hbs +47 -0
- package/templates/frontend/native/{nativewind → bare}/components/tabbar-icon.tsx.hbs +1 -0
- package/templates/frontend/native/{nativewind → bare}/lib/android-navigation-bar.tsx.hbs +1 -0
- package/templates/frontend/native/{nativewind → bare}/lib/constants.ts.hbs +1 -0
- package/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs +20 -0
- package/templates/frontend/native/bare/metro.config.js.hbs +9 -0
- package/templates/frontend/native/{nativewind → bare}/package.json.hbs +1 -2
- package/templates/frontend/native/bare/tsconfig.json.hbs +11 -0
- package/templates/frontend/native/{nativewind → uniwind}/_gitignore +4 -8
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs +46 -0
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs +15 -0
- package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs +15 -0
- package/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs +83 -0
- package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +151 -0
- package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +32 -0
- package/templates/frontend/native/uniwind/app/_layout.tsx.hbs +131 -0
- package/templates/frontend/native/uniwind/app/modal.tsx.hbs +53 -0
- package/templates/frontend/native/uniwind/app.json.hbs +19 -0
- package/templates/frontend/native/uniwind/components/container.tsx.hbs +33 -0
- package/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs +35 -0
- package/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs +62 -0
- package/templates/frontend/native/uniwind/global.css +5 -0
- package/templates/frontend/native/uniwind/metro.config.js.hbs +13 -0
- package/templates/frontend/native/uniwind/package.json.hbs +54 -0
- package/templates/frontend/native/{nativewind → uniwind}/tsconfig.json.hbs +4 -8
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +0 -86
- package/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +0 -97
- package/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs +0 -95
- package/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs +0 -93
- package/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs +0 -104
- package/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs +0 -295
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs +0 -19
- package/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs +0 -19
- package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +0 -178
- package/templates/frontend/native/nativewind/app/+not-found.tsx.hbs +0 -29
- package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +0 -175
- package/templates/frontend/native/nativewind/app/modal.tsx.hbs +0 -14
- package/templates/frontend/native/nativewind/babel.config.js.hbs +0 -14
- package/templates/frontend/native/nativewind/components/container.tsx.hbs +0 -8
- package/templates/frontend/native/nativewind/components/header-button.tsx.hbs +0 -26
- package/templates/frontend/native/nativewind/global.css +0 -50
- package/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs +0 -12
- package/templates/frontend/native/nativewind/metro.config.js.hbs +0 -12
- package/templates/frontend/native/nativewind/tailwind.config.js.hbs +0 -59
- /package/templates/auth/clerk/convex/native/base/app/(auth)/{sign-out.tsx.hbs → sign-up.tsx.hbs} +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Tabs } from "expo-router";
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
|
+
import { useThemeColor } from "heroui-native";
|
|
4
|
+
|
|
5
|
+
export default function TabLayout() {
|
|
6
|
+
const themeColorForeground = useThemeColor("foreground");
|
|
7
|
+
const themeColorBackground = useThemeColor("background");
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Tabs
|
|
11
|
+
screenOptions=\{{
|
|
12
|
+
headerShown: false,
|
|
13
|
+
headerStyle: {
|
|
14
|
+
backgroundColor: themeColorBackground,
|
|
15
|
+
},
|
|
16
|
+
headerTintColor: themeColorForeground,
|
|
17
|
+
headerTitleStyle: {
|
|
18
|
+
color: themeColorForeground,
|
|
19
|
+
fontWeight: "600",
|
|
20
|
+
},
|
|
21
|
+
tabBarStyle: {
|
|
22
|
+
backgroundColor: themeColorBackground,
|
|
23
|
+
},
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<Tabs.Screen
|
|
27
|
+
name="index"
|
|
28
|
+
options=\{{
|
|
29
|
+
title: "Home",
|
|
30
|
+
tabBarIcon: ({ color, size }: { color: string; size: number }) => (
|
|
31
|
+
<Ionicons name="home" size={size} color={color} />
|
|
32
|
+
),
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<Tabs.Screen
|
|
36
|
+
name="two"
|
|
37
|
+
options=\{{
|
|
38
|
+
title: "Explore",
|
|
39
|
+
tabBarIcon: ({ color, size }: { color: string; size: number }) => (
|
|
40
|
+
<Ionicons name="compass" size={size} color={color} />
|
|
41
|
+
),
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</Tabs>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Container } from "@/components/container";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { Card } from "heroui-native";
|
|
4
|
+
|
|
5
|
+
export default function Home() {
|
|
6
|
+
return (
|
|
7
|
+
<Container className="p-6">
|
|
8
|
+
<View className="flex-1 justify-center items-center">
|
|
9
|
+
<Card variant="secondary" className="p-8 items-center">
|
|
10
|
+
<Card.Title className="text-3xl mb-2">Tab One</Card.Title>
|
|
11
|
+
</Card>
|
|
12
|
+
</View>
|
|
13
|
+
</Container>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Container } from "@/components/container";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { Card } from "heroui-native";
|
|
4
|
+
|
|
5
|
+
export default function TabTwo() {
|
|
6
|
+
return (
|
|
7
|
+
<Container className="p-6">
|
|
8
|
+
<View className="flex-1 justify-center items-center">
|
|
9
|
+
<Card variant="secondary" className="p-8 items-center">
|
|
10
|
+
<Card.Title className="text-3xl mb-2">TabTwo</Card.Title>
|
|
11
|
+
</Card>
|
|
12
|
+
</View>
|
|
13
|
+
</Container>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
|
+
import { Ionicons, MaterialIcons } from "@expo/vector-icons";
|
|
3
|
+
import { Link } from "expo-router";
|
|
4
|
+
import { Drawer } from "expo-router/drawer";
|
|
5
|
+
import { useThemeColor } from "heroui-native";
|
|
6
|
+
import { Pressable } from "react-native";
|
|
7
|
+
import { ThemeToggle } from "@/components/theme-toggle";
|
|
8
|
+
|
|
9
|
+
function DrawerLayout() {
|
|
10
|
+
const themeColorForeground = useThemeColor("foreground");
|
|
11
|
+
const themeColorBackground = useThemeColor("background");
|
|
12
|
+
|
|
13
|
+
const renderThemeToggle = useCallback(() => <ThemeToggle />, []);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Drawer
|
|
17
|
+
screenOptions=\{{
|
|
18
|
+
headerTintColor: themeColorForeground,
|
|
19
|
+
headerStyle: { backgroundColor: themeColorBackground },
|
|
20
|
+
headerTitleStyle: {
|
|
21
|
+
fontWeight: "600",
|
|
22
|
+
color: themeColorForeground,
|
|
23
|
+
},
|
|
24
|
+
headerRight: renderThemeToggle,
|
|
25
|
+
drawerStyle: { backgroundColor: themeColorBackground },
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<Drawer.Screen
|
|
29
|
+
name="index"
|
|
30
|
+
options=\{{
|
|
31
|
+
headerTitle: "Home",
|
|
32
|
+
drawerLabel: "Home",
|
|
33
|
+
drawerIcon: ({ size, color }) => (
|
|
34
|
+
<Ionicons name="home-outline" size={size} color={color} />
|
|
35
|
+
),
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
<Drawer.Screen
|
|
39
|
+
name="(tabs)"
|
|
40
|
+
options=\{{
|
|
41
|
+
headerTitle: "Tabs",
|
|
42
|
+
drawerLabel: "Tabs",
|
|
43
|
+
drawerIcon: ({ size, color }) => (
|
|
44
|
+
<MaterialIcons name="border-bottom" size={size} color={color} />
|
|
45
|
+
),
|
|
46
|
+
headerRight: () => (
|
|
47
|
+
<Link href="/modal" asChild>
|
|
48
|
+
<Pressable className="mr-4">
|
|
49
|
+
<Ionicons name="add-outline" size={24} color={themeColorForeground} />
|
|
50
|
+
</Pressable>
|
|
51
|
+
</Link>
|
|
52
|
+
),
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
{{#if (includes examples "todo")}}
|
|
56
|
+
<Drawer.Screen
|
|
57
|
+
name="todos"
|
|
58
|
+
options=\{{
|
|
59
|
+
headerTitle: "Todos",
|
|
60
|
+
drawerLabel: "Todos",
|
|
61
|
+
drawerIcon: ({ size, color }) => (
|
|
62
|
+
<Ionicons name="checkbox-outline" size={size} color={color} />
|
|
63
|
+
),
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
{{/if}}
|
|
67
|
+
{{#if (includes examples "ai")}}
|
|
68
|
+
<Drawer.Screen
|
|
69
|
+
name="ai"
|
|
70
|
+
options=\{{
|
|
71
|
+
headerTitle: "AI",
|
|
72
|
+
drawerLabel: "AI",
|
|
73
|
+
drawerIcon: ({ size, color }) => (
|
|
74
|
+
<Ionicons name="chatbubble-ellipses-outline" size={size} color={color} />
|
|
75
|
+
),
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
{{/if}}
|
|
79
|
+
</Drawer>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default DrawerLayout;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Text, View } from "react-native";
|
|
2
|
+
import { Container } from "@/components/container";
|
|
3
|
+
{{#if (eq api "orpc")}}
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
|
+
import { orpc } from "@/utils/orpc";
|
|
6
|
+
{{/if}}
|
|
7
|
+
{{#if (eq api "trpc")}}
|
|
8
|
+
import { useQuery } from "@tanstack/react-query";
|
|
9
|
+
import { trpc } from "@/utils/trpc";
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
12
|
+
import { Link } from "expo-router";
|
|
13
|
+
import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
|
|
14
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
15
|
+
import { useUser } from "@clerk/clerk-expo";
|
|
16
|
+
import { SignOutButton } from "@/components/sign-out-button";
|
|
17
|
+
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
18
|
+
import { useConvexAuth, useQuery } from "convex/react";
|
|
19
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
20
|
+
import { authClient } from "@/lib/auth-client";
|
|
21
|
+
import { SignIn } from "@/components/sign-in";
|
|
22
|
+
import { SignUp } from "@/components/sign-up";
|
|
23
|
+
{{else if (eq backend "convex")}}
|
|
24
|
+
import { useQuery } from "convex/react";
|
|
25
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
26
|
+
{{/if}}
|
|
27
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
28
|
+
import { Card, Chip, useThemeColor } from "heroui-native";
|
|
29
|
+
|
|
30
|
+
export default function Home() {
|
|
31
|
+
{{#if (eq api "orpc")}}
|
|
32
|
+
const healthCheck = useQuery(orpc.healthCheck.queryOptions());
|
|
33
|
+
{{/if}}
|
|
34
|
+
{{#if (eq api "trpc")}}
|
|
35
|
+
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
|
|
36
|
+
{{/if}}
|
|
37
|
+
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
38
|
+
const { user } = useUser();
|
|
39
|
+
const healthCheck = useQuery(api.healthCheck.get);
|
|
40
|
+
const privateData = useQuery(api.privateData.get);
|
|
41
|
+
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
42
|
+
const healthCheck = useQuery(api.healthCheck.get);
|
|
43
|
+
const { isAuthenticated } = useConvexAuth();
|
|
44
|
+
const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
|
|
45
|
+
{{else if (eq backend "convex")}}
|
|
46
|
+
const healthCheck = useQuery(api.healthCheck.get);
|
|
47
|
+
{{/if}}
|
|
48
|
+
const mutedColor = useThemeColor("muted");
|
|
49
|
+
const successColor = useThemeColor("success");
|
|
50
|
+
const dangerColor = useThemeColor("danger");
|
|
51
|
+
|
|
52
|
+
{{#if (eq backend "convex")}}
|
|
53
|
+
const isConnected = healthCheck === "OK";
|
|
54
|
+
const isLoading = healthCheck === undefined;
|
|
55
|
+
{{else}}
|
|
56
|
+
{{#unless (eq api "none")}}
|
|
57
|
+
const isConnected = healthCheck?.data === "OK";
|
|
58
|
+
const isLoading = healthCheck?.isLoading;
|
|
59
|
+
{{/unless}}
|
|
60
|
+
{{/if}}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Container className="p-6">
|
|
64
|
+
<View className="py-4 mb-6">
|
|
65
|
+
<Text className="text-4xl font-bold text-foreground mb-2">
|
|
66
|
+
BETTER T STACK
|
|
67
|
+
</Text>
|
|
68
|
+
</View>
|
|
69
|
+
|
|
70
|
+
{{#unless (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
71
|
+
<Card variant="secondary" className="p-6">
|
|
72
|
+
<View className="flex-row items-center justify-between mb-4">
|
|
73
|
+
<Card.Title>System Status</Card.Title>
|
|
74
|
+
<Chip
|
|
75
|
+
variant="secondary"
|
|
76
|
+
color={isConnected ? "success" : "danger"}
|
|
77
|
+
size="sm"
|
|
78
|
+
>
|
|
79
|
+
<Chip.Label>
|
|
80
|
+
{isConnected ? "LIVE" : "OFFLINE"}
|
|
81
|
+
</Chip.Label>
|
|
82
|
+
</Chip>
|
|
83
|
+
</View>
|
|
84
|
+
<Card className="p-4">
|
|
85
|
+
<View className="flex-row items-center">
|
|
86
|
+
<View
|
|
87
|
+
className={`w-3 h-3 rounded-full mr-3 ${
|
|
88
|
+
isConnected ? "bg-success" : "bg-muted"
|
|
89
|
+
}`}
|
|
90
|
+
/>
|
|
91
|
+
<View className="flex-1">
|
|
92
|
+
<Text className="text-foreground font-medium mb-1">
|
|
93
|
+
{{#if (eq backend "convex")}}
|
|
94
|
+
Convex Backend
|
|
95
|
+
{{else}}
|
|
96
|
+
{{#unless (eq api "none")}}
|
|
97
|
+
{{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend
|
|
98
|
+
{{/unless}}
|
|
99
|
+
{{/if}}
|
|
100
|
+
</Text>
|
|
101
|
+
<Card.Description>
|
|
102
|
+
{isLoading
|
|
103
|
+
? "Checking connection..."
|
|
104
|
+
: isConnected
|
|
105
|
+
? "Connected to API"
|
|
106
|
+
: "API Disconnected"}
|
|
107
|
+
</Card.Description>
|
|
108
|
+
</View>
|
|
109
|
+
{isLoading && (
|
|
110
|
+
<Ionicons name="hourglass-outline" size={20} color={mutedColor} />
|
|
111
|
+
)}
|
|
112
|
+
{!isLoading && isConnected && (
|
|
113
|
+
<Ionicons name="checkmark-circle" size={20} color={successColor} />
|
|
114
|
+
)}
|
|
115
|
+
{!isLoading && !isConnected && (
|
|
116
|
+
<Ionicons name="close-circle" size={20} color={dangerColor} />
|
|
117
|
+
)}
|
|
118
|
+
</View>
|
|
119
|
+
</Card>
|
|
120
|
+
</Card>
|
|
121
|
+
{{/unless}}
|
|
122
|
+
|
|
123
|
+
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
124
|
+
<Authenticated>
|
|
125
|
+
<Card variant="secondary" className="mt-6 p-4">
|
|
126
|
+
<Text className="text-foreground text-base mb-2">
|
|
127
|
+
Hello {user?.emailAddresses[0].emailAddress}
|
|
128
|
+
</Text>
|
|
129
|
+
<Text className="text-muted text-sm mb-4">
|
|
130
|
+
Private Data: {privateData?.message}
|
|
131
|
+
</Text>
|
|
132
|
+
<SignOutButton />
|
|
133
|
+
</Card>
|
|
134
|
+
</Authenticated>
|
|
135
|
+
<Unauthenticated>
|
|
136
|
+
<View className="mt-6 gap-4">
|
|
137
|
+
<Link href="/(auth)/sign-in" asChild>
|
|
138
|
+
<Text className="text-primary font-semibold">Sign in</Text>
|
|
139
|
+
</Link>
|
|
140
|
+
<Link href="/(auth)/sign-up" asChild>
|
|
141
|
+
<Text className="text-primary font-semibold">Sign up</Text>
|
|
142
|
+
</Link>
|
|
143
|
+
</View>
|
|
144
|
+
</Unauthenticated>
|
|
145
|
+
<AuthLoading>
|
|
146
|
+
<Text className="text-muted">Loading...</Text>
|
|
147
|
+
</AuthLoading>
|
|
148
|
+
{{/if}}
|
|
149
|
+
</Container>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Container } from "@/components/container";
|
|
2
|
+
import { Link, Stack } from "expo-router";
|
|
3
|
+
import { Text, View, Pressable } from "react-native";
|
|
4
|
+
import { Card } from "heroui-native";
|
|
5
|
+
|
|
6
|
+
export default function NotFoundScreen() {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<Stack.Screen options=\{{ title: "Oops!" }} />
|
|
10
|
+
<Container>
|
|
11
|
+
<View className="flex-1 justify-center items-center p-6">
|
|
12
|
+
<Card variant="secondary" className="items-center p-8 max-w-md">
|
|
13
|
+
<Text className="text-6xl mb-4">🤔</Text>
|
|
14
|
+
<Card.Title className="text-2xl text-center mb-2">
|
|
15
|
+
Page Not Found
|
|
16
|
+
</Card.Title>
|
|
17
|
+
<Card.Description className="text-center mb-6">
|
|
18
|
+
Sorry, the page you're looking for doesn't exist.
|
|
19
|
+
</Card.Description>
|
|
20
|
+
<Link href="/" asChild>
|
|
21
|
+
<Pressable className="bg-accent px-6 py-3 rounded-lg active:opacity-70">
|
|
22
|
+
<Text className="text-accent-foreground font-medium text-base">
|
|
23
|
+
Go to Home
|
|
24
|
+
</Text>
|
|
25
|
+
</Pressable>
|
|
26
|
+
</Link>
|
|
27
|
+
</Card>
|
|
28
|
+
</View>
|
|
29
|
+
</Container>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{{#if (includes examples "ai")}}
|
|
2
|
+
import "@/polyfills";
|
|
3
|
+
{{/if}}
|
|
4
|
+
|
|
5
|
+
import "@/global.css";
|
|
6
|
+
|
|
7
|
+
{{#if (eq backend "convex")}}
|
|
8
|
+
{{#if (eq auth "better-auth")}}
|
|
9
|
+
import { ConvexReactClient } from "convex/react";
|
|
10
|
+
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
|
|
11
|
+
import { authClient } from "@/lib/auth-client";
|
|
12
|
+
{{else}}
|
|
13
|
+
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
14
|
+
{{/if}}
|
|
15
|
+
|
|
16
|
+
{{#if (eq auth "clerk")}}
|
|
17
|
+
import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
|
|
18
|
+
import { ConvexProviderWithClerk } from "convex/react-clerk";
|
|
19
|
+
import { tokenCache } from "@clerk/clerk-expo/token-cache";
|
|
20
|
+
{{/if}}
|
|
21
|
+
{{else}}
|
|
22
|
+
{{#unless (eq api "none")}}
|
|
23
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
24
|
+
{{/unless}}
|
|
25
|
+
{{/if}}
|
|
26
|
+
|
|
27
|
+
import { Stack } from "expo-router";
|
|
28
|
+
import { HeroUINativeProvider } from "heroui-native";
|
|
29
|
+
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
30
|
+
import { KeyboardProvider } from "react-native-keyboard-controller";
|
|
31
|
+
import { AppThemeProvider } from "@/contexts/app-theme-context";
|
|
32
|
+
|
|
33
|
+
{{#if (eq api "trpc")}}
|
|
34
|
+
import { queryClient } from "@/utils/trpc";
|
|
35
|
+
{{/if}}
|
|
36
|
+
{{#if (eq api "orpc")}}
|
|
37
|
+
import { queryClient } from "@/utils/orpc";
|
|
38
|
+
{{/if}}
|
|
39
|
+
|
|
40
|
+
export const unstable_settings = {
|
|
41
|
+
initialRouteName: "(drawer)",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
{{#if (eq backend "convex")}}
|
|
45
|
+
const convexUrl = process.env.EXPO_PUBLIC_CONVEX_URL || "";
|
|
46
|
+
const convex = new ConvexReactClient(convexUrl, {
|
|
47
|
+
unsavedChangesWarning: false,
|
|
48
|
+
});
|
|
49
|
+
{{/if}}
|
|
50
|
+
|
|
51
|
+
function StackLayout() {
|
|
52
|
+
return (
|
|
53
|
+
<Stack screenOptions=\{{}}>
|
|
54
|
+
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
|
|
55
|
+
{{#if (eq auth "clerk")}}
|
|
56
|
+
<Stack.Screen name="(auth)" options=\{{ headerShown: false }} />
|
|
57
|
+
{{/if}}
|
|
58
|
+
<Stack.Screen name="modal" options=\{{ title: "Modal", presentation: "modal" }} />
|
|
59
|
+
</Stack>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default function Layout() {
|
|
64
|
+
return (
|
|
65
|
+
{{#if (eq backend "convex")}}
|
|
66
|
+
{{#if (eq auth "clerk")}}
|
|
67
|
+
<ClerkProvider tokenCache={tokenCache} publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>
|
|
68
|
+
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
|
|
69
|
+
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
|
70
|
+
<KeyboardProvider>
|
|
71
|
+
<AppThemeProvider>
|
|
72
|
+
<HeroUINativeProvider>
|
|
73
|
+
<StackLayout />
|
|
74
|
+
</HeroUINativeProvider>
|
|
75
|
+
</AppThemeProvider>
|
|
76
|
+
</KeyboardProvider>
|
|
77
|
+
</GestureHandlerRootView>
|
|
78
|
+
</ConvexProviderWithClerk>
|
|
79
|
+
</ClerkProvider>
|
|
80
|
+
{{else if (eq auth "better-auth")}}
|
|
81
|
+
<ConvexBetterAuthProvider client={convex} authClient={authClient}>
|
|
82
|
+
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
|
83
|
+
<KeyboardProvider>
|
|
84
|
+
<AppThemeProvider>
|
|
85
|
+
<HeroUINativeProvider>
|
|
86
|
+
<StackLayout />
|
|
87
|
+
</HeroUINativeProvider>
|
|
88
|
+
</AppThemeProvider>
|
|
89
|
+
</KeyboardProvider>
|
|
90
|
+
</GestureHandlerRootView>
|
|
91
|
+
</ConvexBetterAuthProvider>
|
|
92
|
+
{{else}}
|
|
93
|
+
<ConvexProvider client={convex}>
|
|
94
|
+
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
|
95
|
+
<KeyboardProvider>
|
|
96
|
+
<AppThemeProvider>
|
|
97
|
+
<HeroUINativeProvider>
|
|
98
|
+
<StackLayout />
|
|
99
|
+
</HeroUINativeProvider>
|
|
100
|
+
</AppThemeProvider>
|
|
101
|
+
</KeyboardProvider>
|
|
102
|
+
</GestureHandlerRootView>
|
|
103
|
+
</ConvexProvider>
|
|
104
|
+
{{/if}}
|
|
105
|
+
{{else}}
|
|
106
|
+
{{#unless (eq api "none")}}
|
|
107
|
+
<QueryClientProvider client={queryClient}>
|
|
108
|
+
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
|
109
|
+
<KeyboardProvider>
|
|
110
|
+
<AppThemeProvider>
|
|
111
|
+
<HeroUINativeProvider>
|
|
112
|
+
<StackLayout />
|
|
113
|
+
</HeroUINativeProvider>
|
|
114
|
+
</AppThemeProvider>
|
|
115
|
+
</KeyboardProvider>
|
|
116
|
+
</GestureHandlerRootView>
|
|
117
|
+
</QueryClientProvider>
|
|
118
|
+
{{else}}
|
|
119
|
+
<GestureHandlerRootView style=\{{ flex: 1 }}>
|
|
120
|
+
<KeyboardProvider>
|
|
121
|
+
<AppThemeProvider>
|
|
122
|
+
<HeroUINativeProvider>
|
|
123
|
+
<StackLayout />
|
|
124
|
+
</HeroUINativeProvider>
|
|
125
|
+
</AppThemeProvider>
|
|
126
|
+
</KeyboardProvider>
|
|
127
|
+
</GestureHandlerRootView>
|
|
128
|
+
{{/unless}}
|
|
129
|
+
{{/if}}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Container } from "@/components/container";
|
|
2
|
+
import { Text, View, Pressable } from "react-native";
|
|
3
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
4
|
+
import { router } from "expo-router";
|
|
5
|
+
import { Card, useThemeColor } from "heroui-native";
|
|
6
|
+
|
|
7
|
+
function Modal() {
|
|
8
|
+
const accentForegroundColor = useThemeColor("accent-foreground");
|
|
9
|
+
|
|
10
|
+
function handleClose() {
|
|
11
|
+
router.back();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Container>
|
|
16
|
+
<View className="flex-1 justify-center items-center p-6">
|
|
17
|
+
<Card variant="secondary" className="p-6 w-full max-w-sm">
|
|
18
|
+
<Card.Body className="gap-4 items-center">
|
|
19
|
+
<View className="w-16 h-16 bg-accent rounded-full items-center justify-center mb-2">
|
|
20
|
+
<Ionicons name="checkmark" size={32} color={accentForegroundColor} />
|
|
21
|
+
</View>
|
|
22
|
+
<Card.Title className="text-center text-xl">
|
|
23
|
+
Modal Screen
|
|
24
|
+
</Card.Title>
|
|
25
|
+
<Card.Description className="text-center">
|
|
26
|
+
This is an example modal screen. You can use this pattern for
|
|
27
|
+
dialogs, confirmations, or any overlay content.
|
|
28
|
+
</Card.Description>
|
|
29
|
+
</Card.Body>
|
|
30
|
+
<Card.Footer className="mt-4">
|
|
31
|
+
<Pressable
|
|
32
|
+
onPress={handleClose}
|
|
33
|
+
className="bg-accent p-4 rounded-lg w-full active:opacity-70"
|
|
34
|
+
>
|
|
35
|
+
<View className="flex-row items-center justify-center">
|
|
36
|
+
<Text className="text-accent-foreground font-semibold text-base mr-2">
|
|
37
|
+
Close Modal
|
|
38
|
+
</Text>
|
|
39
|
+
<Ionicons
|
|
40
|
+
name="close-circle"
|
|
41
|
+
size={20}
|
|
42
|
+
color={accentForegroundColor}
|
|
43
|
+
/>
|
|
44
|
+
</View>
|
|
45
|
+
</Pressable>
|
|
46
|
+
</Card.Footer>
|
|
47
|
+
</Card>
|
|
48
|
+
</View>
|
|
49
|
+
</Container>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default Modal;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"scheme": "{{projectName}}",
|
|
4
|
+
"userInterfaceStyle": "automatic",
|
|
5
|
+
"orientation": "default",
|
|
6
|
+
"web": {
|
|
7
|
+
"bundler": "metro"
|
|
8
|
+
},
|
|
9
|
+
"name": "{{projectName}}",
|
|
10
|
+
"slug": "{{projectName}}",
|
|
11
|
+
"plugins": [
|
|
12
|
+
"expo-font"
|
|
13
|
+
],
|
|
14
|
+
"experiments": {
|
|
15
|
+
"typedRoutes": true,
|
|
16
|
+
"reactCompiler": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "heroui-native";
|
|
2
|
+
import { type PropsWithChildren } from "react";
|
|
3
|
+
import { ScrollView, View, type ViewProps } from "react-native";
|
|
4
|
+
import Animated, { type AnimatedProps } from "react-native-reanimated";
|
|
5
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
|
+
|
|
7
|
+
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
8
|
+
|
|
9
|
+
type Props = AnimatedProps<ViewProps> & {
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function Container({
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: PropsWithChildren<Props>) {
|
|
18
|
+
const insets = useSafeAreaInsets();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<AnimatedView
|
|
22
|
+
className={cn("flex-1 bg-background", className)}
|
|
23
|
+
style=\{{
|
|
24
|
+
paddingBottom: insets.bottom,
|
|
25
|
+
}}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<ScrollView contentContainerStyle=\{{ flexGrow: 1 }}>
|
|
29
|
+
{children}
|
|
30
|
+
</ScrollView>
|
|
31
|
+
</AnimatedView>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
2
|
+
import * as Haptics from 'expo-haptics';
|
|
3
|
+
import { Platform, Pressable } from 'react-native';
|
|
4
|
+
import Animated, { FadeOut, ZoomIn } from 'react-native-reanimated';
|
|
5
|
+
import { withUniwind } from 'uniwind';
|
|
6
|
+
import { useAppTheme } from '@/contexts/app-theme-context';
|
|
7
|
+
|
|
8
|
+
const StyledIonicons = withUniwind(Ionicons);
|
|
9
|
+
|
|
10
|
+
export function ThemeToggle() {
|
|
11
|
+
const { toggleTheme, isLight } = useAppTheme();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Pressable
|
|
15
|
+
onPress={() => {
|
|
16
|
+
if (Platform.OS === 'ios') {
|
|
17
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
18
|
+
}
|
|
19
|
+
toggleTheme();
|
|
20
|
+
}}
|
|
21
|
+
className="px-2.5"
|
|
22
|
+
>
|
|
23
|
+
{isLight ? (
|
|
24
|
+
<Animated.View key="moon" entering={ZoomIn} exiting={FadeOut}>
|
|
25
|
+
<StyledIonicons name="moon" size={20} className="text-foreground" />
|
|
26
|
+
</Animated.View>
|
|
27
|
+
) : (
|
|
28
|
+
<Animated.View key="sun" entering={ZoomIn} exiting={FadeOut}>
|
|
29
|
+
<StyledIonicons name="sunny" size={20} className="text-foreground" />
|
|
30
|
+
</Animated.View>
|
|
31
|
+
)}
|
|
32
|
+
</Pressable>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useContext, useMemo } from 'react';
|
|
2
|
+
import { Uniwind, useUniwind } from 'uniwind';
|
|
3
|
+
|
|
4
|
+
type ThemeName = 'light' | 'dark';
|
|
5
|
+
|
|
6
|
+
type AppThemeContextType = {
|
|
7
|
+
currentTheme: string;
|
|
8
|
+
isLight: boolean;
|
|
9
|
+
isDark: boolean;
|
|
10
|
+
setTheme: (theme: ThemeName) => void;
|
|
11
|
+
toggleTheme: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const AppThemeContext = createContext<AppThemeContextType | undefined>(
|
|
15
|
+
undefined
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const AppThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
|
19
|
+
const { theme } = useUniwind();
|
|
20
|
+
|
|
21
|
+
const isLight = useMemo(() => {
|
|
22
|
+
return theme === 'light';
|
|
23
|
+
}, [theme]);
|
|
24
|
+
|
|
25
|
+
const isDark = useMemo(() => {
|
|
26
|
+
return theme === 'dark';
|
|
27
|
+
}, [theme]);
|
|
28
|
+
|
|
29
|
+
const setTheme = useCallback((newTheme: ThemeName) => {
|
|
30
|
+
Uniwind.setTheme(newTheme);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const toggleTheme = useCallback(() => {
|
|
34
|
+
Uniwind.setTheme(theme === 'light' ? 'dark' : 'light');
|
|
35
|
+
}, [theme]);
|
|
36
|
+
|
|
37
|
+
const value = useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
currentTheme: theme,
|
|
40
|
+
isLight,
|
|
41
|
+
isDark,
|
|
42
|
+
setTheme,
|
|
43
|
+
toggleTheme,
|
|
44
|
+
}),
|
|
45
|
+
[theme, isLight, isDark, setTheme, toggleTheme]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<AppThemeContext.Provider value={value}>
|
|
50
|
+
{children}
|
|
51
|
+
</AppThemeContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function useAppTheme() {
|
|
56
|
+
const context = useContext(AppThemeContext);
|
|
57
|
+
if (!context) {
|
|
58
|
+
throw new Error('useAppTheme must be used within AppThemeProvider');
|
|
59
|
+
}
|
|
60
|
+
return context;
|
|
61
|
+
}
|
|
62
|
+
|