@mesob/auth-react 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/components/auth-card.d.ts +9 -0
  2. package/dist/components/auth-card.js +11 -0
  3. package/dist/components/auth-card.js.map +1 -0
  4. package/dist/components/auth-page-layout.d.ts +14 -0
  5. package/dist/components/auth-page-layout.js +28 -0
  6. package/dist/components/auth-page-layout.js.map +1 -0
  7. package/dist/components/countdown.d.ts +10 -0
  8. package/dist/components/countdown.js +57 -0
  9. package/dist/components/countdown.js.map +1 -0
  10. package/dist/components/forgot-password.d.ts +13 -0
  11. package/dist/components/forgot-password.js +67 -0
  12. package/dist/components/forgot-password.js.map +1 -0
  13. package/dist/components/pages/forgot-password-page.d.ts +17 -0
  14. package/dist/components/pages/forgot-password-page.js +224 -0
  15. package/dist/components/pages/forgot-password-page.js.map +1 -0
  16. package/dist/components/pages/reset-password-page.d.ts +19 -0
  17. package/dist/components/pages/reset-password-page.js +357 -0
  18. package/dist/components/pages/reset-password-page.js.map +1 -0
  19. package/dist/components/pages/sign-in-page.d.ts +19 -0
  20. package/dist/components/pages/sign-in-page.js +343 -0
  21. package/dist/components/pages/sign-in-page.js.map +1 -0
  22. package/dist/components/pages/sign-up-page.d.ts +18 -0
  23. package/dist/components/pages/sign-up-page.js +360 -0
  24. package/dist/components/pages/sign-up-page.js.map +1 -0
  25. package/dist/components/pages/verify-email-page.d.ts +19 -0
  26. package/dist/components/pages/verify-email-page.js +356 -0
  27. package/dist/components/pages/verify-email-page.js.map +1 -0
  28. package/dist/components/pages/verify-phone-page.d.ts +20 -0
  29. package/dist/components/pages/verify-phone-page.js +368 -0
  30. package/dist/components/pages/verify-phone-page.js.map +1 -0
  31. package/dist/components/reset-password-form.d.ts +16 -0
  32. package/dist/components/reset-password-form.js +142 -0
  33. package/dist/components/reset-password-form.js.map +1 -0
  34. package/dist/components/sign-in.d.ts +16 -0
  35. package/dist/components/sign-in.js +128 -0
  36. package/dist/components/sign-in.js.map +1 -0
  37. package/dist/components/sign-up.d.ts +17 -0
  38. package/dist/components/sign-up.js +179 -0
  39. package/dist/components/sign-up.js.map +1 -0
  40. package/dist/components/verification-form.d.ts +15 -0
  41. package/dist/components/verification-form.js +155 -0
  42. package/dist/components/verification-form.js.map +1 -0
  43. package/dist/index.d.ts +222 -0
  44. package/dist/index.js +1640 -0
  45. package/dist/index.js.map +1 -0
  46. package/package.json +48 -0
