@mesob/auth-react 0.1.1 → 0.2.1
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/dist/components/auth/auth-card.js +1 -1
- package/dist/components/auth/auth-card.js.map +1 -1
- package/dist/components/auth/{auth-page-layout.d.ts → auth-layout.d.ts} +3 -3
- package/dist/components/auth/{auth-page-layout.js → auth-layout.js} +4 -4
- package/dist/components/auth/auth-layout.js.map +1 -0
- package/dist/components/auth/countdown.js +15 -6
- package/dist/components/auth/countdown.js.map +1 -1
- package/dist/components/auth/forgot-password.d.ts +1 -9
- package/dist/components/auth/forgot-password.js +267 -43
- package/dist/components/auth/forgot-password.js.map +1 -1
- package/dist/components/auth/reset-password-form.d.ts +2 -10
- package/dist/components/auth/reset-password-form.js +366 -118
- package/dist/components/auth/reset-password-form.js.map +1 -1
- package/dist/components/auth/sign-in.d.ts +2 -10
- package/dist/components/auth/sign-in.js +358 -130
- package/dist/components/auth/sign-in.js.map +1 -1
- package/dist/components/auth/sign-up.d.ts +2 -10
- package/dist/components/auth/sign-up.js +379 -131
- package/dist/components/auth/sign-up.js.map +1 -1
- package/dist/components/auth/verification-form.d.ts +2 -16
- package/dist/components/auth/verification-form.js +15 -6
- package/dist/components/auth/verification-form.js.map +1 -1
- package/dist/components/auth/verify-email.d.ts +10 -0
- package/dist/components/auth/{pages/verify-email-page.js → verify-email.js} +54 -34
- package/dist/components/auth/verify-email.js.map +1 -0
- package/dist/components/auth/verify-phone.d.ts +11 -0
- package/dist/components/auth/{pages/verify-phone-page.js → verify-phone.js} +53 -46
- package/dist/components/auth/verify-phone.js.map +1 -0
- package/dist/components/iam/permissions.d.ts +5 -0
- package/dist/components/iam/{permissions/permissions-page.js → permissions.js} +29 -13
- package/dist/components/iam/permissions.js.map +1 -0
- package/dist/components/iam/roles.d.ts +5 -0
- package/dist/components/iam/{roles/roles-page.js → roles.js} +29 -13
- package/dist/components/iam/roles.js.map +1 -0
- package/dist/components/iam/sessions.d.ts +5 -0
- package/dist/components/iam/{sessions/sessions-page.js → sessions.js} +13 -11
- package/dist/components/iam/sessions.js.map +1 -0
- package/dist/components/iam/tenants.d.ts +5 -0
- package/dist/components/iam/{tenants/tenants-page.js → tenants.js} +13 -11
- package/dist/components/iam/tenants.js.map +1 -0
- package/dist/components/iam/users.d.ts +5 -0
- package/dist/components/iam/{users/users-page.js → users.js} +13 -11
- package/dist/components/iam/users.js.map +1 -0
- package/dist/components/profile/account.d.ts +5 -0
- package/dist/components/profile/account.js +66 -0
- package/dist/components/profile/account.js.map +1 -0
- package/dist/components/profile/change-email-form.d.ts +5 -0
- package/dist/components/profile/change-email-form.js +721 -0
- package/dist/components/profile/change-email-form.js.map +1 -0
- package/dist/components/profile/change-password-form.d.ts +5 -0
- package/dist/components/profile/change-password-form.js +256 -0
- package/dist/components/profile/change-password-form.js.map +1 -0
- package/dist/components/profile/change-phone-form.d.ts +5 -0
- package/dist/components/profile/change-phone-form.js +758 -0
- package/dist/components/profile/change-phone-form.js.map +1 -0
- package/dist/components/profile/change-profile.d.ts +9 -0
- package/dist/components/profile/change-profile.js +37 -0
- package/dist/components/profile/change-profile.js.map +1 -0
- package/dist/components/profile/otp-verification-modal.d.ts +15 -0
- package/dist/components/profile/otp-verification-modal.js +261 -0
- package/dist/components/profile/otp-verification-modal.js.map +1 -0
- package/dist/components/profile/request-change-email-form.d.ts +10 -0
- package/dist/components/profile/request-change-email-form.js +293 -0
- package/dist/components/profile/request-change-email-form.js.map +1 -0
- package/dist/components/profile/request-change-phone-form.d.ts +10 -0
- package/dist/components/profile/request-change-phone-form.js +331 -0
- package/dist/components/profile/request-change-phone-form.js.map +1 -0
- package/dist/components/profile/security.d.ts +5 -0
- package/dist/components/profile/security.js +1439 -0
- package/dist/components/profile/security.js.map +1 -0
- package/dist/components/profile/verify-change-email-form.d.ts +11 -0
- package/dist/components/profile/verify-change-email-form.js +402 -0
- package/dist/components/profile/verify-change-email-form.js.map +1 -0
- package/dist/components/profile/verify-change-phone-form.d.ts +11 -0
- package/dist/components/profile/verify-change-phone-form.js +406 -0
- package/dist/components/profile/verify-change-phone-form.js.map +1 -0
- package/dist/components/shared/{data-table/data-table.js → data-table.js} +2 -2
- package/dist/components/shared/data-table.js.map +1 -0
- package/dist/index.d.ts +42 -93
- package/dist/index.js +2298 -1300
- package/dist/index.js.map +1 -1
- package/dist/types-D3s9oE-5.d.ts +75 -0
- package/dist/verification-form-ipSRTtQB.d.ts +22 -0
- package/package.json +3 -2
- package/dist/components/auth/auth-page-layout.js.map +0 -1
- package/dist/components/auth/pages/forgot-password-page.d.ts +0 -6
- package/dist/components/auth/pages/forgot-password-page.js +0 -362
- package/dist/components/auth/pages/forgot-password-page.js.map +0 -1
- package/dist/components/auth/pages/reset-password-page.d.ts +0 -9
- package/dist/components/auth/pages/reset-password-page.js +0 -514
- package/dist/components/auth/pages/reset-password-page.js.map +0 -1
- package/dist/components/auth/pages/sign-in-page.d.ts +0 -8
- package/dist/components/auth/pages/sign-in-page.js +0 -565
- package/dist/components/auth/pages/sign-in-page.js.map +0 -1
- package/dist/components/auth/pages/sign-up-page.d.ts +0 -9
- package/dist/components/auth/pages/sign-up-page.js +0 -518
- package/dist/components/auth/pages/sign-up-page.js.map +0 -1
- package/dist/components/auth/pages/verify-email-page.d.ts +0 -10
- package/dist/components/auth/pages/verify-email-page.js.map +0 -1
- package/dist/components/auth/pages/verify-phone-page.d.ts +0 -11
- package/dist/components/auth/pages/verify-phone-page.js.map +0 -1
- package/dist/components/iam/permissions/permissions-page.d.ts +0 -5
- package/dist/components/iam/permissions/permissions-page.js.map +0 -1
- package/dist/components/iam/roles/roles-page.d.ts +0 -5
- package/dist/components/iam/roles/roles-page.js.map +0 -1
- package/dist/components/iam/sessions/sessions-page.d.ts +0 -5
- package/dist/components/iam/sessions/sessions-page.js.map +0 -1
- package/dist/components/iam/tenants/tenants-page.d.ts +0 -5
- package/dist/components/iam/tenants/tenants-page.js.map +0 -1
- package/dist/components/iam/users/users-page.d.ts +0 -5
- package/dist/components/iam/users/users-page.js.map +0 -1
- package/dist/components/profile/profile-page.d.ts +0 -8
- package/dist/components/profile/profile-page.js +0 -163
- package/dist/components/profile/profile-page.js.map +0 -1
- package/dist/components/shared/data-table/data-table.js.map +0 -1
- package/dist/handle-error-BqDMxnQZ.d.ts +0 -8
- /package/dist/components/shared/{data-table/data-table.d.ts → data-table.d.ts} +0 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/profile/change-phone-form.tsx
|
|
4
|
+
import { Button as Button4 } from "@mesob/ui/components/button";
|
|
5
|
+
import {
|
|
6
|
+
Collapsible,
|
|
7
|
+
CollapsibleContent,
|
|
8
|
+
CollapsibleTrigger
|
|
9
|
+
} from "@mesob/ui/components/collapsible";
|
|
10
|
+
import { IconChevronDown } from "@tabler/icons-react";
|
|
11
|
+
import { useState as useState5 } from "react";
|
|
12
|
+
|
|
13
|
+
// src/provider.tsx
|
|
14
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
15
|
+
import { deepmerge } from "deepmerge-ts";
|
|
16
|
+
import createFetchClient from "openapi-fetch";
|
|
17
|
+
import createClient from "openapi-react-query";
|
|
18
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
19
|
+
|
|
20
|
+
// src/lib/translations.ts
|
|
21
|
+
function createTranslator(messages, namespace) {
|
|
22
|
+
return (key, params) => {
|
|
23
|
+
const fullKey = namespace ? `${namespace}.${key}` : key;
|
|
24
|
+
const keys = fullKey.split(".");
|
|
25
|
+
let value = messages;
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
if (value && typeof value === "object" && value !== null) {
|
|
28
|
+
value = value[k];
|
|
29
|
+
} else {
|
|
30
|
+
return fullKey;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
return fullKey;
|
|
35
|
+
}
|
|
36
|
+
if (params) {
|
|
37
|
+
return value.replace(
|
|
38
|
+
/\{(\w+)\}/g,
|
|
39
|
+
(_, param) => String(params[param] ?? `{${param}}`)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/utils/cookie.ts
|
|
47
|
+
var isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
|
|
48
|
+
|
|
49
|
+
// src/provider.tsx
|
|
50
|
+
import { jsx } from "react/jsx-runtime";
|
|
51
|
+
var SessionContext = createContext(null);
|
|
52
|
+
var ApiContext = createContext(null);
|
|
53
|
+
var ConfigContext = createContext(null);
|
|
54
|
+
var queryClient = new QueryClient({
|
|
55
|
+
defaultOptions: {
|
|
56
|
+
queries: {
|
|
57
|
+
refetchOnWindowFocus: false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
function useSession() {
|
|
62
|
+
const context = useContext(SessionContext);
|
|
63
|
+
if (!context) {
|
|
64
|
+
throw new Error("useSession must be used within MesobAuthProvider");
|
|
65
|
+
}
|
|
66
|
+
return context;
|
|
67
|
+
}
|
|
68
|
+
function useApi() {
|
|
69
|
+
const context = useContext(ApiContext);
|
|
70
|
+
if (!context) {
|
|
71
|
+
throw new Error("useApi must be used within MesobAuthProvider");
|
|
72
|
+
}
|
|
73
|
+
return context;
|
|
74
|
+
}
|
|
75
|
+
function useConfig() {
|
|
76
|
+
const context = useContext(ConfigContext);
|
|
77
|
+
if (!context) {
|
|
78
|
+
throw new Error("useConfig must be used within MesobAuthProvider");
|
|
79
|
+
}
|
|
80
|
+
return context;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/components/profile/request-change-phone-form.tsx
|
|
84
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
85
|
+
import { Button } from "@mesob/ui/components/button";
|
|
86
|
+
import { Input } from "@mesob/ui/components/input";
|
|
87
|
+
import { Label } from "@mesob/ui/components/label";
|
|
88
|
+
import { Spinner } from "@mesob/ui/components/spinner";
|
|
89
|
+
import { IconEye, IconEyeOff } from "@tabler/icons-react";
|
|
90
|
+
import { useEffect, useState as useState2 } from "react";
|
|
91
|
+
import { useForm } from "react-hook-form";
|
|
92
|
+
import { toast } from "sonner";
|
|
93
|
+
import { z } from "zod";
|
|
94
|
+
|
|
95
|
+
// src/utils/normalize-phone.ts
|
|
96
|
+
function normalizePhone(phone) {
|
|
97
|
+
const cleaned = phone.trim().replace(/\s/g, "");
|
|
98
|
+
if (cleaned.startsWith("+2519") || cleaned.startsWith("+2517")) {
|
|
99
|
+
return cleaned;
|
|
100
|
+
}
|
|
101
|
+
if (cleaned.startsWith("2519") || cleaned.startsWith("2517")) {
|
|
102
|
+
return `+${cleaned}`;
|
|
103
|
+
}
|
|
104
|
+
if (cleaned.startsWith("09") || cleaned.startsWith("07")) {
|
|
105
|
+
return `+251${cleaned.slice(1)}`;
|
|
106
|
+
}
|
|
107
|
+
if ((cleaned.startsWith("9") || cleaned.startsWith("7")) && cleaned.length === 9) {
|
|
108
|
+
return `+251${cleaned}`;
|
|
109
|
+
}
|
|
110
|
+
return cleaned;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/components/profile/request-change-phone-form.tsx
|
|
114
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
115
|
+
var phonePasswordSchema = (phoneRegex) => z.object({
|
|
116
|
+
phone: z.string().trim().min(1, { message: "Phone number is required" }).refine(
|
|
117
|
+
(val) => {
|
|
118
|
+
const isPhone = phoneRegex.test(val);
|
|
119
|
+
return isPhone;
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
message: "Invalid phone number"
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
password: z.string().min(8, "Password must be at least 8 characters").max(128, "Password too long")
|
|
126
|
+
});
|
|
127
|
+
function isAuthError(error) {
|
|
128
|
+
return typeof error === "object" && error !== null && ("code" in error || "message" in error || "name" in error);
|
|
129
|
+
}
|
|
130
|
+
function getErrorCode(error) {
|
|
131
|
+
if (error.code) {
|
|
132
|
+
return error.code;
|
|
133
|
+
}
|
|
134
|
+
if (error.message) {
|
|
135
|
+
const upperMessage = error.message.toUpperCase().trim();
|
|
136
|
+
const validCodes = [
|
|
137
|
+
"USER_NOT_FOUND",
|
|
138
|
+
"USER_EXISTS",
|
|
139
|
+
"INVALID_PASSWORD",
|
|
140
|
+
"VERIFICATION_EXPIRED",
|
|
141
|
+
"VERIFICATION_MISMATCH",
|
|
142
|
+
"VERIFICATION_NOT_FOUND",
|
|
143
|
+
"TOO_MANY_ATTEMPTS",
|
|
144
|
+
"UNAUTHORIZED"
|
|
145
|
+
];
|
|
146
|
+
if (validCodes.includes(upperMessage)) {
|
|
147
|
+
return upperMessage;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
function getErrorMessage(error) {
|
|
153
|
+
if (isAuthError(error)) {
|
|
154
|
+
const errorCode = getErrorCode(error);
|
|
155
|
+
switch (errorCode) {
|
|
156
|
+
case "USER_EXISTS":
|
|
157
|
+
return "This phone number is already taken. Please use a different number.";
|
|
158
|
+
case "VERIFICATION_EXPIRED":
|
|
159
|
+
return "Verification code has expired. Please request a new one.";
|
|
160
|
+
case "VERIFICATION_MISMATCH":
|
|
161
|
+
return "Invalid verification code. Please try again.";
|
|
162
|
+
case "VERIFICATION_NOT_FOUND":
|
|
163
|
+
return "Verification not found. Please request a new code.";
|
|
164
|
+
default:
|
|
165
|
+
return error.message || "An error occurred. Please try again.";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (error instanceof Error) {
|
|
169
|
+
return error.message;
|
|
170
|
+
}
|
|
171
|
+
return "An error occurred. Please try again.";
|
|
172
|
+
}
|
|
173
|
+
function RequestChangePhoneForm({
|
|
174
|
+
onSuccess,
|
|
175
|
+
onCancel,
|
|
176
|
+
buttonText
|
|
177
|
+
}) {
|
|
178
|
+
const { user } = useSession();
|
|
179
|
+
const { hooks } = useApi();
|
|
180
|
+
const { config } = useConfig();
|
|
181
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
182
|
+
const [isChecking, setIsChecking] = useState2(true);
|
|
183
|
+
const [showPassword, setShowPassword] = useState2(false);
|
|
184
|
+
const phoneRegex = typeof config.phoneRegex === "string" ? new RegExp(config.phoneRegex) : config.phoneRegex || /^(\+2519|\+2517|2519|2517|09|07)\d{8}$/;
|
|
185
|
+
const getPendingAccountChangeQuery = hooks.useQuery(
|
|
186
|
+
"get",
|
|
187
|
+
"/account-change/pending",
|
|
188
|
+
{},
|
|
189
|
+
{ enabled: false }
|
|
190
|
+
);
|
|
191
|
+
const verifyPasswordMutation = hooks.useMutation("post", "/password/verify");
|
|
192
|
+
const checkUserMutation = hooks.useMutation("post", "/check-account");
|
|
193
|
+
const requestPhoneOtpMutation = hooks.useMutation(
|
|
194
|
+
"post",
|
|
195
|
+
"/phone/verification/request"
|
|
196
|
+
);
|
|
197
|
+
const phonePasswordForm = useForm({
|
|
198
|
+
resolver: zodResolver(phonePasswordSchema(phoneRegex)),
|
|
199
|
+
defaultValues: {
|
|
200
|
+
phone: "",
|
|
201
|
+
password: ""
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const {
|
|
205
|
+
register,
|
|
206
|
+
handleSubmit,
|
|
207
|
+
getValues,
|
|
208
|
+
setValue,
|
|
209
|
+
formState: { errors }
|
|
210
|
+
} = phonePasswordForm;
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
let active = true;
|
|
213
|
+
const run = async () => {
|
|
214
|
+
try {
|
|
215
|
+
const data = await getPendingAccountChangeQuery.refetch();
|
|
216
|
+
if (!active) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const accountChange = data.data?.accountChange;
|
|
220
|
+
const verificationId = data.data?.verificationId;
|
|
221
|
+
if (accountChange?.changeType !== "phone") {
|
|
222
|
+
setIsChecking(false);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!accountChange.newPhone) {
|
|
226
|
+
setIsChecking(false);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (getValues("phone")) {
|
|
230
|
+
setIsChecking(false);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
setValue("phone", accountChange.newPhone, { shouldValidate: true });
|
|
234
|
+
if (verificationId) {
|
|
235
|
+
toast.message("Resuming verification\u2026");
|
|
236
|
+
onSuccess(verificationId, accountChange.newPhone);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setIsChecking(false);
|
|
240
|
+
} catch {
|
|
241
|
+
setIsChecking(false);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
run().catch(() => void 0);
|
|
245
|
+
return () => {
|
|
246
|
+
active = false;
|
|
247
|
+
};
|
|
248
|
+
}, [getPendingAccountChangeQuery.refetch, getValues, onSuccess, setValue]);
|
|
249
|
+
const onPhonePasswordSubmit = async (data) => {
|
|
250
|
+
if (!user) {
|
|
251
|
+
toast.error("User not found");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
setIsSubmitting(true);
|
|
256
|
+
const normalizedPhone = normalizePhone(data.phone);
|
|
257
|
+
await verifyPasswordMutation.mutateAsync({
|
|
258
|
+
body: { password: data.password }
|
|
259
|
+
});
|
|
260
|
+
const checkResult = await checkUserMutation.mutateAsync({
|
|
261
|
+
body: { identifier: normalizedPhone }
|
|
262
|
+
});
|
|
263
|
+
if (checkResult.data?.exists) {
|
|
264
|
+
if (user?.phone?.replace(/\s/g, "") === normalizedPhone.replace(/\s/g, "")) {
|
|
265
|
+
toast.error("This is already your current phone number.");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
toast.error(
|
|
269
|
+
"This phone number is already taken. Please use a different number."
|
|
270
|
+
);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const verification = await requestPhoneOtpMutation.mutateAsync({
|
|
274
|
+
body: {
|
|
275
|
+
phone: normalizedPhone,
|
|
276
|
+
context: "change-phone"
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
toast.success("Verification code sent to your phone");
|
|
280
|
+
onSuccess(verification.data?.verificationId ?? "", normalizedPhone);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const errorMessage = getErrorMessage(error);
|
|
283
|
+
if (isAuthError(error)) {
|
|
284
|
+
const errorCode = getErrorCode(error);
|
|
285
|
+
if (errorCode === "INVALID_PASSWORD" || errorCode === "USER_NOT_FOUND") {
|
|
286
|
+
toast.error("Incorrect password. Please try again.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
toast.error(errorMessage);
|
|
291
|
+
} finally {
|
|
292
|
+
setIsSubmitting(false);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
const isLoading = isSubmitting || isChecking;
|
|
296
|
+
return /* @__PURE__ */ jsxs(
|
|
297
|
+
"form",
|
|
298
|
+
{
|
|
299
|
+
onSubmit: handleSubmit(onPhonePasswordSubmit),
|
|
300
|
+
className: "p-4 space-y-4 border-t",
|
|
301
|
+
children: [
|
|
302
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 w-full md:w-1/2", children: [
|
|
303
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
304
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "phone", children: "Phone Number" }),
|
|
305
|
+
/* @__PURE__ */ jsx2(
|
|
306
|
+
Input,
|
|
307
|
+
{
|
|
308
|
+
id: "phone",
|
|
309
|
+
type: "tel",
|
|
310
|
+
placeholder: "Enter your new phone number",
|
|
311
|
+
...register("phone"),
|
|
312
|
+
disabled: isLoading
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
errors.phone && /* @__PURE__ */ jsx2("p", { className: "text-sm text-destructive", children: errors.phone.message })
|
|
316
|
+
] }),
|
|
317
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
318
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "password", children: "Password" }),
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
320
|
+
/* @__PURE__ */ jsx2(
|
|
321
|
+
Input,
|
|
322
|
+
{
|
|
323
|
+
id: "password",
|
|
324
|
+
type: showPassword ? "text" : "password",
|
|
325
|
+
autoComplete: "current-password",
|
|
326
|
+
placeholder: "Enter your password",
|
|
327
|
+
...register("password"),
|
|
328
|
+
disabled: isLoading
|
|
329
|
+
}
|
|
330
|
+
),
|
|
331
|
+
/* @__PURE__ */ jsx2(
|
|
332
|
+
"button",
|
|
333
|
+
{
|
|
334
|
+
type: "button",
|
|
335
|
+
onClick: () => setShowPassword(!showPassword),
|
|
336
|
+
className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
337
|
+
children: showPassword ? /* @__PURE__ */ jsx2(IconEyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx2(IconEye, { className: "h-4 w-4" })
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
] }),
|
|
341
|
+
errors.password && /* @__PURE__ */ jsx2("p", { className: "text-sm text-destructive", children: errors.password.message })
|
|
342
|
+
] })
|
|
343
|
+
] }),
|
|
344
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
345
|
+
/* @__PURE__ */ jsx2(
|
|
346
|
+
Button,
|
|
347
|
+
{
|
|
348
|
+
type: "button",
|
|
349
|
+
variant: "outline",
|
|
350
|
+
onClick: onCancel,
|
|
351
|
+
disabled: isLoading,
|
|
352
|
+
children: "Cancel"
|
|
353
|
+
}
|
|
354
|
+
),
|
|
355
|
+
/* @__PURE__ */ jsxs(Button, { type: "submit", disabled: isLoading, children: [
|
|
356
|
+
isLoading && /* @__PURE__ */ jsx2(Spinner, { className: "mr-2 h-4 w-4" }),
|
|
357
|
+
isChecking ? "Checking\u2026" : buttonText
|
|
358
|
+
] })
|
|
359
|
+
] })
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/components/profile/verify-change-phone-form.tsx
|
|
366
|
+
import { useState as useState4 } from "react";
|
|
367
|
+
import { toast as toast2 } from "sonner";
|
|
368
|
+
|
|
369
|
+
// src/components/profile/otp-verification-modal.tsx
|
|
370
|
+
import {
|
|
371
|
+
Dialog,
|
|
372
|
+
DialogContent,
|
|
373
|
+
DialogDescription,
|
|
374
|
+
DialogHeader,
|
|
375
|
+
DialogTitle
|
|
376
|
+
} from "@mesob/ui/components/dialog";
|
|
377
|
+
|
|
378
|
+
// src/components/auth/verification-form.tsx
|
|
379
|
+
import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
|
|
380
|
+
import { Button as Button3 } from "@mesob/ui/components/button";
|
|
381
|
+
import { Field, FieldError, FieldGroup } from "@mesob/ui/components/field";
|
|
382
|
+
import {
|
|
383
|
+
InputOTP,
|
|
384
|
+
InputOTPGroup,
|
|
385
|
+
InputOTPSlot
|
|
386
|
+
} from "@mesob/ui/components/input-otp";
|
|
387
|
+
import { Spinner as Spinner3 } from "@mesob/ui/components/spinner";
|
|
388
|
+
import { Controller, useForm as useForm2 } from "react-hook-form";
|
|
389
|
+
import { z as z2 } from "zod";
|
|
390
|
+
|
|
391
|
+
// src/hooks/use-translator.ts
|
|
392
|
+
import { useMesob } from "@mesob/ui/components/mesob-context";
|
|
393
|
+
function useTranslator(namespace) {
|
|
394
|
+
const mesob = useMesob();
|
|
395
|
+
const { config } = useConfig();
|
|
396
|
+
if (mesob?.t) {
|
|
397
|
+
return (key, params) => mesob.t(namespace ? `${namespace}.${key}` : key, params);
|
|
398
|
+
}
|
|
399
|
+
return createTranslator(config.messages || {}, namespace);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/components/auth/countdown.tsx
|
|
403
|
+
import { Button as Button2 } from "@mesob/ui/components/button";
|
|
404
|
+
import { Spinner as Spinner2 } from "@mesob/ui/components/spinner";
|
|
405
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
406
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
407
|
+
var Countdown = ({
|
|
408
|
+
initialSeconds = 60,
|
|
409
|
+
onResend,
|
|
410
|
+
resending = false
|
|
411
|
+
}) => {
|
|
412
|
+
const t = useTranslator("Common");
|
|
413
|
+
const [seconds, setSeconds] = useState3(initialSeconds);
|
|
414
|
+
const [isResending, setIsResending] = useState3(false);
|
|
415
|
+
useEffect2(() => {
|
|
416
|
+
if (seconds <= 0) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const timer = setInterval(() => {
|
|
420
|
+
setSeconds((prev) => {
|
|
421
|
+
if (prev <= 1) {
|
|
422
|
+
clearInterval(timer);
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
return prev - 1;
|
|
426
|
+
});
|
|
427
|
+
}, 1e3);
|
|
428
|
+
return () => clearInterval(timer);
|
|
429
|
+
}, [seconds]);
|
|
430
|
+
const handleResend = async () => {
|
|
431
|
+
setIsResending(true);
|
|
432
|
+
try {
|
|
433
|
+
await onResend();
|
|
434
|
+
setSeconds(initialSeconds);
|
|
435
|
+
} catch (_error) {
|
|
436
|
+
} finally {
|
|
437
|
+
setIsResending(false);
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
if (seconds > 0) {
|
|
441
|
+
return /* @__PURE__ */ jsx3(Button2, { variant: "ghost", disabled: true, children: t("resendIn", { seconds }) });
|
|
442
|
+
}
|
|
443
|
+
return /* @__PURE__ */ jsxs2(
|
|
444
|
+
Button2,
|
|
445
|
+
{
|
|
446
|
+
variant: "ghost",
|
|
447
|
+
onClick: handleResend,
|
|
448
|
+
disabled: isResending || resending,
|
|
449
|
+
children: [
|
|
450
|
+
isResending || resending && /* @__PURE__ */ jsx3(Spinner2, {}),
|
|
451
|
+
t("resend")
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// src/components/auth/verification-form.tsx
|
|
458
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
459
|
+
var verificationSchema = (t) => z2.object({
|
|
460
|
+
code: z2.string().length(6, t("form.codeLength"))
|
|
461
|
+
});
|
|
462
|
+
var VerificationForm = ({
|
|
463
|
+
onSubmit,
|
|
464
|
+
onResend,
|
|
465
|
+
isLoading = false
|
|
466
|
+
}) => {
|
|
467
|
+
const t = useTranslator("Auth.verification");
|
|
468
|
+
const form = useForm2({
|
|
469
|
+
resolver: zodResolver2(verificationSchema(t)),
|
|
470
|
+
defaultValues: {
|
|
471
|
+
code: ""
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
const handleSubmit = form.handleSubmit(async (values) => {
|
|
475
|
+
await onSubmit(values);
|
|
476
|
+
});
|
|
477
|
+
return /* @__PURE__ */ jsxs3("form", { id: "verification-form", onSubmit: handleSubmit, children: [
|
|
478
|
+
/* @__PURE__ */ jsx4(FieldGroup, { children: /* @__PURE__ */ jsx4(
|
|
479
|
+
Controller,
|
|
480
|
+
{
|
|
481
|
+
name: "code",
|
|
482
|
+
control: form.control,
|
|
483
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsxs3(Field, { "data-invalid": fieldState.invalid, children: [
|
|
484
|
+
/* @__PURE__ */ jsx4("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx4(
|
|
485
|
+
InputOTP,
|
|
486
|
+
{
|
|
487
|
+
maxLength: 6,
|
|
488
|
+
id: "otp",
|
|
489
|
+
required: true,
|
|
490
|
+
value: field.value ?? "",
|
|
491
|
+
onChange: field.onChange,
|
|
492
|
+
onBlur: field.onBlur,
|
|
493
|
+
containerClassName: "gap-4 justify-center mb-2 flex items-center",
|
|
494
|
+
"aria-invalid": fieldState.invalid,
|
|
495
|
+
children: /* @__PURE__ */ jsxs3(InputOTPGroup, { className: "gap-3 *:data-[slot=input-otp-slot]:h-12 *:data-[slot=input-otp-slot]:w-12 *:data-[slot=input-otp-slot]:rounded-md *:data-[slot=input-otp-slot]:border *:data-[slot=input-otp-slot]:text-xl", children: [
|
|
496
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 0 }),
|
|
497
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 1 }),
|
|
498
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 2 }),
|
|
499
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 3 }),
|
|
500
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 4 }),
|
|
501
|
+
/* @__PURE__ */ jsx4(InputOTPSlot, { className: "h-12", index: 5 })
|
|
502
|
+
] })
|
|
503
|
+
}
|
|
504
|
+
) }),
|
|
505
|
+
fieldState.invalid && /* @__PURE__ */ jsx4(FieldError, { errors: [fieldState.error] })
|
|
506
|
+
] })
|
|
507
|
+
}
|
|
508
|
+
) }),
|
|
509
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center mt-4", children: [
|
|
510
|
+
/* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }),
|
|
511
|
+
/* @__PURE__ */ jsxs3(
|
|
512
|
+
Button3,
|
|
513
|
+
{
|
|
514
|
+
type: "submit",
|
|
515
|
+
form: "verification-form",
|
|
516
|
+
disabled: isLoading || form.watch("code").length !== 6,
|
|
517
|
+
children: [
|
|
518
|
+
isLoading && /* @__PURE__ */ jsx4(Spinner3, {}),
|
|
519
|
+
t("form.confirm")
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
)
|
|
523
|
+
] })
|
|
524
|
+
] });
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// src/components/profile/otp-verification-modal.tsx
|
|
528
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
529
|
+
function OtpVerificationModal({
|
|
530
|
+
open,
|
|
531
|
+
title,
|
|
532
|
+
description,
|
|
533
|
+
verificationId,
|
|
534
|
+
isLoading,
|
|
535
|
+
onSubmit,
|
|
536
|
+
onResend,
|
|
537
|
+
onCancel
|
|
538
|
+
}) {
|
|
539
|
+
return /* @__PURE__ */ jsx5(
|
|
540
|
+
Dialog,
|
|
541
|
+
{
|
|
542
|
+
open,
|
|
543
|
+
onOpenChange: (nextOpen) => {
|
|
544
|
+
if (!nextOpen) {
|
|
545
|
+
onCancel?.();
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
children: /* @__PURE__ */ jsxs4(DialogContent, { children: [
|
|
549
|
+
/* @__PURE__ */ jsxs4(DialogHeader, { children: [
|
|
550
|
+
/* @__PURE__ */ jsx5(DialogTitle, { children: title }),
|
|
551
|
+
description && /* @__PURE__ */ jsx5(DialogDescription, { children: description })
|
|
552
|
+
] }),
|
|
553
|
+
/* @__PURE__ */ jsx5(
|
|
554
|
+
VerificationForm,
|
|
555
|
+
{
|
|
556
|
+
verificationId,
|
|
557
|
+
isLoading,
|
|
558
|
+
onSubmit: async ({ code }) => onSubmit(code),
|
|
559
|
+
onResend: onResend ?? (() => void 0)
|
|
560
|
+
}
|
|
561
|
+
)
|
|
562
|
+
] })
|
|
563
|
+
}
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/components/profile/verify-change-phone-form.tsx
|
|
568
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
569
|
+
function isAuthError2(error) {
|
|
570
|
+
return typeof error === "object" && error !== null && ("code" in error || "message" in error || "name" in error);
|
|
571
|
+
}
|
|
572
|
+
function getErrorCode2(error) {
|
|
573
|
+
if (error.code) {
|
|
574
|
+
return error.code;
|
|
575
|
+
}
|
|
576
|
+
if (error.message) {
|
|
577
|
+
const upperMessage = error.message.toUpperCase().trim();
|
|
578
|
+
const validCodes = [
|
|
579
|
+
"USER_NOT_FOUND",
|
|
580
|
+
"USER_EXISTS",
|
|
581
|
+
"INVALID_PASSWORD",
|
|
582
|
+
"VERIFICATION_EXPIRED",
|
|
583
|
+
"VERIFICATION_MISMATCH",
|
|
584
|
+
"VERIFICATION_NOT_FOUND",
|
|
585
|
+
"TOO_MANY_ATTEMPTS",
|
|
586
|
+
"UNAUTHORIZED"
|
|
587
|
+
];
|
|
588
|
+
if (validCodes.includes(upperMessage)) {
|
|
589
|
+
return upperMessage;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return void 0;
|
|
593
|
+
}
|
|
594
|
+
function getErrorMessage2(error) {
|
|
595
|
+
if (isAuthError2(error)) {
|
|
596
|
+
const errorCode = getErrorCode2(error);
|
|
597
|
+
switch (errorCode) {
|
|
598
|
+
case "USER_EXISTS":
|
|
599
|
+
return "This phone number is already taken. Please use a different number.";
|
|
600
|
+
case "VERIFICATION_EXPIRED":
|
|
601
|
+
return "Verification code has expired. Please request a new one.";
|
|
602
|
+
case "VERIFICATION_MISMATCH":
|
|
603
|
+
return "Invalid verification code. Please try again.";
|
|
604
|
+
case "VERIFICATION_NOT_FOUND":
|
|
605
|
+
return "Verification not found. Please request a new code.";
|
|
606
|
+
default:
|
|
607
|
+
return error.message || "An error occurred. Please try again.";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (error instanceof Error) {
|
|
611
|
+
return error.message;
|
|
612
|
+
}
|
|
613
|
+
return "An error occurred. Please try again.";
|
|
614
|
+
}
|
|
615
|
+
function VerifyChangePhoneForm({
|
|
616
|
+
phone,
|
|
617
|
+
verificationId,
|
|
618
|
+
onSuccess,
|
|
619
|
+
onCancel
|
|
620
|
+
}) {
|
|
621
|
+
const { refresh } = useSession();
|
|
622
|
+
const { hooks } = useApi();
|
|
623
|
+
const [isSubmitting, setIsSubmitting] = useState4(false);
|
|
624
|
+
const [currentVerificationId, setCurrentVerificationId] = useState4(verificationId);
|
|
625
|
+
const verifyPhoneOtpMutation = hooks.useMutation(
|
|
626
|
+
"post",
|
|
627
|
+
"/phone/verification/confirm"
|
|
628
|
+
);
|
|
629
|
+
const updatePhoneMutation = hooks.useMutation("put", "/profile/phone");
|
|
630
|
+
const requestPhoneOtpMutation = hooks.useMutation(
|
|
631
|
+
"post",
|
|
632
|
+
"/phone/verification/request"
|
|
633
|
+
);
|
|
634
|
+
const onOtpSubmit = async (code) => {
|
|
635
|
+
if (!currentVerificationId) {
|
|
636
|
+
toast2.error("Verification not found. Please request a new code.");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
setIsSubmitting(true);
|
|
641
|
+
await verifyPhoneOtpMutation.mutateAsync({
|
|
642
|
+
body: {
|
|
643
|
+
verificationId: currentVerificationId,
|
|
644
|
+
code,
|
|
645
|
+
context: "change-phone"
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
await updatePhoneMutation.mutateAsync({
|
|
649
|
+
body: { phone }
|
|
650
|
+
});
|
|
651
|
+
toast2.success("Phone number updated successfully");
|
|
652
|
+
await refresh();
|
|
653
|
+
onSuccess();
|
|
654
|
+
} catch (error) {
|
|
655
|
+
const errorMessage = getErrorMessage2(error);
|
|
656
|
+
toast2.error(errorMessage);
|
|
657
|
+
} finally {
|
|
658
|
+
setIsSubmitting(false);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
if (!currentVerificationId) {
|
|
662
|
+
toast2.error("Verification not found. Please request a new code.");
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return /* @__PURE__ */ jsx6(
|
|
666
|
+
OtpVerificationModal,
|
|
667
|
+
{
|
|
668
|
+
open: true,
|
|
669
|
+
title: "Verify phone",
|
|
670
|
+
description: `Enter the verification code sent to ${phone}`,
|
|
671
|
+
verificationId: currentVerificationId,
|
|
672
|
+
isLoading: isSubmitting,
|
|
673
|
+
onSubmit: onOtpSubmit,
|
|
674
|
+
onResend: async () => {
|
|
675
|
+
try {
|
|
676
|
+
setIsSubmitting(true);
|
|
677
|
+
const next = await requestPhoneOtpMutation.mutateAsync({
|
|
678
|
+
body: {
|
|
679
|
+
phone,
|
|
680
|
+
context: "change-phone"
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
setCurrentVerificationId(next.data?.verificationId ?? null);
|
|
684
|
+
toast2.success("Verification code resent");
|
|
685
|
+
} catch (error) {
|
|
686
|
+
toast2.error(getErrorMessage2(error));
|
|
687
|
+
} finally {
|
|
688
|
+
setIsSubmitting(false);
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
onCancel
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/components/profile/change-phone-form.tsx
|
|
697
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
698
|
+
function ChangePhoneForm() {
|
|
699
|
+
const { user } = useSession();
|
|
700
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
701
|
+
const [showOtp, setShowOtp] = useState5(false);
|
|
702
|
+
const [verificationId, setVerificationId] = useState5(null);
|
|
703
|
+
const [newPhone, setNewPhone] = useState5("");
|
|
704
|
+
const resetForms = () => {
|
|
705
|
+
setShowOtp(false);
|
|
706
|
+
setVerificationId(null);
|
|
707
|
+
setNewPhone("");
|
|
708
|
+
};
|
|
709
|
+
const handleRequestSuccess = (id, phone) => {
|
|
710
|
+
setVerificationId(id);
|
|
711
|
+
setNewPhone(phone);
|
|
712
|
+
setShowOtp(true);
|
|
713
|
+
};
|
|
714
|
+
const handleVerifySuccess = () => {
|
|
715
|
+
resetForms();
|
|
716
|
+
setIsOpen(false);
|
|
717
|
+
};
|
|
718
|
+
const handleCancel = () => {
|
|
719
|
+
resetForms();
|
|
720
|
+
setIsOpen(false);
|
|
721
|
+
};
|
|
722
|
+
const title = user?.phone ? "Change Phone" : "Add Phone";
|
|
723
|
+
const description = user?.phone ? "Update your phone number" : "Add a phone number to your account";
|
|
724
|
+
return /* @__PURE__ */ jsx7(Collapsible, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs5("div", { className: "border rounded-lg", children: [
|
|
725
|
+
/* @__PURE__ */ jsx7(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(Button4, { variant: "ghost", className: "w-full justify-between p-4 h-auto", children: [
|
|
726
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-start", children: [
|
|
727
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium", children: title }),
|
|
728
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm text-muted-foreground", children: description })
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ jsx7(
|
|
731
|
+
IconChevronDown,
|
|
732
|
+
{
|
|
733
|
+
className: `h-4 w-4 transition-transform ${isOpen ? "rotate-180" : ""}`
|
|
734
|
+
}
|
|
735
|
+
)
|
|
736
|
+
] }) }),
|
|
737
|
+
/* @__PURE__ */ jsx7(CollapsibleContent, { children: showOtp ? /* @__PURE__ */ jsx7(
|
|
738
|
+
VerifyChangePhoneForm,
|
|
739
|
+
{
|
|
740
|
+
phone: newPhone,
|
|
741
|
+
verificationId,
|
|
742
|
+
onSuccess: handleVerifySuccess,
|
|
743
|
+
onCancel: handleCancel
|
|
744
|
+
}
|
|
745
|
+
) : /* @__PURE__ */ jsx7(
|
|
746
|
+
RequestChangePhoneForm,
|
|
747
|
+
{
|
|
748
|
+
onSuccess: handleRequestSuccess,
|
|
749
|
+
onCancel: handleCancel,
|
|
750
|
+
buttonText: title
|
|
751
|
+
}
|
|
752
|
+
) })
|
|
753
|
+
] }) });
|
|
754
|
+
}
|
|
755
|
+
export {
|
|
756
|
+
ChangePhoneForm
|
|
757
|
+
};
|
|
758
|
+
//# sourceMappingURL=change-phone-form.js.map
|