@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
@@ -13,19 +13,49 @@ export const tabNavigationConfig: TabConfig[] = [
13
13
  baseIcon: "home",
14
14
  },
15
15
  {
16
- name: "search",
17
- title: "Search",
18
- baseIcon: "search",
16
+ name: "explore",
17
+ title: "Explore",
18
+ baseIcon: "compass",
19
19
  },
20
20
  {
21
- name: "dashboard",
22
- title: "Dashboard",
23
- baseIcon: "cube",
21
+ name: "payments",
22
+ title: "Payments",
23
+ baseIcon: "wallet",
24
+ },
25
+ {
26
+ name: "notifications",
27
+ title: "Notifications",
28
+ baseIcon: "notifications",
24
29
  },
25
30
  {
26
31
  name: "profile",
27
32
  title: "Profile",
28
- baseIcon: "person",
33
+ baseIcon: "person-circle",
34
+ },
35
+ {
36
+ name: "properties",
37
+ title: "Properties",
38
+ baseIcon: "business",
39
+ },
40
+ {
41
+ name: "tenant",
42
+ title: "Tenant",
43
+ baseIcon: "people",
44
+ },
45
+ {
46
+ name: "users",
47
+ title: "Users",
48
+ baseIcon: "people",
49
+ },
50
+ {
51
+ name: "requests",
52
+ title: "Requests",
53
+ baseIcon: "clipboard",
54
+ },
55
+ {
56
+ name: "more",
57
+ title: "More",
58
+ baseIcon: "ellipsis-horizontal",
29
59
  },
30
60
  ];
31
61
 
@@ -0,0 +1,9 @@
1
+ import { drizzle } from 'drizzle-orm/expo-sqlite';
2
+ import * as SQLite from 'expo-sqlite';
3
+ import * as schema from './schema';
4
+
5
+ // Open the local database. If it doesn't exist, it will be created.
6
+ const expoDb = SQLite.openDatabaseSync('imksh.db');
7
+
8
+ // Pass the database instance to Drizzle
9
+ export const db = drizzle(expoDb, { schema });
@@ -0,0 +1,14 @@
1
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
2
+
3
+ export const users = sqliteTable('users', {
4
+ id: integer('id').primaryKey({ autoIncrement: true }),
5
+ email: text('email').notNull().unique(),
6
+ name: text('name'),
7
+ role: text('role').default('user'),
8
+ createdAt: text('created_at').notNull().default('CURRENT_TIMESTAMP'),
9
+ });
10
+
11
+ export const preferences = sqliteTable('preferences', {
12
+ key: text('key').primaryKey(),
13
+ value: text('value').notNull(),
14
+ });
@@ -0,0 +1,17 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ export function useDebounce<T>(value: T, delay: number): T {
4
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
5
+
6
+ useEffect(() => {
7
+ const handler = setTimeout(() => {
8
+ setDebouncedValue(value);
9
+ }, delay);
10
+
11
+ return () => {
12
+ clearTimeout(handler);
13
+ };
14
+ }, [value, delay]);
15
+
16
+ return debouncedValue;
17
+ }
@@ -0,0 +1,36 @@
1
+ import { io, Socket } from 'socket.io-client';
2
+ import { get } from '../utils/storage';
3
+ import Constants from 'expo-constants';
4
+
5
+ let socket: Socket | null = null;
6
+
7
+ // Set your backend socket URL
8
+ const SOCKET_URL = Constants.expoConfig?.extra?.apiUrl || 'http://localhost:3000';
9
+
10
+ export const initializeSocket = async () => {
11
+ if (socket) return;
12
+ const token = await get('token');
13
+ if (!token) return;
14
+
15
+ socket = io(SOCKET_URL, {
16
+ auth: { token },
17
+ transports: ['websocket'],
18
+ });
19
+
20
+ socket.on('connect', () => {
21
+ console.log('Connected to socket server');
22
+ });
23
+
24
+ socket.on('disconnect', () => {
25
+ console.log('Disconnected from socket server');
26
+ });
27
+ };
28
+
29
+ export const getSocket = () => socket;
30
+
31
+ export const disconnectSocket = () => {
32
+ if (socket) {
33
+ socket.disconnect();
34
+ socket = null;
35
+ }
36
+ };
@@ -1,20 +1,15 @@
1
1
  import { create } from "zustand";
2
2
  import { authService } from "../services/auth.service";
3
- import { save, remove } from "../utils/storage";
4
-
5
- export interface User {
6
- _id: string;
7
- name: string;
8
- email: string;
9
- phone?: string;
10
- role: string;
11
- avatar?: {
12
- url?: string;
13
- publicId?: string;
14
- };
15
- createdAt?: string;
16
- updatedAt?: string;
17
- }
3
+ import { save, remove, get } from "../utils/storage";
4
+ import { User } from "../types/auth.type";
5
+ import { initializeSocket, disconnectSocket } from "../services/socket.service";
6
+ import { useNotificationStore } from "./useNotificationStore";
7
+
8
+ // ── Module-level prefetch ──────────────────────────────────────────────────
9
+ // Kick off the AsyncStorage read the instant this module is imported
10
+ // (before any React component mounts). By the time loadCachedUser() is
11
+ // called from a useEffect, the promise is already resolved or very close.
12
+ const _prefetchedUser: Promise<User | null> = get("user");
18
13
 
