@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useState , useCallback } from "react";
|
|
2
|
+
import { View, TouchableOpacity } from "react-native";
|
|
3
|
+
import { useAuthStore } from "../../store/useAuthStore";
|
|
4
|
+
import { Txt } from "../../components/common/Typography";
|
|
5
|
+
import { router, useFocusEffect } from "expo-router";
|
|
6
|
+
import * as ImagePicker from "expo-image-picker";
|
|
7
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
8
|
+
import Avatar from "../../components/ui/data-display/Avatar";
|
|
9
|
+
|
|
10
|
+
import ScrollContainer from "../../components/ui/layout/ScrollContainer";
|
|
11
|
+
import Input from "../../components/ui/inputs/Input";
|
|
12
|
+
import Button from "../../components/ui/buttons/Button";
|
|
13
|
+
import ScreenHeader from "@/components/common/ScreenHeader";
|
|
14
|
+
import { toast } from "@/utils/toast";
|
|
15
|
+
import { authService } from "@/services/auth.service";
|
|
16
|
+
|
|
17
|
+
export default function EditProfileScreen() {
|
|
18
|
+
const { user, error, setUser, clearError } = useAuthStore();
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
|
|
21
|
+
useFocusEffect(
|
|
22
|
+
useCallback(() => {
|
|
23
|
+
return () => clearError();
|
|
24
|
+
}, [clearError])
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const [name, setName] = useState(user?.name || "");
|
|
28
|
+
const [phone, setPhone] = useState(user?.phone || "");
|
|
29
|
+
const [avatarUri, setAvatarUri] = useState<string | null>(null);
|
|
30
|
+
|
|
31
|
+
const pickImage = async () => {
|
|
32
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
33
|
+
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
34
|
+
allowsEditing: true,
|
|
35
|
+
aspect: [1, 1],
|
|
36
|
+
quality: 0.8,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!result.canceled && result.assets[0]) {
|
|
40
|
+
setAvatarUri(result.assets[0].uri);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleSave = async () => {
|
|
45
|
+
const formData = new FormData();
|
|
46
|
+
try {
|
|
47
|
+
setLoading(true);
|
|
48
|
+
if (name !== user?.name) formData.append("name", name);
|
|
49
|
+
if (phone !== user?.phone) formData.append("phone", phone);
|
|
50
|
+
|
|
51
|
+
if (avatarUri) {
|
|
52
|
+
const filename = avatarUri.split("/").pop() || "avatar.jpg";
|
|
53
|
+
const match = /\.(\w+)$/.exec(filename);
|
|
54
|
+
const type = match ? `image/${match[1]}` : `image`;
|
|
55
|
+
|
|
56
|
+
formData.append("file", {
|
|
57
|
+
uri: avatarUri,
|
|
58
|
+
name: filename,
|
|
59
|
+
type,
|
|
60
|
+
} as any);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Only update if something changed
|
|
64
|
+
if (
|
|
65
|
+
(formData as any).getParts &&
|
|
66
|
+
(formData as any).getParts().length === 0
|
|
67
|
+
) {
|
|
68
|
+
router.back();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const res = await authService.updateMe(formData);
|
|
73
|
+
if (res.data.success) {
|
|
74
|
+
setUser(res.data.data);
|
|
75
|
+
toast.success("Profile updated successfully");
|
|
76
|
+
router.back();
|
|
77
|
+
} else {
|
|
78
|
+
toast.error(res.data.message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(res.data.data);
|
|
82
|
+
} catch (error: any) {
|
|
83
|
+
toast.error(error.response?.data?.message || error.message);
|
|
84
|
+
} finally {
|
|
85
|
+
setLoading(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<ScreenHeader title="Edit Profile" />
|
|
92
|
+
<ScrollContainer
|
|
93
|
+
contentContainerStyle={{
|
|
94
|
+
flexGrow: 1,
|
|
95
|
+
padding: 16,
|
|
96
|
+
}}
|
|
97
|
+
className="bg-base-100"
|
|
98
|
+
>
|
|
99
|
+
<View className="items-center mb-8">
|
|
100
|
+
<View className="relative">
|
|
101
|
+
<View className="w-28 h-28 rounded-full items-center justify-center border-4 border-base-100 shadow-sm shadow-base-content/20 overflow-hidden">
|
|
102
|
+
{avatarUri ? (
|
|
103
|
+
<Avatar url={avatarUri} size={104} />
|
|
104
|
+
) : (
|
|
105
|
+
<Avatar
|
|
106
|
+
url={
|
|
107
|
+
user?.avatar?.url ||
|
|
108
|
+
`https://placehold.co/600x400?text=${user?.name?.charAt(0).toUpperCase() || "U"}`
|
|
109
|
+
}
|
|
110
|
+
size={104}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
113
|
+
</View>
|
|
114
|
+
<TouchableOpacity
|
|
115
|
+
onPress={pickImage}
|
|
116
|
+
className="absolute bottom-0 right-0 bg-primary p-2 rounded-full border-2 border-base-100"
|
|
117
|
+
>
|
|
118
|
+
<Ionicons name="camera" size={18} color="#fff" />
|
|
119
|
+
</TouchableOpacity>
|
|
120
|
+
</View>
|
|
121
|
+
</View>
|
|
122
|
+
|
|
123
|
+
{error ? (
|
|
124
|
+
<View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
|
|
125
|
+
<Txt variant="md" className="text-error text-xs text-center">
|
|
126
|
+
⚠️ {error}
|
|
127
|
+
</Txt>
|
|
128
|
+
</View>
|
|
129
|
+
) : null}
|
|
130
|
+
|
|
131
|
+
<View className="gap-4">
|
|
132
|
+
<Input
|
|
133
|
+
leftIcon="person"
|
|
134
|
+
placeholder="Full name"
|
|
135
|
+
value={name}
|
|
136
|
+
onChangeText={setName}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<Input
|
|
140
|
+
leftIcon="call"
|
|
141
|
+
placeholder="Phone number"
|
|
142
|
+
value={phone}
|
|
143
|
+
onChangeText={setPhone}
|
|
144
|
+
keyboardType="phone-pad"
|
|
145
|
+
/>
|
|
146
|
+
|
|
147
|
+
<View className="mt-4">
|
|
148
|
+
<Button
|
|
149
|
+
label="Save Changes"
|
|
150
|
+
onPress={handleSave}
|
|
151
|
+
isLoading={loading}
|
|
152
|
+
variant="primary"
|
|
153
|
+
isFullWidth
|
|
154
|
+
/>
|
|
155
|
+
</View>
|
|
156
|
+
</View>
|
|
157
|
+
</ScrollContainer>
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React, { useState, useEffect , useCallback } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useAuthStore } from "../../store/useAuthStore";
|
|
4
|
+
import { Txt } from "../../components/common/Typography";
|
|
5
|
+
import { router, useFocusEffect } from "expo-router";
|
|
6
|
+
import ScrollContainer from "../../components/ui/layout/ScrollContainer";
|
|
7
|
+
import Input from "../../components/ui/inputs/Input";
|
|
8
|
+
import Button from "../../components/ui/buttons/Button";
|
|
9
|
+
|
|
10
|
+
export default function ForgotPasswordScreen() {
|
|
11
|
+
const { genOtp, verifyOtp, resetPassword, loading, error, clearError } = useAuthStore();
|
|
12
|
+
const [step, setStep] = useState<"email" | "otp" | "password">("email");
|
|
13
|
+
|
|
14
|
+
useFocusEffect(
|
|
15
|
+
useCallback(() => {
|
|
16
|
+
return () => clearError();
|
|
17
|
+
}, [clearError])
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const [email, setEmail] = useState("");
|
|
21
|
+
const [otp, setOtp] = useState("");
|
|
22
|
+
const [newPassword, setNewPassword] = useState("");
|
|
23
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
24
|
+
|
|
25
|
+
const [timer, setTimer] = useState(0);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
let interval: ReturnType<typeof setInterval>;
|
|
29
|
+
if (timer > 0) {
|
|
30
|
+
interval = setInterval(() => {
|
|
31
|
+
setTimer((prev) => prev - 1);
|
|
32
|
+
}, 1000);
|
|
33
|
+
}
|
|
34
|
+
return () => clearInterval(interval);
|
|
35
|
+
}, [timer]);
|
|
36
|
+
|
|
37
|
+
const handleSendOtp = async () => {
|
|
38
|
+
if (!email) return;
|
|
39
|
+
const success = await genOtp(email);
|
|
40
|
+
if (success) {
|
|
41
|
+
setStep("otp");
|
|
42
|
+
setTimer(60);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleVerifyOtp = async () => {
|
|
47
|
+
if (!otp) return;
|
|
48
|
+
const success = await verifyOtp(email, otp);
|
|
49
|
+
if (success) {
|
|
50
|
+
setStep("password");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleResetPassword = async () => {
|
|
55
|
+
if (!newPassword) return;
|
|
56
|
+
const success = await resetPassword(newPassword);
|
|
57
|
+
if (success) {
|
|
58
|
+
router.replace("/(auth)/login");
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ScrollContainer
|
|
64
|
+
contentContainerStyle={{
|
|
65
|
+
flexGrow: 1,
|
|
66
|
+
justifyContent: "center",
|
|
67
|
+
padding: 16,
|
|
68
|
+
}}
|
|
69
|
+
className="bg-base-100"
|
|
70
|
+
>
|
|
71
|
+
<View className="w-full p-6">
|
|
72
|
+
<View className="items-center mb-8 w-full">
|
|
73
|
+
<Txt variant="xl" className="text-2xl mb-1 text-center">
|
|
74
|
+
{step === "email" && "Forgot Password"}
|
|
75
|
+
{step === "otp" && "Verify Email"}
|
|
76
|
+
{step === "password" && "Reset Password"}
|
|
77
|
+
</Txt>
|
|
78
|
+
<Txt
|
|
79
|
+
variant="sm"
|
|
80
|
+
className="text-xs text-base-content/50 text-center px-2"
|
|
81
|
+
>
|
|
82
|
+
{step === "email" && "Enter your email to receive an OTP"}
|
|
83
|
+
{step === "otp" && `Enter the OTP sent to ${email}`}
|
|
84
|
+
{step === "password" && "Enter your new password"}
|
|
85
|
+
</Txt>
|
|
86
|
+
</View>
|
|
87
|
+
|
|
88
|
+
{error ? (
|
|
89
|
+
<View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
|
|
90
|
+
<Txt variant="md" className="text-error text-xs text-center">
|
|
91
|
+
⚠️ {error}
|
|
92
|
+
</Txt>
|
|
93
|
+
</View>
|
|
94
|
+
) : null}
|
|
95
|
+
|
|
96
|
+
<View className="gap-2">
|
|
97
|
+
{step === "email" && (
|
|
98
|
+
<>
|
|
99
|
+
<Input
|
|
100
|
+
leftIcon="mail"
|
|
101
|
+
placeholder="Email address"
|
|
102
|
+
value={email}
|
|
103
|
+
onChangeText={setEmail}
|
|
104
|
+
autoCapitalize="none"
|
|
105
|
+
keyboardType="email-address"
|
|
106
|
+
/>
|
|
107
|
+
<View className="mt-2">
|
|
108
|
+
<Button
|
|
109
|
+
label="Send OTP →"
|
|
110
|
+
onPress={handleSendOtp}
|
|
111
|
+
isLoading={loading}
|
|
112
|
+
variant="primary"
|
|
113
|
+
isFullWidth
|
|
114
|
+
/>
|
|
115
|
+
</View>
|
|
116
|
+
</>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
{step === "otp" && (
|
|
120
|
+
<>
|
|
121
|
+
<Input
|
|
122
|
+
leftIcon="key"
|
|
123
|
+
placeholder="Enter OTP"
|
|
124
|
+
value={otp}
|
|
125
|
+
onChangeText={setOtp}
|
|
126
|
+
keyboardType="numeric"
|
|
127
|
+
/>
|
|
128
|
+
<View className="mt-2">
|
|
129
|
+
<Button
|
|
130
|
+
label="Verify OTP →"
|
|
131
|
+
onPress={handleVerifyOtp}
|
|
132
|
+
isLoading={loading}
|
|
133
|
+
variant="primary"
|
|
134
|
+
isFullWidth
|
|
135
|
+
/>
|
|
136
|
+
</View>
|
|
137
|
+
<View className="mt-4">
|
|
138
|
+
<Button
|
|
139
|
+
label={timer > 0 ? `Resend OTP in ${timer}s` : "Resend OTP"}
|
|
140
|
+
onPress={handleSendOtp}
|
|
141
|
+
disabled={timer > 0 || loading}
|
|
142
|
+
variant="outline"
|
|
143
|
+
isFullWidth
|
|
144
|
+
/>
|
|
145
|
+
</View>
|
|
146
|
+
</>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
{step === "password" && (
|
|
150
|
+
<>
|
|
151
|
+
<Input
|
|
152
|
+
leftIcon="lock-closed"
|
|
153
|
+
rightIcon={showPassword ? "eye-off" : "eye"}
|
|
154
|
+
onRightIconPress={() => setShowPassword(!showPassword)}
|
|
155
|
+
placeholder="New Password"
|
|
156
|
+
value={newPassword}
|
|
157
|
+
onChangeText={setNewPassword}
|
|
158
|
+
secureTextEntry={!showPassword}
|
|
159
|
+
/>
|
|
160
|
+
<View className="mt-2">
|
|
161
|
+
<Button
|
|
162
|
+
label="Reset Password →"
|
|
163
|
+
onPress={handleResetPassword}
|
|
164
|
+
isLoading={loading}
|
|
165
|
+
variant="primary"
|
|
166
|
+
isFullWidth
|
|
167
|
+
/>
|
|
168
|
+
</View>
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
171
|
+
</View>
|
|
172
|
+
|
|
173
|
+
<View className="mt-8">
|
|
174
|
+
<Txt
|
|
175
|
+
variant="sm"
|
|
176
|
+
className="text-sm text-base-content/50 text-center"
|
|
177
|
+
>
|
|
178
|
+
Remember your password?{" "}
|
|
179
|
+
<Txt
|
|
180
|
+
variant="md"
|
|
181
|
+
className="text-primary text-sm"
|
|
182
|
+
onPress={() => router.push("/(auth)/login")}
|
|
183
|
+
>
|
|
184
|
+
Sign in
|
|
185
|
+
</Txt>
|
|
186
|
+
</Txt>
|
|
187
|
+
</View>
|
|
188
|
+
</View>
|
|
189
|
+
</ScrollContainer>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useState, useCallback } from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import { useAuthStore } from "
|
|
4
|
-
import { Txt } from "
|
|
5
|
-
import { Redirect } from "expo-router";
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import Tabs from "../components/ui/navigation/Tabs";
|
|
3
|
+
import { useAuthStore } from "../../store/useAuthStore";
|
|
4
|
+
import { Txt } from "../../components/common/Typography";
|
|
5
|
+
import { Redirect, 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 Tabs from "../../components/ui/navigation/Tabs";
|
|
11
10
|
import Image from "@/components/ui/data-display/Image";
|
|
12
|
-
import logo from "
|
|
11
|
+
import logo from "../../../assets/images/logo/logo.png";
|
|
13
12
|
|
|
14
13
|
export default function LoginScreen() {
|
|
15
14
|
const {
|
|
16
15
|
login,
|
|
17
|
-
demoLogin,
|
|
18
16
|
signup,
|
|
19
17
|
sendOtp,
|
|
20
18
|
loading,
|
|
21
19
|
error,
|
|
22
20
|
user,
|
|
23
21
|
isCheckingAuth,
|
|
22
|
+
clearError,
|
|
23
|
+
demoLogin,
|
|
24
24
|
} = useAuthStore();
|
|
25
25
|
|
|
26
|
+
useFocusEffect(
|
|
27
|
+
useCallback(() => {
|
|
28
|
+
return () => clearError();
|
|
29
|
+
}, [clearError]),
|
|
30
|
+
);
|
|
31
|
+
|
|
26
32
|
const [mode, setMode] = useState("login");
|
|
27
33
|
const [showPassword, setShowPassword] = useState(false);
|
|
28
34
|
const [form, setForm] = useState({
|
|
@@ -32,6 +38,17 @@ export default function LoginScreen() {
|
|
|
32
38
|
otp: "",
|
|
33
39
|
});
|
|
34
40
|
const [otpSent, setOtpSent] = useState(false);
|
|
41
|
+
const [timer, setTimer] = useState(0);
|
|
42
|
+
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
let interval: ReturnType<typeof setInterval>;
|
|
45
|
+
if (timer > 0) {
|
|
46
|
+
interval = setInterval(() => {
|
|
47
|
+
setTimer((prev) => prev - 1);
|
|
48
|
+
}, 1000);
|
|
49
|
+
}
|
|
50
|
+
return () => clearInterval(interval);
|
|
51
|
+
}, [timer]);
|
|
35
52
|
|
|
36
53
|
const handleChange = (name: string, value: string) => {
|
|
37
54
|
setForm((prev) => ({ ...prev, [name]: value }));
|
|
@@ -44,7 +61,10 @@ export default function LoginScreen() {
|
|
|
44
61
|
} else {
|
|
45
62
|
if (!otpSent) {
|
|
46
63
|
ok = await sendOtp(form.email);
|
|
47
|
-
if (ok)
|
|
64
|
+
if (ok) {
|
|
65
|
+
setOtpSent(true);
|
|
66
|
+
setTimer(60);
|
|
67
|
+
}
|
|
48
68
|
} else {
|
|
49
69
|
ok = await signup(form.name, form.email, form.password, form.otp);
|
|
50
70
|
}
|
|
@@ -62,22 +82,23 @@ export default function LoginScreen() {
|
|
|
62
82
|
justifyContent: "center",
|
|
63
83
|
padding: 16,
|
|
64
84
|
}}
|
|
85
|
+
className="bg-base-100"
|
|
65
86
|
>
|
|
66
|
-
<View className="w-full p-6">
|
|
87
|
+
<View className="w-full p-6 ">
|
|
67
88
|
{/* Header */}
|
|
68
89
|
<View className="items-center mb-8 w-full">
|
|
69
90
|
<View className="w-16 h-16 rounded-2xl bg-primary/10 border border-primary/20 items-center justify-center mb-4">
|
|
70
91
|
<Image src={logo} height={40} width={40} />
|
|
71
92
|
</View>
|
|
72
93
|
<Txt
|
|
73
|
-
variant="
|
|
94
|
+
variant="xl"
|
|
74
95
|
className="text-2xl mb-1 text-center"
|
|
75
96
|
numberOfLines={1}
|
|
76
97
|
>
|
|
77
98
|
{mode === "login" ? "Welcome back" : "Create account"}
|
|
78
99
|
</Txt>
|
|
79
100
|
<Txt
|
|
80
|
-
variant="
|
|
101
|
+
variant="sm"
|
|
81
102
|
className="text-xs text-base-content/50 text-center px-2"
|
|
82
103
|
>
|
|
83
104
|
{mode === "login"
|
|
@@ -105,7 +126,7 @@ export default function LoginScreen() {
|
|
|
105
126
|
{/* Error */}
|
|
106
127
|
{error ? (
|
|
107
128
|
<View className="bg-error/10 border border-error/20 rounded-2xl p-3 mb-4">
|
|
108
|
-
<Txt variant="
|
|
129
|
+
<Txt variant="md" className="text-error text-xs text-center">
|
|
109
130
|
⚠️ {error}
|
|
110
131
|
</Txt>
|
|
111
132
|
</View>
|
|
@@ -141,6 +162,18 @@ export default function LoginScreen() {
|
|
|
141
162
|
secureTextEntry={!showPassword}
|
|
142
163
|
/>
|
|
143
164
|
|
|
165
|
+
{mode === "login" && (
|
|
166
|
+
<View className="items-end mt-1 mb-2">
|
|
167
|
+
<Txt
|
|
168
|
+
variant="md"
|
|
169
|
+
className="text-primary text-xs"
|
|
170
|
+
onPress={() => router.push("/(auth)/forgot-password")}
|
|
171
|
+
>
|
|
172
|
+
Forgot Password?
|
|
173
|
+
</Txt>
|
|
174
|
+
</View>
|
|
175
|
+
)}
|
|
176
|
+
|
|
144
177
|
{mode === "signup" && otpSent && (
|
|
145
178
|
<Input
|
|
146
179
|
leftIcon="key"
|
|
@@ -170,9 +203,24 @@ export default function LoginScreen() {
|
|
|
170
203
|
{mode === "login" && (
|
|
171
204
|
<View className="mt-2">
|
|
172
205
|
<Button
|
|
173
|
-
label="
|
|
174
|
-
onPress={demoLogin}
|
|
175
|
-
|
|
206
|
+
label="Demo Login"
|
|
207
|
+
onPress={() => demoLogin()}
|
|
208
|
+
isLoading={loading}
|
|
209
|
+
variant="outline"
|
|
210
|
+
isFullWidth
|
|
211
|
+
/>
|
|
212
|
+
</View>
|
|
213
|
+
)}
|
|
214
|
+
|
|
215
|
+
{mode === "signup" && otpSent && (
|
|
216
|
+
<View className="mt-2">
|
|
217
|
+
<Button
|
|
218
|
+
label={timer > 0 ? `Resend OTP in ${timer}s` : "Resend OTP"}
|
|
219
|
+
onPress={async () => {
|
|
220
|
+
const ok = await sendOtp(form.email);
|
|
221
|
+
if (ok) setTimer(60);
|
|
222
|
+
}}
|
|
223
|
+
disabled={timer > 0 || loading}
|
|
176
224
|
variant="outline"
|
|
177
225
|
isFullWidth
|
|
178
226
|
/>
|
|
@@ -183,12 +231,12 @@ export default function LoginScreen() {
|
|
|
183
231
|
{/* Footer */}
|
|
184
232
|
<View className="mt-8">
|
|
185
233
|
<Txt
|
|
186
|
-
variant="
|
|
234
|
+
variant="sm"
|
|
187
235
|
className="text-sm text-base-content/50 text-center"
|
|
188
236
|
>
|
|
189
237
|
{mode === "login" ? "New here? " : "Already have an account? "}
|
|
190
238
|
<Txt
|
|
191
|
-
variant="
|
|
239
|
+
variant="md"
|
|
192
240
|
className="text-primary text-sm"
|
|
193
241
|
onPress={() => {
|
|
194
242
|
setMode(mode === "login" ? "signup" : "login");
|
|
@@ -9,6 +9,7 @@ import { toast } from "@/utils/toast";
|
|
|
9
9
|
import ScrollContainer from "@/components/ui/layout/ScrollContainer";
|
|
10
10
|
import Container from "@/components/ui/layout/Container";
|
|
11
11
|
import Section from "@/components/ui/layout/Section";
|
|
12
|
+
import Divider from "@/components/ui/layout/Divider";
|
|
12
13
|
import { HStack, VStack } from "@/components/ui/layout/Stack";
|
|
13
14
|
|
|
14
15
|
import Button from "@/components/ui/buttons/Button";
|
|
@@ -79,7 +80,7 @@ const Index = () => {
|
|
|
79
80
|
>
|
|
80
81
|
<HStack justify="space-between" align="center">
|
|
81
82
|
<VStack>
|
|
82
|
-
<Txt variant="
|
|
83
|
+
<Txt variant="3xl" className="font-extrabold tracking-tight">
|
|
83
84
|
imksh-create
|
|
84
85
|
</Txt>
|
|
85
86
|
<Txt
|
|
@@ -115,12 +116,12 @@ const Index = () => {
|
|
|
115
116
|
<Badge label="SOFT" variant="primary" soft={true} />
|
|
116
117
|
</HStack>
|
|
117
118
|
<Txt
|
|
118
|
-
variant="
|
|
119
|
+
variant="4xl"
|
|
119
120
|
className="text-primary font-black leading-tight"
|
|
120
121
|
>
|
|
121
122
|
Stop configuring. Start building.
|
|
122
123
|
</Txt>
|
|
123
|
-
<Txt variant="
|
|
124
|
+
<Txt variant="base" className="text-primary/70 font-medium">
|
|
124
125
|
Skip the setup. Build your product. A complete boilerplate with auth, state management, navigation, and beautiful prebuilt components.
|
|
125
126
|
</Txt>
|
|
126
127
|
</VStack>
|
|
@@ -197,7 +198,7 @@ const Index = () => {
|
|
|
197
198
|
size={50}
|
|
198
199
|
/>
|
|
199
200
|
<VStack style={{ flex: 1 }}>
|
|
200
|
-
<Txt variant="
|
|
201
|
+
<Txt variant="md" className="font-bold">
|
|
201
202
|
Swipe me left!
|
|
202
203
|
</Txt>
|
|
203
204
|
<Txt
|
|
@@ -234,7 +235,7 @@ const Index = () => {
|
|
|
234
235
|
color={colors.accent}
|
|
235
236
|
style={{ marginBottom: 12 }}
|
|
236
237
|
/>
|
|
237
|
-
<Txt variant="
|
|
238
|
+
<Txt variant="2xl" className="text-accent text-center">
|
|
238
239
|
Double Tap or Swipe Up
|
|
239
240
|
</Txt>
|
|
240
241
|
<Txt
|
|
@@ -258,11 +259,11 @@ const Index = () => {
|
|
|
258
259
|
placeholder="Search components..."
|
|
259
260
|
/>
|
|
260
261
|
|
|
261
|
-
<
|
|
262
|
+
<Divider />
|
|
262
263
|
|
|
263
264
|
<HStack justify="space-between" align="center">
|
|
264
265
|
<VStack>
|
|
265
|
-
<Txt variant="
|
|
266
|
+
<Txt variant="md" className="font-bold">
|
|
266
267
|
Biometric Login
|
|
267
268
|
</Txt>
|
|
268
269
|
<Txt
|
|
@@ -275,11 +276,11 @@ const Index = () => {
|
|
|
275
276
|
<Switch checked={isEnabled} onChange={setIsEnabled} />
|
|
276
277
|
</HStack>
|
|
277
278
|
|
|
278
|
-
<
|
|
279
|
+
<Divider />
|
|
279
280
|
|
|
280
281
|
<VStack spacing={12}>
|
|
281
282
|
<HStack justify="space-between" align="center">
|
|
282
|
-
<Txt variant="
|
|
283
|
+
<Txt variant="md" className="font-bold">
|
|
283
284
|
Intensity
|
|
284
285
|
</Txt>
|
|
285
286
|
<Badge label={`${sliderVal}%`} variant="secondary" />
|
|
@@ -292,7 +293,7 @@ const Index = () => {
|
|
|
292
293
|
/>
|
|
293
294
|
</VStack>
|
|
294
295
|
|
|
295
|
-
<
|
|
296
|
+
<Divider style={{ marginTop: 8 }} />
|
|
296
297
|
|
|
297
298
|
{/* Text Input */}
|
|
298
299
|
<Input
|
|
@@ -301,7 +302,7 @@ const Index = () => {
|
|
|
301
302
|
leftIcon="mail-outline"
|
|
302
303
|
/>
|
|
303
304
|
|
|
304
|
-
<
|
|
305
|
+
<Divider />
|
|
305
306
|
|
|
306
307
|
{/* Selection Controls */}
|
|
307
308
|
<VStack spacing={16}>
|
|
@@ -341,7 +342,7 @@ const Index = () => {
|
|
|
341
342
|
]}
|
|
342
343
|
/>
|
|
343
344
|
<View className="py-6 items-center justify-center">
|
|
344
|
-
<Txt variant="
|
|
345
|
+
<Txt variant="base" className="text-base-content/60 italic">
|
|
345
346
|
Currently viewing {activeTab} content.
|
|
346
347
|
</Txt>
|
|
347
348
|
</View>
|
|
@@ -380,7 +381,7 @@ const Index = () => {
|
|
|
380
381
|
|
|
381
382
|
<Card elevation={1} className="rounded-3xl p-6 mt-4">
|
|
382
383
|
<HStack justify="space-between" align="center" className="mb-4">
|
|
383
|
-
<Txt variant="
|
|
384
|
+
<Txt variant="md" className="font-bold">
|
|
384
385
|
Background Sync
|
|
385
386
|
</Txt>
|
|
386
387
|
<Spinner size={24} />
|
|
@@ -399,14 +400,14 @@ const Index = () => {
|
|
|
399
400
|
<Section title="Information & FAQs" className="pb-20">
|
|
400
401
|
<VStack spacing={12}>
|
|
401
402
|
<Accordion title="How to use this library?">
|
|
402
|
-
<Txt variant="
|
|
403
|
+
<Txt variant="base">
|
|
403
404
|
Simply import the components from the `@/components/ui`
|
|
404
405
|
directory. They are fully typed, gesture-driven, and styled
|
|
405
406
|
with NativeWind.
|
|
406
407
|
</Txt>
|
|
407
408
|
</Accordion>
|
|
408
409
|
<Accordion title="Does it support dark mode?">
|
|
409
|
-
<Txt variant="
|
|
410
|
+
<Txt variant="base">
|
|
410
411
|
Yes! All components automatically respond to the device's
|
|
411
412
|
color scheme, utilizing the custom CSS variables defined in
|
|
412
413
|
global.css.
|
|
@@ -437,11 +438,11 @@ const Index = () => {
|
|
|
437
438
|
color={colors.primary}
|
|
438
439
|
/>
|
|
439
440
|
</View>
|
|
440
|
-
<Txt variant="
|
|
441
|
+
<Txt variant="3xl" className="text-center font-black">
|
|
441
442
|
Premium Modal
|
|
442
443
|
</Txt>
|
|
443
444
|
<Txt
|
|
444
|
-
variant="
|
|
445
|
+
variant="base"
|
|
445
446
|
className="text-center text-base-content/70 leading-relaxed px-4"
|
|
446
447
|
>
|
|
447
448
|
This highly responsive bottom sheet is gesture-driven. You can
|