@imksh/create 1.0.23 → 1.0.25

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 (163) hide show
  1. package/package.json +1 -1
  2. package/src/utils/copyTemplate.js +6 -0
  3. package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
  4. package/templates/mern-react-native/mobile/babel.config.js +1 -0
  5. package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
  6. package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
  7. package/templates/mern-react-native/mobile/package.json +6 -0
  8. package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
  9. package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
  10. package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +160 -0
  11. package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
  12. package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  13. package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
  14. package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
  15. package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
  16. package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
  17. package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
  18. package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
  19. package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
  20. package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
  21. package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
  22. package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
  23. package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
  24. package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
  25. package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
  26. package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
  27. package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
  28. package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
  29. package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
  30. package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
  31. package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
  32. package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
  33. package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
  34. package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
  35. package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
  36. package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
  37. package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
  38. package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  39. package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
  40. package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
  41. package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
  42. package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
  43. package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
  44. package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
  45. package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
  46. package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
  47. package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
  48. package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  49. package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
  50. package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
  51. package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
  52. package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  53. package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
  54. package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
  55. package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +72 -0
  56. package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +37 -7
  57. package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
  58. package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
  59. package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
  60. package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
  61. package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
  62. package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +53 -0
  63. package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
  64. package/templates/mern-react-native/mobile/tsconfig.json +1 -0
  65. package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
  66. package/templates/react-native/babel.config.js +1 -0
  67. package/templates/react-native/drizzle.config.ts +8 -0
  68. package/templates/react-native/expo-env.d.ts +3 -0
  69. package/templates/react-native/package.json +6 -0
  70. package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
  71. package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
  72. package/templates/react-native/src/app/(auth)/edit-profile.tsx +160 -0
  73. package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
  74. package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
  75. package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
  76. package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
  77. package/templates/react-native/src/app/_layout.tsx +22 -4
  78. package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
  79. package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
  80. package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
  81. package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
  82. package/templates/react-native/src/components/common/Typography.tsx +49 -30
  83. package/templates/react-native/src/components/common/Welcome.tsx +2 -2
  84. package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
  85. package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
  86. package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
  87. package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
  88. package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
  89. package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
  90. package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
  91. package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
  92. package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
  93. package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
  94. package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
  95. package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
  96. package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
  97. package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
  98. package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
  99. package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
  100. package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
  101. package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
  102. package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
  103. package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
  104. package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
  105. package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
  106. package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
  107. package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
  108. package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
  109. package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
  110. package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
  111. package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
  112. package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
  113. package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
  114. package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
  115. package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
  116. package/templates/react-native/src/config/api.ts +12 -1
  117. package/templates/react-native/src/config/sidebar.config.ts +72 -0
  118. package/templates/react-native/src/config/tabNavigation.ts +37 -7
  119. package/templates/react-native/src/db/index.ts +9 -0
  120. package/templates/react-native/src/db/schema.ts +14 -0
  121. package/templates/react-native/src/hooks/useDebounce.ts +17 -0
  122. package/templates/react-native/src/services/socket.service.ts +36 -0
  123. package/templates/react-native/src/store/useAuthStore.ts +122 -27
  124. package/templates/react-native/src/store/useNotificationStore.ts +53 -0
  125. package/templates/react-native/src/types/auth.type.ts +14 -0
  126. package/templates/react-native/tsconfig.json +1 -0
  127. package/templates/react-ts/src/vite-env.d.ts +1 -0
  128. package/templates/mern-react-native/mobile/.env +0 -2
  129. package/templates/mern-react-native/mobile/.env.example +0 -2
  130. package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
  131. package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
  132. package/templates/mern-react-native/mobile/README.md +0 -159
  133. package/templates/mern-react-native/mobile/docs/README.md +0 -17
  134. package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
  135. package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
  136. package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
  137. package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
  138. package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
  139. package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
  140. package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
  141. package/templates/mern-react-native/mobile/docs/features.md +0 -85
  142. package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
  143. package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
  144. package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
  145. package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
  146. package/templates/react-native/.env +0 -2
  147. package/templates/react-native/.env.example +0 -2
  148. package/templates/react-native/.vscode/extensions.json +0 -1
  149. package/templates/react-native/.vscode/settings.json +0 -7
  150. package/templates/react-native/README.md +0 -159
  151. package/templates/react-native/docs/README.md +0 -17
  152. package/templates/react-native/docs/components/buttons.md +0 -40
  153. package/templates/react-native/docs/components/data-display.md +0 -44
  154. package/templates/react-native/docs/components/feedback.md +0 -36
  155. package/templates/react-native/docs/components/gestures.md +0 -60
  156. package/templates/react-native/docs/components/inputs.md +0 -41
  157. package/templates/react-native/docs/components/layout.md +0 -34
  158. package/templates/react-native/docs/components/navigation.md +0 -31
  159. package/templates/react-native/docs/features.md +0 -85
  160. package/templates/react-native/docs/getting-started.md +0 -47
  161. package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
  162. package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
  163. package/templates/react-native/src/components/settings/SettingsOptions.tsx +0 -47
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imksh/create",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "A production-ready create for scaffolding modern applications.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,4 +33,10 @@ export async function copyTemplate(templateName, targetDir, projectName) {
33
33
  if (fs.existsSync(gitignorePath)) {
34
34
  await fs.rename(gitignorePath, path.join(targetDir, '.gitignore'));
35
35
  }
36
+
37
+ // Copy .env.example to .env if it exists
38
+ const envExamplePath = path.join(targetDir, '.env.example');
39
+ if (fs.existsSync(envExamplePath)) {
40
+ await fs.copy(envExamplePath, path.join(targetDir, '.env'));
41
+ }
36
42
  }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -6,6 +6,7 @@ module.exports = function (api) {
6
6
  "nativewind/babel",
7
7
  ],
