@fadyshawky/react-native-magic 2.0.9 → 2.1.0

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 CHANGED
@@ -1,266 +1,107 @@
1
1
  # ReactNativeMagic
2
2
 
3
- A modern, production-ready React Native template with best practices, common dependencies, and a scalable architecture.
3
+ **Plug and play** create your app and start developing without hassle.
4
+
5
+ A production-ready React Native template with TypeScript, Redux, React Navigation, and a scalable architecture (Uprise-style). Use it to bootstrap new apps with one command.
4
6
 
5
7
  ## Requirements
6
8
 
7
- - Node.js >= 14 ([Download](https://nodejs.org/en/download/))
9
+ - **Node.js >= 20** ([Download](https://nodejs.org/en/download/))
8
10
  - JDK >= 11 ([Download](https://www.oracle.com/java/technologies/downloads/))
9
- - Ruby >= 2.7.5 (for iOS) ([Download](https://www.ruby-lang.org/en/downloads/))
10
- - Xcode (for iOS) ([Mac App Store](https://apps.apple.com/us/app/xcode/id497799835))
11
- - Android Studio (for Android) ([Download](https://developer.android.com/studio))
11
+ - Ruby >= 2.7.5 (for iOS)
12
+ - Xcode (for iOS) / Android Studio (for Android)
12
13
 
13
- ## Installation
14
+ ## Quick start
14
15
 
15
16
  ```bash
16
17
  npx @react-native-community/cli init YourAppName --template @fadyshawky/react-native-magic
17
18
  cd YourAppName
18
19
  ```
19
20
 
20
- For iOS, install pods:
21
+ Optional: set your bundle ID at creation:
21
22
 
22
23
  ```bash
23
- cd ios && pod install && cd ..
24
- ```
25
-
26
- ## Project Structure
27
-
24
+ npx @react-native-community/cli init YourAppName --template @fadyshawky/react-native-magic --package-name com.yourcompany.yourapp
28
25
  ```
29
- YourAppName/
30
- ├── src/
31
- │ ├── components/ # Reusable UI components
32
- │ ├── screens/ # Screen components
33
- │ ├── navigation/ # Navigation configurations
34
- │ ├── services/ # API services and other external services
35
- │ ├── store/ # State management
36
- │ │ ├── slices/ # Redux slices
37
- │ │ └── index.ts # Store configuration
38
- │ ├── theme/ # Theme configurations
39
- │ ├── utils/ # Utility functions
40
- │ └── types/ # TypeScript type definitions
41
- ├── android/
42
- ├── ios/
43
- └── ...
44
- ```
45
-
46
- ## Features
47
-
48
- ### 1. Type Safety
49
-
50
- - Full TypeScript support
51
- - Pre-configured tsconfig.json
52
- - Type definitions for all components
53
-
54
- ### 2. Navigation
55
-
56
- - React Navigation v6
57
- - Type-safe navigation
58
- - Bottom tabs setup
59
- - Stack navigation setup
60
-
61
- Documentation: [React Navigation](https://reactnavigation.org/docs/getting-started)
62
-
63
- ### 3. State Management
64
-
65
- - Redux Toolkit
66
- - Async storage integration
67
- - Predefined store setup
68
26
 
69
- Documentation: [Redux Toolkit](https://redux-toolkit.js.org/introduction/getting-started)
70
-
71
- ### 4. Environment Variables
72
-
73
- - React Native Config integration
74
- - Secure environment configuration
75
- - Type-safe environment variables
76
-
77
- Documentation: [React Native Config](https://github.com/luggit/react-native-config)
78
-
79
- ### 5. Styling
80
-
81
- - React Native Paper
82
- - Custom theming system
83
- - Dark mode support
84
-
85
- Documentation: [React Native Paper](https://callstack.github.io/react-native-paper/)
86
-
87
- ### 6. Testing
88
-
89
- - Jest configuration
90
- - React Native Testing Library
91
- - Example tests included
92
-
93
- Documentation:
94
-
95
- - [Jest](https://jestjs.io/docs/getting-started)
96
- - [React Native Testing Library](https://callstack.github.io/react-native-testing-library/)
97
-
98
- ## Available Scripts
27
+ For iOS, install pods:
99
28
 
100
29
  ```bash
101
- # Start the Metro bundler
102
- npm start
103
-
104
- # Run on iOS
105
- npm run ios
106
-
107
- # Run on Android
108
- npm run android
109
-
110
- # Run tests
111
- npm test
112
-
113
- # Lint code
114
- npm run lint
115
-
116
- # Type checking
117
- npm run typescript
118
- ```
119
-
120
- ## Dependencies
121
-
122
- ### Production Dependencies
123
-
124
- ```json
125
- {
126
- "@react-navigation/bottom-tabs": "^6.x",
127
- "@react-navigation/native": "^6.x",
128
- "@react-navigation/native-stack": "^6.x",
129
- "@reduxjs/toolkit": "^1.x",
130
- "react-native-paper": "^5.x",
131
- "react-native-safe-area-context": "^4.x",
132
- "react-native-screens": "^3.x",
133
- "@react-native-async-storage/async-storage": "^1.x",
134
- "react-native-config": "^1.x"
135
- }
136
- ```
137
-
138
- ### Development Dependencies
139
-
140
- ```json
141
- {
142
- "@testing-library/react-native": "^11.x",
143
- "@types/jest": "^29.x",
144
- "@types/react": "^18.x",
145
- "typescript": "^4.x",
146
- "jest": "^29.x"
147
- }
30
+ cd ios && pod install && cd ..
148
31
  ```
149
32
 
150
- ## Common Issues & Solutions
151
-
152
- ### iOS Build Issues
153
-
154
- 1. Pod installation fails:
33
+ Then run:
155
34
 
156
35
  ```bash
157
- cd ios
158
- pod deintegrate
159
- pod install
36
+ npm start
37
+ npm run ios # or: npm run android
160
38
  ```
161
39
 
162
- 2. Clean build:
40
+ ## First steps after creating your app
163
41
 
164
- ```bash
165
- cd ios
166
- xcodebuild clean
167
- cd ..
168
- npm run ios
169
- ```
42
+ 1. **App name & bundle ID** – Set at init (or you’ll be prompted for package name if you didn’t pass `--package-name`). See [CUSTOMIZATION.md](template/docs/CUSTOMIZATION.md#app-name-and-bundle-id).
43
+ 2. **API** – Copy `.env.example` to `.env` and set `API_BASE_URL` (and other vars) for your backend.
44
+ 3. **Theme** – Edit `src/core/theme/colors.ts` (and `fonts.ts`, `commonSizes.ts` if needed) for your brand.
45
+ 4. **Config** – Optional: adjust `src/core/config/index.ts` for feature toggles or app-level constants.
170
46
 
171
- ### Android Build Issues
47
+ ## Documentation
172
48
 
173
- 1. Gradle issues:
49
+ In your generated project you’ll have:
174
50
 
175
- ```bash
176
- cd android
177
- ./gradlew clean
178
- cd ..
179
- npm run android
180
- ```
51
+ - **[docs/ARCHITECTURE.md](template/docs/ARCHITECTURE.md)** – Layers, folder map, data flow, SOLID.
52
+ - **[docs/CUSTOMIZATION.md](template/docs/CUSTOMIZATION.md)** – App name, bundle ID, API, theme, adding a screen/slice/language.
53
+ - **[docs/BEST_PRACTICES.md](template/docs/BEST_PRACTICES.md)** – Code style, structure, testing, security, upgrades.
181
54
 
182
- 2. SDK location issues:
183
- Create a `local.properties` file in the android folder with your SDK path:
55
+ ## Project structure (in your app)
184
56
 
185
- ```properties
186
- sdk.dir=/Users/USERNAME/Library/Android/sdk
187
57
  ```
188
-
189
- ## Customization
190
-
191
- ### 1. Changing Theme
192
-
193
- Edit `src/theme/index.ts`:
194
-
195
- ```typescript
196
- export const theme = {
197
- colors: {
198
- primary: "#YOUR_COLOR",
199
- // ...
200
- },
201
- // ...
202
- };
58
+ src/
59
+ ├── common/ # Shared components, localization, helpers, validations, utils
60
+ ├── core/ # Store (Redux), API, theme, config
61
+ ├── navigation/ # Auth stack, main stack, tabs
62
+ ├── screens/ # Feature screens
63
+ └── sheetManager/ # Action sheets
203
64
  ```
204
65
 
205
- ### 2. Adding New Navigation Screens
66
+ ## Scripts
206
67
 
207
- 1. Create screen in `src/screens`
208
- 2. Add to navigation stack in `src/navigation`
209
- 3. Update types in `src/types/navigation.ts`
68
+ | Command | Description |
69
+ |---------|-------------|
70
+ | `npm start` | Start Metro bundler |
71
+ | `npm run ios` | Run on iOS |
72
+ | `npm run android` | Run on Android |
73
+ | `npm test` | Run tests |
74
+ | `npm run lint` | Lint code |
210
75
 
211
- ### 3. Environment Variables
76
+ ## Versioning
212
77
 
213
- 1. Create `.env` file in root directory:
78
+ - **React Native**: ^0.84.x (current stable at release).
79
+ - **React**: ^19.2.x.
80
+ - **Node**: >= 20 (LTS).
214
81
 
215
- ```env
216
- API_URL=https://api.example.com
217
- ENV=development
218
- ```
82
+ Dependencies use **caret (^)** so your app can get patch/minor updates independently.
219
83
 
220
- 2. Access variables in your code:
84
+ ## Common issues
221
85
 
222
- ```typescript
223
- import Config from "react-native-config";
86
+ **iOS – Pod install fails**
224
87
 
225
- console.log(Config.API_URL);
88
+ ```bash
89
+ cd ios && pod deintegrate && pod install && cd ..
226
90
  ```
227
91
 
228
- Note: Remember to add `.env` to your `.gitignore` file and provide a `.env.example` template.
92
+ **Android Gradle / SDK**
229
93
 
230
- ## Contributing
94
+ - Run `./gradlew clean` in `android/`.
95
+ - Ensure `android/local.properties` has `sdk.dir` set to your Android SDK path.
231
96
 
232
- 1. Fork the repository
233
- 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
234
- 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
235
- 4. Push to the branch (`git push origin feature/AmazingFeature`)
236
- 5. Open a Pull Request
97
+ **Upgrading React Native**
237
98
 
238
- ## Useful Links
239
-
240
- - [React Native Documentation](https://reactnative.dev/docs/getting-started)
241
- - [TypeScript Documentation](https://www.typescriptlang.org/docs/)
242
- - [React Navigation Documentation](https://reactnavigation.org/docs/getting-started)
243
- - [Redux Toolkit Documentation](https://redux-toolkit.js.org/introduction/getting-started)
244
- - [React Native Paper Documentation](https://callstack.github.io/react-native-paper/)
99
+ Use [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) (select current → target version) and apply the suggested changes.
245
100
 
246
101
  ## License
247
102
 
248
- This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
249
-
250
- ## Support
251
-
252
- If you find this template helpful, consider buying me a beer! 🍺
253
-
254
- <a href="https://www.buymeacoffee.com/fadytshawke"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a beer&emoji=🍺&slug=fadytshawke&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" /></a>
103
+ MIT see [LICENSE.md](LICENSE.md).
255
104
 
256
105
  ## Author
257
106
 
258
- Fady Shawky
259
-
260
- - GitHub: [@fadyshawky](https://github.com/fadyshawky)
261
-
262
- ## Acknowledgments
263
-
264
- - React Native Team
265
- - React Navigation Team
266
- - All contributors who help maintain this template
107
+ Fady Shawky – [GitHub](https://github.com/fadyshawky)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fadyshawky/react-native-magic",
3
- "version": "2.0.9",
4
- "description": "react native template with ready components, hooks, react navigation, redux, typescript, etc.",
3
+ "version": "2.1.0",
4
+ "description": "Plug-and-play React Native template: TypeScript, Redux, React Navigation, scalable architecture. Init and start developing without hassle.",
5
5
  "keywords": [
6
6
  "react-native-magic",
7
7
  "react-native",
@@ -110,6 +110,13 @@
110
110
  "scripts": {
111
111
  "test": "exit 0"
112
112
  },
113
+ "peerDependencies": {
114
+ "react": "^19.2.0",
115
+ "react-native": "^0.84.0"
116
+ },
117
+ "engines": {
118
+ "node": ">=20"
119
+ },
113
120
  "repository": {
114
121
  "type": "git",
115
122
  "url": "https://github.com/fadyshawky/ReactNativeMagic.git"
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-init script: if the user did not pass --package-name at init,
5
+ * prompt for a package name and apply it to .env and iOS project.
6
+ * Runs with cwd = the new project directory.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const readline = require('readline');
12
+
13
+ const PACKAGE_NAME_REGEX = /^([a-zA-Z][a-zA-Z0-9_]*\.)+[a-zA-Z][a-zA-Z0-9_]*$/;
14
+
15
+ function prompt(question) {
16
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
17
+ return new Promise((resolve) => {
18
+ rl.question(question, (answer) => {
19
+ rl.close();
20
+ resolve((answer || '').trim());
21
+ });
22
+ });
23
+ }
24
+
25
+ function validatePackageName(packageName) {
26
+ const parts = packageName.split('.');
27
+ if (parts.length < 2) {
28
+ return 'Package name must have at least two segments (e.g. com.app)';
29
+ }
30
+ if (!PACKAGE_NAME_REGEX.test(packageName)) {
31
+ return 'Package name can only contain letters, numbers, underscores and dots';
32
+ }
33
+ return null;
34
+ }
35
+
36
+ function ensureEnvHasPackageIds(envPath, packageName) {
37
+ const fullPath = path.join(process.cwd(), envPath);
38
+ let content = '';
39
+ if (fs.existsSync(fullPath)) {
40
+ content = fs.readFileSync(fullPath, 'utf8');
41
+ }
42
+ const lines = content.split('\n');
43
+ const updated = lines.map((line) => {
44
+ const eq = line.indexOf('=');
45
+ if (eq === -1) return line;
46
+ const key = line.slice(0, eq).trim();
47
+ if (key === 'APP_ID' || key === 'ANDROID_APP_ID') {
48
+ return `${key}=${packageName}`;
49
+ }
50
+ return line;
51
+ });
52
+ const hasAppId = lines.some((l) => /^APP_ID=/.test(l) || /^ANDROID_APP_ID=/.test(l));
53
+ if (!hasAppId) {
54
+ if (updated.length && updated[updated.length - 1] !== '') {
55
+ updated.push('');
56
+ }
57
+ updated.push(`APP_ID=${packageName}`);
58
+ updated.push(`ANDROID_APP_ID=${packageName}`);
59
+ }
60
+ fs.writeFileSync(fullPath, updated.join('\n'), 'utf8');
61
+ }
62
+
63
+ function getCurrentIosBundleId() {
64
+ const iosDir = path.join(process.cwd(), 'ios');
65
+ if (!fs.existsSync(iosDir)) return null;
66
+ const entries = fs.readdirSync(iosDir, { withFileTypes: true });
67
+ const pbxprojPaths = entries
68
+ .filter((d) => d.isDirectory() && d.name.endsWith('.xcodeproj'))
69
+ .map((d) => path.join(iosDir, d.name, 'project.pbxproj'))
70
+ .filter((p) => fs.existsSync(p));
71
+ const customPattern = /PRODUCT_BUNDLE_IDENTIFIER = "([^"]+)"/;
72
+ for (const pbx of pbxprojPaths) {
73
+ const content = fs.readFileSync(pbx, 'utf8');
74
+ const customMatch = content.match(customPattern);
75
+ if (customMatch) {
76
+ const id = customMatch[1];
77
+ if (!id.includes('$(')) return id;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+
83
+ function updateIosBundleId(packageName) {
84
+ const iosDir = path.join(process.cwd(), 'ios');
85
+ if (!fs.existsSync(iosDir)) return;
86
+
87
+ const entries = fs.readdirSync(iosDir, { withFileTypes: true });
88
+ const pbxprojPaths = entries
89
+ .filter((d) => d.isDirectory() && d.name.endsWith('.xcodeproj'))
90
+ .map((d) => path.join(iosDir, d.name, 'project.pbxproj'))
91
+ .filter((p) => fs.existsSync(p));
92
+
93
+ for (const pbx of pbxprojPaths) {
94
+ let content = fs.readFileSync(pbx, 'utf8');
95
+ const pattern = /PRODUCT_BUNDLE_IDENTIFIER = "org\.reactjs\.native\.example\.\$\(PRODUCT_NAME:rfc1034identifier\)"/g;
96
+ if (pattern.test(content)) {
97
+ content = content.replace(
98
+ /PRODUCT_BUNDLE_IDENTIFIER = "org\.reactjs\.native\.example\.\$\(PRODUCT_NAME:rfc1034identifier\)"/g,
99
+ `PRODUCT_BUNDLE_IDENTIFIER = "${packageName}"`
100
+ );
101
+ fs.writeFileSync(pbx, content, 'utf8');
102
+ }
103
+ }
104
+ }
105
+
106
+ async function main() {
107
+ const existingBundleId = getCurrentIosBundleId();
108
+ if (existingBundleId) {
109
+ // User passed --package-name; sync to .env for Android
110
+ const envPath = path.join(process.cwd(), '.env');
111
+ const examplePath = path.join(process.cwd(), '.env.example');
112
+ if (!fs.existsSync(envPath) && fs.existsSync(examplePath)) {
113
+ fs.copyFileSync(examplePath, envPath);
114
+ }
115
+ ensureEnvHasPackageIds('.env', existingBundleId);
116
+ for (const f of ['.env.development', '.env.staging', '.env.production']) {
117
+ if (fs.existsSync(path.join(process.cwd(), f))) {
118
+ ensureEnvHasPackageIds(f, existingBundleId);
119
+ }
120
+ }
121
+ console.log(`\nSynced package name ${existingBundleId} to .env (Android).\n`);
122
+ return;
123
+ }
124
+
125
+ console.log('\nYou did not pass --package-name. Set a bundle ID (Android applicationId / iOS PRODUCT_BUNDLE_IDENTIFIER) now.\n');
126
+
127
+ const answer = await prompt('Package name (e.g. com.company.app), or press Enter to skip: ');
128
+
129
+ if (!answer) {
130
+ console.log('\nSkipped. You can set APP_ID and ANDROID_APP_ID in .env later, and set the iOS bundle ID in Xcode or use react-native-rename.\n');
131
+ return;
132
+ }
133
+
134
+ const err = validatePackageName(answer);
135
+ if (err) {
136
+ console.error('\nInvalid package name:', err);
137
+ process.exit(1);
138
+ }
139
+
140
+ const packageName = answer;
141
+
142
+ // Ensure .env exists
143
+ const envPath = path.join(process.cwd(), '.env');
144
+ const examplePath = path.join(process.cwd(), '.env.example');
145
+ if (!fs.existsSync(envPath) && fs.existsSync(examplePath)) {
146
+ fs.copyFileSync(examplePath, envPath);
147
+ }
148
+
149
+ ensureEnvHasPackageIds('.env', packageName);
150
+ const envFiles = ['.env.development', '.env.staging', '.env.production'];
151
+ for (const f of envFiles) {
152
+ if (fs.existsSync(path.join(process.cwd(), f))) {
153
+ ensureEnvHasPackageIds(f, packageName);
154
+ }
155
+ }
156
+
157
+ updateIosBundleId(packageName);
158
+
159
+ console.log(`\nSet package name to ${packageName} in .env and iOS project.\n`);
160
+ }
161
+
162
+ main().catch((e) => {
163
+ console.error(e);
164
+ process.exit(1);
165
+ });
@@ -0,0 +1,9 @@
1
+ # Copy this file to .env and set your values.
2
+ # Do not commit .env (it is in .gitignore).
3
+
4
+ ENV=development
5
+ API_BASE_URL=https://api.example.com
6
+
7
+ # Bundle ID (Android applicationId / iOS). Set at init with --package-name or when prompted.
8
+ APP_ID=com.yourcompany.yourapp
9
+ ANDROID_APP_ID=com.yourcompany.yourapp
package/template/App.tsx CHANGED
@@ -1,20 +1,20 @@
1
1
  /**
2
- * Sample React Native App
3
- * https://github.com/facebook/react-native
4
- *
5
2
  * @format
6
3
  */
7
4
  import React from 'react';
8
- import {LogBox, SafeAreaView, StatusBar} from 'react-native';
5
+ import {LogBox, View} from 'react-native';
9
6
  import {SheetProvider} from 'react-native-actions-sheet';
10
- import {SafeAreaProvider} from 'react-native-safe-area-context';
7
+ import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context';
11
8
  import {Provider} from 'react-redux';
12
9
  import {PersistGate} from 'redux-persist/integration/react';
10
+ import {AppStatusBar} from './src/common/components/AppStatusBar';
11
+ import {SnackbarProvider} from './src/common/components/SnackbarProvider';
13
12
  import {LocalizationProvider} from './src/common/localization/LocalizationProvider';
14
13
  import {RTLInitializer} from './src/common/localization/RTLInitializer';
15
14
  import {useAppSelector} from './src/core/store/reduxHelpers';
16
15
  import {persistor, store} from './src/core/store/store';
17
16
  import {ThemeProvider, useTheme} from './src/core/theme/ThemeProvider';
17
+ import {NaturalColors} from './src/core/theme/colors';
18
18
  import AppNavigator from './src/navigation/MainNavigation';
19
19
 
20
20
  LogBox.ignoreAllLogs();
@@ -27,14 +27,16 @@ const ThemedApp = () => {
27
27
  <RTLInitializer>
28
28
  <LocalizationProvider initialLanguage={language}>
29
29
  <SafeAreaProvider>
30
- <SafeAreaView style={{position: 'absolute'}} />
31
- <StatusBar
32
- barStyle={theme.mode === 'dark' ? 'light-content' : 'dark-content'}
33
- backgroundColor={theme.colors.background_2}
34
- />
35
- <SheetProvider>
36
- <AppNavigator />
37
- </SheetProvider>
30
+ <View style={{flex: 1, backgroundColor: theme?.colors?.background_2 ?? NaturalColors.background_2}}>
31
+ <SafeAreaView style={{position: 'absolute'}} />
32
+ <AppStatusBar
33
+ barStyle={theme?.mode === 'dark' ? 'light-content' : 'dark-content'}
34
+ backgroundColor={theme?.colors?.background_2 ?? NaturalColors.background_2}
35
+ />
36
+ <SheetProvider>
37
+ <AppNavigator />
38
+ </SheetProvider>
39
+ </View>
38
40
  </SafeAreaProvider>
39
41
  </LocalizationProvider>
40
42
  </RTLInitializer>
@@ -45,9 +47,11 @@ function App(): React.JSX.Element {
45
47
  return (
46
48
  <Provider store={store}>
47
49
  <PersistGate loading={null} persistor={persistor}>
48
- <ThemeProvider initialTheme="dark">
49
- <ThemedApp />
50
- </ThemeProvider>
50
+ <SnackbarProvider>
51
+ <ThemeProvider initialTheme="dark">
52
+ <ThemedApp />
53
+ </ThemeProvider>
54
+ </SnackbarProvider>
51
55
  </PersistGate>
52
56
  </Provider>
53
57
  );
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - Architecture aligned with Uprise-style layout: `common/`, `core/`, `navigation/`, `screens/`, `sheetManager/`.
8
+ - Single app config module (`src/core/config`) for API base URL and feature toggles; base URL from `.env` via react-native-config.
9
+ - `AppStatusBar` component to avoid deprecated Android status bar APIs.
10
+ - `.env.example` for API_BASE_URL and ENV; `.env` in `.gitignore`.
11
+ - Documentation: `docs/ARCHITECTURE.md`, `docs/CUSTOMIZATION.md`, `docs/BEST_PRACTICES.md`.
12
+
13
+ ### Changed
14
+
15
+ - Dependencies updated to caret (^) versions; React Native ^0.84, React ^19.2; Node >= 20.
16
+ - Store persist simplified: whitelist only `user` (accessToken, user) and `app` (language, isRTL); removed CryptoJS/deviceId encryption.
17
+ - API layer uses `src/core/config` for base URL; request interceptor adds Bearer token and locale; response interceptor handles 401 (logout).
18
+ - Theme: added “Customize for your brand” comments in `colors.ts`, `fonts.ts`, `commonSizes.ts`.
19
+ - Bootstrap order: Provider → PersistGate → SnackbarProvider → ThemeProvider → ThemedApp (RTLInitializer → LocalizationProvider → SafeAreaProvider → AppStatusBar → SheetProvider → AppNavigator).
20
+ - `index.js` imports `./src/sheetManager/sheets` so sheets are registered.
21
+
22
+ ### Template contract
23
+
24
+ - `template.config.js`: `placeholderName`, `titlePlaceholder`, `templateDir`.
25
+ - Bundle ID set at init via `--package-name`; app name and title replaced by CLI.
@@ -0,0 +1,27 @@
1
+ # Architecture
2
+
3
+ ## Overview
4
+
5
+ This app uses a layered structure: **UI** (screens, components) → **navigation** → **core** (store, api, theme, config) and **common** (components, localization, helpers).
6
+
7
+ ## Folder map
8
+
9
+ | Folder | Purpose |
10
+ |--------|--------|
11
+ | `src/common/` | Shared UI components, localization (i18n), helpers, validations, hooks, urls, utils |
12
+ | `src/core/` | Store (Redux + persist), API client, theme, config, services, hooks |
13
+ | `src/navigation/` | Navigator setup, auth stack, main stack, tab bar, header components |
14
+ | `src/screens/` | Feature screens (Login, Home, Profile, etc.); each may have local `components/` and `hooks/` |
15
+ | `src/sheetManager/` | Action sheet registration |
16
+
17
+ ## Data flow
18
+
19
+ - **Auth**: Token is stored in Redux (`user.accessToken`). Navigation shows Auth stack when there is no token, Main stack when there is.
20
+ - **API**: Single axios instance in `src/core/api/serverHeaders.ts`. Base URL comes from `src/core/config` (and `.env`). Request interceptor adds `Authorization: Bearer` and `locale`; response interceptor handles 401 (e.g. logout).
21
+ - **Redux**: Slices live under `src/core/store/<domain>/` (e.g. `app`, `user`). Persist whitelist controls what is saved to AsyncStorage.
22
+
23
+ ## SOLID mapping
24
+
25
+ - **SRP**: One domain per store folder; one primary concern per component file; theme split into colors, fonts, sizes, consts, styles.
26
+ - **OCP**: Extend by adding screens, slices, or routes without changing existing stack logic; extend theme by editing theme files.
27
+ - **DIP**: Core (API, store) does not depend on UI; screens depend on core via hooks/selectors; config abstracts environment.
@@ -0,0 +1,33 @@
1
+ # Best practices
2
+
3
+ ## Code style
4
+
5
+ - Use **TypeScript** strictly; avoid `any` where possible.
6
+ - Use typed Redux: `useAppSelector`, `useAppDispatch`, and `createAppAsyncThunk` with `RootState` / `AppDispatch`.
7
+ - Run `npm run lint` and fix issues.
8
+
9
+ ## Structure
10
+
11
+ - **Single responsibility**: One concern per module/folder; keep screens thin (logic in hooks or actions).
12
+ - Put shared UI in `src/common/components/`.
13
+ - Use the API helpers from `src/core/api` (get, post, put, deleteApi); do not create extra axios instances.
14
+
15
+ ## Testing
16
+
17
+ - Use **Jest** and **React Native Testing Library** for unit and component tests.
18
+ - Add tests for store logic and important components; run `npm test`.
19
+
20
+ ## Performance
21
+
22
+ - Avoid creating new objects/functions in render when they are passed as props to children.
23
+ - Use stable keys for list items.
24
+
25
+ ## Security
26
+
27
+ - Do not commit secrets; use `.env` for API keys and base URL (and add `.env` to `.gitignore`).
28
+ - Persist only necessary fields (use the persist whitelist in the store).
29
+
30
+ ## LTS / upgrades
31
+
32
+ - The template targets **Node >= 20** and current stable **React Native** (e.g. 0.84.x).
33
+ - To upgrade React Native, use [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/): select your current version and the target version, then apply the suggested changes to `package.json`, `android/`, `ios/`, and config files.
@@ -0,0 +1,53 @@
1
+ # Customization
2
+
3
+ ## App name and bundle ID
4
+
5
+ Set at project creation:
6
+
7
+ ```bash
8
+ npx @react-native-community/cli init YourAppName --template @fadyshawky/react-native-magic --package-name com.yourcompany.yourapp
9
+ ```
10
+
11
+ - **App name**: Replaces the template placeholder in `app.json` and file names.
12
+ - **Bundle ID** (`--package-name`): Sets Android `applicationId` and iOS `PRODUCT_BUNDLE_IDENTIFIER` and restructures Android package folders.
13
+
14
+ If you omit `--package-name`, the template will **prompt you** after init to enter a package name (e.g. `com.company.app`). You can enter it then, or press Enter to skip and set `APP_ID` / `ANDROID_APP_ID` in `.env` later and the iOS bundle ID in Xcode or via [react-native-rename](https://www.npmjs.com/package/react-native-rename).
15
+
16
+ ## API base URL and environment
17
+
18
+ 1. Copy `.env.example` to `.env`.
19
+ 2. Set `API_BASE_URL` (and any other keys) for your backend.
20
+ 3. The app reads these via `react-native-config`; `src/core/config/index.ts` re-exports them for type-safe use.
21
+
22
+ Optional: use multiple env files (e.g. `.env.development`, `.env.staging`, `.env.production`) and build variants so each build uses the right URL.
23
+
24
+ ## Theme
25
+
26
+ Edit these files for your brand (no need to touch components):
27
+
28
+ - **Colors**: `src/core/theme/colors.ts`
29
+ - **Fonts**: `src/core/theme/fonts.ts`
30
+ - **Sizes / spacing**: `src/core/theme/commonSizes.ts`
31
+
32
+ ## Adding a new language
33
+
34
+ 1. Add a new translation file under `src/common/localization/translations/`, e.g. `frLocalization.ts`, following the same shape as `commonLocalization.ts` or `loginLocalization.ts`.
35
+ 2. In `src/common/localization/localization.ts`, import the new file and add it to the `localization` object, e.g. `fr: new LocalizedStrings(frLocalization)`.
36
+ 3. Add the language to the `Languages` enum in `localization.ts` if you use it for switching (e.g. `fr = 'fr'`).
37
+ 4. Use the new keys in your components via the existing `t(key, section)` or the relevant `localization.*` object.
38
+
39
+ ## Adding a screen
40
+
41
+ 1. Create a folder under `src/screens/<Feature>/` with `Feature.tsx` and optional `components/` and `hooks/`.
42
+ 2. Register the screen in the right stack in `src/navigation/` (e.g. `AuthStack.tsx` or `MainStack.tsx`).
43
+ 3. Add the route and component to the stack’s screen list.
44
+
45
+ ## Adding a Redux slice
46
+
47
+ 1. Create a folder under `src/core/store/<domain>/` with `*State.ts`, `*Slice.ts`, and optionally `*Actions.ts`.
48
+ 2. Add the slice to `src/core/store/rootReducer.ts`.
49
+ 3. If the slice should persist, add a `createWhitelistFilter('<domain>', ['field1', 'field2'])` entry in `src/core/store/store.tsx` persist config.
50
+
51
+ ## Feature flags / app config
52
+
53
+ Toggle features or app-level constants in `src/core/config/index.ts` or via env vars read there (e.g. `enableRTL`).
package/template/index.js CHANGED
@@ -5,5 +5,6 @@ import 'react-native-gesture-handler';
5
5
  import {AppRegistry} from 'react-native';
6
6
  import App from './App';
7
7
  import {name as appName} from './app.json';
8
+ import './src/sheetManager/sheets';
8
9
 
9
10
  AppRegistry.registerComponent(appName, () => App);
@@ -1,116 +1,62 @@
1
1
  {
2
- "name": "@fadyshawky/react-native-magic",
2
+ "name": "reactnativemagic",
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "android": "react-native run-android",
7
- "android:staging": "react-native run-android --variant=stagingDebug",
8
- "android:staging-release": "react-native run-android --variant=stagingRelease",
9
- "android:dev": "ENVFILE=.env.development react-native run-android --variant=developmentDebug",
10
- "android:dev-release": "ENVFILE=.env.development react-native run-android --variant=developmentRelease",
11
- "android:prod": "ENVFILE=.env.production react-native run-android --variant=productionDebug",
12
- "android:prod-release": "ENVFILE=.env.production react-native run-android --variant=productionRelease",
13
7
  "ios": "react-native run-ios",
14
8
  "lint": "eslint .",
15
9
  "start": "react-native start",
16
- "start:development": "ENVFILE=.env.development react-native start --reset-cache",
17
- "start:production": "ENVFILE=.env.production react-native start --reset-cache",
18
- "start:staging": "ENVFILE=.env.staging react-native start --reset-cache",
19
10
  "test": "jest"
20
11
  },
21
12
  "dependencies": {
22
- "@d11/react-native-fast-image": "^8.9.2",
23
- "@react-native-async-storage/async-storage": "^2.1.0",
24
- "@react-native-camera-roll/camera-roll": "^7.9.0",
25
- "@react-native-community/datetimepicker": "^8.2.0",
26
- "@react-native-community/image-editor": "^4.2.1",
27
- "@react-native-community/netinfo": "^11.4.1",
28
- "@react-native-community/slider": "^4.5.5",
29
- "@react-navigation/bottom-tabs": "^7.2.0",
30
- "@react-navigation/drawer": "^7.1.1",
31
- "@react-navigation/elements": "^2.2.5",
32
- "@react-navigation/material-top-tabs": "^7.1.0",
33
- "@react-navigation/native": "^7.0.14",
34
- "@react-navigation/native-stack": "^7.2.0",
35
- "@reduxjs/toolkit": "^2.5.0",
36
- "@shopify/flash-list": "^1.7.2",
37
- "@types/intl": "^1.2.2",
38
- "@types/jest": "^29.5.14",
39
- "@types/lodash": "^4.17.13",
40
- "@types/react-native-vector-icons": "^6.4.18",
41
- "@types/react-redux": "^7.1.34",
42
- "axios": "^1.7.9",
13
+ "@react-native-async-storage/async-storage": "^2.2.0",
14
+ "@react-navigation/bottom-tabs": "^7.14.0",
15
+ "@react-navigation/native": "^7.1.28",
16
+ "@react-navigation/native-stack": "^7.13.0",
17
+ "@reduxjs/toolkit": "^2.11.0",
18
+ "axios": "^1.13.0",
43
19
  "babel-plugin-transform-remove-console": "^6.9.4",
44
- "dayjs": "^1.11.13",
45
- "detox": "^20.28.0",
20
+ "dayjs": "^1.11.19",
46
21
  "intl": "^1.2.5",
47
- "lodash": "^4.17.21",
48
- "mime": "^4.0.6",
49
- "patch-package": "^8.0.0",
50
- "react": "^18.3.1",
51
- "react-native": "^0.76.5",
52
- "react-native-actions-sheet": "^0.9.7",
53
- "react-native-animated-pagination-dots": "^0.1.73",
54
- "react-native-calendars": "^1.1307.0",
55
- "react-native-config": "^1.5.3",
56
- "react-native-device-info": "^14.0.2",
57
- "react-native-dropdown-select-list": "^2.0.5",
58
- "react-native-gesture-handler": "^2.21.2",
59
- "react-native-image-crop-picker": "^0.41.6",
60
- "react-native-image-resource-generator": "^1.0.2",
61
- "react-native-in-app-review": "^4.3.3",
62
- "react-native-keyboard-aware-scroll-view": "^0.9.5",
22
+ "lodash": "^4.17.23",
23
+ "react": "^19.2.0",
24
+ "react-dom": "^19.2.0",
25
+ "react-native": "^0.84.0",
26
+ "react-native-actions-sheet": "^10.1.0",
27
+ "react-native-config": "^1.6.0",
28
+ "react-native-device-info": "^14.0.0",
29
+ "react-native-gesture-handler": "^2.30.0",
63
30
  "react-native-localization": "^2.3.2",
64
- "react-native-mask-input": "^1.2.3",
65
- "react-native-orientation-locker": "^1.7.0",
66
- "react-native-pager-view": "^6.6.1",
67
- "react-native-permissions": "^5.2.1",
68
- "react-native-reanimated": "^3.16.6",
69
- "react-native-reanimated-carousel": "^3.5.1",
70
- "react-native-restart": "^0.0.27",
71
- "react-native-safe-area-context": "^5.0.0",
72
- "react-native-screens": "^4.4.0",
73
- "react-native-sfsymbols": "^1.2.2",
74
- "react-native-share": "^12.0.3",
31
+ "react-native-safe-area-context": "^5.6.0",
32
+ "react-native-screens": "^4.24.0",
75
33
  "react-native-snackbar": "^2.8.0",
76
- "react-native-svg": "^15.10.1",
77
- "react-native-tab-view": "^4.0.5",
78
- "react-native-vector-icons": "^10.2.0",
79
- "react-native-vision-camera": "^4.6.3",
80
- "react-native-webview": "^13.12.5",
81
34
  "react-redux": "^9.2.0",
82
35
  "redux": "^5.0.1",
83
36
  "redux-persist": "^6.0.0",
84
37
  "redux-persist-transform-filter": "^0.0.22"
85
38
  },
86
39
  "devDependencies": {
87
- "@babel/core": "^7.25.2",
40
+ "@babel/core": "^7.29.0",
88
41
  "@babel/preset-env": "^7.26.0",
89
- "@babel/runtime": "^7.26.0",
90
- "@jest/globals": "^29.7.0",
91
- "@react-native-community/cli": "15.0.1",
92
- "@react-native-community/cli-platform-android": "15.0.1",
93
- "@react-native-community/cli-platform-ios": "15.0.1",
94
- "@react-native/babel-preset": "^0.76.5",
95
- "@react-native/eslint-config": "^0.76.5",
96
- "@react-native/metro-config": "^0.76.5",
97
- "@react-native/typescript-config": "^0.76.5",
98
- "@types/react": "^18.3.18",
99
- "@types/react-test-renderer": "^18.3.1",
100
- "babel-jest": "^29.7.0",
101
- "eslint": "^8.57.1",
102
- "eslint-plugin-import": "^2.31.0",
103
- "eslint-plugin-jest": "^28.10.0",
104
- "eslint-plugin-react": "^7.37.2",
105
- "eslint-plugin-react-hooks": "^5.1.0",
106
- "eslint-plugin-react-native": "^4.1.0",
107
- "eslint-plugin-unused-imports": "^4.1.4",
108
- "jest": "^29.7.0",
109
- "prettier": "^2.8.8",
110
- "react-test-renderer": "^18.3.1",
111
- "typescript": "^5.0.4"
42
+ "@babel/runtime": "^7.28.0",
43
+ "@react-native-community/cli": "^20.1.0",
44
+ "@react-native-community/cli-platform-android": "^20.1.0",
45
+ "@react-native-community/cli-platform-ios": "^20.1.0",
46
+ "@react-native/babel-preset": "^0.84.0",
47
+ "@react-native/eslint-config": "^0.84.0",
48
+ "@react-native/metro-config": "^0.84.0",
49
+ "@react-native/typescript-config": "^0.84.0",
50
+ "@types/jest": "^30.0.0",
51
+ "@types/react": "^19.2.0",
52
+ "@types/react-test-renderer": "^19.1.0",
53
+ "eslint": "^10.0.0",
54
+ "jest": "^30.0.0",
55
+ "prettier": "^3.8.0",
56
+ "react-test-renderer": "^19.2.0",
57
+ "typescript": "^5.9.0"
112
58
  },
113
59
  "engines": {
114
- "node": ">=18"
60
+ "node": ">=20"
115
61
  }
116
62
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * StatusBar wrapper that avoids deprecated Android APIs when using edge-to-edge.
3
+ * On Android 15+ with edge-to-edge, Window.setStatusBarColor/setNavigationBarColor
4
+ * are deprecated. Passing backgroundColor on Android triggers those APIs, so we
5
+ * only pass it on iOS.
6
+ */
7
+ import React from 'react';
8
+ import {Platform, StatusBar, StatusBarProps} from 'react-native';
9
+
10
+ type AppStatusBarProps = Omit<StatusBarProps, 'backgroundColor'> & {
11
+ backgroundColor?: string;
12
+ };
13
+
14
+ export function AppStatusBar({
15
+ backgroundColor,
16
+ ...rest
17
+ }: AppStatusBarProps): React.JSX.Element {
18
+ return (
19
+ <StatusBar
20
+ {...rest}
21
+ backgroundColor={Platform.OS === 'ios' ? backgroundColor : undefined}
22
+ />
23
+ );
24
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Wrapper for snackbar/toast. The template uses react-native-snackbar directly.
5
+ * Replace with a custom implementation (e.g. context + showSnackbar) if needed.
6
+ */
7
+ export const SnackbarProvider = ({
8
+ children = null,
9
+ }: {
10
+ children?: React.ReactNode;
11
+ }): React.JSX.Element => <>{children}</>;
@@ -1,28 +1,57 @@
1
- import axios, {AxiosDefaults} from 'axios';
2
- import DeviceInfo from 'react-native-device-info';
1
+ import axios, {AxiosDefaults, AxiosError, AxiosResponse} from 'axios';
2
+ import {store} from '../store/store';
3
+ import {setLogout} from '../store/user/userSlice';
4
+ import {API_BASE_URL} from '../config';
3
5
 
4
- export const defaultHeaders: HeadersInit_ = {
5
- 'app-version': DeviceInfo.getVersion(),
6
- 'serial-number': DeviceInfo.getSerialNumberSync(),
6
+ export const defaultHeaders: HeadersInit = {
7
7
  Connection: 'keep-alive',
8
8
  'Content-Type': 'application/json',
9
9
  };
10
+
10
11
  declare type MethodData = {
11
12
  url: AxiosDefaults['httpsAgent'];
12
13
  data?: AxiosDefaults['data'];
13
14
  config?: any;
14
15
  };
15
16
 
16
- const baseURL = '';
17
-
18
17
  const instance = axios.create({
19
- baseURL: baseURL,
18
+ baseURL: API_BASE_URL,
20
19
  headers: {
21
20
  ...defaultHeaders,
22
21
  },
23
22
  });
24
23
 
25
- // Export HTTP methods using the configured instance
24
+ instance.interceptors.request.use(
25
+ config => {
26
+ const state = store.getState();
27
+ const accessToken = state.user.accessToken;
28
+ const locale = state.app?.language ?? 'en';
29
+ if (accessToken) {
30
+ config.headers.Authorization = `Bearer ${accessToken}`;
31
+ }
32
+ config.headers.locale = locale;
33
+ return config;
34
+ },
35
+ error => Promise.reject(error),
36
+ );
37
+
38
+ instance.interceptors.response.use(
39
+ (response: AxiosResponse) => response,
40
+ async (error: AxiosError) => {
41
+ const originalRequest = error.config;
42
+ if (
43
+ originalRequest?.url?.includes('/login') ||
44
+ originalRequest?.url?.includes('/refresh-token')
45
+ ) {
46
+ return Promise.reject(error);
47
+ }
48
+ if (error.response?.status === 401 || error.response?.status === 402) {
49
+ store.dispatch(setLogout());
50
+ }
51
+ return Promise.reject(error);
52
+ },
53
+ );
54
+
26
55
  export const post = ({url, data, config}: MethodData) =>
27
56
  instance.post(url, data, config);
28
57
  export const get = ({url, config}: MethodData) => instance.get(url, config);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * App config – single place for API base URL, env, and feature toggles.
3
+ * Change your app's API and feature toggles here and in .env.
4
+ */
5
+ import Config from 'react-native-config';
6
+
7
+ const config = Config as Record<string, string | undefined>;
8
+ export const API_BASE_URL =
9
+ config.API_BASE_URL || config.API_URL || 'https://api.example.com';
10
+ export const ENV = config.ENV || config.ENVIRONMENT || 'development';
11
+
12
+ /** Optional: enable RTL layout (e.g. for Arabic). */
13
+ export const enableRTL = true;
@@ -1,49 +1,11 @@
1
1
  import React from 'react';
2
2
  import {Action, configureStore, ThunkAction} from '@reduxjs/toolkit';
3
- import {
4
- createTransform,
5
- PersistConfig,
6
- persistReducer,
7
- persistStore,
8
- } from 'redux-persist';
3
+ import {PersistConfig, persistReducer, persistStore} from 'redux-persist';
9
4
  import {rootReducer, RootState} from './rootReducer';
10
5
  import {Provider} from 'react-redux';
11
6
  import {PersistGate} from 'redux-persist/integration/react';
12
7
  import AsyncStorage from '@react-native-async-storage/async-storage';
13
8
  import {createWhitelistFilter} from 'redux-persist-transform-filter';
14
- import {getUniqueId} from 'react-native-device-info';
15
- import CryptoJS from 'crypto-js';
16
-
17
- let deviceId: string;
18
- getUniqueId()
19
- .then(res => {
20
- deviceId = res;
21
- })
22
- .catch(error => {});
23
-
24
- const encrypt = createTransform(
25
- (inboundState, key) => {
26
- if (!inboundState) {
27
- return inboundState;
28
- }
29
-
30
- const cryptedText = CryptoJS.AES.encrypt(
31
- JSON.stringify(inboundState),
32
- deviceId,
33
- );
34
-
35
- return cryptedText?.toString();
36
- },
37
- (outboundState, key) => {
38
- if (!outboundState) {
39
- return outboundState;
40
- }
41
- const bytes = CryptoJS.AES.decrypt(outboundState, deviceId);
42
- const decrypted = bytes.toString(CryptoJS.enc.Utf8);
43
-
44
- return JSON.parse(decrypted);
45
- },
46
- );
47
9
 
48
10
  const persistConfig: PersistConfig<RootState> = {
49
11
  key: 'root',
@@ -51,11 +13,8 @@ const persistConfig: PersistConfig<RootState> = {
51
13
  version: 1,
52
14
  timeout: 1000,
53
15
  transforms: [
54
- createWhitelistFilter('app'),
55
- createWhitelistFilter('user'),
56
- createWhitelistFilter('services'),
57
- createWhitelistFilter('providers'),
58
- createWhitelistFilter('categories'),
16
+ createWhitelistFilter('user', ['accessToken', 'user']),
17
+ createWhitelistFilter('app', ['language', 'isRTL']),
59
18
  ],
60
19
  };
61
20
 
@@ -66,16 +25,7 @@ export const store = configureStore({
66
25
  middleware: getDefaultMiddleware =>
67
26
  getDefaultMiddleware({
68
27
  serializableCheck: false,
69
- immutableCheck: {
70
- warnAfter: 300,
71
- ignoredPaths: [
72
- 'services.inquiredBill',
73
- 'providers.providers',
74
- 'categories.categories',
75
- ],
76
- },
77
28
  }),
78
- devTools: process.env.NODE_ENV !== 'production',
79
29
  });
80
30
 
81
31
  export const persistor = persistStore(store);
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Customize for your brand – this is the only place to change app colors.
3
+ */
1
4
  export enum PrimaryColors {
2
5
  PlatinateBlue_0 = '#E9EDFF',
3
6
  PlatinateBlue_25 = '#D3DAFF',
@@ -1,5 +1,8 @@
1
1
  import {ISize} from './types';
2
2
 
3
+ /**
4
+ * Customize for your brand – this is the only place to change app sizes/spacing.
5
+ */
3
6
  export const CommonSizes = {
4
7
  font: {
5
8
  // Body Text Sizes
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Customize for your brand – this is the only place to change app fonts.
3
+ */
1
4
  export const Fonts = {
2
5
  // Almarai Font Family
3
6
  light: 'Almarai-Light',
@@ -1,9 +1,7 @@
1
1
  /**
2
- * Safely converts any value to a string
3
- * This is especially useful for error messages that might be objects
2
+ * Safely converts any value to a string.
3
+ * Useful for error messages that might be objects.
4
4
  */
5
- import CryptoJS from 'crypto-js';
6
-
7
5
  export const ensureString = (value: any): string => {
8
6
  if (value === null || value === undefined) {
9
7
  return '';
@@ -31,47 +29,6 @@ export const ensureString = (value: any): string => {
31
29
  return String(value);
32
30
  };
33
31
 
34
- export function decryptTripleDES(
35
- encryptedBase64: string,
36
- secretKeyBase64: string,
37
- ) {
38
- try {
39
- // Decode the key from base64
40
- const decodedKey = CryptoJS.enc.Base64.parse(secretKeyBase64);
41
-
42
- // Decode the encrypted value from base64
43
- const encryptedWordArray = CryptoJS.enc.Base64.parse(encryptedBase64);
44
-
45
- // Decrypt using Triple DES
46
- const decrypted = CryptoJS.TripleDES.decrypt(
47
- {ciphertext: encryptedWordArray},
48
- decodedKey,
49
- {
50
- mode: CryptoJS.mode.ECB,
51
- padding: CryptoJS.pad.Pkcs7,
52
- },
53
- );
54
-
55
- let decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
56
-
57
- if (!decryptedText) {
58
- throw new Error('Decryption resulted in empty string');
59
- }
60
-
61
- decryptedText = decryptedText.replace(/(.{4})/g, '$1-');
62
- if (decryptedText.endsWith('-')) {
63
- decryptedText = decryptedText.slice(0, -1);
64
- }
65
-
66
- return decryptedText;
67
- } catch (error) {
68
- throw new Error(`Decryption failed`);
69
- }
70
- }
71
-
72
- // Example usage:
73
- // const decrypted = decryptBase64('encrypted_base64_string', 'your_secret_key');
74
-
75
32
  /**
76
33
  * Converts a camelCase string to PascalCase with specific formatting
77
34
  * Example: billerId -> BillerId
@@ -1,3 +1,7 @@
1
1
  {
2
- "extends": "@react-native/typescript-config/tsconfig.json"
2
+ "extends": "@react-native/typescript-config/tsconfig.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-native",
5
+ "lib": ["ES2015", "ES2017", "ES2020", "ESNext", "DOM"]
6
+ }
3
7
  }
@@ -1,4 +1,6 @@
1
1
  module.exports = {
2
- placeholderName: "reactnativemagic",
3
- templateDir: "./template",
2
+ placeholderName: 'reactnativemagic',
3
+ titlePlaceholder: 'React Native Magic App',
4
+ templateDir: './template',
5
+ postInitScript: 'scripts/askPackageName.js',
4
6
  };