@highbeek/create-rnstarterkit 1.0.2-beta.10 → 1.0.2-beta.12

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/dist/bin/create-rnstarterkit.js +6 -0
  4. package/dist/src/generators/appGenerator.js +1251 -116
  5. package/dist/templates/cli-base/App.tsx +199 -21
  6. package/dist/templates/cli-base/assets/images/icon.png +0 -0
  7. package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
  8. package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
  9. package/dist/templates/cli-base/jest.config.js +4 -0
  10. package/dist/templates/cli-base/package.json +5 -3
  11. package/dist/templates/cli-base/tsconfig.json +1 -0
  12. package/dist/templates/expo-base/app/_layout.tsx +6 -3
  13. package/dist/templates/expo-base/app/index.tsx +164 -5
  14. package/dist/templates/expo-base/package.json +5 -2
  15. package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
  16. package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +33 -5
  17. package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +4 -3
  18. package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +4 -3
  19. package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +4 -3
  20. package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +4 -3
  21. package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +4 -3
  22. package/dist/templates/optional/auth-context/screens/WelcomeScreen.tsx +174 -0
  23. package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
  24. package/dist/templates/optional/auth-redux/navigation/ProtectedStack.tsx +39 -2
  25. package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +4 -3
  26. package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +4 -3
  27. package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +7 -10
  28. package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +4 -3
  29. package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +6 -9
  30. package/dist/templates/optional/auth-redux/screens/WelcomeScreen.tsx +174 -0
  31. package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
  32. package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +34 -6
  33. package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +4 -3
  34. package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +4 -3
  35. package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +4 -3
  36. package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +4 -3
  37. package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +4 -3
  38. package/dist/templates/optional/auth-zustand/screens/WelcomeScreen.tsx +174 -0
  39. package/dist/templates/optional/ci/.github/workflows/ci.yml +32 -0
  40. package/dist/templates/optional/error-boundary/components/ErrorBoundary.tsx +83 -0
  41. package/dist/templates/optional/formik/components/formik/FormikInput.tsx +45 -0
  42. package/dist/templates/optional/formik/components/formik/LoginForm.tsx +60 -0
  43. package/dist/templates/optional/formik/schemas/auth.schema.ts +17 -0
  44. package/dist/templates/optional/mmkv/utils/storage.ts +17 -0
  45. package/dist/templates/optional/react-hook-form/components/rhf/LoginForm.tsx +63 -0
  46. package/dist/templates/optional/react-hook-form/components/rhf/RHFInput.tsx +50 -0
  47. package/dist/templates/optional/react-hook-form/schemas/auth.schema.ts +29 -0
  48. package/dist/templates/optional/react-query/hooks/useAppMutation.ts +16 -0
  49. package/dist/templates/optional/react-query/hooks/useAppQuery.ts +12 -0
  50. package/dist/templates/optional/react-query/services/queryClient.ts +14 -0
  51. package/dist/templates/optional/redux/store/hooks.ts +6 -0
  52. package/dist/templates/optional/redux/store/store.ts +11 -0
  53. package/dist/templates/optional/swr/hooks/useSWRFetch.ts +14 -0
  54. package/dist/templates/optional/swr/providers/SWRProvider.tsx +21 -0
  55. package/dist/templates/optional/tsconfig.json +17 -0
  56. package/dist/templates/optional/zustand/store/appStore.ts +13 -0
  57. package/package.json +1 -1
  58. package/dist/templates/expo-base/components/ui/collapsible.tsx +0 -45
  59. package/dist/templates/expo-base/components/ui/icon-symbol.ios.tsx +0 -32
  60. package/dist/templates/expo-base/components/ui/icon-symbol.tsx +0 -41
