@imksh/create 1.0.24 → 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/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.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.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" />
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "recommendations": ["expo.vscode-expo-tools"] }
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# 📱 Mobile App (React Native + Expo)
|
|
2
|
-
|
|
3
|
-
This is the mobile application of the starter template, built entirely with **React Native** and **Expo**. It is designed to be a highly scalable boilerplate featuring a modern tech stack, a unified permissions system, and a robust custom UI component library.
|
|
4
|
-
|
|
5
|
-
## ✨ Key Features
|
|
6
|
-
|
|
7
|
-
- **Expo Router**: File-based routing that makes navigation predictable and deeply linkable.
|
|
8
|
-
- **NativeWind (Tailwind CSS)**: Styling is powered by Tailwind CSS classes, complete with dynamic Light/Dark mode support.
|
|
9
|
-
- **Custom UI Library**: Beautiful, highly-reusable components built from scratch (e.g., `Tabs`, `Button`, `Image`, `ScrollContainer`, `Input`) designed to work perfectly across both themes.
|
|
10
|
-
- **Zustand State Management**: Lightweight and fast global state management, used to handle the user's auth session securely.
|
|
11
|
-
- **Unified Permission Service**: A centralized handler for iOS and Android hardware permissions using `react-native-permissions`.
|
|
12
|
-
- **Push Notification Pipeline**: Built-in support for generating Expo Push Tokens on physical devices and syncing them to your backend.
|
|
13
|
-
|
|
14
|
-
## 🧩 Included UI Components
|
|
15
|
-
|
|
16
|
-
The starter kit comes with a massive, completely customizable, and gesture-driven UI library built from scratch out of atomic elements.
|
|
17
|
-
|
|
18
|
-
- **Layout**: `Container`, `Section`, `ScrollContainer`, `HStack`, `VStack`
|
|
19
|
-
- **Buttons**: `Button` (with variants, sizes, and soft props), `IconButton`, `FloatingActionButton`
|
|
20
|
-
- **Data Display**: `Avatar`, `Badge`, `Accordion`, `SwipeableRow`, `Card`
|
|
21
|
-
- **Inputs**: `Input`, `Checkbox`, `Radio`, `Switch`, `Slider`, `SearchBar`
|
|
22
|
-
- **Feedback & Loading**: `Spinner`, `ProgressBar`, `CircularProgress`, `Skeleton`, `GestureWrapper`
|
|
23
|
-
- **Navigation**: `Tabs` (Underline & Button variants)
|
|
24
|
-
- **Overlay & Modals**: `BottomModal` (Gesture-driven bottom sheet)
|
|
25
|
-
|
|
26
|
-
## 🔌 Included Services
|
|
27
|
-
|
|
28
|
-
Pre-configured service wrappers allow you to focus on logic rather than boilerplate:
|
|
29
|
-
|
|
30
|
-
- **`auth.service.ts`**: Prebuilt Axios integrations for `sendOtp`, `signup`, `login`, `updateMe`, `logout`, and `savePushToken`.
|
|
31
|
-
- **`permission.service.ts`**: A unified permissions handler exposing `requirePermission`, `openSettingsForApp`, and `registerForPushNotificationsAsync`.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 🛠 Tech Stack
|
|
36
|
-
|
|
37
|
-
- **Framework**: Expo / React Native
|
|
38
|
-
- **Styling**: NativeWind (Tailwind v3) & `react-native-reanimated`
|
|
39
|
-
- **Routing**: `expo-router`
|
|
40
|
-
- **State**: `zustand`
|
|
41
|
-
- **Storage**: `@react-native-async-storage/async-storage`
|
|
42
|
-
- **API**: `axios`
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 🚀 Getting Started
|
|
47
|
-
|
|
48
|
-
### Prerequisites
|
|
49
|
-
|
|
50
|
-
Make sure you have the following installed on your machine:
|
|
51
|
-
|
|
52
|
-
- Node.js (v18+)
|
|
53
|
-
- [EAS CLI](https://docs.expo.dev/build/setup/) (`npm install -g eas-cli`)
|
|
54
|
-
- iOS Simulator (macOS only) or Android Emulator
|
|
55
|
-
|
|
56
|
-
### Installation
|
|
57
|
-
|
|
58
|
-
1. Navigate to the `mobile` directory:
|
|
59
|
-
```bash
|
|
60
|
-
cd mobile
|
|
61
|
-
```
|
|
62
|
-
2. Install dependencies:
|
|
63
|
-
```bash
|
|
64
|
-
npm install
|
|
65
|
-
```
|
|
66
|
-
3. Start the development server:
|
|
67
|
-
```bash
|
|
68
|
-
npm run dev
|
|
69
|
-
```
|
|
70
|
-
4. Press `i` to open the app on an iOS Simulator, or `a` to open it on an Android Emulator.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## 🏗 Project Structure
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
src/
|
|
78
|
-
├── app/ # Expo Router screens (e.g., _layout.tsx, login.tsx, (tabs)/)
|
|
79
|
-
├── components/ # Reusable UI components
|
|
80
|
-
│ ├── common/ # Generic components (Loading, etc.)
|
|
81
|
-
│ └── ui/ # Custom UI library (Button, Tabs, Image, Input)
|
|
82
|
-
├── constants/ # Theme tokens (Colors, Typography)
|
|
83
|
-
├── services/ # API and business logic wrappers
|
|
84
|
-
│ ├── auth.service.ts # Axios API calls for authentication
|
|
85
|
-
│ └── permission.service.ts # Unified hardware permission logic
|
|
86
|
-
├── store/ # Zustand global stores (useAuthStore.ts)
|
|
87
|
-
└── utils/ # Helper functions (storage.ts, api.ts)
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 📦 Build Instructions
|
|
93
|
-
|
|
94
|
-
This template is configured for both cloud-based builds (via EAS) and fast local compilations using standard Gradle scripts.
|
|
95
|
-
|
|
96
|
-
### Cloud Builds (Expo Application Services)
|
|
97
|
-
|
|
98
|
-
If you don't have Android Studio or Xcode configured, you can build directly in the cloud:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
# Build an Android APK (Uses the "preview" profile in eas.json)
|
|
102
|
-
npm run build:eas:apk
|
|
103
|
-
|
|
104
|
-
# Build an Android App Bundle (.aab) for the Play Store (Uses "production" profile)
|
|
105
|
-
npm run build:eas:aab
|
|
106
|
-
|
|
107
|
-
# Build an iOS app
|
|
108
|
-
npm run build:ios
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Local Builds (Requires Android Studio/SDK)
|
|
112
|
-
|
|
113
|
-
If you have your local SDK set up, you can compile extremely fast on your own machine:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
# Compile a local .apk
|
|
117
|
-
npm run build:apk
|
|
118
|
-
|
|
119
|
-
# Compile a local .aab
|
|
120
|
-
npm run build:aab
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
_(Note: Rebuilding natively is required whenever you add new npm packages containing native code, or modify the `app.json` plugins!)_
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## 🔒 Handling Permissions
|
|
128
|
-
|
|
129
|
-
Rather than installing different disjointed `expo-*` libraries every time you need hardware access, this template centralizes all permission requests using the `PermissionService`.
|
|
130
|
-
|
|
131
|
-
To check or request access:
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
import { permissionService } from "@/services/permission.service";
|
|
135
|
-
|
|
136
|
-
// Calling requirePermission() will automatically prompt the user if they haven't been asked yet
|
|
137
|
-
const hasCameraAccess = await permissionService.requirePermission("camera");
|
|
138
|
-
|
|
139
|
-
if (!hasCameraAccess) {
|
|
140
|
-
// Deep link the user to the OS settings if they permanently blocked the permission
|
|
141
|
-
permissionService.openSettingsForApp();
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## 🔔 Push Notifications
|
|
146
|
-
|
|
147
|
-
To request push notifications, just call the utility function. It will automatically detect if it is running on a physical device, set up the required Android notification channels, fetch the token from Apple/Google, and return it:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import { permissionService } from "@/services/permission.service";
|
|
151
|
-
import { authService } from "@/services/auth.service";
|
|
152
|
-
|
|
153
|
-
const token = await permissionService.registerForPushNotificationsAsync();
|
|
154
|
-
|
|
155
|
-
// Send it to the backend!
|
|
156
|
-
if (token) {
|
|
157
|
-
await authService.savePushToken(token);
|
|
158
|
-
}
|
|
159
|
-
```
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Mobile App Documentation
|
|
2
|
-
|
|
3
|
-
Welcome to the documentation for the Expo React Native Mobile App. This folder contains detailed information on how to use the app, navigate its architecture, and utilize the custom UI component library.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
### Core
|
|
8
|
-
- [Getting Started](./getting-started.md)
|
|
9
|
-
|
|
10
|
-
### UI Component Library
|
|
11
|
-
- [Buttons](./components/buttons.md)
|
|
12
|
-
- [Data Display](./components/data-display.md) (e.g., SwipeableRow, Accordion, Avatar)
|
|
13
|
-
- [Feedback](./components/feedback.md) (e.g., Progress, Spinners)
|
|
14
|
-
- [Gestures & Touch](./components/gestures.md) (e.g., GestureWrapper)
|
|
15
|
-
- [Inputs](./components/inputs.md) (e.g., TextInput, OTPInput, Switches)
|
|
16
|
-
- [Layout](./components/layout.md) (e.g., Container, Divider, Grid)
|
|
17
|
-
- [Navigation](./components/navigation.md) (e.g., Tabs, Pagination, ProgressStepper)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Button Components
|
|
2
|
-
|
|
3
|
-
Buttons trigger actions. The custom UI library provides several highly polished button variations.
|
|
4
|
-
|
|
5
|
-
## `Button`
|
|
6
|
-
The primary button component for the application, supporting different variants (solid, outline, ghost), colors, and loading states.
|
|
7
|
-
|
|
8
|
-
### Usage Example
|
|
9
|
-
```tsx
|
|
10
|
-
import { Button } from '@/components/ui/buttons/Button';
|
|
11
|
-
|
|
12
|
-
export default function Actions() {
|
|
13
|
-
return (
|
|
14
|
-
<>
|
|
15
|
-
<Button
|
|
16
|
-
title="Submit"
|
|
17
|
-
onPress={() => console.log('Primary pressed')}
|
|
18
|
-
/>
|
|
19
|
-
<Button
|
|
20
|
-
title="Cancel"
|
|
21
|
-
variant="outline"
|
|
22
|
-
onPress={() => console.log('Outline pressed')}
|
|
23
|
-
/>
|
|
24
|
-
<Button
|
|
25
|
-
title="Saving..."
|
|
26
|
-
loading={true}
|
|
27
|
-
/>
|
|
28
|
-
</>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## `IconButton`
|
|
34
|
-
A button that only contains an icon without text. Perfect for header buttons or tight layouts.
|
|
35
|
-
|
|
36
|
-
## `FloatingActionButton` (FAB)
|
|
37
|
-
A persistent button that hovers over the screen, typically placed in the bottom right corner. Ideal for primary creation actions like "Compose Message".
|
|
38
|
-
|
|
39
|
-
## `ButtonGroup`
|
|
40
|
-
A wrapper to seamlessly group multiple related buttons together horizontally.
|