create-react-native-airborne 0.0.1
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 +24 -0
- package/package.json +21 -0
- package/src/index.mjs +103 -0
- package/template/.agents/skills/convex-best-practices/SKILL.md +333 -0
- package/template/.agents/skills/convex-file-storage/SKILL.md +466 -0
- package/template/.agents/skills/convex-security-audit/SKILL.md +538 -0
- package/template/.agents/skills/convex-security-check/SKILL.md +377 -0
- package/template/.github/workflows/ci.yml +130 -0
- package/template/.prettierignore +8 -0
- package/template/.prettierrc.json +6 -0
- package/template/AGENTS.md +156 -0
- package/template/Justfile +48 -0
- package/template/README.md +94 -0
- package/template/client/.env.example +3 -0
- package/template/client/.vscode/extensions.json +1 -0
- package/template/client/.vscode/settings.json +7 -0
- package/template/client/README.md +33 -0
- package/template/client/app/(app)/_layout.tsx +34 -0
- package/template/client/app/(app)/index.tsx +66 -0
- package/template/client/app/(app)/push.tsx +75 -0
- package/template/client/app/(app)/settings.tsx +36 -0
- package/template/client/app/(auth)/_layout.tsx +22 -0
- package/template/client/app/(auth)/sign-in.tsx +358 -0
- package/template/client/app/(auth)/sign-up.tsx +237 -0
- package/template/client/app/_layout.tsx +30 -0
- package/template/client/app/index.tsx +127 -0
- package/template/client/app.config.ts +30 -0
- package/template/client/assets/images/android-icon-background.png +0 -0
- package/template/client/assets/images/android-icon-foreground.png +0 -0
- package/template/client/assets/images/android-icon-monochrome.png +0 -0
- package/template/client/assets/images/favicon.png +0 -0
- package/template/client/assets/images/icon.png +0 -0
- package/template/client/assets/images/partial-react-logo.png +0 -0
- package/template/client/assets/images/react-logo.png +0 -0
- package/template/client/assets/images/react-logo@2x.png +0 -0
- package/template/client/assets/images/react-logo@3x.png +0 -0
- package/template/client/assets/images/splash-icon.png +0 -0
- package/template/client/eslint.config.js +10 -0
- package/template/client/global.css +2 -0
- package/template/client/metro.config.js +9 -0
- package/template/client/package.json +51 -0
- package/template/client/src/components/auth-shell.tsx +63 -0
- package/template/client/src/components/form-input.tsx +62 -0
- package/template/client/src/components/primary-button.tsx +37 -0
- package/template/client/src/components/screen.tsx +17 -0
- package/template/client/src/components/sign-out-button.tsx +32 -0
- package/template/client/src/hooks/use-theme-sync.ts +11 -0
- package/template/client/src/lib/convex.ts +6 -0
- package/template/client/src/lib/env-schema.ts +13 -0
- package/template/client/src/lib/env.test.ts +24 -0
- package/template/client/src/lib/env.ts +19 -0
- package/template/client/src/lib/notifications.ts +47 -0
- package/template/client/src/store/preferences-store.ts +42 -0
- package/template/client/src/types/theme.ts +1 -0
- package/template/client/tsconfig.json +18 -0
- package/template/client/uniwind-types.d.ts +10 -0
- package/template/client/vitest.config.ts +7 -0
- package/template/package.json +22 -0
- package/template/server/.env.example +8 -0
- package/template/server/README.md +31 -0
- package/template/server/convex/_generated/api.d.ts +55 -0
- package/template/server/convex/_generated/api.js +23 -0
- package/template/server/convex/_generated/dataModel.d.ts +60 -0
- package/template/server/convex/_generated/server.d.ts +143 -0
- package/template/server/convex/_generated/server.js +93 -0
- package/template/server/convex/auth.config.ts +11 -0
- package/template/server/convex/env.ts +18 -0
- package/template/server/convex/lib.ts +12 -0
- package/template/server/convex/push.ts +148 -0
- package/template/server/convex/schema.ts +22 -0
- package/template/server/convex/users.ts +54 -0
- package/template/server/convex.json +3 -0
- package/template/server/eslint.config.js +51 -0
- package/template/server/package.json +29 -0
- package/template/server/tests/convex.test.ts +52 -0
- package/template/server/tests/import-meta.d.ts +3 -0
- package/template/server/tsconfig.json +15 -0
- package/template/server/vitest.config.ts +13 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
set shell := ["zsh", "-cu"]
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
bun install --workspaces
|
|
5
|
+
|
|
6
|
+
fmt:
|
|
7
|
+
bunx prettier --write client server
|
|
8
|
+
|
|
9
|
+
prebuild:
|
|
10
|
+
cd client && bunx expo prebuild --platform all
|
|
11
|
+
|
|
12
|
+
dev:
|
|
13
|
+
bunx concurrently -n CLIENT,SERVER -c blue,green "just dev-client" "just dev-server"
|
|
14
|
+
|
|
15
|
+
dev-client:
|
|
16
|
+
cd client && bun run start
|
|
17
|
+
|
|
18
|
+
dev-server:
|
|
19
|
+
cd server && bun run dev
|
|
20
|
+
|
|
21
|
+
ios:
|
|
22
|
+
cd client && bun run ios
|
|
23
|
+
|
|
24
|
+
android:
|
|
25
|
+
cd client && bun run android
|
|
26
|
+
|
|
27
|
+
lint:
|
|
28
|
+
cd client && bun run lint
|
|
29
|
+
cd server && bun run lint
|
|
30
|
+
|
|
31
|
+
typecheck:
|
|
32
|
+
cd client && bun run typecheck
|
|
33
|
+
cd server && bun run typecheck
|
|
34
|
+
|
|
35
|
+
test:
|
|
36
|
+
cd client && bun run test
|
|
37
|
+
cd server && bun run test
|
|
38
|
+
|
|
39
|
+
test-client:
|
|
40
|
+
cd client && bun run test
|
|
41
|
+
|
|
42
|
+
test-server:
|
|
43
|
+
cd server && bun run test
|
|
44
|
+
|
|
45
|
+
ci:
|
|
46
|
+
just lint
|
|
47
|
+
just typecheck
|
|
48
|
+
just test
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# React Native Airborne
|
|
2
|
+
|
|
3
|
+
Opinionated React Native starter for mobile-first apps with Expo + Convex.
|
|
4
|
+
|
|
5
|
+
## 🧰 Stack
|
|
6
|
+
|
|
7
|
+
- Bun workspaces monorepo (`client/`, `server/`)
|
|
8
|
+
- Expo + Expo Router + Native Tabs (SDK 55)
|
|
9
|
+
- Uniwind + Tailwind v4
|
|
10
|
+
- Clerk authentication
|
|
11
|
+
- Convex backend + `convex-test`
|
|
12
|
+
- Zustand + MMKV persistence
|
|
13
|
+
- Expo push notifications
|
|
14
|
+
|
|
15
|
+
## 🤝 Contributor Guide
|
|
16
|
+
|
|
17
|
+
Detailed implementation and maintenance notes for engineers/agents live in `AGENTS.md`.
|
|
18
|
+
|
|
19
|
+
## ✅ Prerequisites
|
|
20
|
+
|
|
21
|
+
- Bun `1.3.4+`
|
|
22
|
+
- `just` command runner
|
|
23
|
+
- Expo toolchain for iOS/Android simulators
|
|
24
|
+
- Clerk app + Convex project
|
|
25
|
+
|
|
26
|
+
## ⚡ Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bun install --workspaces
|
|
30
|
+
cp client/.env.example client/.env
|
|
31
|
+
cp server/.env.example server/.env
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
First-time Convex setup (one-time per new deployment):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd server
|
|
38
|
+
bun run dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then run both apps:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
just dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🧪 Commands
|
|
48
|
+
|
|
49
|
+
- `just dev`: start Expo + Convex
|
|
50
|
+
- `just dev-client`: start Expo only
|
|
51
|
+
- `just dev-server`: start Convex only
|
|
52
|
+
- `just fmt`: run Prettier on client and server
|
|
53
|
+
- `just prebuild`: generate local iOS/Android native folders
|
|
54
|
+
- `just ios`: launch iOS app
|
|
55
|
+
- `just android`: launch Android app
|
|
56
|
+
- `just lint`: lint/type lint checks
|
|
57
|
+
- `just typecheck`: TypeScript checks
|
|
58
|
+
- `just test`: client + server tests
|
|
59
|
+
- `just test-client`: client tests only
|
|
60
|
+
- `just test-server`: server tests only
|
|
61
|
+
- `just ci`: lint + typecheck + tests
|
|
62
|
+
|
|
63
|
+
## 📱 Native Projects
|
|
64
|
+
|
|
65
|
+
Generate native projects locally when needed:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
just prebuild
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`client/ios` and `client/android` are intentionally gitignored and should not be committed.
|
|
72
|
+
|
|
73
|
+
## 🔐 Environment Variables
|
|
74
|
+
|
|
75
|
+
### Client (`client/.env`)
|
|
76
|
+
|
|
77
|
+
- `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`
|
|
78
|
+
- `EXPO_PUBLIC_CONVEX_URL`
|
|
79
|
+
- `EXPO_PUBLIC_EAS_PROJECT_ID` (optional)
|
|
80
|
+
|
|
81
|
+
### Server (`server/.env`)
|
|
82
|
+
|
|
83
|
+
- `CLERK_JWT_ISSUER_DOMAIN`
|
|
84
|
+
- `EXPO_PUSH_ENDPOINT` (optional)
|
|
85
|
+
- `EXPO_ACCESS_TOKEN` (optional)
|
|
86
|
+
|
|
87
|
+
## 📝 Notes
|
|
88
|
+
|
|
89
|
+
- Mobile-only target (iOS/Android).
|
|
90
|
+
- Do not store sensitive auth tokens in MMKV.
|
|
91
|
+
- Uniwind classes are enabled by `client/global.css` and `client/metro.config.js`.
|
|
92
|
+
- `SafeAreaView` is wrapped with `withUniwind` in `client/src/components/screen.tsx` for className support.
|
|
93
|
+
- `server/convex/_generated` ships with starter stubs so typecheck/tests pass before deployment setup.
|
|
94
|
+
After connecting Convex, run `cd server && bun run codegen` to regenerate.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "recommendations": ["expo.vscode-expo-tools"] }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Airborne Client
|
|
2
|
+
|
|
3
|
+
Expo Router app configured for:
|
|
4
|
+
|
|
5
|
+
- Clerk authentication
|
|
6
|
+
- Convex client integration
|
|
7
|
+
- Uniwind (Tailwind v4)
|
|
8
|
+
- Zustand + MMKV local state persistence
|
|
9
|
+
- Expo push notifications
|
|
10
|
+
|
|
11
|
+
## Run
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun install
|
|
15
|
+
bun run start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Generate native folders locally when you need native runs:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bunx expo prebuild --platform all
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then use:
|
|
25
|
+
|
|
26
|
+
- `bun run ios`
|
|
27
|
+
- `bun run android`
|
|
28
|
+
|
|
29
|
+
## Required env (`.env`)
|
|
30
|
+
|
|
31
|
+
- `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`
|
|
32
|
+
- `EXPO_PUBLIC_CONVEX_URL`
|
|
33
|
+
- `EXPO_PUBLIC_EAS_PROJECT_ID` (optional)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useAuth } from "@clerk/clerk-expo";
|
|
2
|
+
import { Redirect } from "expo-router";
|
|
3
|
+
import { NativeTabs } from "expo-router/unstable-native-tabs";
|
|
4
|
+
|
|
5
|
+
export default function AppLayout() {
|
|
6
|
+
const { isLoaded, isSignedIn } = useAuth();
|
|
7
|
+
|
|
8
|
+
if (!isLoaded) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!isSignedIn) {
|
|
13
|
+
return <Redirect href="/(auth)/sign-in" />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<NativeTabs>
|
|
18
|
+
<NativeTabs.Trigger name="index" disableTransparentOnScrollEdge>
|
|
19
|
+
<NativeTabs.Trigger.Icon sf={{ default: "house", selected: "house.fill" }} md="home" />
|
|
20
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
21
|
+
</NativeTabs.Trigger>
|
|
22
|
+
|
|
23
|
+
<NativeTabs.Trigger name="push" disableTransparentOnScrollEdge>
|
|
24
|
+
<NativeTabs.Trigger.Icon sf={{ default: "bell", selected: "bell.fill" }} md="notifications" />
|
|
25
|
+
<NativeTabs.Trigger.Label>Push</NativeTabs.Trigger.Label>
|
|
26
|
+
</NativeTabs.Trigger>
|
|
27
|
+
|
|
28
|
+
<NativeTabs.Trigger name="settings" disableTransparentOnScrollEdge>
|
|
29
|
+
<NativeTabs.Trigger.Icon sf="gearshape" md="settings" />
|
|
30
|
+
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
|
|
31
|
+
</NativeTabs.Trigger>
|
|
32
|
+
</NativeTabs>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SignedIn, useUser } from "@clerk/clerk-expo";
|
|
2
|
+
import { useMutation, useQuery } from "convex/react";
|
|
3
|
+
import { Link } from "expo-router";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { Text, View } from "react-native";
|
|
6
|
+
import { Screen } from "@/src/components/screen";
|
|
7
|
+
import { SignOutButton } from "@/src/components/sign-out-button";
|
|
8
|
+
import { api } from "@convex/_generated/api";
|
|
9
|
+
|
|
10
|
+
export default function HomeScreen() {
|
|
11
|
+
const { user } = useUser();
|
|
12
|
+
const currentUser = useQuery(api.users.current, {});
|
|
13
|
+
const bootstrapUser = useMutation(api.users.bootstrap);
|
|
14
|
+
const didBootstrap = useRef(false);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!user || currentUser !== null || didBootstrap.current) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
didBootstrap.current = true;
|
|
22
|
+
void bootstrapUser({
|
|
23
|
+
email: user.primaryEmailAddress?.emailAddress,
|
|
24
|
+
name: user.fullName ?? undefined,
|
|
25
|
+
imageUrl: user.imageUrl ?? undefined,
|
|
26
|
+
});
|
|
27
|
+
}, [bootstrapUser, currentUser, user]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Screen>
|
|
31
|
+
<SignedIn>
|
|
32
|
+
<View className="flex-1 gap-5">
|
|
33
|
+
<View className="gap-2">
|
|
34
|
+
<Text className="text-3xl font-bold text-zinc-900 dark:text-zinc-100">
|
|
35
|
+
React Native Airborne
|
|
36
|
+
</Text>
|
|
37
|
+
<Text className="text-zinc-600 dark:text-zinc-300">
|
|
38
|
+
Opinionated Expo + Convex + Clerk starter with Uniwind.
|
|
39
|
+
</Text>
|
|
40
|
+
</View>
|
|
41
|
+
|
|
42
|
+
<View className="rounded-xl border border-zinc-300 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-900">
|
|
43
|
+
<Text className="text-sm text-zinc-600 dark:text-zinc-300">Authenticated user</Text>
|
|
44
|
+
<Text className="text-base font-semibold text-zinc-900 dark:text-zinc-100">
|
|
45
|
+
{currentUser?.email ?? "Loading profile..."}
|
|
46
|
+
</Text>
|
|
47
|
+
<Text className="text-sm text-zinc-500 dark:text-zinc-400">
|
|
48
|
+
Clerk ID: {currentUser?.clerkUserId ?? "..."}
|
|
49
|
+
</Text>
|
|
50
|
+
</View>
|
|
51
|
+
|
|
52
|
+
<View className="gap-3">
|
|
53
|
+
<Link href="/(app)/settings" className="text-base font-medium text-blue-600">
|
|
54
|
+
Open Settings (theme)
|
|
55
|
+
</Link>
|
|
56
|
+
<Link href="/(app)/push" className="text-base font-medium text-blue-600">
|
|
57
|
+
Open Push Demo
|
|
58
|
+
</Link>
|
|
59
|
+
</View>
|
|
60
|
+
|
|
61
|
+
<SignOutButton />
|
|
62
|
+
</View>
|
|
63
|
+
</SignedIn>
|
|
64
|
+
</Screen>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useAction, useMutation } from "convex/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Platform, Text, View } from "react-native";
|
|
4
|
+
import { PrimaryButton } from "@/src/components/primary-button";
|
|
5
|
+
import { Screen } from "@/src/components/screen";
|
|
6
|
+
import { registerForPushNotificationsAsync } from "@/src/lib/notifications";
|
|
7
|
+
import { usePreferencesStore } from "@/src/store/preferences-store";
|
|
8
|
+
import { api } from "@convex/_generated/api";
|
|
9
|
+
|
|
10
|
+
export default function PushScreen() {
|
|
11
|
+
const [status, setStatus] = useState<string>("Idle");
|
|
12
|
+
const lastPushToken = usePreferencesStore((state) => state.lastPushToken);
|
|
13
|
+
const setLastPushToken = usePreferencesStore((state) => state.setLastPushToken);
|
|
14
|
+
|
|
15
|
+
const registerToken = useMutation(api.push.registerToken);
|
|
16
|
+
const unregisterToken = useMutation(api.push.unregisterToken);
|
|
17
|
+
const sendTestNotification = useAction(api.push.sendTestNotification);
|
|
18
|
+
|
|
19
|
+
const onRegister = async () => {
|
|
20
|
+
try {
|
|
21
|
+
setStatus("Requesting notification permissions...");
|
|
22
|
+
const token = await registerForPushNotificationsAsync();
|
|
23
|
+
setStatus("Registering token in Convex...");
|
|
24
|
+
await registerToken({ token, platform: Platform.OS === "ios" ? "ios" : "android" });
|
|
25
|
+
setLastPushToken(token);
|
|
26
|
+
setStatus("Token registered.");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
setStatus(error instanceof Error ? error.message : "Failed to register push token.");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const onSendTest = async () => {
|
|
33
|
+
try {
|
|
34
|
+
setStatus("Sending test notification...");
|
|
35
|
+
const response = await sendTestNotification({});
|
|
36
|
+
setStatus(`Sent. Status ${response.status}.`);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
setStatus(error instanceof Error ? error.message : "Failed to send notification.");
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const onUnregister = async () => {
|
|
43
|
+
if (!lastPushToken) {
|
|
44
|
+
setStatus("No token stored to remove.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await unregisterToken({ token: lastPushToken });
|
|
50
|
+
setLastPushToken(null);
|
|
51
|
+
setStatus("Token removed.");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
setStatus(error instanceof Error ? error.message : "Failed to remove token.");
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Screen>
|
|
59
|
+
<View className="flex-1 gap-4">
|
|
60
|
+
<Text className="text-2xl font-bold text-zinc-900 dark:text-zinc-100">Push Demo</Text>
|
|
61
|
+
|
|
62
|
+
<Text className="text-sm text-zinc-600 dark:text-zinc-300">Status: {status}</Text>
|
|
63
|
+
<Text className="text-xs text-zinc-500 dark:text-zinc-400">
|
|
64
|
+
Last token: {lastPushToken ?? "Not registered yet"}
|
|
65
|
+
</Text>
|
|
66
|
+
|
|
67
|
+
<View className="gap-3">
|
|
68
|
+
<PrimaryButton onPress={onRegister}>Register push token</PrimaryButton>
|
|
69
|
+
<PrimaryButton onPress={onSendTest}>Send test notification</PrimaryButton>
|
|
70
|
+
<PrimaryButton onPress={onUnregister}>Remove token</PrimaryButton>
|
|
71
|
+
</View>
|
|
72
|
+
</View>
|
|
73
|
+
</Screen>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useUniwind } from "uniwind";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { PrimaryButton } from "@/src/components/primary-button";
|
|
4
|
+
import { Screen } from "@/src/components/screen";
|
|
5
|
+
import { usePreferencesStore } from "@/src/store/preferences-store";
|
|
6
|
+
import type { ThemePreference } from "@/src/types/theme";
|
|
7
|
+
|
|
8
|
+
export default function SettingsScreen() {
|
|
9
|
+
const { theme, hasAdaptiveThemes } = useUniwind();
|
|
10
|
+
const selectedTheme = usePreferencesStore((state) => state.theme);
|
|
11
|
+
const setTheme = usePreferencesStore((state) => state.setTheme);
|
|
12
|
+
|
|
13
|
+
const activeTheme = hasAdaptiveThemes ? "system" : theme;
|
|
14
|
+
|
|
15
|
+
const onChangeTheme = (nextTheme: ThemePreference) => {
|
|
16
|
+
setTheme(nextTheme);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Screen>
|
|
21
|
+
<View className="flex-1 gap-4">
|
|
22
|
+
<Text className="text-2xl font-bold text-zinc-900 dark:text-zinc-100">Settings</Text>
|
|
23
|
+
|
|
24
|
+
<Text className="text-sm text-zinc-600 dark:text-zinc-300">
|
|
25
|
+
Active theme: {activeTheme}. Stored preference: {selectedTheme}.
|
|
26
|
+
</Text>
|
|
27
|
+
|
|
28
|
+
<View className="gap-3">
|
|
29
|
+
<PrimaryButton onPress={() => onChangeTheme("system")}>Use system theme</PrimaryButton>
|
|
30
|
+
<PrimaryButton onPress={() => onChangeTheme("light")}>Use light theme</PrimaryButton>
|
|
31
|
+
<PrimaryButton onPress={() => onChangeTheme("dark")}>Use dark theme</PrimaryButton>
|
|
32
|
+
</View>
|
|
33
|
+
</View>
|
|
34
|
+
</Screen>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useAuth } from "@clerk/clerk-expo";
|
|
2
|
+
import { Redirect, Stack } from "expo-router";
|
|
3
|
+
|
|
4
|
+
export default function AuthLayout() {
|
|
5
|
+
const { isLoaded, isSignedIn } = useAuth();
|
|
6
|
+
|
|
7
|
+
if (!isLoaded) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (isSignedIn) {
|
|
12
|
+
return <Redirect href="/" />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Stack
|
|
17
|
+
screenOptions={{
|
|
18
|
+
headerShown: false,
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|