19
14
  export interface AuthState {
20
15
  user: User | null;
@@ -34,6 +29,13 @@ export interface AuthState {
34
29
  getMe: () => Promise<void>;
35
30
  updateProfile: (updates: Partial<User> | FormData) => Promise<boolean>;
36
31
  logout: () => Promise<void>;
32
+ genOtp: (email: string) => Promise<boolean>;
33
+ verifyOtp: (email: string, otp: string) => Promise<boolean>;
34
+ resetPassword: (newPassword: string) => Promise<boolean>;
35
+ changePassword: (oldPassword: string, newPassword: string) => Promise<boolean>;
36
+ setUser: (user: User) => void;
37
+ clearError: () => void;
38
+ loadCachedUser: () => Promise<void>;
37
39
  }
38
40
 
39
41
  export const useAuthStore = create<AuthState>((set) => ({
@@ -42,6 +44,27 @@ export const useAuthStore = create<AuthState>((set) => ({
42
44
  isCheckingAuth: true,
43
45
  error: null,
44
46
 
47
+ setUser: (user: User) => set({ user }),
48
+
49
+ clearError: () => set({ error: null }),
50
+
51
+ loadCachedUser: async () => {
52
+ // Await the already-in-flight prefetch — typically resolves in <1ms here
53
+ const cached = await _prefetchedUser;
54
+ if (cached) {
55
+ set({ user: cached, isCheckingAuth: false });
56
+ // Re-establish socket + push on app resume with a cached session
57
+ initializeSocket();
58
+ const notifStore = useNotificationStore.getState();
59
+ notifStore.listenForSocketEvents();
60
+ notifStore.registerForPushNotificationsAsync();
61
+ notifStore.fetchNotifications();
62
+ } else {
63
+ // No cache — mark auth check done so routing doesn't hang
64
+ set({ isCheckingAuth: false });
65
+ }
66
+ },
67
+
45
68
  sendOtp: async (email: string) => {
46
69
  set({ loading: true, error: null });
47
70
  try {
@@ -74,13 +97,21 @@ export const useAuthStore = create<AuthState>((set) => ({
74
97
  otp,
75
98
  role,
76
99
  );
77
- if (data.token) {
78
- save("token", data.token);
100
+ if (data.data?.token) {
101
+ save("token", data.data.token);
79
102
  }
103
+ const user = data.data?.user;
104
+ if (user) save("user", user);
80
105
  set({
81
- user: data.data,
106
+ user,
82
107
  loading: false,
83
108
  });
109
+ // Initialise socket + push after signup
110
+ initializeSocket();
111
+ const notifStore = useNotificationStore.getState();
112
+ notifStore.listenForSocketEvents();
113
+ notifStore.registerForPushNotificationsAsync();
114
+ notifStore.fetchNotifications();
84
115
  return true;
85
116
  } catch (err: any) {
86
117
  set({
@@ -97,13 +128,21 @@ export const useAuthStore = create<AuthState>((set) => ({
97
128
  set({ loading: true, error: null });
98
129
  try {
99
130
  const { data } = await authService.login(email, password);
100
- if (data.token) {
101
- save("token", data.token);
131
+ if (data.data?.token) {
132
+ save("token", data.data.token);
102
133
  }
134
+ const user = data.data?.user;
135
+ if (user) save("user", user);
103
136
  set({
104
- user: data.data,
137
+ user,
105
138
  loading: false,
106
139
  });
140
+ // Initialise socket + push after login
141
+ initializeSocket();
142
+ const notifStore = useNotificationStore.getState();
143
+ notifStore.listenForSocketEvents();
144
+ notifStore.registerForPushNotificationsAsync();
145
+ notifStore.fetchNotifications();
107
146
  return true;
108
147
  } catch (err: any) {
109
148
  set({
@@ -123,7 +162,7 @@ export const useAuthStore = create<AuthState>((set) => ({
123
162
  save("token", "demo-token-123");
124
163
  set({
125
164
  user: {
126
- _id: "demo_user_123",
165
+ id: "demo_user_123",
127
166
  name: "Demo User",
128
167
  email: "demo@imksh.com",
129
168
  role: "admin",
@@ -133,12 +172,13 @@ export const useAuthStore = create<AuthState>((set) => ({
133
172
  },
134
173
 
135
174
  getMe: async () => {
136
- set({ isCheckingAuth: true });
137
175
  try {
138
176
  const { data } = await authService.getMe();
139
- set({ user: data.data, isCheckingAuth: false });
140
- } catch {
141
- set({ isCheckingAuth: false });
177
+ const user = data.data;
178
+ // Always update cache with fresh server data
179
+ if (user) save("user", user);
180
+ set({ user, isCheckingAuth: false });
181
+ // Network failed — cached user (already loaded) stays in place
142
182
  } finally {
143
183
  set({ isCheckingAuth: false });
144
184
  }
@@ -167,7 +207,9 @@ export const useAuthStore = create<AuthState>((set) => ({
167
207
  } catch {
168
208
  // ignore errors — cookie may already be expired
169
209
  } finally {
170
- remove("token");
210
+ await remove("token");
211
+ await remove("user"); // clear cached user on logout
212
+ disconnectSocket();
171
213
  set({ loading: false });
172
214
  }
173
215
  set({
@@ -175,4 +217,57 @@ export const useAuthStore = create<AuthState>((set) => ({
175
217
  error: null,
176
218
  });
177
219
  },
220
+
221
+ genOtp: async (email: string) => {
222
+ set({ loading: true, error: null });
223
+ try {
224
+ await authService.genOtp(email);
225
+ return true;
226
+ } catch (err: any) {
227
+ set({ error: err.response?.data?.message || err.message, loading: false });
228
+ return false;
229
+ } finally {
230
+ set({ loading: false });
231
+ }
232
+ },
233
+
234
+ verifyOtp: async (email: string, otp: string) => {
235
+ set({ loading: true, error: null });
236
+ try {
237
+ const { data } = await authService.verifyOtp(email, otp);
238
+ if (data.token) save("token", data.token);
239
+ return true;
240
+ } catch (err: any) {
241
+ set({ error: err.response?.data?.message || err.message, loading: false });
242
+ return false;
243
+ } finally {
244
+ set({ loading: false });
245
+ }
246
+ },
247
+
248
+ resetPassword: async (newPassword: string) => {
249
+ set({ loading: true, error: null });
250
+ try {
251
+ await authService.resetPassword(newPassword);
252
+ return true;
253
+ } catch (err: any) {
254
+ set({ error: err.response?.data?.message || err.message, loading: false });
255
+ return false;
256
+ } finally {
257
+ set({ loading: false });
258
+ }
259
+ },
260
+
261
+ changePassword: async (oldPassword: string, newPassword: string) => {
262
+ set({ loading: true, error: null });
263
+ try {
264
+ await authService.changePassword(oldPassword, newPassword);
265
+ return true;
266
+ } catch (err: any) {
267
+ set({ error: err.response?.data?.message || err.message, loading: false });
268
+ return false;
269
+ } finally {
270
+ set({ loading: false });
271
+ }
272
+ },
178
273
  }));
@@ -0,0 +1,53 @@
1
+ import { create } from 'zustand';
2
+ import { getSocket } from '../services/socket.service';
3
+ import * as Notifications from 'expo-notifications';
4
+
5
+ export interface NotificationState {
6
+ notifications: any[];
7
+ pushToken: string | null;
8
+ listenForSocketEvents: () => void;
9
+ registerForPushNotificationsAsync: () => Promise<void>;
10
+ fetchNotifications: () => Promise<void>;
11
+ }
12
+
13
+ export const useNotificationStore = create<NotificationState>((set, get) => ({
14
+ notifications: [],
15
+ pushToken: null,
16
+
17
+ listenForSocketEvents: () => {
18
+ const socket = getSocket();
19
+ if (!socket) return;
20
+
21
+ socket.on('new_notification', (data) => {
22
+ set((state) => ({ notifications: [data, ...state.notifications] }));
23
+ });
24
+ },
25
+
26
+ registerForPushNotificationsAsync: async () => {
27
+ try {
28
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
29
+ let finalStatus = existingStatus;
30
+ if (existingStatus !== 'granted') {
31
+ const { status } = await Notifications.requestPermissionsAsync();
32
+ finalStatus = status;
33
+ }
34
+ if (finalStatus !== 'granted') {
35
+ console.log('Failed to get push token for push notification!');
36
+ return;
37
+ }
38
+ // Note: projectId is required for Expo Go and EAS.
39
+ const token = (await Notifications.getExpoPushTokenAsync()).data;
40
+ set({ pushToken: token });
41
+
42
+ // Here you would typically send the pushToken to your backend
43
+ } catch (e) {
44
+ console.warn("Failed to register for push notifications", e);
45
+ }
46
+ },
47
+
48
+ fetchNotifications: async () => {
49
+ // API call to fetch notifications could go here
50
+ // e.g., const data = await api.get('/notifications');
51
+ set({ notifications: [] });
52
+ }
53
+ }));
@@ -0,0 +1,14 @@
1
+ export interface User {
2
+ id: string; // Updated from _id for Prisma consistency
3
+ _id?: string; // Fallback for mongo legacy if any
4
+ name: string;
5
+ email: string;
6
+ phone?: string;
7
+ role: string;
8
+ avatar?: {
9
+ url?: string;
10
+ publicId?: string;
11
+ };
12
+ createdAt?: string;
13
+ updatedAt?: string;
14
+ }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "extends": "expo/tsconfig.base",
3
3
  "compilerOptions": {
4
+ "jsx": "react-jsx",
4
5
  "strict": true,
5
6
  "noImplicitAny": false,
6
7
  "paths": {
@@ -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
+ }