@highbeek/create-rnstarterkit 1.0.2-beta.1 → 1.0.2-beta.11

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/dist/bin/create-rnstarterkit.js +21 -1
  4. package/dist/src/generators/appGenerator.js +2086 -67
  5. package/dist/templates/cli-base/.bundle/config +2 -0
  6. package/dist/templates/cli-base/.eslintrc.js +4 -0
  7. package/dist/templates/cli-base/.prettierrc.js +5 -0
  8. package/dist/templates/cli-base/.watchmanconfig +1 -0
  9. package/dist/templates/cli-base/App.tsx +223 -0
  10. package/dist/templates/cli-base/Gemfile +16 -0
  11. package/dist/templates/cli-base/Gemfile.lock +169 -0
  12. package/dist/templates/cli-base/README.md +97 -0
  13. package/dist/templates/cli-base/__tests__/App.test.tsx +13 -0
  14. package/dist/templates/cli-base/android/app/build.gradle +119 -0
  15. package/dist/templates/cli-base/android/app/debug.keystore +0 -0
  16. package/dist/templates/cli-base/android/app/proguard-rules.pro +10 -0
  17. package/dist/templates/cli-base/android/app/src/main/AndroidManifest.xml +27 -0
  18. package/dist/templates/cli-base/android/app/src/main/java/com/baseapp/MainActivity.kt +22 -0
  19. package/dist/templates/cli-base/android/app/src/main/java/com/baseapp/MainApplication.kt +27 -0
  20. package/dist/templates/cli-base/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  21. package/dist/templates/cli-base/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  22. package/dist/templates/cli-base/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  23. package/dist/templates/cli-base/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  24. package/dist/templates/cli-base/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  25. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  26. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  27. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  28. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  29. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  30. package/dist/templates/cli-base/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  31. package/dist/templates/cli-base/android/app/src/main/res/values/strings.xml +3 -0
  32. package/dist/templates/cli-base/android/app/src/main/res/values/styles.xml +9 -0
  33. package/dist/templates/cli-base/android/build.gradle +21 -0
  34. package/dist/templates/cli-base/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  35. package/dist/templates/cli-base/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  36. package/dist/templates/cli-base/android/gradle.properties +44 -0
  37. package/dist/templates/cli-base/android/gradlew +251 -0
  38. package/dist/templates/cli-base/android/gradlew.bat +99 -0
  39. package/dist/templates/cli-base/android/settings.gradle +6 -0
  40. package/dist/templates/cli-base/app.json +4 -0
  41. package/dist/templates/cli-base/assets/images/icon.png +0 -0
  42. package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
  43. package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
  44. package/dist/templates/cli-base/babel.config.js +3 -0
  45. package/dist/templates/cli-base/index.js +9 -0
  46. package/dist/templates/cli-base/ios/.xcode.env +11 -0
  47. package/dist/templates/cli-base/ios/BaseApp/AppDelegate.swift +48 -0
  48. package/dist/templates/cli-base/ios/BaseApp/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  49. package/dist/templates/cli-base/ios/BaseApp/Images.xcassets/Contents.json +6 -0
  50. package/dist/templates/cli-base/ios/BaseApp/Info.plist +60 -0
  51. package/dist/templates/cli-base/ios/BaseApp/LaunchScreen.storyboard +47 -0
  52. package/dist/templates/cli-base/ios/BaseApp/PrivacyInfo.xcprivacy +37 -0
  53. package/dist/templates/cli-base/ios/BaseApp.xcodeproj/project.pbxproj +494 -0
  54. package/dist/templates/cli-base/ios/BaseApp.xcodeproj/xcshareddata/xcschemes/BaseApp.xcscheme +88 -0
  55. package/dist/templates/cli-base/ios/BaseApp.xcworkspace/contents.xcworkspacedata +10 -0
  56. package/dist/templates/cli-base/ios/Podfile +34 -0
  57. package/dist/templates/cli-base/ios/Podfile.lock +2165 -0
  58. package/dist/templates/cli-base/jest.config.js +7 -0
  59. package/dist/templates/cli-base/metro.config.js +11 -0
  60. package/dist/templates/cli-base/package-lock.json +11859 -0
  61. package/dist/templates/cli-base/package.json +43 -0
  62. package/dist/templates/cli-base/tsconfig.json +9 -0
  63. package/dist/templates/expo-base/.vscode/extensions.json +1 -0
  64. package/dist/templates/expo-base/.vscode/settings.json +7 -0
  65. package/dist/templates/expo-base/README.md +50 -0
  66. package/dist/templates/expo-base/app/_layout.tsx +12 -0
  67. package/dist/templates/expo-base/app/index.tsx +168 -0
  68. package/dist/templates/expo-base/app.json +48 -0
  69. package/dist/templates/expo-base/assets/images/android-icon-background.png +0 -0
  70. package/dist/templates/expo-base/assets/images/android-icon-foreground.png +0 -0
  71. package/dist/templates/expo-base/assets/images/android-icon-monochrome.png +0 -0
  72. package/dist/templates/expo-base/assets/images/favicon.png +0 -0
  73. package/dist/templates/expo-base/assets/images/icon.png +0 -0
  74. package/dist/templates/expo-base/assets/images/partial-react-logo.png +0 -0
  75. package/dist/templates/expo-base/assets/images/react-logo.png +0 -0
  76. package/dist/templates/expo-base/assets/images/react-logo@2x.png +0 -0
  77. package/dist/templates/expo-base/assets/images/react-logo@3x.png +0 -0
  78. package/dist/templates/expo-base/assets/images/splash-icon.png +0 -0
  79. package/dist/templates/expo-base/eslint.config.js +10 -0
  80. package/dist/templates/expo-base/package-lock.json +12916 -0
  81. package/dist/templates/expo-base/package.json +49 -0
  82. package/dist/templates/expo-base/tsconfig.json +17 -0
  83. package/dist/templates/optional/apiClient/api/client.ts +142 -0
  84. package/dist/templates/optional/apiClient/api/index.ts +1 -0
  85. package/dist/templates/optional/apiClient/config/env.cli.ts +5 -0
  86. package/dist/templates/optional/apiClient/config/env.expo.ts +4 -0
  87. package/dist/templates/optional/apiClient/config/env.ts +1 -0
  88. package/dist/templates/optional/apiClient/env.d.ts +3 -0
  89. package/dist/templates/optional/auth-context/api/endpoints/auth.ts +14 -0
  90. package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
  91. package/dist/templates/optional/auth-context/context/AuthContext.tsx +47 -0
  92. package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +66 -0
  93. package/dist/templates/optional/auth-context/navigation/cli/BottomTabs.tsx +17 -0
  94. package/dist/templates/optional/auth-context/navigation/expo/BottomTabs.tsx +29 -0
  95. package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +15 -0
  96. package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +63 -0
  97. package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +11 -0
  98. package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +63 -0
  99. package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +11 -0
  100. package/dist/templates/optional/auth-context/screens/WelcomeScreen.tsx +174 -0
  101. package/dist/templates/optional/auth-context/utils/storage.ts +13 -0
  102. package/dist/templates/optional/auth-redux/api/endpoints/auth.ts +14 -0
  103. package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
  104. package/dist/templates/optional/auth-redux/navigation/ProtectedStack.tsx +67 -0
  105. package/dist/templates/optional/auth-redux/navigation/cli/BottomTabs.tsx +17 -0
  106. package/dist/templates/optional/auth-redux/navigation/expo/BottomTabs.tsx +31 -0
  107. package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +16 -0
  108. package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +64 -0
  109. package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +11 -0
  110. package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +64 -0
  111. package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +11 -0
  112. package/dist/templates/optional/auth-redux/screens/WelcomeScreen.tsx +174 -0
  113. package/dist/templates/optional/auth-redux/store/authSlice.ts +25 -0
  114. package/dist/templates/optional/auth-redux/store/store.ts +11 -0
  115. package/dist/templates/optional/auth-zustand/api/endpoints/auth.ts +14 -0
  116. package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
  117. package/dist/templates/optional/auth-zustand/navigation/BottomTabs.tsx +1 -0
  118. package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +72 -0
  119. package/dist/templates/optional/auth-zustand/navigation/cli/BottomTabs.tsx +17 -0
  120. package/dist/templates/optional/auth-zustand/navigation/expo/BottomTabs.tsx +31 -0
  121. package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +15 -0
  122. package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +63 -0
  123. package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +11 -0
  124. package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +63 -0
  125. package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +11 -0
  126. package/dist/templates/optional/auth-zustand/screens/WelcomeScreen.tsx +174 -0
  127. package/dist/templates/optional/auth-zustand/store/authStore.ts +30 -0
  128. package/dist/templates/optional/auth-zustand/utils/storage.ts +13 -0
  129. package/dist/templates/optional/ci/.github/workflows/ci.yml +32 -0
  130. package/dist/templates/optional/error-boundary/components/ErrorBoundary.tsx +83 -0
  131. package/dist/templates/optional/formik/components/formik/FormikInput.tsx +45 -0
  132. package/dist/templates/optional/formik/components/formik/LoginForm.tsx +60 -0
  133. package/dist/templates/optional/formik/schemas/auth.schema.ts +17 -0
  134. package/dist/templates/optional/mmkv/utils/storage.ts +17 -0
  135. package/dist/templates/optional/react-hook-form/components/rhf/LoginForm.tsx +63 -0
  136. package/dist/templates/optional/react-hook-form/components/rhf/RHFInput.tsx +50 -0
  137. package/dist/templates/optional/react-hook-form/schemas/auth.schema.ts +29 -0
  138. package/dist/templates/optional/react-query/hooks/useAppMutation.ts +16 -0
  139. package/dist/templates/optional/react-query/hooks/useAppQuery.ts +12 -0
  140. package/dist/templates/optional/react-query/services/queryClient.ts +14 -0
  141. package/dist/templates/optional/redux/store/hooks.ts +6 -0
  142. package/dist/templates/optional/redux/store/store.ts +11 -0
  143. package/dist/templates/optional/swr/hooks/useSWRFetch.ts +14 -0
  144. package/dist/templates/optional/swr/providers/SWRProvider.tsx +21 -0
  145. package/dist/templates/optional/tsconfig.json +17 -0
  146. package/dist/templates/optional/zustand/store/appStore.ts +13 -0
  147. package/package.json +2 -2