@@ -0,0 +1,368 @@
1
+ "use client";
2
+
3
+ // src/components/pages/verify-phone-page.tsx
4
+ import { useTranslations as useTranslations3 } from "next-intl";
5
+ import { useState as useState3 } from "react";
6
+
7
+ // src/context/auth-provider.tsx
8
+ import {
9
+ createContext,
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useState
14
+ } from "react";
15
+ import { jsx } from "react/jsx-runtime";
16
+ var AuthContext = createContext(null);
17
+ var useAuth = () => {
18
+ const context = useContext(AuthContext);
19
+ if (!context) {
20
+ throw new Error("useAuth must be used within AuthProvider");
21
+ }
22
+ return context;
23
+ };
24
+
25
+ // src/client.ts
26
+ var AuthError = class extends Error {
27
+ code;
28
+ status;
29
+ details;
30
+ constructor(message, code, status, details) {
31
+ super(message);
32
+ this.name = "AuthError";
33
+ this.code = code;
34
+ this.status = status;
35
+ this.details = details;
36
+ }
37
+ };
38
+
39
+ // src/constants/auth.error.codes.ts
40
+ var validCodes = [
41
+ "USER_NOT_FOUND",
42
+ "INVALID_PASSWORD",
43
+ "USER_EXISTS",
44
+ "VERIFICATION_EXPIRED",
45
+ "VERIFICATION_MISMATCH",
46
+ "VERIFICATION_NOT_FOUND",
47
+ "TOO_MANY_ATTEMPTS",
48
+ "REQUIRES_VERIFICATION",
49
+ "UNAUTHORIZED",
50
+ "ACCESS_DENIED",
51
+ "HAS_NO_PASSWORD"
52
+ ];
53
+
54
+ // src/utils/handle-error.ts
55
+ var handleError = (err, setError, t) => {
56
+ if (err instanceof AuthError) {
57
+ let errorKey = "errors.fallback";
58
+ if (err.code) {
59
+ errorKey = `errors.${err.code.toLowerCase()}`;
60
+ } else if (err.message) {
61
+ const messageUpper = err.message.toUpperCase().trim();
62
+ if (validCodes.includes(messageUpper)) {
63
+ errorKey = `errors.${messageUpper.toLowerCase()}`;
64
+ }
65
+ }
66
+ setError(t(errorKey, { defaultValue: err.message }));
67
+ } else {
68
+ setError(err instanceof Error ? err.message : t("errors.fallback"));
69
+ }
70
+ };
71
+
72
+ // src/components/auth-page-layout.tsx
73
+ import { Alert, AlertDescription } from "@mesob/ui/components/alert";
74
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
75
+ var AuthPageLayout = ({
76
+ title,
77
+ description,
78
+ children,
79
+ error,
80
+ footer,
81
+ logoImage
82
+ }) => {
83
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
84
+ /* @__PURE__ */ jsx2("div", { className: "flex size-8 mb-6 w-full items-center justify-center rounded-md", children: /* @__PURE__ */ jsx2("img", { src: logoImage || "", alt: "Jiret", width: 42, height: 42 }) }),
85
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
86
+ /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
87
+ description && /* @__PURE__ */ jsx2("p", { className: "mt-2 text-sm text-muted-foreground", children: description })
88
+ ] }),
89
+ children,
90
+ error && /* @__PURE__ */ jsx2(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx2(AlertDescription, { children: error }) }),
91
+ /* @__PURE__ */ jsx2("div", { className: "mt-2 w-full", children: footer && /* @__PURE__ */ jsx2("div", { className: "w-full text-center text-sm text-muted-foreground", children: footer }) })
92
+ ] });
93
+ };
94
+
95
+ // src/components/verification-form.tsx
96
+ import { zodResolver } from "@hookform/resolvers/zod";
97
+ import { Button as Button2 } from "@mesob/ui/components/button";
98
+ import {
99
+ Form,
100
+ FormControl,
101
+ FormField,
102
+ FormItem,
103
+ FormMessage
104
+ } from "@mesob/ui/components/form";
105
+ import {
106
+ InputOTP,
107
+ InputOTPGroup,
108
+ InputOTPSlot
109
+ } from "@mesob/ui/components/input-otp";
110
+ import { useTranslations as useTranslations2 } from "next-intl";
111
+ import { useMemo } from "react";
112
+ import { useForm } from "react-hook-form";
113
+ import { z } from "zod";
114
+
115
+ // src/components/countdown.tsx
116
+ import { Button } from "@mesob/ui/components/button";
117
+ import { useTranslations } from "next-intl";
118
+ import { useEffect as useEffect2, useState as useState2 } from "react";
119
+ import { jsx as jsx3 } from "react/jsx-runtime";
120
+ var Countdown = ({
121
+ initialSeconds = 60,
122
+ onResend,
123
+ resending = false
124
+ }) => {
125
+ const t = useTranslations("Common");
126
+ const [seconds, setSeconds] = useState2(initialSeconds);
127
+ const [isResending, setIsResending] = useState2(false);
128
+ useEffect2(() => {
129
+ if (seconds <= 0) {
130
+ return;
131
+ }
132
+ const timer = setInterval(() => {
133
+ setSeconds((prev) => {
134
+ if (prev <= 1) {
135
+ clearInterval(timer);
136
+ return 0;
137
+ }
138
+ return prev - 1;
139
+ });
140
+ }, 1e3);
141
+ return () => clearInterval(timer);
142
+ }, [seconds]);
143
+ const handleResend = async () => {
144
+ setIsResending(true);
145
+ try {
146
+ await onResend();
147
+ setSeconds(initialSeconds);
148
+ } catch (_error) {
149
+ } finally {
150
+ setIsResending(false);
151
+ }
152
+ };
153
+ if (seconds > 0) {
154
+ return /* @__PURE__ */ jsx3(Button, { variant: "ghost", disabled: true, children: t("resendIn", { seconds }) });
155
+ }
156
+ return /* @__PURE__ */ jsx3(
157
+ Button,
158
+ {
159
+ variant: "ghost",
160
+ onClick: handleResend,
161
+ disabled: isResending || resending,
162
+ children: isResending || resending ? t("resending") : t("resend")
163
+ }
164
+ );
165
+ };
166
+
167
+ // src/components/verification-form.tsx
168
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
169
+ var VerificationForm = ({
170
+ onSubmit,
171
+ onResend,
172
+ isLoading = false
173
+ }) => {
174
+ const t = useTranslations2("Auth.verification");
175
+ const verificationSchema = useMemo(
176
+ () => z.object({
177
+ code: z.string().length(6, t("form.codeLength"))
178
+ }),
179
+ [t]
180
+ );
181
+ const form = useForm({
182
+ resolver: zodResolver(verificationSchema),
183
+ defaultValues: {
184
+ code: ""
185
+ }
186
+ });
187
+ const handleComplete = async (value) => {
188
+ const valid = await form.trigger("code");
189
+ if (valid) {
190
+ await onSubmit({ code: value });
191
+ }
192
+ };
193
+ return /* @__PURE__ */ jsx4(Form, { ...form, children: /* @__PURE__ */ jsxs2("form", { className: "space-y-4", children: [
194
+ /* @__PURE__ */ jsx4(
195
+ FormField,
196
+ {
197
+ control: form.control,
198
+ name: "code",
199
+ render: ({ field }) => /* @__PURE__ */ jsxs2(FormItem, { children: [
200
+ /* @__PURE__ */ jsx4(FormControl, { children: /* @__PURE__ */ jsx4("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx4(
201
+ InputOTP,
202
+ {
203
+ maxLength: 6,
204
+ value: field.value,
205
+ onChange: (value) => {
206
+ field.onChange(value);
207
+ if (value.length === 6) {
208
+ handleComplete(value);
209
+ }
210
+ },
211
+ disabled: isLoading,
212
+ children: /* @__PURE__ */ jsxs2(InputOTPGroup, { children: [
213
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 0 }),
214
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 1 }),
215
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 2 }),
216
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 3 }),
217
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 4 }),
218
+ /* @__PURE__ */ jsx4(InputOTPSlot, { index: 5 })
219
+ ] })
220
+ }
221
+ ) }) }),
222
+ /* @__PURE__ */ jsx4(FormMessage, {})
223
+ ] })
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-center", children: [
227
+ /* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }),
228
+ /* @__PURE__ */ jsx4(
229
+ Button2,
230
+ {
231
+ type: "button",
232
+ onClick: () => {
233
+ form.handleSubmit(async (values) => {
234
+ await onSubmit(values);
235
+ })();
236
+ },
237
+ disabled: isLoading || form.watch("code").length !== 6,
238
+ children: t("form.confirm")
239
+ }
240
+ )
241
+ ] })
242
+ ] }) });
243
+ };
244
+
245
+ // src/components/pages/verify-phone-page.tsx
246
+ import { jsx as jsx5 } from "react/jsx-runtime";
247
+ var VerifyPhonePage = ({
248
+ verificationId,
249
+ context,
250
+ phone = "",
251
+ onNavigate,
252
+ linkComponent: Link,
253
+ links,
254
+ logoImage
255
+ }) => {
256
+ const t = useTranslations3("Auth.verification");
257
+ const common = useTranslations3("Common");
258
+ const footer = useTranslations3("Auth.forgotPassword.footer");
259
+ const { client, refresh, setAuth } = useAuth();
260
+ const [isLoading, setIsLoading] = useState3(false);
261
+ const [error, setError] = useState3(null);
262
+ const signInLink = links?.signIn || "/auth/sign-in";
263
+ const handleSubmit = async (values) => {
264
+ if (!verificationId) {
265
+ setError(t("errors.fallback"));
266
+ return;
267
+ }
268
+ setIsLoading(true);
269
+ setError(null);
270
+ try {
271
+ const res = await client.verifyPhoneOtp({
272
+ verificationId,
273
+ code: values.code,
274
+ context
275
+ });
276
+ if (res && "user" in res && "session" in res && res.session) {
277
+ setAuth(res);
278
+ onNavigate("/dashboard");
279
+ return;
280
+ }
281
+ await refresh();
282
+ onNavigate("/dashboard");
283
+ } catch (err) {
284
+ handleError(err, setError, t);
285
+ } finally {
286
+ setIsLoading(false);
287
+ }
288
+ };
289
+ const handleResend = async () => {
290
+ setError(null);
291
+ try {
292
+ const targetPhone = context === "sign-up" ? phone : null;
293
+ if (!targetPhone) {
294
+ setError(t("phone.missingPhone"));
295
+ return;
296
+ }
297
+ const res = await client.requestPhoneOtp({ phone: targetPhone, context });
298
+ if (res && "verificationId" in res && res.verificationId) {
299
+ onNavigate(
300
+ `/auth/verify-phone?context=${context}&verificationId=${res.verificationId}&phone=${targetPhone}`
301
+ );
302
+ return;
303
+ }
304
+ setError(t("phone.resendFailed"));
305
+ } catch (err) {
306
+ handleError(err, setError, t);
307
+ }
308
+ };
309
+ if (!verificationId) {
310
+ return /* @__PURE__ */ jsx5(
311
+ AuthPageLayout,
312
+ {
313
+ title: common("invalidLinkTitle"),
314
+ description: common("invalidLinkDescription"),
315
+ footer: Link ? /* @__PURE__ */ jsx5(Link, { href: signInLink, className: "text-primary hover:underline", children: footer("backToSignIn") }) : /* @__PURE__ */ jsx5(
316
+ "a",
317
+ {
318
+ href: signInLink,
319
+ onClick: (e) => {
320
+ e.preventDefault();
321
+ onNavigate(signInLink);
322
+ },
323
+ className: "text-primary hover:underline",
324
+ children: footer("backToSignIn")
325
+ }
326
+ ),
327
+ children: /* @__PURE__ */ jsx5("div", {})
328
+ }
329
+ );
330
+ }
331
+ return /* @__PURE__ */ jsx5(
332
+ AuthPageLayout,
333
+ {
334
+ title: t("phone.title"),
335
+ description: t("phone.description", {
336
+ target: phone || t("phone.missingPhone")
337
+ }),
338
+ error,
339
+ logoImage,
340
+ footer: Link ? /* @__PURE__ */ jsx5(Link, { href: signInLink, className: "text-primary hover:underline", children: footer("backToSignIn") }) : /* @__PURE__ */ jsx5(
341
+ "a",
342
+ {
343
+ href: signInLink,
344
+ onClick: (e) => {
345
+ e.preventDefault();
346
+ onNavigate(signInLink);
347
+ },
348
+ className: "text-primary hover:underline",
349
+ children: footer("backToSignIn")
350
+ }
351
+ ),
352
+ children: /* @__PURE__ */ jsx5(
353
+ VerificationForm,
354
+ {
355
+ verificationId,
356
+ onSubmit: handleSubmit,
357
+ onResend: handleResend,
358
+ isLoading,
359
+ error
360
+ }
361
+ )
362
+ }
363
+ );
364
+ };
365
+ export {
366
+ VerifyPhonePage
367
+ };
368
+ //# sourceMappingURL=verify-phone-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/pages/verify-phone-page.tsx","../../../src/context/auth-provider.tsx","../../../src/client.ts","../../../src/constants/auth.error.codes.ts","../../../src/utils/handle-error.ts","../../../src/components/auth-page-layout.tsx","../../../src/components/verification-form.tsx","../../../src/components/countdown.tsx"],"sourcesContent":["'use client';\n\nimport { useTranslations } from 'next-intl';\nimport type { ComponentProps } from 'react';\nimport { useState } from 'react';\nimport { useAuth } from '../../context/auth-provider';\nimport type { AuthResponse } from '../../types';\nimport { handleError } from '../../utils/handle-error';\nimport { AuthPageLayout } from '../auth-page-layout';\nimport { VerificationForm } from '../verification-form';\n\ntype VerifyPhonePageProps = {\n locale?: string;\n verificationId: string;\n context: 'sign-in' | 'sign-up';\n phone?: string;\n onNavigate: (path: string) => void;\n linkComponent?: React.ComponentType<ComponentProps<'a'> & { href: string }>;\n links?: {\n signIn?: string;\n };\n logoImage?: string;\n};\nexport const VerifyPhonePage = ({\n verificationId,\n context,\n phone = '',\n onNavigate,\n linkComponent: Link,\n links,\n logoImage,\n}: VerifyPhonePageProps) => {\n const t = useTranslations('Auth.verification');\n const common = useTranslations('Common');\n const footer = useTranslations('Auth.forgotPassword.footer');\n const { client, refresh, setAuth } = useAuth();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const signInLink = links?.signIn || '/auth/sign-in';\n\n const handleSubmit = async (values: { code: string }) => {\n if (!verificationId) {\n setError(t('errors.fallback'));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const res = await client.verifyPhoneOtp({\n verificationId,\n code: values.code,\n context,\n });\n if (res && 'user' in res && 'session' in res && res.session) {\n setAuth(res as AuthResponse);\n onNavigate('/dashboard');\n return;\n }\n await refresh();\n onNavigate('/dashboard');\n } catch (err) {\n handleError(err, setError, t);\n } finally {\n setIsLoading(false);\n }\n };\n\n const handleResend = async () => {\n setError(null);\n try {\n const targetPhone = context === 'sign-up' ? phone : null;\n if (!targetPhone) {\n setError(t('phone.missingPhone'));\n return;\n }\n const res = await client.requestPhoneOtp({ phone: targetPhone, context });\n if (res && 'verificationId' in res && res.verificationId) {\n onNavigate(\n `/auth/verify-phone?context=${context}&verificationId=${res.verificationId}&phone=${targetPhone}`,\n );\n return;\n }\n setError(t('phone.resendFailed'));\n } catch (err) {\n handleError(err, setError, t);\n }\n };\n\n if (!verificationId) {\n return (\n <AuthPageLayout\n title={common('invalidLinkTitle')}\n description={common('invalidLinkDescription')}\n footer={\n Link ? (\n <Link href={signInLink} className=\"text-primary hover:underline\">\n {footer('backToSignIn')}\n </Link>\n ) : (\n <a\n href={signInLink}\n onClick={(e) => {\n e.preventDefault();\n onNavigate(signInLink);\n }}\n className=\"text-primary hover:underline\"\n >\n {footer('backToSignIn')}\n </a>\n )\n }\n >\n <div />\n </AuthPageLayout>\n );\n }\n\n return (\n <AuthPageLayout\n title={t('phone.title')}\n description={t('phone.description', {\n target: phone || t('phone.missingPhone'),\n })}\n error={error}\n logoImage={logoImage}\n footer={\n Link ? (\n <Link href={signInLink} className=\"text-primary hover:underline\">\n {footer('backToSignIn')}\n </Link>\n ) : (\n <a\n href={signInLink}\n onClick={(e) => {\n e.preventDefault();\n onNavigate(signInLink);\n }}\n className=\"text-primary hover:underline\"\n >\n {footer('backToSignIn')}\n </a>\n )\n }\n >\n <VerificationForm\n verificationId={verificationId}\n onSubmit={handleSubmit}\n onResend={handleResend}\n isLoading={isLoading}\n error={error}\n />\n </AuthPageLayout>\n );\n};\n","'use client';\n\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from 'react';\nimport type { AuthClient } from '../client';\nimport type { AuthResponse, Session, User } from '../types';\n\ntype AuthContextValue = {\n user: User | null;\n session: Session | null;\n loading: boolean;\n error: Error | null;\n client: AuthClient;\n refresh: () => Promise<void>;\n setAuth: (data: AuthResponse) => void;\n};\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\nexport const useAuth = () => {\n const context = useContext(AuthContext);\n if (!context) {\n throw new Error('useAuth must be used within AuthProvider');\n }\n return context;\n};\n\ntype AuthProviderProps = {\n client: AuthClient;\n children: ReactNode;\n};\n\nexport const AuthProvider = ({ client, children }: AuthProviderProps) => {\n const [user, setUser] = useState<User | null>(null);\n const [session, setSession] = useState<Session | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n try {\n setLoading(true);\n setError(null);\n const data = await client.getSession();\n setUser(data.user);\n setSession(data.session);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error('Failed to fetch session'),\n );\n setUser(null);\n setSession(null);\n } finally {\n setLoading(false);\n }\n }, [client]);\n\n const setAuth = useCallback((data: AuthResponse) => {\n setUser(data.user);\n setSession(data.session);\n setError(null);\n setLoading(false);\n }, []);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n return (\n <AuthContext.Provider\n value={{\n user,\n session,\n loading,\n error,\n client,\n refresh,\n setAuth,\n }}\n >\n {children}\n </AuthContext.Provider>\n );\n};\n","import type {\n AuthErrorCode,\n AuthErrorResponse,\n AuthResponse,\n SessionResponse,\n User,\n VerificationResponse,\n} from './types';\n\n// Backend returns error code in 'error' field (AUTH_ERRORS values)\n// or in 'code' field, message in 'message' or 'error' field\nconst validErrorCodes: readonly AuthErrorCode[] = [\n 'USER_NOT_FOUND',\n 'INVALID_PASSWORD',\n 'USER_EXISTS',\n 'VERIFICATION_EXPIRED',\n 'VERIFICATION_MISMATCH',\n 'VERIFICATION_NOT_FOUND',\n 'TOO_MANY_ATTEMPTS',\n 'REQUIRES_VERIFICATION',\n 'UNAUTHORIZED',\n 'ACCESS_DENIED',\n 'HAS_NO_PASSWORD',\n] as const;\n\nexport class AuthError extends Error {\n code?: AuthErrorCode;\n status?: number;\n details?: Record<string, unknown>;\n\n constructor(\n message: string,\n code?: AuthErrorCode,\n status?: number,\n details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'AuthError';\n this.code = code;\n this.status = status;\n this.details = details;\n }\n}\n\nexport type AuthClientConfig = {\n baseURL: string;\n};\n\nexport class AuthClient {\n private baseURL: string;\n\n constructor(config: AuthClientConfig) {\n this.baseURL = config.baseURL.replace(/\\/$/, '');\n }\n\n private async request<T>(\n endpoint: string,\n options: RequestInit = {},\n ): Promise<T> {\n const url = `${this.baseURL}${endpoint}`;\n const response = await fetch(url, {\n ...options,\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n ...options.headers,\n },\n });\n if (!response.ok) {\n const text = await response.text();\n let errorData: AuthErrorResponse;\n try {\n errorData = JSON.parse(text);\n } catch {\n errorData = { error: 'Unknown error', message: text };\n }\n\n // Extract error code from code, error, or message field\n // Backend returns error codes in 'error' field (e.g., { error: 'INVALID_PASSWORD' })\n const potentialCode =\n errorData.code ||\n (typeof errorData.error === 'string' ? errorData.error : null) ||\n (typeof errorData.message === 'string' ? errorData.message : null);\n const upperCode = potentialCode?.toUpperCase().trim();\n const errorCode =\n upperCode && validErrorCodes.includes(upperCode as AuthErrorCode)\n ? (upperCode as AuthErrorCode)\n : undefined;\n const errorMessage =\n errorData.message || errorData.error || 'Request failed';\n\n throw new AuthError(\n errorMessage,\n errorCode as AuthErrorCode | undefined,\n response.status,\n errorData.details,\n );\n }\n\n const data = await response.json();\n return data;\n }\n\n signUpWithEmail(data: {\n email: string;\n password: string;\n fullName: string;\n handle?: string;\n }): Promise<AuthResponse | VerificationResponse> {\n return this.request<AuthResponse | VerificationResponse>('/sign-up', {\n method: 'POST',\n body: JSON.stringify({\n email: data.email,\n password: data.password,\n fullName: data.fullName,\n handle: data.handle,\n }),\n });\n }\n\n signUpWithPhone(data: {\n phone: string;\n password: string;\n fullName: string;\n handle?: string;\n }): Promise<AuthResponse | VerificationResponse> {\n return this.request<AuthResponse | VerificationResponse>('/sign-up', {\n method: 'POST',\n body: JSON.stringify({\n phone: data.phone,\n password: data.password,\n fullName: data.fullName,\n handle: data.handle,\n }),\n });\n }\n\n checkUser(data: { identifier: string }): Promise<{ exists: boolean }> {\n return this.request<{ exists: boolean }>('/check-user', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n signInWithPassword(data: {\n identifier: string;\n password: string;\n }): Promise<AuthResponse | VerificationResponse> {\n return this.request<AuthResponse | VerificationResponse>('/sign-in', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n signOut(): Promise<{ message: string }> {\n return this.request<{ message: string }>('/sign-out', {\n method: 'POST',\n });\n }\n\n requestEmailVerification(data?: {\n email?: string;\n }): Promise<{ verificationId: string }> {\n return this.request<{ verificationId: string }>(\n '/email/verification/request',\n {\n method: 'POST',\n body: JSON.stringify(data || {}),\n },\n );\n }\n\n verifyEmail(data: {\n verificationId: string;\n code: string;\n }): Promise<AuthResponse> {\n return this.request<AuthResponse>('/email/verification/confirm', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n resendVerification(_verificationId: string): Promise<{\n verificationId: string;\n }> {\n // For email, request new verification\n return this.requestEmailVerification();\n }\n\n requestPhoneOtp(data: {\n phone: string;\n context: 'sign-up' | 'sign-in' | 'change-phone';\n }): Promise<{ verificationId: string }> {\n return this.request<{ verificationId: string }>(\n '/phone/verification/request',\n {\n method: 'POST',\n body: JSON.stringify(data),\n },\n );\n }\n\n verifyPhoneOtp(data: {\n verificationId: string;\n code: string;\n context: 'sign-up' | 'sign-in' | 'change-phone';\n }): Promise<\n AuthResponse | { user: User | null; session: null; verified: boolean }\n > {\n return this.request('/phone/verification/confirm', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n forgotPassword(data: { identifier: string }): Promise<{\n message: string;\n }> {\n return this.request<{ message: string }>('/password/forgot', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n resetPassword(data: {\n verificationId: string;\n code: string;\n password: string;\n }): Promise<AuthResponse> {\n return this.request<AuthResponse>('/password/reset', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n changePassword(data: {\n currentPassword: string;\n newPassword: string;\n }): Promise<{ message: string }> {\n return this.request<{ message: string }>('/password/change', {\n method: 'POST',\n body: JSON.stringify(data),\n });\n }\n\n getSession(): Promise<SessionResponse> {\n return this.request<SessionResponse>('/session', {\n method: 'GET',\n });\n }\n}\n","export const validCodes = [\n 'USER_NOT_FOUND',\n 'INVALID_PASSWORD',\n 'USER_EXISTS',\n 'VERIFICATION_EXPIRED',\n 'VERIFICATION_MISMATCH',\n 'VERIFICATION_NOT_FOUND',\n 'TOO_MANY_ATTEMPTS',\n 'REQUIRES_VERIFICATION',\n 'UNAUTHORIZED',\n 'ACCESS_DENIED',\n 'HAS_NO_PASSWORD',\n];\n","import type { _Translator } from 'next-intl';\nimport { AuthError } from '../client';\nimport { validCodes } from '../constants/auth.error.codes';\n\nexport const handleError = (\n err: any,\n setError: (error: string | null) => void,\n t: _Translator<Record<string, any>, string>,\n) => {\n if (err instanceof AuthError) {\n let errorKey = 'errors.fallback';\n if (err.code) {\n errorKey = `errors.${err.code.toLowerCase()}`;\n } else if (err.message) {\n // Fallback: try to extract error code from message\n const messageUpper = err.message.toUpperCase().trim();\n\n if (validCodes.includes(messageUpper)) {\n errorKey = `errors.${messageUpper.toLowerCase()}`;\n }\n }\n setError(t(errorKey, { defaultValue: err.message }));\n } else {\n setError(err instanceof Error ? err.message : t('errors.fallback'));\n }\n};\n","'use client';\n\nimport { Alert, AlertDescription } from '@mesob/ui/components/alert';\nimport type { ReactNode } from 'react';\n\ntype AuthPageLayoutProps = {\n title: string;\n description?: string;\n children: ReactNode;\n error?: string | null;\n footer?: ReactNode;\n logoImage?: string;\n};\n\nexport const AuthPageLayout = ({\n title,\n description,\n children,\n error,\n footer,\n logoImage,\n}: AuthPageLayoutProps) => {\n return (\n <div className=\"space-y-4\">\n <div className=\"flex size-8 mb-6 w-full items-center justify-center rounded-md\">\n {/** biome-ignore lint/performance/noImgElement: logo image */}\n <img src={logoImage || ''} alt=\"Jiret\" width={42} height={42} />\n </div>\n <div className=\"text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{title}</h1>\n {description && (\n <p className=\"mt-2 text-sm text-muted-foreground\">{description}</p>\n )}\n </div>\n\n {children}\n\n {error && (\n <Alert variant=\"destructive\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n <div className=\"mt-2 w-full\">\n {footer && (\n <div className=\"w-full text-center text-sm text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n </div>\n );\n};\n","'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { Button } from '@mesob/ui/components/button';\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormMessage,\n} from '@mesob/ui/components/form';\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from '@mesob/ui/components/input-otp';\nimport { useTranslations } from 'next-intl';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { Countdown } from './countdown';\n\ntype VerificationFormValues = {\n code: string;\n};\n\ntype VerificationFormProps = {\n verificationId: string;\n onSubmit: (values: VerificationFormValues) => Promise<void> | void;\n onResend: () => Promise<void> | void;\n isLoading?: boolean;\n error?: string | null;\n};\n\nexport const VerificationForm = ({\n onSubmit,\n onResend,\n isLoading = false,\n}: VerificationFormProps) => {\n const t = useTranslations('Auth.verification');\n const verificationSchema = useMemo(\n () =>\n z.object({\n code: z.string().length(6, t('form.codeLength')),\n }),\n [t],\n );\n const form = useForm<VerificationFormValues>({\n resolver: zodResolver(verificationSchema),\n defaultValues: {\n code: '',\n },\n });\n const handleComplete = async (value: string) => {\n const valid = await form.trigger('code');\n if (valid) {\n await onSubmit({ code: value });\n }\n };\n\n return (\n <Form {...form}>\n <form className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"code\"\n render={({ field }) => (\n <FormItem>\n <FormControl>\n <div className=\"flex justify-center\">\n <InputOTP\n maxLength={6}\n value={field.value}\n onChange={(value) => {\n field.onChange(value);\n if (value.length === 6) {\n handleComplete(value);\n }\n }}\n disabled={isLoading}\n >\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <div className=\"flex justify-between items-center\">\n <Countdown onResend={onResend} resending={isLoading} />\n <Button\n type=\"button\"\n onClick={() => {\n form.handleSubmit(async (values) => {\n await onSubmit(values);\n })();\n }}\n disabled={isLoading || form.watch('code').length !== 6}\n >\n {t('form.confirm')}\n </Button>\n </div>\n </form>\n </Form>\n );\n};\n","'use client';\n\nimport { Button } from '@mesob/ui/components/button';\nimport { useTranslations } from 'next-intl';\nimport { useEffect, useState } from 'react';\n\ntype CountdownProps = {\n initialSeconds?: number;\n onResend: () => Promise<void> | void;\n resending?: boolean;\n};\n\nexport const Countdown = ({\n initialSeconds = 60,\n onResend,\n resending = false,\n}: CountdownProps) => {\n const t = useTranslations('Common');\n const [seconds, setSeconds] = useState(initialSeconds);\n const [isResending, setIsResending] = useState(false);\n\n useEffect(() => {\n if (seconds <= 0) {\n return;\n }\n\n const timer = setInterval(() => {\n setSeconds((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [seconds]);\n\n const handleResend = async () => {\n setIsResending(true);\n try {\n await onResend();\n setSeconds(initialSeconds);\n } catch (_error) {\n // Error handling is done by parent\n } finally {\n setIsResending(false);\n }\n };\n\n if (seconds > 0) {\n return (\n <Button variant=\"ghost\" disabled>\n {t('resendIn', { seconds })}\n </Button>\n );\n }\n\n return (\n <Button\n variant=\"ghost\"\n onClick={handleResend}\n disabled={isResending || resending}\n >\n {isResending || resending ? t('resending') : t('resend')}\n </Button>\n );\n};\n"],"mappings":"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","names":["useTranslations","useState","jsx","Button","useTranslations","useEffect","useState","jsx","jsx","jsxs","useTranslations","Button","jsx","useTranslations","useState"]}
@@ -0,0 +1,16 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type ResetPasswordFormValues = {
4
+ code: string;
5
+ password: string;
6
+ confirmPassword: string;
7
+ };
8
+ type ResetPasswordFormProps = {
9
+ verificationId: string;
10
+ onSubmit: (values: ResetPasswordFormValues) => Promise<void> | void;
11
+ isLoading?: boolean;
12
+ error?: string | null;
13
+ };
14
+ declare const ResetPasswordForm: ({ verificationId, onSubmit, isLoading, error, }: ResetPasswordFormProps) => react_jsx_runtime.JSX.Element;
15
+
16
+ export { ResetPasswordForm };
@@ -0,0 +1,142 @@
1
+ "use client";
2
+
3
+ // src/components/reset-password-form.tsx
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+ import { Button } from "@mesob/ui/components/button";
6
+ import {
7
+ Form,
8
+ FormControl,
9
+ FormField,
10
+ FormItem,
11
+ FormLabel,
12
+ FormMessage
13
+ } from "@mesob/ui/components/form";
14
+ import { Input } from "@mesob/ui/components/input";
15
+ import {
16
+ InputOTP,
17
+ InputOTPGroup,
18
+ InputOTPSlot
19
+ } from "@mesob/ui/components/input-otp";
20
+ import { Eye, EyeOff } from "lucide-react";
21
+ import { useTranslations } from "next-intl";
22
+ import { useMemo, useState } from "react";
23
+ import { useForm } from "react-hook-form";
24
+ import { z } from "zod";
25
+ import { jsx, jsxs } from "react/jsx-runtime";
26
+ var ResetPasswordForm = ({
27
+ verificationId,
28
+ onSubmit,
29
+ isLoading = false,
30
+ error
31
+ }) => {
32
+ const t = useTranslations("Auth.resetPassword");
33
+ const [showPassword, setShowPassword] = useState(false);
34
+ const resetPasswordSchema = useMemo(
35
+ () => z.object({
36
+ code: z.string().length(6, t("errors.codeLength")),
37
+ password: z.string().min(8, t("errors.passwordLength")),
38
+ confirmPassword: z.string()
39
+ }).refine((data) => data.password === data.confirmPassword, {
40
+ message: t("errors.passwordsMismatch"),
41
+ path: ["confirmPassword"]
42
+ }),
43
+ [t]
44
+ );
45
+ const form = useForm({
46
+ resolver: zodResolver(resetPasswordSchema),
47
+ defaultValues: {
48
+ code: "",
49
+ password: "",
50
+ confirmPassword: ""
51
+ }
52
+ });
53
+ const handleSubmit = form.handleSubmit(async (values) => {
54
+ await onSubmit(values);
55
+ });
56
+ return /* @__PURE__ */ jsx(Form, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
57
+ /* @__PURE__ */ jsx(
58
+ FormField,
59
+ {
60
+ control: form.control,
61
+ name: "code",
62
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
63
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(FormLabel, { children: t("form.codeLabel") }) }),
64
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(InputOTP, { maxLength: 6, ...field, children: /* @__PURE__ */ jsxs(InputOTPGroup, { children: [
65
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 0 }),
66
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 1 }),
67
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 2 }),
68
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 3 }),
69
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 4 }),
70
+ /* @__PURE__ */ jsx(InputOTPSlot, { index: 5 })
71
+ ] }) }) }) }),
72
+ /* @__PURE__ */ jsx(FormMessage, {})
73
+ ] })
74
+ }
75
+ ),
76
+ /* @__PURE__ */ jsx(
77
+ FormField,
78
+ {
79
+ control: form.control,
80
+ name: "password",
81
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
82
+ /* @__PURE__ */ jsx(FormLabel, { children: t("form.passwordLabel") }),
83
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
84
+ /* @__PURE__ */ jsx(
85
+ Input,
86
+ {
87
+ type: showPassword ? "text" : "password",
88
+ placeholder: t("form.passwordPlaceholder"),
89
+ ...field
90
+ }
91
+ ),
92
+ /* @__PURE__ */ jsx(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ onClick: () => setShowPassword(!showPassword),
97
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
98
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" })
99
+ }
100
+ )
101
+ ] }) }),
102
+ /* @__PURE__ */ jsx(FormMessage, {})
103
+ ] })
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx(
107
+ FormField,
108
+ {
109
+ control: form.control,
110
+ name: "confirmPassword",
111
+ render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
112
+ /* @__PURE__ */ jsx(FormLabel, { children: t("form.confirmPasswordLabel") }),
113
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
114
+ /* @__PURE__ */ jsx(
115
+ Input,
116
+ {
117
+ type: showPassword ? "text" : "password",
118
+ placeholder: t("form.passwordPlaceholder"),
119
+ ...field
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsx(
123
+ "button",
124
+ {
125
+ type: "button",
126
+ onClick: () => setShowPassword(!showPassword),
127
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
128
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" })
129
+ }
130
+ )
131
+ ] }) }),
132
+ /* @__PURE__ */ jsx(FormMessage, {})
133
+ ] })
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? t("form.submitting") : t("form.submit") })
137
+ ] }) });
138
+ };
139
+ export {
140
+ ResetPasswordForm
141
+ };
142
+ //# sourceMappingURL=reset-password-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/reset-password-form.tsx"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { Button } from '@mesob/ui/components/button';\nimport {\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from '@mesob/ui/components/form';\nimport { Input } from '@mesob/ui/components/input';\nimport {\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from '@mesob/ui/components/input-otp';\nimport { Eye, EyeOff } from 'lucide-react';\nimport { useTranslations } from 'next-intl';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\n\ntype ResetPasswordFormValues = {\n code: string;\n password: string;\n confirmPassword: string;\n};\n\ntype ResetPasswordFormProps = {\n verificationId: string;\n onSubmit: (values: ResetPasswordFormValues) => Promise<void> | void;\n isLoading?: boolean;\n error?: string | null;\n};\n\nexport const ResetPasswordForm = ({\n verificationId,\n onSubmit,\n isLoading = false,\n error,\n}: ResetPasswordFormProps) => {\n const t = useTranslations('Auth.resetPassword');\n const [showPassword, setShowPassword] = useState(false);\n const resetPasswordSchema = useMemo(\n () =>\n z\n .object({\n code: z.string().length(6, t('errors.codeLength')),\n password: z.string().min(8, t('errors.passwordLength')),\n confirmPassword: z.string(),\n })\n .refine((data) => data.password === data.confirmPassword, {\n message: t('errors.passwordsMismatch'),\n path: ['confirmPassword'],\n }),\n [t],\n );\n\n const form = useForm<ResetPasswordFormValues>({\n resolver: zodResolver(resetPasswordSchema),\n defaultValues: {\n code: '',\n password: '',\n confirmPassword: '',\n },\n });\n\n const handleSubmit = form.handleSubmit(async (values) => {\n await onSubmit(values);\n });\n\n return (\n <Form {...form}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"code\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex justify-center\">\n <FormLabel>{t('form.codeLabel')}</FormLabel>\n </div>\n <FormControl>\n <div className=\"flex justify-center\">\n <InputOTP maxLength={6} {...field}>\n <InputOTPGroup>\n <InputOTPSlot index={0} />\n <InputOTPSlot index={1} />\n <InputOTPSlot index={2} />\n <InputOTPSlot index={3} />\n <InputOTPSlot index={4} />\n <InputOTPSlot index={5} />\n </InputOTPGroup>\n </InputOTP>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{t('form.passwordLabel')}</FormLabel>\n <FormControl>\n <div className=\"relative\">\n <Input\n type={showPassword ? 'text' : 'password'}\n placeholder={t('form.passwordPlaceholder')}\n {...field}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n >\n {showPassword ? (\n <EyeOff className=\"h-4 w-4\" />\n ) : (\n <Eye className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"confirmPassword\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{t('form.confirmPasswordLabel')}</FormLabel>\n <FormControl>\n <div className=\"relative\">\n <Input\n type={showPassword ? 'text' : 'password'}\n placeholder={t('form.passwordPlaceholder')}\n {...field}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n >\n {showPassword ? (\n <EyeOff className=\"h-4 w-4\" />\n ) : (\n <Eye className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? t('form.submitting') : t('form.submit')}\n </Button>\n </form>\n </Form>\n );\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,16 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type SignInFormValues = {
4
+ account: string;
5
+ password: string;
6
+ };
7
+ type SignInProps = {
8
+ onSubmit: (values: SignInFormValues, step: 'identifier' | 'password') => Promise<void> | void;
9
+ isLoading?: boolean;
10
+ identifier?: string;
11
+ step?: 'identifier' | 'password';
12
+ onBack?: () => void;
13
+ };
14
+ declare const SignIn: ({ onSubmit, isLoading, identifier, step, onBack: _onBack, }: SignInProps) => react_jsx_runtime.JSX.Element;
15
+
16
+ export { SignIn };