8
8
  plugins: [
9
+ ["inline-import", { "extensions": [".sql"] }],
9
10
  "react-native-reanimated/plugin"
10
11
  ],
11
12
  };
@@ -0,0 +1,8 @@
1
+ import type { Config } from 'drizzle-kit';
2
+
3
+ export default {
4
+ schema: './src/db/schema.ts',
5
+ out: './drizzle',
6
+ dialect: 'sqlite',
7
+ driver: 'expo',
8
+ } satisfies Config;
@@ -0,0 +1,3 @@
1
+ /// <reference types="expo/types" />
2
+
3
+ // NOTE: This file should not be edited and should be in your git ignore
@@ -11,6 +11,7 @@
11
11
  "@react-navigation/bottom-tabs": "^7.18.10",
12
12
  "axios": "^1.18.1",
13
13
  "babel-preset-expo": "^57.0.3",
14
+ "drizzle-orm": "^0.45.2",
14
15
  "expo": "~57.0.7",
15
16
  "expo-blur": "~57.0.2",
16
17
  "expo-constants": "~57.0.6",
@@ -18,10 +19,12 @@
18
19
  "expo-font": "~57.0.1",
19
20
  "expo-glass-effect": "~57.0.1",
20
21
  "expo-image": "~57.0.1",
22
+ "expo-linear-gradient": "^57.0.1",
21
23
  "expo-linking": "~57.0.3",
22
24
  "expo-notifications": "~57.0.6",
23
25
  "expo-router": "~57.0.7",
24
26
  "expo-splash-screen": "~57.0.4",
27
+ "expo-sqlite": "^57.0.1",
25
28
  "expo-status-bar": "~57.0.1",
26
29
  "expo-symbols": "~57.0.1",
27
30
  "expo-system-ui": "~57.0.1",
@@ -45,11 +48,14 @@
45
48
  "react-native-toast-message": "^2.4.0",
46
49
  "react-native-web": "~0.21.0",
47
50
  "react-native-worklets": "0.10.0",
51
+ "socket.io-client": "^4.8.3",
48
52
  "tailwindcss": "^3.4.19",
49
53
  "zustand": "^5.0.14"
50
54
  },