@@ -0,0 +1,174 @@
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 WelcomeScreenProps = {
16
+ onContinue: () => void;
17
+ };
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: "Build faster with a clean, production-ready app foundation.",
31
+ image: require("../assets/images/react-logo.png"),
32
+ },
33
+ {
34
+ id: "2",
35
+ title: "Structure That Scales",
36
+ description: "Auth, state, and API setup are organized for real-world apps.",
37
+ image: require("../assets/images/partial-react-logo.png"),
38
+ },
39
+ {
40
+ id: "3",
41
+ title: "Make It Yours",
42
+ description: "Swap this welcome flow with your own brand and product journey.",
43
+ image: require("../assets/images/icon.png"),
44
+ },
45
+ ];
46
+
47
+ export default function WelcomeScreen({ onContinue }: WelcomeScreenProps) {
48
+ const { width } = useWindowDimensions();
49
+ const [activeIndex, setActiveIndex] = useState(0);
50
+ const listRef = useRef<FlatList<Slide>>(null);
51
+
52
+ const onViewableItemsChanged = useRef(
53
+ ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
54
+ if (viewableItems[0]?.index != null) {
55
+ setActiveIndex(viewableItems[0].index);
56
+ }
57
+ },
58
+ ).current;
59
+
60
+ const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;
61
+
62
+ const handleNext = () => {
63
+ const nextIndex = activeIndex + 1;
64
+ if (nextIndex < slides.length) {
65
+ listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
66
+ return;
67
+ }
68
+ onContinue();
69
+ };
70
+
71
+ const isLastSlide = activeIndex === slides.length - 1;
72
+
73
+ return (
74
+ <SafeAreaView style={styles.safeArea}>
75
+ <FlatList
76
+ ref={listRef}
77
+ data={slides}
78
+ horizontal
79
+ pagingEnabled
80
+ bounces={false}
81
+ keyExtractor={(item) => item.id}
82
+ showsHorizontalScrollIndicator={false}
83
+ renderItem={({ item }) => (
84
+ <View style={[styles.slide, { width }]}>
85
+ <Image source={item.image} style={styles.image} resizeMode="contain" />
86
+ <Text style={styles.title}>{item.title}</Text>
87
+ <Text style={styles.description}>{item.description}</Text>
88
+ </View>
89
+ )}
90
+ onViewableItemsChanged={onViewableItemsChanged}
91
+ viewabilityConfig={viewabilityConfig}
92
+ />
93
+
94
+ <View style={styles.footer}>
95
+ <View style={styles.dotsRow}>
96
+ {slides.map((slide, index) => (
97
+ <View
98
+ key={slide.id}
99
+ style={[styles.dot, index === activeIndex && styles.dotActive]}
100
+ />
101
+ ))}
102
+ </View>
103
+
104
+ <Pressable style={styles.button} onPress={handleNext}>
105
+ <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
106
+ </Pressable>
107
+ </View>
108
+ </SafeAreaView>
109
+ );
110
+ }
111
+
112
+ const styles = StyleSheet.create({
113
+ safeArea: {
114
+ flex: 1,
115
+ backgroundColor: "#F7F7F9",
116
+ },
117
+ slide: {
118
+ flex: 1,
119
+ paddingHorizontal: 24,
120
+ justifyContent: "center",
121
+ alignItems: "center",
122
+ },
123
+ image: {
124
+ width: 220,
125
+ height: 220,
126
+ marginBottom: 28,
127
+ },
128
+ title: {
129
+ fontSize: 28,
130
+ fontWeight: "700",
131
+ color: "#111827",
132
+ textAlign: "center",
133
+ marginBottom: 12,
134
+ },
135
+ description: {
136
+ fontSize: 16,
137
+ lineHeight: 24,
138
+ color: "#4B5563",
139
+ textAlign: "center",
140
+ maxWidth: 320,
141
+ },
142
+ footer: {
143
+ paddingHorizontal: 24,
144
+ paddingBottom: 28,
145
+ gap: 20,
146
+ },
147
+ dotsRow: {
148
+ flexDirection: "row",
149
+ justifyContent: "center",
150
+ gap: 8,
151
+ },
152
+ dot: {
153
+ width: 8,
154
+ height: 8,
155
+ borderRadius: 999,
156
+ backgroundColor: "#D1D5DB",
157
+ },
158
+ dotActive: {
159
+ width: 24,
160
+ backgroundColor: "#111827",
161
+ },
162
+ button: {
163
+ height: 52,
164
+ borderRadius: 12,
165
+ backgroundColor: "#111827",
166
+ alignItems: "center",
167
+ justifyContent: "center",
168
+ },
169
+ buttonLabel: {
170
+ color: "#FFFFFF",
171
+ fontSize: 16,
172
+ fontWeight: "600",
173
+ },
174
+ });
@@ -0,0 +1,13 @@
1
+ import AsyncStorage from "@react-native-async-storage/async-storage";
2
+
3
+ export const storeToken = async (token: string) => {
4
+ await AsyncStorage.setItem("@token", token);
5
+ };
6
+
7
+ export const getToken = async () => {
8
+ return await AsyncStorage.getItem("@token");
9
+ };
10
+
11
+ export const removeToken = async () => {
12
+ await AsyncStorage.removeItem("@token");
13
+ };
@@ -0,0 +1,14 @@
1
+ function assertCredentials(email: string, password: string) {
2
+ if (!email.trim()) throw new Error("Email is required");
3
+ if (!password.trim()) throw new Error("Password is required");
4
+ }
5
+
6
+ export const loginApi = async (email: string, password: string) => {
7
+ assertCredentials(email, password);
8
+ return `demo-token-${email.trim().toLowerCase()}`;
9
+ };
10
+
11
+ export const registerApi = async (email: string, password: string) => {
12
+ assertCredentials(email, password);
13
+ return `demo-token-${email.trim().toLowerCase()}`;
14
+ };
@@ -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
+ });
@@ -0,0 +1,67 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { ActivityIndicator } from "react-native";
3
+ import AsyncStorage from "@react-native-async-storage/async-storage";
4
+ import { createNativeStackNavigator } from "@react-navigation/native-stack";
5
+ import { useSelector } from "react-redux";
6
+ import ScreenLayout from "../components/layout/ScreenLayout";
7
+ import LoginScreen from "../screens/LoginScreen";
8
+ import RegisterScreen from "../screens/RegisterScreen";
9
+ import WelcomeScreen from "../screens/WelcomeScreen";
10
+ import type { RootState } from "../store/store";
11
+ import BottomTabs from "./BottomTabs";
12
+
13
+ const Stack = createNativeStackNavigator();
14
+ const WELCOME_SEEN_KEY = "@rnskit_has_seen_welcome";
15
+
16
+ export default function ProtectedStack() {
17
+ const token = useSelector((state: RootState) => state.auth.token);
18
+ const [showWelcome, setShowWelcome] = useState(false);
19
+ const [isCheckingWelcome, setIsCheckingWelcome] = useState(true);
20
+
21
+ useEffect(() => {
22
+ const readWelcomeState = async () => {
23
+ try {
24
+ const seen = await AsyncStorage.getItem(WELCOME_SEEN_KEY);
25
+ setShowWelcome(seen !== "true");
26
+ } finally {
27
+ setIsCheckingWelcome(false);
28
+ }
29
+ };
30
+
31
+ void readWelcomeState();
32
+ }, []);
33
+
34
+ const handleWelcomeContinue = async () => {
35
+ await AsyncStorage.setItem(WELCOME_SEEN_KEY, "true");
36
+ setShowWelcome(false);
37
+ };
38
+
39
+ if (isCheckingWelcome) {
40
+ return (
41
+ <ScreenLayout centered padded={false}>
42
+ <ActivityIndicator />
43
+ </ScreenLayout>
44
+ );
45
+ }
46
+
47
+ if (showWelcome) {
48
+ return <WelcomeScreen onContinue={() => void handleWelcomeContinue()} />;
49
+ }
50
+
51
+ return (
52
+ <Stack.Navigator>
53
+ {token ? (
54
+ <Stack.Screen
55
+ name="Main"
56
+ component={BottomTabs}
57
+ options={{ headerShown: false }}
58
+ />
59
+ ) : (
60
+ <>
61
+ <Stack.Screen name="Login" component={LoginScreen} />
62
+ <Stack.Screen name="Register" component={RegisterScreen} />
63
+ </>
64
+ )}
65
+ </Stack.Navigator>
66
+ );
67
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
3
+ import HomeScreen from "../../screens/HomeScreen";
4
+ import ProfileScreen from "../../screens/ProfileScreen";
5
+ import SettingsScreen from "../../screens/SettingsScreen";
6
+
7
+ const Tab = createBottomTabNavigator();
8
+
9
+ export default function BottomTabs() {
10
+ return (
11
+ <Tab.Navigator screenOptions={{ headerShown: false }}>
12
+ <Tab.Screen name="Home" component={HomeScreen} />
13
+ <Tab.Screen name="Profile" component={ProfileScreen} />
14
+ <Tab.Screen name="Settings" component={SettingsScreen} />
15
+ </Tab.Navigator>
16
+ );
17
+ }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ import HomeScreen from "../../screens/HomeScreen";
5
+ import ProfileScreen from "../../screens/ProfileScreen";
6
+ import SettingsScreen from "../../screens/SettingsScreen";
7
+
8
+ const Tab = createBottomTabNavigator();
9
+
10
+ export default function BottomTabs() {
11
+ return (
12
+ <Tab.Navigator
13
+ screenOptions={({ route }) => ({
14
+ headerShown: false,
15
+ tabBarIcon: ({ color, size }) => {
16
+ let iconName: string = "home";
17
+
18
+ if (route.name === "Home") iconName = "home";
19
+ else if (route.name === "Profile") iconName = "person";
20
+ else if (route.name === "Settings") iconName = "settings";
21
+
22
+ return <Ionicons name={iconName as any} size={size} color={color} />;
23
+ },
24
+ })}
25
+ >
26
+ <Tab.Screen name="Home" component={HomeScreen} />
27
+ <Tab.Screen name="Profile" component={ProfileScreen} />
28
+ <Tab.Screen name="Settings" component={SettingsScreen} />
29
+ </Tab.Navigator>
30
+ );
31
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { Text, Button } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
+ import { useDispatch } from "react-redux";
5
+ import { logout } from "../store/authSlice";
6
+
7
+ export default function HomeScreen() {
8
+ const dispatch = useDispatch();
9
+
10
+ return (
11
+ <ScreenLayout>
12
+ <Text>Welcome Home!</Text>
13
+ <Button title="Logout" onPress={() => dispatch(logout())} />
14
+ </ScreenLayout>
15
+ );
16
+ }
@@ -0,0 +1,64 @@
1
+ import React, { useState } from "react";
2
+ import { TextInput, Button, Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
+ import { useDispatch } from "react-redux";
5
+ import { login } from "../store/authSlice";
6
+ import { loginApi } from "../api";
7
+
8
+ export default function LoginScreen() {
9
+ const dispatch = useDispatch();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [error, setError] = useState("");
13
+
14
+ const handleLogin = async () => {
15
+ try {
16
+ setError("");
17
+ const token = await loginApi(email, password);
18
+ dispatch(login(token));
19
+ } catch (err) {
20
+ setError(err instanceof Error ? err.message : "Login failed");
21
+ }
22
+ };
23
+
24
+ return (
25
+ <ScreenLayout>
26
+ <TextInput
27
+ placeholder="Email"
28
+ value={email}
29
+ onChangeText={setEmail}
30
+ autoCapitalize="none"
31
+ autoCorrect={false}
32
+ keyboardType="email-address"
33
+ placeholderTextColor="#888"
34
+ style={{
35
+ borderWidth: 1,
36
+ borderColor: "#ccc",
37
+ borderRadius: 8,
38
+ paddingHorizontal: 12,
39
+ paddingVertical: 10,
40
+ color: "#111",
41
+ marginBottom: 12,
42
+ }}
43
+ />
44
+ <TextInput
45
+ placeholder="Password"
46
+ value={password}
47
+ onChangeText={setPassword}
48
+ secureTextEntry
49
+ placeholderTextColor="#888"
50
+ style={{
51
+ borderWidth: 1,
52
+ borderColor: "#ccc",
53
+ borderRadius: 8,
54
+ paddingHorizontal: 12,
55
+ paddingVertical: 10,
56
+ color: "#111",
57
+ marginBottom: 12,
58
+ }}
59
+ />
60
+ {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
61
+ <Button title="Login" onPress={() => void handleLogin()} />
62
+ </ScreenLayout>
63
+ );
64
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
+
5
+ export default function ProfileScreen() {
6
+ return (
7
+ <ScreenLayout>
8
+ <Text>ProfileScreen</Text>
9
+ </ScreenLayout>
10
+ );
11
+ }
@@ -0,0 +1,64 @@
1
+ import React, { useState } from "react";
2
+ import { TextInput, Button, Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
+ import { useDispatch } from "react-redux";
5
+ import { login } from "../store/authSlice";
6
+ import { registerApi } from "../api";
7
+
8
+ export default function RegisterScreen() {
9
+ const dispatch = useDispatch();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [error, setError] = useState("");
13
+
14
+ const handleRegister = async () => {
15
+ try {
16
+ setError("");
17
+ const token = await registerApi(email, password);
18
+ dispatch(login(token));
19
+ } catch (err) {
20
+ setError(err instanceof Error ? err.message : "Registration failed");
21
+ }
22
+ };
23
+
24
+ return (
25
+ <ScreenLayout>
26
+ <TextInput
27
+ placeholder="Email"
28
+ value={email}
29
+ onChangeText={setEmail}
30
+ autoCapitalize="none"
31
+ autoCorrect={false}
32
+ keyboardType="email-address"
33
+ placeholderTextColor="#888"
34
+ style={{
35
+ borderWidth: 1,
36
+ borderColor: "#ccc",
37
+ borderRadius: 8,
38
+ paddingHorizontal: 12,
39
+ paddingVertical: 10,
40
+ color: "#111",
41
+ marginBottom: 12,
42
+ }}
43
+ />
44
+ <TextInput
45
+ placeholder="Password"
46
+ value={password}
47
+ onChangeText={setPassword}
48
+ secureTextEntry
49
+ placeholderTextColor="#888"
50
+ style={{
51
+ borderWidth: 1,
52
+ borderColor: "#ccc",
53
+ borderRadius: 8,
54
+ paddingHorizontal: 12,
55
+ paddingVertical: 10,
56
+ color: "#111",
57
+ marginBottom: 12,
58
+ }}
59
+ />
60
+ {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
61
+ <Button title="Register" onPress={() => void handleRegister()} />
62
+ </ScreenLayout>
63
+ );
64
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import ScreenLayout from "../components/layout/ScreenLayout";
4
+
5
+ export default function SettingsScreen() {
6
+ return (
7
+ <ScreenLayout>
8
+ <Text>SettingsScreen</Text>
9
+ </ScreenLayout>
10
+ );
11
+ }