@@ -1,44 +1,222 @@
1
- /**
2
- * Sample React Native App
3
- * https://github.com/facebook/react-native
4
- *
5
- * @format
6
- */
7
-
8
- import { NewAppScreen } from '@react-native/new-app-screen';
9
- import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
1
+ import React, { useRef, useState } from "react";
2
+ import {
3
+ FlatList,
4
+ Image,
5
+ ImageSourcePropType,
6
+ Pressable,
7
+ StatusBar,
8
+ StyleSheet,
9
+ Text,
10
+ useWindowDimensions,
11
+ View,
12
+ ViewToken,
13
+ } from "react-native";
10
14
  import {
11
15
  SafeAreaProvider,
12
- useSafeAreaInsets,
13
- } from 'react-native-safe-area-context';
16
+ SafeAreaView,
17
+ } from "react-native-safe-area-context";
18
+
19
+ type Slide = {
20
+ id: string;
21
+ title: string;
22
+ description: string;
23
+ image: ImageSourcePropType;
24
+ };
25
+
26
+ const slides: Slide[] = [
27
+ {
28
+ id: "1",
29
+ title: "Welcome to RN Starter Kit",
30
+ description: "Start with a clean React Native CLI project structure.",
31
+ image: require("./assets/images/react-logo.png"),
32
+ },
33
+ {
34
+ id: "2",
35
+ title: "Ready for Scale",
36
+ description: "Build faster with sensible architecture you can extend.",
37
+ image: require("./assets/images/partial-react-logo.png"),
38
+ },
39
+ {
40
+ id: "3",
41
+ title: "Make It Yours",
42
+ description: "Use this onboarding as a placeholder for your product design.",
43
+ image: require("./assets/images/icon.png"),
44
+ },
45
+ ];
14
46
 
15
47
  function App() {
16
- const isDarkMode = useColorScheme() === 'dark';
48
+ const [isOnboarded, setIsOnboarded] = useState(false);
17
49
 
18
50
  return (
19
51
  <SafeAreaProvider>
20
- <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
21
- <AppContent />
52
+ <StatusBar barStyle="dark-content" />
53
+ {isOnboarded ? (
54
+ <HomeScreen />
55
+ ) : (
56
+ <WelcomeScreen onFinish={() => setIsOnboarded(true)} />
57
+ )}
22
58
  </SafeAreaProvider>
23
59
  );
24
60
  }
25
61
 
26
- function AppContent() {
27
- const safeAreaInsets = useSafeAreaInsets();
62
+ function WelcomeScreen({ onFinish }: { onFinish: () => void }) {
63
+ const { width } = useWindowDimensions();
64
+ const [activeIndex, setActiveIndex] = useState(0);
65
+ const listRef = useRef<FlatList<Slide>>(null);
66
+
67
+ const onViewableItemsChanged = useRef(
68
+ ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
69
+ if (viewableItems[0]?.index != null) {
70
+ setActiveIndex(viewableItems[0].index);
71
+ }
72
+ },
73
+ ).current;
74
+
75
+ const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;
76
+
77
+ const handleNext = () => {
78
+ const nextIndex = activeIndex + 1;
79
+ if (nextIndex < slides.length) {
80
+ listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
81
+ } else {
82
+ onFinish();
83
+ }
84
+ };
85
+
86
+ const isLastSlide = activeIndex === slides.length - 1;
28
87
 
29
88
  return (
30
- <View style={styles.container}>
31
- <NewAppScreen
32
- templateFileName="App.tsx"
33
- safeAreaInsets={safeAreaInsets}
89
+ <SafeAreaView style={styles.safeArea}>
90
+ <FlatList
91
+ ref={listRef}
92
+ data={slides}
93
+ horizontal
94
+ pagingEnabled
95
+ bounces={false}
96
+ keyExtractor={(item) => item.id}
97
+ showsHorizontalScrollIndicator={false}
98
+ renderItem={({ item }) => (
99
+ <View style={[styles.slide, { width }]}>
100
+ <Image source={item.image} style={styles.image} resizeMode="contain" />
101
+ <Text style={styles.title}>{item.title}</Text>
102
+ <Text style={styles.description}>{item.description}</Text>
103
+ </View>
104
+ )}
105
+ onViewableItemsChanged={onViewableItemsChanged}
106
+ viewabilityConfig={viewabilityConfig}
34
107
  />
35
- </View>
108
+
109
+ <View style={styles.footer}>
110
+ <View style={styles.dotsRow}>
111
+ {slides.map((slide, index) => (
112
+ <View
113
+ key={slide.id}
114
+ style={[styles.dot, index === activeIndex && styles.dotActive]}
115
+ />
116
+ ))}
117
+ </View>
118
+
119
+ <Pressable style={styles.button} onPress={handleNext}>
120
+ <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
121
+ </Pressable>
122
+ </View>
123
+ </SafeAreaView>
124
+ );
125
+ }
126
+
127
+ function HomeScreen() {
128
+ return (
129
+ <SafeAreaView style={homeStyles.container}>
130
+ <Text style={homeStyles.title}>Home</Text>
131
+ <Text style={homeStyles.subtitle}>
132
+ Replace this screen with your app content.
133
+ </Text>
134
+ </SafeAreaView>
36
135
  );
37
136
  }