51
55
  "devDependencies": {
52
56
  "@types/react": "~19.2.2",
57
+ "babel-plugin-inline-import": "^3.0.0",
58
+ "drizzle-kit": "^0.31.10",
53
59
  "eslint": "^9.0.0",
54
60
  "eslint-config-expo": "~57.0.0",
55
61
  "typescript": "~6.0.3"
@@ -0,0 +1,12 @@
1
+ import { Stack } from 'expo-router';
2
+
3
+ export default function AuthLayout() {
4
+ return (
5
+ <Stack screenOptions={{ headerShown: false }}>
6
+ <Stack.Screen name="login" />
7
+ <Stack.Screen name="forgot-password" />
8
+ <Stack.Screen name="edit-profile" options={{ presentation: 'modal' }} />
9
+ <Stack.Screen name="change-password" options={{ presentation: 'modal' }} />
10
+ </Stack>
11
+ );
12
+ }
@@ -0,0 +1,112 @@
1
+ import React, { useState , useCallback } from "react";
2
+ import { View } from "react-native";
3
+ import { useAuthStore } from "../../store/useAuthStore";
4
+ import { Txt } from "../../components/common/Typography";
5
+ import { router, useFocusEffect } from "expo-router";
6
+ import ScrollContainer from "../../components/ui/layout/ScrollContainer";
7
+ import Input from "../../components/ui/inputs/Input";
8
+ import Button from "../../components/ui/buttons/Button";
9
+ import ScreenHeader from "@/components/common/ScreenHeader";
10
+
11
+ export default function ChangePasswordScreen() {
12
+ const { changePassword, loading, error, clearError } = useAuthStore();
13
+
14
+ useFocusEffect(
15
+ useCallback(() => {
16
+ return () => clearError();
17
+ }, [clearError])
18
+ );
19
+
20
+ const [oldPassword, setOldPassword] = useState("");
21
+ const [newPassword, setNewPassword] = useState("");
22
+ const [confirmPassword, setConfirmPassword] = useState("");
23
+
24
+ const [showOldPassword, setShowOldPassword] = useState(false);
25
+ const [showNewPassword, setShowNewPassword] = useState(false);
26
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
27
+
28
+ const [validationError, setValidationError] = useState("");
29
+
30
+ const handleSave = async () => {
31
+ setValidationError("");
32
+ if (!oldPassword || !newPassword || !confirmPassword) {
33
+ setValidationError("Please fill all fields");
34
+ return;
35
+ }
36
+
37
+ if (newPassword !== confirmPassword) {
38
+ setValidationError("New passwords do not match");
39
+ return;
40
+ }
41
+
42
+ const success = await changePassword(oldPassword, newPassword);
43
+ if (success) {
44
+ router.back();
45
+ }
46
+ };
47
+
48
+ return (
49
+ <>
50
+ <ScreenHeader title="Change Password" />
51
+ <ScrollContainer
52
+ contentContainerStyle={{
53
+ flexGrow: 1,
54
+ padding: 16,
55
+ }}
56
+ className="bg-base-100"
57
+ >
58
+ {error || validationError ? (
59
+ <View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
60
+ <Txt variant="md" className="text-error text-xs text-center">
61
+ ⚠️ {validationError || error}
62
+ </Txt>
63
+ </View>
64
+ ) : null}
65
+
66
+ <View className="gap-4">
67
+ <Input
68
+ leftIcon="lock-closed"
69
+ rightIcon={showOldPassword ? "eye-off" : "eye"}
70
+ onRightIconPress={() => setShowOldPassword(!showOldPassword)}
71
+ placeholder="Current Password"
72
+ value={oldPassword}
73
+ onChangeText={setOldPassword}
74
+ secureTextEntry={!showOldPassword}
75
+ />
76
+
77
+ <Input
78
+ leftIcon="key"
79
+ rightIcon={showNewPassword ? "eye-off" : "eye"}
80
+ onRightIconPress={() => setShowNewPassword(!showNewPassword)}
81
+ placeholder="New Password"
82
+ value={newPassword}
83
+ onChangeText={setNewPassword}
84
+ secureTextEntry={!showNewPassword}
85
+ />
86
+
87
+ <Input
88
+ leftIcon="checkmark-circle"
89
+ rightIcon={showConfirmPassword ? "eye-off" : "eye"}
90
+ onRightIconPress={() =>
91
+ setShowConfirmPassword(!showConfirmPassword)
92
+ }
93
+ placeholder="Confirm New Password"
94
+ value={confirmPassword}
95
+ onChangeText={setConfirmPassword}
96
+ secureTextEntry={!showConfirmPassword}
97
+ />
98
+
99
+ <View className="mt-4">
100
+ <Button
101
+ label="Update Password"
102
+ onPress={handleSave}
103
+ isLoading={loading}
104
+ variant="primary"
105
+ isFullWidth
106
+ />
107
+ </View>
108
+ </View>
109
+ </ScrollContainer>
110
+ </>
111
+ );
112
+ }
@@ -0,0 +1,160 @@
1
+ import React, { useState , useCallback } from "react";
2
+ import { View, TouchableOpacity } from "react-native";
3
+ import { useAuthStore } from "../../store/useAuthStore";
4
+ import { Txt } from "../../components/common/Typography";
5
+ import { router, useFocusEffect } from "expo-router";
6
+ import * as ImagePicker from "expo-image-picker";
7
+ import { Ionicons } from "@expo/vector-icons";
8
+ import Avatar from "../../components/ui/data-display/Avatar";
9
+
10
+ import ScrollContainer from "../../components/ui/layout/ScrollContainer";
11
+ import Input from "../../components/ui/inputs/Input";
12
+ import Button from "../../components/ui/buttons/Button";
13
+ import ScreenHeader from "@/components/common/ScreenHeader";
14
+ import { toast } from "@/utils/toast";
15
+ import { authService } from "@/services/auth.service";
16
+
17
+ export default function EditProfileScreen() {
18
+ const { user, error, setUser, clearError } = useAuthStore();
19
+ const [loading, setLoading] = useState(false);
20
+
21
+ useFocusEffect(
22
+ useCallback(() => {
23
+ return () => clearError();
24
+ }, [clearError])
25
+ );
26
+
27
+ const [name, setName] = useState(user?.name || "");
28
+ const [phone, setPhone] = useState(user?.phone || "");
29
+ const [avatarUri, setAvatarUri] = useState<string | null>(null);
30
+
31
+ const pickImage = async () => {
32
+ const result = await ImagePicker.launchImageLibraryAsync({
33
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
34
+ allowsEditing: true,
35
+ aspect: [1, 1],
36
+ quality: 0.8,
37
+ });
38
+
39
+ if (!result.canceled && result.assets[0]) {
40
+ setAvatarUri(result.assets[0].uri);
41
+ }
42
+ };
43
+
44
+ const handleSave = async () => {
45
+ const formData = new FormData();
46
+ try {
47
+ setLoading(true);
48
+ if (name !== user?.name) formData.append("name", name);
49
+ if (phone !== user?.phone) formData.append("phone", phone);
50
+
51
+ if (avatarUri) {
52
+ const filename = avatarUri.split("/").pop() || "avatar.jpg";
53
+ const match = /\.(\w+)$/.exec(filename);
54
+ const type = match ? `image/${match[1]}` : `image`;
55
+
56
+ formData.append("file", {
57
+ uri: avatarUri,
58
+ name: filename,
59
+ type,
60
+ } as any);
61
+ }
62
+
63
+ // Only update if something changed
64
+ if (
65
+ (formData as any).getParts &&
66
+ (formData as any).getParts().length === 0
67
+ ) {
68
+ router.back();
69
+ return;
70
+ }
71
+
72
+ const res = await authService.updateMe(formData);
73
+ if (res.data.success) {
74
+ setUser(res.data.data);
75
+ toast.success("Profile updated successfully");
76
+ router.back();
77
+ } else {
78
+ toast.error(res.data.message);
79
+ }
80
+
81
+ console.log(res.data.data);
82
+ } catch (error: any) {
83
+ toast.error(error.response?.data?.message || error.message);
84
+ } finally {
85
+ setLoading(false);
86
+ }
87
+ };
88
+
89
+ return (
90
+ <>
91
+ <ScreenHeader title="Edit Profile" />
92
+ <ScrollContainer
93
+ contentContainerStyle={{
94
+ flexGrow: 1,
95
+ padding: 16,
96
+ }}
97
+ className="bg-base-100"
98
+ >
99
+ <View className="items-center mb-8">
100
+ <View className="relative">
101
+ <View className="w-28 h-28 rounded-full items-center justify-center border-4 border-base-100 shadow-sm shadow-base-content/20 overflow-hidden">
102
+ {avatarUri ? (
103
+ <Avatar url={avatarUri} size={104} />
104
+ ) : (
105
+ <Avatar
106
+ url={
107
+ user?.avatar?.url ||
108
+ `https://placehold.co/600x400?text=${user?.name?.charAt(0).toUpperCase() || "U"}`
109
+ }
110
+ size={104}
111
+ />
112
+ )}
113
+ </View>
114
+ <TouchableOpacity
115
+ onPress={pickImage}
116
+ className="absolute bottom-0 right-0 bg-primary p-2 rounded-full border-2 border-base-100"
117
+ >
118
+ <Ionicons name="camera" size={18} color="#fff" />
119
+ </TouchableOpacity>
120
+ </View>
121
+ </View>
122
+
123
+ {error ? (
124
+ <View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
125
+ <Txt variant="md" className="text-error text-xs text-center">
126
+ ⚠️ {error}
127
+ </Txt>
128
+ </View>
129
+ ) : null}
130
+
131
+ <View className="gap-4">
132
+ <Input
133
+ leftIcon="person"
134
+ placeholder="Full name"
135
+ value={name}
136
+ onChangeText={setName}
137
+ />
138
+
139
+ <Input
140
+ leftIcon="call"
141
+ placeholder="Phone number"
142
+ value={phone}
143
+ onChangeText={setPhone}
144
+ keyboardType="phone-pad"
145
+ />
146
+
147
+ <View className="mt-4">
148
+ <Button
149
+ label="Save Changes"
150
+ onPress={handleSave}
151
+ isLoading={loading}
152
+ variant="primary"
153
+ isFullWidth
154
+ />
155
+ </View>
156
+ </View>
157
+ </ScrollContainer>
158
+ </>
159
+ );
160
+ }
@@ -0,0 +1,191 @@
1
+ import React, { useState, useEffect , useCallback } from "react";
2
+ import { View } from "react-native";
3
+ import { useAuthStore } from "../../store/useAuthStore";
4
+ import { Txt } from "../../components/common/Typography";
5
+ import { router, useFocusEffect } from "expo-router";
6
+ import ScrollContainer from "../../components/ui/layout/ScrollContainer";
7
+ import Input from "../../components/ui/inputs/Input";
8
+ import Button from "../../components/ui/buttons/Button";
9
+
10
+ export default function ForgotPasswordScreen() {
11
+ const { genOtp, verifyOtp, resetPassword, loading, error, clearError } = useAuthStore();
12
+ const [step, setStep] = useState<"email" | "otp" | "password">("email");
13
+
14
+ useFocusEffect(
15
+ useCallback(() => {
16
+ return () => clearError();
17
+ }, [clearError])
18
+ );
19
+
20
+ const [email, setEmail] = useState("");
21
+ const [otp, setOtp] = useState("");
22
+ const [newPassword, setNewPassword] = useState("");
23
+ const [showPassword, setShowPassword] = useState(false);
24
+
25
+ const [timer, setTimer] = useState(0);
26
+
27
+ useEffect(() => {
28
+ let interval: ReturnType<typeof setInterval>;
29
+ if (timer > 0) {
30
+ interval = setInterval(() => {
31
+ setTimer((prev) => prev - 1);
32
+ }, 1000);
33
+ }
34
+ return () => clearInterval(interval);
35
+ }, [timer]);
36
+
37
+ const handleSendOtp = async () => {
38
+ if (!email) return;
39
+ const success = await genOtp(email);
40
+ if (success) {
41
+ setStep("otp");
42
+ setTimer(60);
43
+ }
44
+ };
45
+
46
+ const handleVerifyOtp = async () => {
47
+ if (!otp) return;
48
+ const success = await verifyOtp(email, otp);
49
+ if (success) {
50
+ setStep("password");
51
+ }
52
+ };
53
+
54
+ const handleResetPassword = async () => {
55
+ if (!newPassword) return;
56
+ const success = await resetPassword(newPassword);
57
+ if (success) {
58
+ router.replace("/(auth)/login");
59
+ }
60
+ };
61
+
62
+ return (
63
+ <ScrollContainer
64
+ contentContainerStyle={{
65
+ flexGrow: 1,
66
+ justifyContent: "center",
67
+ padding: 16,
68
+ }}
69
+ className="bg-base-100"
70
+ >
71
+ <View className="w-full p-6">
72
+ <View className="items-center mb-8 w-full">
73
+ <Txt variant="xl" className="text-2xl mb-1 text-center">
74
+ {step === "email" && "Forgot Password"}
75
+ {step === "otp" && "Verify Email"}
76
+ {step === "password" && "Reset Password"}
77
+ </Txt>
78
+ <Txt
79
+ variant="sm"
80
+ className="text-xs text-base-content/50 text-center px-2"
81
+ >
82
+ {step === "email" && "Enter your email to receive an OTP"}
83
+ {step === "otp" && `Enter the OTP sent to ${email}`}
84
+ {step === "password" && "Enter your new password"}
85
+ </Txt>
86
+ </View>
87
+
88
+ {error ? (
89
+ <View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
90
+ <Txt variant="md" className="text-error text-xs text-center">
91
+ ⚠️ {error}
92
+ </Txt>
93
+ </View>
94
+ ) : null}
95
+
96
+ <View className="gap-2">
97
+ {step === "email" && (
98
+ <>
99
+ <Input
100
+ leftIcon="mail"
101
+ placeholder="Email address"
102
+ value={email}
103
+ onChangeText={setEmail}
104
+ autoCapitalize="none"
105
+ keyboardType="email-address"
106
+ />
107
+ <View className="mt-2">
108
+ <Button
109
+ label="Send OTP →"
110
+ onPress={handleSendOtp}
111
+ isLoading={loading}
112
+ variant="primary"
113
+ isFullWidth
114
+ />
115
+ </View>
116
+ </>
117
+ )}
118
+
119
+ {step === "otp" && (
120
+ <>
121
+ <Input
122
+ leftIcon="key"
123
+ placeholder="Enter OTP"
124
+ value={otp}
125
+ onChangeText={setOtp}
126
+ keyboardType="numeric"
127
+ />
128
+ <View className="mt-2">
129
+ <Button
130
+ label="Verify OTP →"
131
+ onPress={handleVerifyOtp}
132
+ isLoading={loading}
133
+ variant="primary"
134
+ isFullWidth
135
+ />
136
+ </View>
137
+ <View className="mt-4">
138
+ <Button
139
+ label={timer > 0 ? `Resend OTP in ${timer}s` : "Resend OTP"}
140
+ onPress={handleSendOtp}
141
+ disabled={timer > 0 || loading}
142
+ variant="outline"
143
+ isFullWidth
144
+ />
145
+ </View>
146
+ </>
147
+ )}
148
+
149
+ {step === "password" && (
150
+ <>
151
+ <Input
152
+ leftIcon="lock-closed"
153
+ rightIcon={showPassword ? "eye-off" : "eye"}
154
+ onRightIconPress={() => setShowPassword(!showPassword)}
155
+ placeholder="New Password"
156
+ value={newPassword}
157
+ onChangeText={setNewPassword}
158
+ secureTextEntry={!showPassword}
159
+ />
160
+ <View className="mt-2">
161
+ <Button
162
+ label="Reset Password →"
163
+ onPress={handleResetPassword}
164
+ isLoading={loading}
165
+ variant="primary"
166
+ isFullWidth
167
+ />
168
+ </View>
169
+ </>
170
+ )}
171
+ </View>
172
+
173
+ <View className="mt-8">
174
+ <Txt
175
+ variant="sm"
176
+ className="text-sm text-base-content/50 text-center"
177
+ >
178
+ Remember your password?{" "}
179
+ <Txt
180
+ variant="md"
181
+ className="text-primary text-sm"
182
+ onPress={() => router.push("/(auth)/login")}
183
+ >
184
+ Sign in
185
+ </Txt>
186
+ </Txt>
187
+ </View>
188
+ </View>
189
+ </ScrollContainer>
190
+ );
191
+ }