@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.
- package/package.json +1 -1
- package/src/utils/copyTemplate.js +6 -0
- package/templates/mern-react-native/client/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/babel.config.js +1 -0
- package/templates/mern-react-native/mobile/drizzle.config.ts +8 -0
- package/templates/mern-react-native/mobile/expo-env.d.ts +3 -0
- package/templates/mern-react-native/mobile/package.json +6 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/_layout.tsx +12 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/change-password.tsx +112 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/mern-react-native/mobile/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/mern-react-native/mobile/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/mern-react-native/mobile/src/app/(tabs)/index.tsx +18 -17
- package/templates/mern-react-native/mobile/src/app/(tabs)/profile.tsx +23 -60
- package/templates/mern-react-native/mobile/src/app/_layout.tsx +22 -4
- package/templates/mern-react-native/mobile/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/mern-react-native/mobile/src/components/common/LocationLoader.tsx +591 -0
- package/templates/mern-react-native/mobile/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/mern-react-native/mobile/src/components/common/TabHeader.tsx +58 -0
- package/templates/mern-react-native/mobile/src/components/common/Typography.tsx +49 -30
- package/templates/mern-react-native/mobile/src/components/common/Welcome.tsx +2 -2
- package/templates/mern-react-native/mobile/src/components/ui/BottomModal.tsx +31 -20
- package/templates/mern-react-native/mobile/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/mern-react-native/mobile/src/components/ui/Sidebar.tsx +81 -56
- package/templates/mern-react-native/mobile/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/mern-react-native/mobile/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/mern-react-native/mobile/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/mern-react-native/mobile/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/mern-react-native/mobile/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/mern-react-native/mobile/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/mern-react-native/mobile/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/mern-react-native/mobile/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/mern-react-native/mobile/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/mern-react-native/mobile/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/mern-react-native/mobile/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/mern-react-native/mobile/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/mern-react-native/mobile/src/components/ui/layout/Section.tsx +7 -8
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/mern-react-native/mobile/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/mern-react-native/mobile/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/mern-react-native/mobile/src/config/api.ts +12 -1
- package/templates/mern-react-native/mobile/src/config/sidebar.config.ts +72 -0
- package/templates/mern-react-native/mobile/src/config/tabNavigation.ts +37 -7
- package/templates/mern-react-native/mobile/src/db/index.ts +9 -0
- package/templates/mern-react-native/mobile/src/db/schema.ts +14 -0
- package/templates/mern-react-native/mobile/src/hooks/useDebounce.ts +17 -0
- package/templates/mern-react-native/mobile/src/services/socket.service.ts +36 -0
- package/templates/mern-react-native/mobile/src/store/useAuthStore.ts +122 -27
- package/templates/mern-react-native/mobile/src/store/useNotificationStore.ts +53 -0
- package/templates/mern-react-native/mobile/src/types/auth.type.ts +14 -0
- package/templates/mern-react-native/mobile/tsconfig.json +1 -0
- package/templates/mern-ts/client/src/vite-env.d.ts +1 -0
- package/templates/react-native/babel.config.js +1 -0
- package/templates/react-native/drizzle.config.ts +8 -0
- package/templates/react-native/expo-env.d.ts +3 -0
- package/templates/react-native/package.json +6 -0
- package/templates/react-native/src/app/(auth)/_layout.tsx +12 -0
- package/templates/react-native/src/app/(auth)/change-password.tsx +112 -0
- package/templates/react-native/src/app/(auth)/edit-profile.tsx +160 -0
- package/templates/react-native/src/app/(auth)/forgot-password.tsx +191 -0
- package/templates/react-native/src/app/{login.tsx → (auth)/login.tsx} +69 -21
- package/templates/react-native/src/app/(tabs)/index.tsx +18 -17
- package/templates/react-native/src/app/(tabs)/profile.tsx +23 -60
- package/templates/react-native/src/app/_layout.tsx +22 -4
- package/templates/react-native/src/components/common/AnimatedHeader.tsx +328 -0
- package/templates/react-native/src/components/common/LocationLoader.tsx +591 -0
- package/templates/react-native/src/components/common/ScreenHeader.tsx +41 -17
- package/templates/react-native/src/components/common/TabHeader.tsx +58 -0
- package/templates/react-native/src/components/common/Typography.tsx +49 -30
- package/templates/react-native/src/components/common/Welcome.tsx +2 -2
- package/templates/react-native/src/components/ui/BottomModal.tsx +31 -20
- package/templates/react-native/src/components/ui/ClassicTabBar.tsx +4 -3
- package/templates/react-native/src/components/ui/CustomTabBar.tsx +108 -71
- package/templates/react-native/src/components/ui/Sidebar.tsx +81 -56
- package/templates/react-native/src/components/ui/buttons/Button.tsx +106 -37
- package/templates/react-native/src/components/ui/buttons/IconButton.tsx +4 -2
- package/templates/react-native/src/components/ui/data-display/Accordion.tsx +3 -2
- package/templates/react-native/src/components/ui/data-display/Avatar.tsx +4 -3
- package/templates/react-native/src/components/ui/data-display/Badge.tsx +45 -17
- package/templates/react-native/src/components/ui/data-display/ImageViewer.tsx +19 -9
- package/templates/react-native/src/components/ui/feedback/CircularProgress.tsx +5 -4
- package/templates/react-native/src/components/ui/feedback/ProgressBar.tsx +4 -3
- package/templates/react-native/src/components/ui/feedback/Skeleton.tsx +3 -0
- package/templates/react-native/src/components/ui/feedback/Spinner.tsx +11 -3
- package/templates/react-native/src/components/ui/inputs/Checkbox.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/DatePicker.tsx +186 -0
- package/templates/react-native/src/components/ui/inputs/ImagePickerModal.tsx +129 -0
- package/templates/react-native/src/components/ui/inputs/Input.tsx +54 -14
- package/templates/react-native/src/components/ui/inputs/LocationPicker.tsx +211 -0
- package/templates/react-native/src/components/ui/inputs/OTPInput.tsx +32 -9
- package/templates/react-native/src/components/ui/inputs/PasswordInput.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/Radio.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/SearchBar.tsx +41 -4
- package/templates/react-native/src/components/ui/inputs/Slider.tsx +4 -3
- package/templates/react-native/src/components/ui/inputs/Switch.tsx +3 -2
- package/templates/react-native/src/components/ui/inputs/TextArea.tsx +2 -1
- package/templates/react-native/src/components/ui/inputs/WebLocationPicker.tsx +321 -0
- package/templates/react-native/src/components/ui/layout/Grid.tsx +52 -10
- package/templates/react-native/src/components/ui/layout/Section.tsx +7 -8
- package/templates/react-native/src/components/ui/navigation/Pagination.tsx +4 -3
- package/templates/react-native/src/components/ui/navigation/ProgressStepper.tsx +6 -5
- package/templates/react-native/src/components/ui/navigation/Tabs.tsx +30 -4
- package/templates/react-native/src/config/api.ts +12 -1
- package/templates/react-native/src/config/sidebar.config.ts +72 -0
- package/templates/react-native/src/config/tabNavigation.ts +37 -7
- package/templates/react-native/src/db/index.ts +9 -0
- package/templates/react-native/src/db/schema.ts +14 -0
- package/templates/react-native/src/hooks/useDebounce.ts +17 -0
- package/templates/react-native/src/services/socket.service.ts +36 -0
- package/templates/react-native/src/store/useAuthStore.ts +122 -27
- package/templates/react-native/src/store/useNotificationStore.ts +53 -0
- package/templates/react-native/src/types/auth.type.ts +14 -0
- package/templates/react-native/tsconfig.json +1 -0
- package/templates/react-ts/src/vite-env.d.ts +1 -0
- package/templates/mern-react-native/mobile/.env +0 -2
- package/templates/mern-react-native/mobile/.env.example +0 -2
- package/templates/mern-react-native/mobile/.vscode/extensions.json +0 -1
- package/templates/mern-react-native/mobile/.vscode/settings.json +0 -7
- package/templates/mern-react-native/mobile/README.md +0 -159
- package/templates/mern-react-native/mobile/docs/README.md +0 -17
- package/templates/mern-react-native/mobile/docs/components/buttons.md +0 -40
- package/templates/mern-react-native/mobile/docs/components/data-display.md +0 -44
- package/templates/mern-react-native/mobile/docs/components/feedback.md +0 -36
- package/templates/mern-react-native/mobile/docs/components/gestures.md +0 -60
- package/templates/mern-react-native/mobile/docs/components/inputs.md +0 -41
- package/templates/mern-react-native/mobile/docs/components/layout.md +0 -34
- package/templates/mern-react-native/mobile/docs/components/navigation.md +0 -31
- package/templates/mern-react-native/mobile/docs/features.md +0 -85
- package/templates/mern-react-native/mobile/docs/getting-started.md +0 -47
- package/templates/mern-react-native/mobile/src/components/common/HomeHeader.tsx +0 -43
- package/templates/mern-react-native/mobile/src/components/settings/SettingToggle.tsx +0 -26
- package/templates/mern-react-native/mobile/src/components/settings/SettingsOptions.tsx +0 -47
- package/templates/react-native/.env +0 -2
- package/templates/react-native/.env.example +0 -2
- package/templates/react-native/.vscode/extensions.json +0 -1
- package/templates/react-native/.vscode/settings.json +0 -7
- package/templates/react-native/README.md +0 -159
- package/templates/react-native/docs/README.md +0 -17
- package/templates/react-native/docs/components/buttons.md +0 -40
- package/templates/react-native/docs/components/data-display.md +0 -44
- package/templates/react-native/docs/components/feedback.md +0 -36
- package/templates/react-native/docs/components/gestures.md +0 -60
- package/templates/react-native/docs/components/inputs.md +0 -41
- package/templates/react-native/docs/components/layout.md +0 -34
- package/templates/react-native/docs/components/navigation.md +0 -31
- package/templates/react-native/docs/features.md +0 -85
- package/templates/react-native/docs/getting-started.md +0 -47
- package/templates/react-native/src/components/common/HomeHeader.tsx +0 -43
- package/templates/react-native/src/components/settings/SettingToggle.tsx +0 -26
- 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: "
|
|
17
|
-
title: "
|
|
18
|
-
baseIcon: "
|
|
16
|
+
name: "explore",
|
|
17
|
+
title: "Explore",
|
|
18
|
+
baseIcon: "compass",
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
name: "
|
|
22
|
-
title: "
|
|
23
|
-
baseIcon: "
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -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
|
+
}
|