38
137
 
39
138
  const styles = StyleSheet.create({
139
+ safeArea: {
140
+ flex: 1,
141
+ backgroundColor: "#F7F7F9",
142
+ },
143
+ slide: {
144
+ flex: 1,
145
+ paddingHorizontal: 24,
146
+ justifyContent: "center",
147
+ alignItems: "center",
148
+ },
149
+ image: {
150
+ width: 220,
151
+ height: 220,
152
+ marginBottom: 28,
153
+ },
154
+ title: {
155
+ fontSize: 28,
156
+ fontWeight: "700",
157
+ color: "#111827",
158
+ textAlign: "center",
159
+ marginBottom: 12,
160
+ },
161
+ description: {
162
+ fontSize: 16,
163
+ lineHeight: 24,
164
+ color: "#4B5563",
165
+ textAlign: "center",
166
+ maxWidth: 320,
167
+ },
168
+ footer: {
169
+ paddingHorizontal: 24,
170
+ paddingBottom: 28,
171
+ gap: 20,
172
+ },
173
+ dotsRow: {
174
+ flexDirection: "row",
175
+ justifyContent: "center",
176
+ gap: 8,
177
+ },
178
+ dot: {
179
+ width: 8,
180
+ height: 8,
181
+ borderRadius: 999,
182
+ backgroundColor: "#D1D5DB",
183
+ },
184
+ dotActive: {
185
+ width: 24,
186
+ backgroundColor: "#111827",
187
+ },
188
+ button: {
189
+ height: 52,
190
+ borderRadius: 12,
191
+ backgroundColor: "#111827",
192
+ alignItems: "center",
193
+ justifyContent: "center",
194
+ },
195
+ buttonLabel: {
196
+ color: "#FFFFFF",
197
+ fontSize: 16,
198
+ fontWeight: "600",
199
+ },
200
+ });
201
+
202
+ const homeStyles = StyleSheet.create({
40
203
  container: {
41
204
  flex: 1,
205
+ backgroundColor: "#F7F7F9",
206
+ alignItems: "center",
207
+ justifyContent: "center",
208
+ padding: 24,
209
+ },
210
+ title: {
211
+ fontSize: 28,
212
+ fontWeight: "700",
213
+ color: "#111827",
214
+ marginBottom: 8,
215
+ },
216
+ subtitle: {
217
+ fontSize: 16,
218
+ color: "#4B5563",
219
+ textAlign: "center",
42
220
  },
43
221
  });
44
222
 
@@ -1,3 +1,7 @@
1
1
  module.exports = {
2
2
  preset: 'react-native',
3
+ setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
4
+ transformIgnorePatterns: [
5
+ 'node_modules/(?!(react-native|@react-native|@react-navigation|react-native-reanimated)/)',
6
+ ],
3
7
  };
@@ -5,15 +5,17 @@
5
5
  "scripts": {
6
6
  "android": "react-native run-android",
7
7
  "ios": "react-native run-ios",
8
- "lint": "eslint .",
9
8
  "start": "react-native start",
9
+ "lint": "eslint . --max-warnings 0",
10
+ "format": "prettier --write .",
11
+ "type-check": "tsc --noEmit",
10
12
  "test": "jest"
11
13
  },
12
14
  "dependencies": {
13
15
  "react": "19.2.3",
14
16
  "react-native": "0.84.0",
15
- "@react-native/new-app-screen": "0.84.0",
16
- "react-native-safe-area-context": "^5.5.2"
17
+ "react-native-safe-area-context": "^5.5.2",
18
+ "react-native-reanimated": "^3.17.4"
17
19
  },
