@highbeek/create-rnstarterkit 1.0.2-beta.7 → 1.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/bin/create-rnstarterkit.js +205 -7
  4. package/dist/src/generators/appGenerator.js +1949 -127
  5. package/dist/src/generators/codeGenerator.js +289 -0
  6. package/dist/templates/cli-base/App.tsx +199 -21
  7. package/dist/templates/cli-base/assets/images/icon.png +0 -0
  8. package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
  9. package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
  10. package/dist/templates/cli-base/babel.config.js +1 -0
  11. package/dist/templates/cli-base/ios/BaseApp.xcodeproj/project.pbxproj +6 -0
  12. package/dist/templates/cli-base/ios/Podfile +5 -0
  13. package/dist/templates/cli-base/jest.config.js +4 -0
  14. package/dist/templates/cli-base/package.json +8 -5
  15. package/dist/templates/cli-base/tsconfig.json +1 -0
  16. package/dist/templates/expo-base/app/_layout.tsx +7 -3
  17. package/dist/templates/expo-base/app/home.tsx +37 -0
  18. package/dist/templates/expo-base/app/index.tsx +166 -5
  19. package/dist/templates/expo-base/app.json +1 -2
  20. package/dist/templates/expo-base/package.json +5 -2
  21. package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
  22. package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +33 -5
  23. package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +4 -3
  24. package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +41 -6
  25. package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +4 -3
  26. package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +41 -6
  27. package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +4 -3
  28. package/dist/templates/optional/auth-context/screens/WelcomeScreen.tsx +174 -0
  29. package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
  30. package/dist/templates/optional/auth-redux/navigation/ProtectedStack.tsx +39 -2
  31. package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +4 -3
  32. package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +42 -7
  33. package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +7 -10
  34. package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +61 -11
  35. package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +6 -9
  36. package/dist/templates/optional/auth-redux/screens/WelcomeScreen.tsx +174 -0
  37. package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
  38. package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +34 -6
  39. package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +4 -3
  40. package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +41 -6
  41. package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +4 -3
  42. package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +41 -6
  43. package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +4 -3
  44. package/dist/templates/optional/auth-zustand/screens/WelcomeScreen.tsx +174 -0
  45. package/dist/templates/optional/ci/.github/workflows/ci.yml +32 -0
  46. package/dist/templates/optional/error-boundary/components/ErrorBoundary.tsx +83 -0
  47. package/dist/templates/optional/formik/components/formik/FormikInput.tsx +45 -0
  48. package/dist/templates/optional/formik/components/formik/LoginForm.tsx +60 -0
  49. package/dist/templates/optional/formik/schemas/auth.schema.ts +17 -0
  50. package/dist/templates/optional/i18n/src/i18n/hooks/useAppTranslation.ts +28 -0
  51. package/dist/templates/optional/i18n/src/i18n/i18n.ts +30 -0
  52. package/dist/templates/optional/i18n/src/i18n/locales/en.json +32 -0
  53. package/dist/templates/optional/i18n/src/i18n/locales/es.json +32 -0
  54. package/dist/templates/optional/maestro/.maestro/flows/01_welcome.yaml +5 -0
  55. package/dist/templates/optional/maestro-auth/.maestro/flows/01_welcome.yaml +5 -0
  56. package/dist/templates/optional/maestro-auth/.maestro/flows/02_login.yaml +13 -0
  57. package/dist/templates/optional/maestro-auth/.maestro/flows/03_logout.yaml +16 -0
  58. package/dist/templates/optional/mmkv/utils/storage.ts +17 -0
  59. package/dist/templates/optional/react-hook-form/components/rhf/LoginForm.tsx +63 -0
  60. package/dist/templates/optional/react-hook-form/components/rhf/RHFInput.tsx +50 -0
  61. package/dist/templates/optional/react-hook-form/schemas/auth.schema.ts +29 -0
  62. package/dist/templates/optional/react-query/hooks/useAppMutation.ts +16 -0
  63. package/dist/templates/optional/react-query/hooks/useAppQuery.ts +12 -0
  64. package/dist/templates/optional/react-query/services/queryClient.ts +14 -0
  65. package/dist/templates/optional/redux/store/hooks.ts +6 -0
  66. package/dist/templates/optional/redux/store/store.ts +11 -0
  67. package/dist/templates/optional/sentry/src/utils/sentry.ts +24 -0
  68. package/dist/templates/optional/swr/hooks/useSWRFetch.ts +14 -0
  69. package/dist/templates/optional/swr/providers/SWRProvider.tsx +21 -0
  70. package/dist/templates/optional/tsconfig.json +17 -0
  71. package/dist/templates/optional/zustand/store/appStore.ts +13 -0
  72. package/package.json +40 -5
  73. package/dist/templates/expo-base/components/ui/collapsible.tsx +0 -45
  74. package/dist/templates/expo-base/components/ui/icon-symbol.ios.tsx +0 -32
  75. package/dist/templates/expo-base/components/ui/icon-symbol.tsx +0 -41
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateCode = generateCode;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ // ---------------------------------------------------------------------------
10
+ // Project context detection
11
+ // ---------------------------------------------------------------------------
12
+ async function detectProjectConfig() {
13
+ let dir = process.cwd();
14
+ const root = path_1.default.parse(dir).root;
15
+ while (dir !== root) {
16
+ const pkgPath = path_1.default.join(dir, "package.json");
17
+ if (await fs_extra_1.default.pathExists(pkgPath)) {
18
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
19
+ if (pkg.rnstarterkitConfig) {
20
+ return { config: pkg.rnstarterkitConfig, projectRoot: dir };
21
+ }
22
+ }
23
+ dir = path_1.default.dirname(dir);
24
+ }
25
+ throw new Error("❌ No rnstarterkitConfig found in package.json.\n" +
26
+ " Run this command from inside a project created with create-rnstarterkit.");
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Helpers
30
+ // ---------------------------------------------------------------------------
31
+ function toPascalCase(str) {
32
+ return str.charAt(0).toUpperCase() + str.slice(1);
33
+ }
34
+ function toCamelCase(str) {
35
+ return str.charAt(0).toLowerCase() + str.slice(1);
36
+ }
37
+ async function writeAndLog(filePath, content) {
38
+ if (await fs_extra_1.default.pathExists(filePath)) {
39
+ console.error(`❌ File already exists: ${filePath}`);
40
+ process.exit(1);
41
+ }
42
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
43
+ await fs_extra_1.default.writeFile(filePath, content, "utf8");
44
+ console.log(`✅ Created ${path_1.default.relative(process.cwd(), filePath)}`);
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // screen generator
48
+ // ---------------------------------------------------------------------------
49
+ function screenStateImports(config) {
50
+ if (!config.auth)
51
+ return "";
52
+ if (config.state === "Redux Toolkit") {
53
+ return `import { useAppSelector } from '../store/hooks';\n`;
54
+ }
55
+ if (config.state === "Zustand") {
56
+ return `import { useAuthStore } from '../store/authStore';\n`;
57
+ }
58
+ if (config.state === "Context API") {
59
+ return `import { useContext } from 'react';\nimport { AuthContext } from '../context/AuthContext';\n`;
60
+ }
61
+ return "";
62
+ }
63
+ function screenStateHook(config) {
64
+ if (!config.auth)
65
+ return "";
66
+ if (config.state === "Redux Toolkit") {
67
+ return `\n const token = useAppSelector((state) => state.auth.token);\n`;
68
+ }
69
+ if (config.state === "Zustand") {
70
+ return `\n const { token } = useAuthStore();\n`;
71
+ }
72
+ if (config.state === "Context API") {
73
+ return `\n const { token } = useContext(AuthContext);\n`;
74
+ }
75
+ return "";
76
+ }
77
+ async function generateScreen(name, config, projectRoot) {
78
+ const screenName = toPascalCase(name);
79
+ const fileName = `${screenName}Screen.tsx`;
80
+ const targetDir = config.platform === "Expo"
81
+ ? path_1.default.join(projectRoot, "app")
82
+ : path_1.default.join(projectRoot, "screens");
83
+ const filePath = path_1.default.join(targetDir, fileName);
84
+ const stateImports = screenStateImports(config);
85
+ const stateHook = screenStateHook(config);
86
+ const layoutImport = config.platform === "React Native CLI"
87
+ ? `import ScreenLayout from '../components/layout/ScreenLayout';\n`
88
+ : "";
89
+ const wrapper = config.platform === "React Native CLI"
90
+ ? ["<ScreenLayout>", " </ScreenLayout>"]
91
+ : ["<View style={styles.container}>", " </View>"];
92
+ const content = config.platform === "Expo"
93
+ ? `import React from 'react';
94
+ import { View, Text, StyleSheet } from 'react-native';
95
+ ${stateImports}
96
+ export default function ${screenName}Screen() {${stateHook}
97
+ return (
98
+ ${wrapper[0]}
99
+ <Text style={styles.title}>${screenName}</Text>
100
+ ${wrapper[1]}
101
+ );
102
+ }
103
+
104
+ const styles = StyleSheet.create({
105
+ container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 20 },
106
+ title: { fontSize: 24, fontWeight: 'bold', color: '#111827' },
107
+ });
108
+ `
109
+ : `import React from 'react';
110
+ import { Text, StyleSheet } from 'react-native';
111
+ ${layoutImport}${stateImports}
112
+ export default function ${screenName}Screen() {${stateHook}
113
+ return (
114
+ ${wrapper[0]}
115
+ <Text style={styles.title}>${screenName}</Text>
116
+ ${wrapper[1]}
117
+ );
118
+ }
119
+
120
+ const styles = StyleSheet.create({
121
+ title: { fontSize: 24, fontWeight: 'bold', color: '#111827' },
122
+ });
123
+ `;
124
+ await writeAndLog(filePath, content);
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // component generator
128
+ // ---------------------------------------------------------------------------
129
+ async function generateComponent(name, projectRoot) {
130
+ const componentName = toPascalCase(name);
131
+ const filePath = path_1.default.join(projectRoot, "components", `${componentName}.tsx`);
132
+ const content = `import React from 'react';
133
+ import { View, Text, StyleSheet, type ViewStyle } from 'react-native';
134
+
135
+ type ${componentName}Props = {
136
+ label?: string;
137
+ style?: ViewStyle;
138
+ };
139
+
140
+ export function ${componentName}({ label = '${componentName}', style }: ${componentName}Props) {
141
+ return (
142
+ <View style={[styles.container, style]}>
143
+ <Text style={styles.label}>{label}</Text>
144
+ </View>
145
+ );
146
+ }
147
+
148
+ const styles = StyleSheet.create({
149
+ container: { padding: 12 },
150
+ label: { fontSize: 14, color: '#374151' },
151
+ });
152
+ `;
153
+ await writeAndLog(filePath, content);
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // hook generator
157
+ // ---------------------------------------------------------------------------
158
+ async function generateHook(name, projectRoot) {
159
+ const baseName = name.startsWith("use") ? name.slice(3) : name;
160
+ const hookName = `use${toPascalCase(baseName)}`;
161
+ const filePath = path_1.default.join(projectRoot, "hooks", `${hookName}.ts`);
162
+ const content = `import { useState, useCallback } from 'react';
163
+
164
+ type ${toPascalCase(baseName)}State = {
165
+ data: null;
166
+ isLoading: boolean;
167
+ error: string | null;
168
+ };
169
+
170
+ export function ${hookName}() {
171
+ const [state, setState] = useState<${toPascalCase(baseName)}State>({
172
+ data: null,
173
+ isLoading: false,
174
+ error: null,
175
+ });
176
+
177
+ const execute = useCallback(async () => {
178
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
179
+ try {
180
+ // TODO: implement hook logic
181
+ setState((prev) => ({ ...prev, isLoading: false }));
182
+ } catch (err) {
183
+ setState((prev) => ({
184
+ ...prev,
185
+ isLoading: false,
186
+ error: err instanceof Error ? err.message : 'Unknown error',
187
+ }));
188
+ }
189
+ }, []);
190
+
191
+ return { ...state, execute };
192
+ }
193
+ `;
194
+ await writeAndLog(filePath, content);
195
+ }
196
+ // ---------------------------------------------------------------------------
197
+ // slice generator
198
+ // ---------------------------------------------------------------------------
199
+ async function generateSlice(name, config, projectRoot) {
200
+ if (config.state !== "Redux Toolkit") {
201
+ console.error(`❌ Slice generation requires Redux Toolkit. This project uses: ${config.state || "None"}`);
202
+ process.exit(1);
203
+ }
204
+ const sliceName = toCamelCase(name);
205
+ const SliceName = toPascalCase(name);
206
+ const filePath = path_1.default.join(projectRoot, "store", `${sliceName}Slice.ts`);
207
+ // --- write the slice file ---
208
+ const sliceContent = `import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
209
+
210
+ interface ${SliceName}State {
211
+ // TODO: define your state shape
212
+ data: null;
213
+ isLoading: boolean;
214
+ error: string | null;
215
+ }
216
+
217
+ const initialState: ${SliceName}State = {
218
+ data: null,
219
+ isLoading: false,
220
+ error: null,
221
+ };
222
+
223
+ const ${sliceName}Slice = createSlice({
224
+ name: '${sliceName}',
225
+ initialState,
226
+ reducers: {
227
+ setLoading(state, _action: PayloadAction<boolean>) {
228
+ state.isLoading = _action.payload;
229
+ },
230
+ setError(state, action: PayloadAction<string | null>) {
231
+ state.error = action.payload;
232
+ },
233
+ reset() {
234
+ return initialState;
235
+ },
236
+ },
237
+ });
238
+
239
+ export const { setLoading, setError, reset } = ${sliceName}Slice.actions;
240
+ export default ${sliceName}Slice.reducer;
241
+ `;
242
+ await writeAndLog(filePath, sliceContent);
243
+ // --- patch store.ts to register the new reducer ---
244
+ const storePath = path_1.default.join(projectRoot, "store", "store.ts");
245
+ if (await fs_extra_1.default.pathExists(storePath)) {
246
+ let storeContent = await fs_extra_1.default.readFile(storePath, "utf8");
247
+ const importLine = `import ${sliceName}Reducer from './${sliceName}Slice';\n`;
248
+ if (!storeContent.includes(importLine)) {
249
+ // Add import after last existing import
250
+ storeContent = storeContent.replace(/^(import .+\n)+/m, (match) => `${match}${importLine}`);
251
+ }
252
+ // Add reducer key inside configureStore reducer object
253
+ if (!storeContent.includes(`${sliceName}:`)) {
254
+ storeContent = storeContent.replace(/reducer:\s*\{/, `reducer: {\n ${sliceName}: ${sliceName}Reducer,`);
255
+ }
256
+ await fs_extra_1.default.writeFile(storePath, storeContent, "utf8");
257
+ console.log(`🔗 Registered ${sliceName} in store/store.ts`);
258
+ }
259
+ else {
260
+ console.warn(`⚠️ store/store.ts not found — add the reducer manually:\n` +
261
+ ` import ${sliceName}Reducer from './${sliceName}Slice';\n` +
262
+ ` // add ${sliceName}: ${sliceName}Reducer to your store`);
263
+ }
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // Public entry point
267
+ // ---------------------------------------------------------------------------
268
+ async function generateCode(type, name) {
269
+ const { config, projectRoot } = await detectProjectConfig();
270
+ const validTypes = ["screen", "component", "hook", "slice"];
271
+ if (!validTypes.includes(type)) {
272
+ console.error(`❌ Unknown type "${type}". Valid types: ${validTypes.join(" | ")}`);
273
+ process.exit(1);
274
+ }
275
+ switch (type) {
276
+ case "screen":
277
+ await generateScreen(name, config, projectRoot);
278
+ break;
279
+ case "component":
280
+ await generateComponent(name, projectRoot);
281
+ break;
282
+ case "hook":
283
+ await generateHook(name, projectRoot);
284
+ break;
285
+ case "slice":
286
+ await generateSlice(name, config, projectRoot);
287
+ break;
288
+ }
289
+ }
@@ -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,4 @@
1
1
  module.exports = {
2
2
  presets: ['module:@react-native/babel-preset'],
3
+ plugins: ['react-native-worklets/plugin'],
3
4
  };
@@ -259,7 +259,10 @@
259
259
  buildSettings = {
260
260
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
261
261
  CLANG_ENABLE_MODULES = YES;
262
+ CODE_SIGN_IDENTITY = "";
263
+ CODE_SIGN_STYLE = Manual;
262
264
  CURRENT_PROJECT_VERSION = 1;
265
+ DEVELOPMENT_TEAM = "";
263
266
  ENABLE_BITCODE = NO;
264
267
  INFOPLIST_FILE = BaseApp/Info.plist;
265
268
  IPHONEOS_DEPLOYMENT_TARGET = 15.1;
@@ -289,7 +292,10 @@
289
292
  buildSettings = {
290
293
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
291
294
  CLANG_ENABLE_MODULES = YES;
295
+ CODE_SIGN_IDENTITY = "";
296
+ CODE_SIGN_STYLE = Manual;
292
297
  CURRENT_PROJECT_VERSION = 1;
298
+ DEVELOPMENT_TEAM = "";
293
299
  INFOPLIST_FILE = BaseApp/Info.plist;
294
300
  IPHONEOS_DEPLOYMENT_TARGET = 15.1;
295
301
  LD_RUNPATH_SEARCH_PATHS = (
@@ -30,5 +30,10 @@ target 'BaseApp' do
30
30
  :mac_catalyst_enabled => false,
31
31
  # :ccache_enabled => true
32
32
  )
33
+ installer.pods_project.targets.each do |target|
34
+ target.build_configurations.each do |config|
35
+ config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
36
+ end
37
+ end
33
38
  end
34
39
  end
@@ -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,20 +5,23 @@
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": {
15
+ "@babel/runtime": "^7.25.0",
13
16
  "react": "19.2.3",
14
17
  "react-native": "0.84.0",
15
- "@react-native/new-app-screen": "0.84.0",
16
- "react-native-safe-area-context": "^5.5.2"
18
+ "react-native-safe-area-context": "^5.5.2",
19
+ "react-native-worklets": "^0.7.0",
20
+ "react-native-reanimated": "^4.2.0"
17
21
  },
18
22
  "devDependencies": {
19
23
  "@babel/core": "^7.25.2",
20
24
  "@babel/preset-env": "^7.25.3",
21
- "@babel/runtime": "^7.25.0",
22
25
  "@react-native-community/cli": "20.1.0",
23
26
  "@react-native-community/cli-platform-android": "20.1.0",
24
27
  "@react-native-community/cli-platform-ios": "20.1.0",
@@ -31,7 +34,7 @@
31
34
  "@types/react-test-renderer": "^19.1.0",
32
35
  "eslint": "^8.19.0",
33
36
  "jest": "^29.6.3",
34
- "prettier": "2.8.8",
37
+ "prettier": "^3.4.2",
35
38
  "react-test-renderer": "19.2.3",
36
39
  "typescript": "^5.8.3"
37
40
  },
@@ -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,13 @@
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 screenOptions={{ headerShown: false }}>
8
+ <Stack.Screen name="index" />
9
+ <Stack.Screen name="home" />
10
+ </Stack>
11
+ </SafeAreaProvider>
8
12
  );
9
13
  }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { StyleSheet, Text, View } from "react-native";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
4
+
5
+ export default function HomeScreen() {
6
+ return (
7
+ <SafeAreaView style={styles.safeArea}>
8
+ <View style={styles.container}>
9
+ <Text style={styles.title}>{{projectName}}</Text>
10
+ <Text style={styles.subtitle}>Your app starts here.</Text>
11
+ </View>
12
+ </SafeAreaView>
13
+ );
14
+ }
15
+
16
+ const styles = StyleSheet.create({
17
+ safeArea: {
18
+ flex: 1,
19
+ backgroundColor: "#F7F7F9",
20
+ },
21
+ container: {
22
+ flex: 1,
23
+ alignItems: "center",
24
+ justifyContent: "center",
25
+ paddingHorizontal: 24,
26
+ },
27
+ title: {
28
+ fontSize: 32,
29
+ fontWeight: "700",
30
+ color: "#111827",
31
+ marginBottom: 8,
32
+ },
33
+ subtitle: {
34
+ fontSize: 16,
35
+ color: "#6B7280",
36
+ },
37
+ });