18
20
  "devDependencies": {
19
21
  "@babel/core": "^7.25.2",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "@react-native/typescript-config",
3
3
  "compilerOptions": {
4
+ "strict": true,
4
5
  "types": ["jest"]
5
6
  },
6
7
  "include": ["**/*.ts", "**/*.tsx"],
@@ -1,9 +1,12 @@
1
1
  import { Stack } from "expo-router";
2
+ import { SafeAreaProvider } from "react-native-safe-area-context";
2
3
 
3
4
  export default function RootLayout() {
4
5
  return (
5
- <Stack>
6
- <Stack.Screen name="index" options={{ title: "Home" }} />
7
- </Stack>
6
+ <SafeAreaProvider>
7
+ <Stack>
8
+ <Stack.Screen name="index" options={{ title: "Home" }} />
9
+ </Stack>
10
+ </SafeAreaProvider>
8
11
  );
9
12
  }
@@ -1,9 +1,168 @@
1
- import { Text, View } from "react-native";
1
+ import React, { useRef, useState } from "react";
2
+ import {
3
+ FlatList,
4
+ Image,
5
+ ImageSourcePropType,
6
+ Pressable,
7
+ StyleSheet,
8
+ Text,
9
+ useWindowDimensions,
10
+ View,
11
+ ViewToken,
12
+ } from "react-native";
13
+ import { SafeAreaView } from "react-native-safe-area-context";
14
+
15
+ type Slide = {
16
+ id: string;
17
+ title: string;
18
+ description: string;
19
+ image: ImageSourcePropType;
20
+ };
21
+
22
+ const slides: Slide[] = [
23
+ {
24
+ id: "1",
25
+ title: "Welcome to RN Starter Kit",
26
+ description: "Kickstart your app with Expo and a clean production-ready baseline.",
27
+ image: require("../assets/images/react-logo.png"),
28
+ },
29
+ {
30
+ id: "2",
31
+ title: "Built for Real Projects",
32
+ description: "State, auth, API layers, and folder structure are ready for scaling.",
33
+ image: require("../assets/images/partial-react-logo.png"),
34
+ },
35
+ {
36
+ id: "3",
37
+ title: "Customize and Ship",
38
+ description: "Replace this screen with your own brand and ship faster.",
39
+ image: require("../assets/images/icon.png"),
40
+ },
41
+ ];
42
+
43
+ export default function WelcomeScreen() {
44
+ const { width } = useWindowDimensions();
45
+ const [activeIndex, setActiveIndex] = useState(0);
46
+ const listRef = useRef<FlatList<Slide>>(null);
47
+
48
+ const onViewableItemsChanged = useRef(
49
+ ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
50
+ if (viewableItems[0]?.index != null) {
51
+ setActiveIndex(viewableItems[0].index);
52
+ }
53
+ },
54
+ ).current;
55
+
56
+ const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;
57
+
58
+ const goNext = () => {
59
+ const nextIndex = activeIndex + 1;
60
+ if (nextIndex < slides.length) {
61
+ listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
62
+ }
63
+ };
64
+
65
+ const isLastSlide = activeIndex === slides.length - 1;
2
66
 
3
- export default function HomeScreen() {
4
67
  return (
5
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
6
- <Text>Welcome to RN Starter Kit</Text>
7
- </View>
68
+ <SafeAreaView style={styles.safeArea}>
69
+ <FlatList
70
+ ref={listRef}
71
+ data={slides}
72
+ horizontal
73
+ pagingEnabled
74
+ bounces={false}
75
+ keyExtractor={(item) => item.id}
76
+ showsHorizontalScrollIndicator={false}
77
+ renderItem={({ item }) => (
78
+ <View style={[styles.slide, { width }]}>
79
+ <Image source={item.image} style={styles.image} resizeMode="contain" />
80
+ <Text style={styles.title}>{item.title}</Text>
81
+ <Text style={styles.description}>{item.description}</Text>
82
+ </View>
83
+ )}
84
+ onViewableItemsChanged={onViewableItemsChanged}
85
+ viewabilityConfig={viewabilityConfig}
86
+ />
87
+
88
+ <View style={styles.footer}>
89
+ <View style={styles.dotsRow}>
90
+ {slides.map((slide, index) => (
91
+ <View
92
+ key={slide.id}
93
+ style={[styles.dot, index === activeIndex && styles.dotActive]}
94
+ />
95
+ ))}
96
+ </View>
97
+
98
+ <Pressable style={styles.button} onPress={goNext}>
99
+ <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
100
+ </Pressable>
101
+ </View>
102
+ </SafeAreaView>
8
103
  );
9
104
  }
105
+
106
+ const styles = StyleSheet.create({
107
+ safeArea: {
108
+ flex: 1,
109
+ backgroundColor: "#F7F7F9",
110
+ },
111
+ slide: {
112
+ flex: 1,
113
+ paddingHorizontal: 24,
114
+ justifyContent: "center",
115
+ alignItems: "center",
116
+ },
117
+ image: {
118
+ width: 220,
119
+ height: 220,
120
+ marginBottom: 28,
121
+ },
122
+ title: {
123
+ fontSize: 28,
124
+ fontWeight: "700",
125
+ color: "#111827",
126
+ textAlign: "center",
127
+ marginBottom: 12,
128
+ },
129
+ description: {
130
+ fontSize: 16,
131
+ lineHeight: 24,
132
+ color: "#4B5563",
133
+ textAlign: "center",
134
+ maxWidth: 320,
135
+ },
136
+ footer: {
137
+ paddingHorizontal: 24,
138
+ paddingBottom: 28,
139
+ gap: 20,
140
+ },
141
+ dotsRow: {
142
+ flexDirection: "row",
143
+ justifyContent: "center",
144
+ gap: 8,
145
+ },
146
+ dot: {
147
+ width: 8,
148
+ height: 8,
149
+ borderRadius: 999,
150
+ backgroundColor: "#D1D5DB",
151
+ },
152
+ dotActive: {
153
+ width: 24,
154
+ backgroundColor: "#111827",
155
+ },
156
+ button: {
157
+ height: 52,
158
+ borderRadius: 12,
159
+ backgroundColor: "#111827",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ },
163
+ buttonLabel: {
164
+ color: "#FFFFFF",
165
+ fontSize: 16,
166
+ fontWeight: "600",
167
+ },
168
+ });
@@ -7,7 +7,9 @@
7
7
  "android": "expo start --android",
8
8
  "ios": "expo start --ios",
9
9
  "web": "expo start --web",
10
- "lint": "expo lint"
10
+ "lint": "expo lint",
11
+ "format": "prettier --write .",
12
+ "type-check": "tsc --noEmit"
11
13
  },
12
14
  "dependencies": {
13
15
  "@expo/vector-icons": "^15.0.3",
@@ -40,7 +42,8 @@
40
42
  "@types/react": "~19.1.0",
41
43
  "typescript": "~5.9.2",
42
44
  "eslint": "^9.25.0",
43
- "eslint-config-expo": "~10.0.0"
45
+ "eslint-config-expo": "~10.0.0",
46
+ "prettier": "^3.5.0"
44
47
  },
45
48
  "private": true
46
49
  }
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { StyleProp, StyleSheet, ViewStyle } from "react-native";
3
+ import { Edge, SafeAreaView } from "react-native-safe-area-context";
4
+
5
+ type ScreenLayoutProps = {
6
+ children: React.ReactNode;
7
+ padded?: boolean;
8
+ centered?: boolean;
9
+ edges?: Edge[];
10
+ style?: StyleProp<ViewStyle>;
11
+ };
12
+
13
+ export default function ScreenLayout({
14
+ children,
15
+ padded = true,
16
+ centered = false,
17
+ edges = ["top", "left", "right"],
18
+ style,
19
+ }: ScreenLayoutProps) {
20
+ return (
21
+ <SafeAreaView
22
+ edges={edges}
23
+ style={[
24
+ styles.container,
25
+ padded && styles.padded,
26
+ centered && styles.centered,
27
+ style,
28
+ ]}
29
+ >
30
+ {children}
31
+ </SafeAreaView>
32
+ );
33
+ }
34
+
35
+ const styles = StyleSheet.create({
36
+ container: {
37
+ flex: 1,
38
+ },
39
+ padded: {
40
+ padding: 20,
41
+ },
42
+ centered: {
43
+ alignItems: "center",
44
+ justifyContent: "center",
45
+ },
46
+ });
@@ -1,24 +1,52 @@
1
- import React, { useContext } from "react";
2
- import { ActivityIndicator, View } from "react-native";
1
+ import React, { useContext, useEffect, useState } from "react";
2
+ import { ActivityIndicator } from "react-native";
3
+ import AsyncStorage from "@react-native-async-storage/async-storage";
3
4
  import { createNativeStackNavigator } from "@react-navigation/native-stack";
5
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
6
  import { AuthContext } from "../context/AuthContext";
5
7
  import LoginScreen from "../screens/LoginScreen";
6
8
  import RegisterScreen from "../screens/RegisterScreen";
9
+ import WelcomeScreen from "../screens/WelcomeScreen";
7
10
  import BottomTabs from "./BottomTabs";
8
11
 
9
12
  const Stack = createNativeStackNavigator();
13
+ const WELCOME_SEEN_KEY = "@rnskit_has_seen_welcome";
10
14
 
11
15
  export default function ProtectedStack() {
12
16
  const { token, isHydrated } = useContext(AuthContext);
17
+ const [showWelcome, setShowWelcome] = useState(false);
18
+ const [isCheckingWelcome, setIsCheckingWelcome] = useState(true);
13
19
 
14
- if (!isHydrated) {
20
+ useEffect(() => {
21
+ const readWelcomeState = async () => {
22
+ try {
23
+ const seen = await AsyncStorage.getItem(WELCOME_SEEN_KEY);
24
+ setShowWelcome(seen !== "true");
25
+ } finally {
26
+ setIsCheckingWelcome(false);
27
+ }
28
+ };
29
+
30
+ void readWelcomeState();
31
+ }, []);
32
+
33
+ const handleWelcomeContinue = async () => {
34
+ await AsyncStorage.setItem(WELCOME_SEEN_KEY, "true");
35
+ setShowWelcome(false);
36
+ };
37
+
38
+ if (!isHydrated || isCheckingWelcome) {
15
39
  return (
16
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
40
+ <ScreenLayout centered padded={false}>
17
41
  <ActivityIndicator />
18
- </View>
42
+ </ScreenLayout>
19
43
  );
20
44
  }
21
45
 
46
+ if (showWelcome) {
47
+ return <WelcomeScreen onContinue={() => void handleWelcomeContinue()} />;
48
+ }
49
+
22
50
  return (
23
51
  <Stack.Navigator>
24
52
  {token ? (
@@ -1,14 +1,15 @@
1
1
  import React, { useContext } from "react";
2
- import { View, Text, Button } from "react-native";
2
+ import { Text, Button } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
3
4
  import { AuthContext } from "../context/AuthContext";
4
5
 
5
6
  export default function HomeScreen() {
6
7
  const { logout } = useContext(AuthContext);
7
8
 
8
9
  return (
9
- <View style={{ padding: 20 }}>
10
+ <ScreenLayout>
10
11
  <Text>Welcome Home!</Text>
11
12
  <Button title="Logout" onPress={() => void logout()} />
12
- </View>
13
+ </ScreenLayout>
13
14
  );
14
15
  }
@@ -1,5 +1,6 @@
1
1
  import React, { useContext, useState } from "react";
2
- import { View, TextInput, Button, Text } from "react-native";
2
+ import { TextInput, Button, Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
3
4
  import { AuthContext } from "../context/AuthContext";
4
5
  import { loginApi } from "../api";
5
6
 
@@ -20,7 +21,7 @@ export default function LoginScreen() {
20
21
  };
21
22
 
22
23
  return (
23
- <View style={{ padding: 20 }}>
24
+ <ScreenLayout>
24
25
  <TextInput
25
26
  placeholder="Email"
26
27
  value={email}
@@ -57,6 +58,6 @@ export default function LoginScreen() {
57
58
  />
58
59
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
59
60
  <Button title="Login" onPress={() => void handleLogin()} />
60
- </View>
61
+ </ScreenLayout>
61
62
  );
62
63
  }
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
- import { View, Text } from "react-native";
2
+ import { Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
3
4
 
4
5
  export default function ProfileScreen() {
5
6
  return (
6
- <View style={{ padding: 20 }}>
7
+ <ScreenLayout>
7
8
  <Text>This is the Profile Screen</Text>
8
- </View>
9
+ </ScreenLayout>
9
10
  );
10
11
  }
@@ -1,5 +1,6 @@
1
1
  import React, { useContext, useState } from "react";
2
- import { View, TextInput, Button, Text } from "react-native";
2
+ import { TextInput, Button, Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
3
4
  import { AuthContext } from "../context/AuthContext";
4
5
  import { registerApi } from "../api";
5
6
 
@@ -20,7 +21,7 @@ export default function RegisterScreen() {
20
21
  };
21
22
 
22
23
  return (
23
- <View style={{ padding: 20 }}>
24
+ <ScreenLayout>
24
25
  <TextInput
25
26
  placeholder="Email"
26
27
  value={email}
@@ -57,6 +58,6 @@ export default function RegisterScreen() {
57
58
  />
58
59
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
59
60
  <Button title="Register" onPress={() => void handleRegister()} />
60
- </View>
61
+ </ScreenLayout>
61
62
  );
62
63
  }