@mesob/auth-react 0.0.8 → 0.1.0

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 (75) hide show
  1. package/dist/components/auth/auth-page-layout.d.ts +1 -3
  2. package/dist/components/auth/auth-page-layout.js +1 -17
  3. package/dist/components/auth/auth-page-layout.js.map +1 -1
  4. package/dist/components/auth/countdown.js +70 -9
  5. package/dist/components/auth/countdown.js.map +1 -1
  6. package/dist/components/auth/forgot-password.js +101 -35
  7. package/dist/components/auth/forgot-password.js.map +1 -1
  8. package/dist/components/auth/pages/forgot-password-page.d.ts +2 -13
  9. package/dist/components/auth/pages/forgot-password-page.js +198 -126
  10. package/dist/components/auth/pages/forgot-password-page.js.map +1 -1
  11. package/dist/components/auth/pages/reset-password-page.d.ts +1 -12
  12. package/dist/components/auth/pages/reset-password-page.js +288 -200
  13. package/dist/components/auth/pages/reset-password-page.js.map +1 -1
  14. package/dist/components/auth/pages/sign-in-page.d.ts +1 -12
  15. package/dist/components/auth/pages/sign-in-page.js +352 -230
  16. package/dist/components/auth/pages/sign-in-page.js.map +1 -1
  17. package/dist/components/auth/pages/sign-up-page.d.ts +1 -11
  18. package/dist/components/auth/pages/sign-up-page.js +310 -216
  19. package/dist/components/auth/pages/sign-up-page.js.map +1 -1
  20. package/dist/components/auth/pages/verify-email-page.d.ts +2 -12
  21. package/dist/components/auth/pages/verify-email-page.js +203 -135
  22. package/dist/components/auth/pages/verify-email-page.js.map +1 -1
  23. package/dist/components/auth/pages/verify-phone-page.d.ts +1 -11
  24. package/dist/components/auth/pages/verify-phone-page.js +206 -137
  25. package/dist/components/auth/pages/verify-phone-page.js.map +1 -1
  26. package/dist/components/auth/reset-password-form.d.ts +1 -1
  27. package/dist/components/auth/reset-password-form.js +188 -106
  28. package/dist/components/auth/reset-password-form.js.map +1 -1
  29. package/dist/components/auth/sign-in.d.ts +3 -3
  30. package/dist/components/auth/sign-in.js +228 -109
  31. package/dist/components/auth/sign-in.js.map +1 -1
  32. package/dist/components/auth/sign-up.js +210 -122
  33. package/dist/components/auth/sign-up.js.map +1 -1
  34. package/dist/components/auth/verification-form.d.ts +1 -1
  35. package/dist/components/auth/verification-form.js +101 -53
  36. package/dist/components/auth/verification-form.js.map +1 -1
  37. package/dist/components/error-boundary.d.ts +27 -0
  38. package/dist/components/error-boundary.js +49 -0
  39. package/dist/components/error-boundary.js.map +1 -0
  40. package/dist/components/iam/permissions/permissions-page.d.ts +5 -0
  41. package/dist/components/iam/permissions/permissions-page.js +201 -0
  42. package/dist/components/iam/permissions/permissions-page.js.map +1 -0
  43. package/dist/components/iam/roles/roles-page.d.ts +5 -0
  44. package/dist/components/iam/roles/roles-page.js +199 -0
  45. package/dist/components/iam/roles/roles-page.js.map +1 -0
  46. package/dist/components/iam/sessions/sessions-page.d.ts +5 -0
  47. package/dist/components/iam/sessions/sessions-page.js +202 -0
  48. package/dist/components/iam/sessions/sessions-page.js.map +1 -0
  49. package/dist/components/iam/tenants/tenants-page.d.ts +5 -0
  50. package/dist/components/iam/tenants/tenants-page.js +202 -0
  51. package/dist/components/iam/tenants/tenants-page.js.map +1 -0
  52. package/dist/components/iam/users/users-page.d.ts +5 -0
  53. package/dist/components/iam/users/users-page.js +211 -0
  54. package/dist/components/iam/users/users-page.js.map +1 -0
  55. package/dist/components/profile/profile-page.d.ts +8 -0
  56. package/dist/components/profile/profile-page.js +163 -0
  57. package/dist/components/profile/profile-page.js.map +1 -0
  58. package/dist/components/shared/data-table/data-table.d.ts +22 -0
  59. package/dist/components/shared/data-table/data-table.js +85 -0
  60. package/dist/components/shared/data-table/data-table.js.map +1 -0
  61. package/dist/components/skeletons/auth-form-skeleton.d.ts +5 -0
  62. package/dist/components/skeletons/auth-form-skeleton.js +32 -0
  63. package/dist/components/skeletons/auth-form-skeleton.js.map +1 -0
  64. package/dist/components/skeletons/profile-skeleton.d.ts +5 -0
  65. package/dist/components/skeletons/profile-skeleton.js +33 -0
  66. package/dist/components/skeletons/profile-skeleton.js.map +1 -0
  67. package/dist/components/skeletons/table-skeleton.d.ts +9 -0
  68. package/dist/components/skeletons/table-skeleton.js +39 -0
  69. package/dist/components/skeletons/table-skeleton.js.map +1 -0
  70. package/dist/handle-error-BqDMxnQZ.d.ts +8 -0
  71. package/dist/index.d.ts +75 -208
  72. package/dist/index.js +2091 -1057
  73. package/dist/index.js.map +1 -1
  74. package/package.json +9 -3
  75. package/dist/handle-error-H0iqQxJ5.d.ts +0 -6
@@ -4,21 +4,85 @@
4
4
  import { zodResolver } from "@hookform/resolvers/zod";
5
5
  import { Button } from "@mesob/ui/components/button";
6
6
  import {
7
- Form,
8
- FormControl,
9
- FormField,
10
- FormItem,
11
- FormLabel,
12
- FormMessage
13
- } from "@mesob/ui/components/form";
7
+ Field,
8
+ FieldError,
9
+ FieldGroup,
10
+ FieldLabel
11
+ } from "@mesob/ui/components/field";
14
12
  import { Input } from "@mesob/ui/components/input";
15
13
  import { Spinner } from "@mesob/ui/components/spinner";
16
14
  import { IconEye, IconEyeOff } from "@tabler/icons-react";
17
- import { useTranslations } from "next-intl";
18
- import { useMemo, useState } from "react";
19
- import { useForm } from "react-hook-form";
15
+ import { useEffect as useEffect2, useState as useState2 } from "react";
16
+ import { Controller, useForm } from "react-hook-form";
20
17
  import { z } from "zod";
21
18
 
19
+ // src/lib/translations.ts
20
+ function createTranslator(messages, namespace) {
21
+ return (key, params) => {
22
+ const fullKey = namespace ? `${namespace}.${key}` : key;
23
+ const keys = fullKey.split(".");
24
+ let value = messages;
25
+ for (const k of keys) {
26
+ if (value && typeof value === "object" && value !== null) {
27
+ value = value[k];
28
+ } else {
29
+ return fullKey;
30
+ }
31
+ }
32
+ if (typeof value !== "string") {
33
+ return fullKey;
34
+ }
35
+ if (params) {
36
+ return value.replace(
37
+ /\{(\w+)\}/g,
38
+ (_, param) => String(params[param] ?? `{${param}}`)
39
+ );
40
+ }
41
+ return value;
42
+ };
43
+ }
44
+
45
+ // src/provider.tsx
46
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
47
+ import { deepmerge } from "deepmerge-ts";
48
+ import createFetchClient from "openapi-fetch";
49
+ import createClient from "openapi-react-query";
50
+ import { createContext, useContext, useEffect, useState } from "react";
51
+ import { jsx } from "react/jsx-runtime";
52
+ var SessionContext = createContext(null);
53
+ var ApiContext = createContext(null);
54
+ var ConfigContext = createContext(null);
55
+ var queryClient = new QueryClient({
56
+ defaultOptions: {
57
+ queries: {
58
+ staleTime: 1e3 * 60 * 5,
59
+ gcTime: 1e3 * 60 * 10,
60
+ retry: 1,
61
+ refetchOnWindowFocus: false
62
+ }
63
+ }
64
+ });
65
+ function useApi() {
66
+ const context = useContext(ApiContext);
67
+ if (!context) {
68
+ throw new Error("useApi must be used within MesobAuthProvider");
69
+ }
70
+ return context;
71
+ }
72
+ function useConfig() {
73
+ const context = useContext(ConfigContext);
74
+ if (!context) {
75
+ throw new Error("useConfig must be used within MesobAuthProvider");
76
+ }
77
+ return context;
78
+ }
79
+
80
+ // src/hooks/use-translator.ts
81
+ function useTranslator(namespace) {
82
+ const { config } = useConfig();
83
+ return createTranslator(config.messages || {}, namespace);
84
+ }
85
+
22
86
  // src/utils/normalize-phone.ts
23
87
  function normalizePhone(phone) {
24
88
  const cleaned = phone.trim().replace(/\s/g, "");
@@ -38,126 +102,181 @@ function normalizePhone(phone) {
38
102
  }
39
103
 
40
104
  // src/components/auth/sign-in.tsx
41
- import { jsx, jsxs } from "react/jsx-runtime";
105
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
106
+ var identifierSchema = (t) => z.object({
107
+ identifier: z.string().trim().min(1, { message: t("errors.requiredField") }).refine(
108
+ (val) => {
109
+ const isEmail = z.email().safeParse(val).success;
110
+ const phoneRegex = /^(\+2519|2519|09)\d{8}$/;
111
+ const isPhone = phoneRegex.test(val);
112
+ return isEmail || isPhone;
113
+ },
114
+ {
115
+ message: t("errors.invalidEmailOrPhone")
116
+ }
117
+ )
118
+ });
119
+ var passwordSchema = (t) => z.object({
120
+ password: z.string().min(8, t("errors.passwordLength")).max(128, t("errors.longPasswordError"))
121
+ });
42
122
  var SignIn = ({
43
123
  onSubmit,
44
124
  isLoading = false,
45
- identifier = "",
125
+ identifier: initialIdentifier = "",
46
126
  step = "identifier",
47
127
  onBack: _onBack
48
128
  }) => {
49
- const t = useTranslations("Auth.signIn");
50
- const [showPassword, setShowPassword] = useState(false);
51
- const identifierSchema = useMemo(
52
- () => z.object({
53
- account: z.string().trim().min(1, { message: t("errors.requiredField") }).refine(
54
- (val) => {
55
- const isEmail = z.string().email().safeParse(val).success;
56
- const phoneRegex = /^(\+2519|2519|09)\d{8}$/;
57
- const isPhone = phoneRegex.test(val);
58
- return isEmail || isPhone;
59
- },
60
- {
61
- message: t("errors.invalidEmailOrPhone")
62
- }
63
- )
64
- }),
65
- [t]
66
- );
67
- const passwordSchema = useMemo(
68
- () => z.object({
69
- password: z.string().min(8, t("errors.passwordLength")).max(128, t("errors.longPasswordError"))
70
- }),
71
- [t]
72
- );
129
+ const { hooks } = useApi();
130
+ const t = useTranslator("Auth.signIn");
131
+ const [showPassword, setShowPassword] = useState2(false);
132
+ const [currentIdentifier, setCurrentIdentifier] = useState2(initialIdentifier);
73
133
  const identifierForm = useForm({
74
- resolver: zodResolver(identifierSchema),
134
+ resolver: zodResolver(identifierSchema(t)),
75
135
  defaultValues: {
76
- account: identifier
77
- }
136
+ identifier: initialIdentifier
137
+ },
138
+ mode: "onChange"
78
139
  });
79
140
  const passwordForm = useForm({
80
- resolver: zodResolver(passwordSchema),
141
+ resolver: zodResolver(passwordSchema(t)),
81
142
  defaultValues: {
82
143
  password: ""
83
- }
144
+ },
145
+ mode: "onChange"
84
146
  });
85
- const handleIdentifierSubmit = identifierForm.handleSubmit(async (values) => {
147
+ const signInMutation = hooks.useMutation("post", "/sign-in");
148
+ useEffect2(() => {
149
+ if (initialIdentifier) {
150
+ identifierForm.setValue("identifier", initialIdentifier);
151
+ setCurrentIdentifier(initialIdentifier);
152
+ }
153
+ }, [initialIdentifier, identifierForm]);
154
+ const handleIdentifierSubmit = async (values) => {
86
155
  const phoneRegex = /^(\+2519|2519|09)\d{8}$/;
87
- const normalizedAccount = phoneRegex.test(values.account) ? normalizePhone(values.account) : values.account;
88
- await onSubmit({ account: normalizedAccount, password: "" }, "identifier");
89
- });
90
- const handlePasswordSubmit = passwordForm.handleSubmit(async (values) => {
156
+ const normalizedIdentifier = phoneRegex.test(values.identifier) ? normalizePhone(values.identifier) : values.identifier;
157
+ setCurrentIdentifier(normalizedIdentifier);
158
+ identifierForm.setValue("identifier", normalizedIdentifier);
91
159
  await onSubmit(
92
- { account: identifier, password: values.password },
93
- "password"
160
+ { identifier: normalizedIdentifier, password: "" },
161
+ "identifier"
94
162
  );
95
- });
96
- if (step === "password") {
97
- return /* @__PURE__ */ jsx(Form, { ...passwordForm, children: /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4", children: [
98
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
99
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-2", children: t("form.enterPasswordFor") }),
100
- /* @__PURE__ */ jsx("p", { className: "font-bold", children: identifier })
101
- ] }),
102
- /* @__PURE__ */ jsx(
103
- FormField,
163
+ };
164
+ const handlePasswordSubmit = async (values) => {
165
+ try {
166
+ await signInMutation.mutateAsync({
167
+ body: {
168
+ identifier: currentIdentifier || identifierForm.getValues("identifier"),
169
+ password: values.password
170
+ }
171
+ });
172
+ await onSubmit(
104
173
  {
105
- control: passwordForm.control,
106
- name: "password",
107
- render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
108
- /* @__PURE__ */ jsx(FormLabel, { children: t("form.passwordLabel") }),
109
- /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
110
- /* @__PURE__ */ jsx(
174
+ identifier: currentIdentifier || identifierForm.getValues("identifier"),
175
+ password: values.password
176
+ },
177
+ "password"
178
+ );
179
+ } catch {
180
+ await onSubmit(
181
+ {
182
+ identifier: currentIdentifier || identifierForm.getValues("identifier"),
183
+ password: values.password
184
+ },
185
+ "password"
186
+ );
187
+ }
188
+ };
189
+ const isPasswordStep = step === "password";
190
+ const isSubmitting = isLoading || signInMutation.isPending;
191
+ if (isPasswordStep) {
192
+ return /* @__PURE__ */ jsxs(
193
+ "form",
194
+ {
195
+ id: "sign-in-password-form",
196
+ onSubmit: passwordForm.handleSubmit(handlePasswordSubmit),
197
+ children: [
198
+ /* @__PURE__ */ jsxs(FieldGroup, { children: [
199
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
200
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground mb-2", children: t("form.enterPasswordFor") }),
201
+ /* @__PURE__ */ jsx2("p", { className: "font-bold", children: currentIdentifier || identifierForm.getValues("identifier") })
202
+ ] }),
203
+ /* @__PURE__ */ jsx2(
204
+ Controller,
205
+ {
206
+ name: "password",
207
+ control: passwordForm.control,
208
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field, { "data-invalid": fieldState.invalid, children: [
209
+ /* @__PURE__ */ jsx2(FieldLabel, { htmlFor: "sign-in-password", children: t("form.passwordLabel") }),
210
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
211
+ /* @__PURE__ */ jsx2(
212
+ Input,
213
+ {
214
+ ...field,
215
+ id: "sign-in-password",
216
+ type: showPassword ? "text" : "password",
217
+ placeholder: t("form.passwordPlaceholder"),
218
+ autoComplete: "current-password",
219
+ "aria-invalid": fieldState.invalid
220
+ }
221
+ ),
222
+ /* @__PURE__ */ jsx2(
223
+ "button",
224
+ {
225
+ type: "button",
226
+ onClick: () => setShowPassword(!showPassword),
227
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
228
+ children: showPassword ? /* @__PURE__ */ jsx2(IconEyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx2(IconEye, { className: "h-4 w-4" })
229
+ }
230
+ )
231
+ ] }),
232
+ fieldState.invalid && /* @__PURE__ */ jsx2(FieldError, { errors: [fieldState.error] })
233
+ ] })
234
+ }
235
+ )
236
+ ] }),
237
+ /* @__PURE__ */ jsx2("div", { className: "mt-4", children: /* @__PURE__ */ jsxs(Button, { type: "submit", className: "w-full", disabled: isSubmitting, children: [
238
+ isSubmitting && /* @__PURE__ */ jsx2(Spinner, {}),
239
+ isSubmitting ? t("form.submitting") : t("form.submit")
240
+ ] }) })
241
+ ]
242
+ }
243
+ );
244
+ }
245
+ return /* @__PURE__ */ jsxs(
246
+ "form",
247
+ {
248
+ id: "sign-in-identifier-form",
249
+ onSubmit: identifierForm.handleSubmit(handleIdentifierSubmit),
250
+ children: [
251
+ /* @__PURE__ */ jsx2(FieldGroup, { children: /* @__PURE__ */ jsx2(
252
+ Controller,
253
+ {
254
+ name: "identifier",
255
+ control: identifierForm.control,
256
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field, { "data-invalid": fieldState.invalid, children: [
257
+ /* @__PURE__ */ jsx2(FieldLabel, { htmlFor: "sign-in-identifier", children: t("form.accountLabel") }),
258
+ /* @__PURE__ */ jsx2(
111
259
  Input,
112
260
  {
113
- type: showPassword ? "text" : "password",
114
- placeholder: t("form.passwordPlaceholder"),
115
- autoComplete: "current-password",
116
- ...field
261
+ ...field,
262
+ id: "sign-in-identifier",
263
+ type: "text",
264
+ placeholder: t("form.accountPlaceholder"),
265
+ autoComplete: "username",
266
+ "aria-invalid": fieldState.invalid
117
267
  }
118
268
  ),
119
- /* @__PURE__ */ jsx(
120
- "button",
121
- {
122
- type: "button",
123
- onClick: () => setShowPassword(!showPassword),
124
- className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
125
- children: showPassword ? /* @__PURE__ */ jsx(IconEyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(IconEye, { className: "h-4 w-4" })
126
- }
127
- )
128
- ] }) }),
129
- /* @__PURE__ */ jsx(FormMessage, {})
130
- ] })
131
- }
132
- ),
133
- /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? t("form.submitting") : t("form.submit") })
134
- ] }) });
135
- }
136
- return /* @__PURE__ */ jsx(Form, { ...identifierForm, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleIdentifierSubmit, className: "space-y-4", children: [
137
- /* @__PURE__ */ jsx(
138
- FormField,
139
- {
140
- control: identifierForm.control,
141
- name: "account",
142
- render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
143
- /* @__PURE__ */ jsx(FormLabel, { children: t("form.accountLabel") }),
144
- /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
145
- Input,
146
- {
147
- type: "text",
148
- placeholder: t("form.accountPlaceholder"),
149
- ...field
150
- }
151
- ) }),
152
- /* @__PURE__ */ jsx(FormMessage, {})
153
- ] })
154
- }
155
- ),
156
- /* @__PURE__ */ jsxs(Button, { type: "submit", className: "w-full", disabled: isLoading, children: [
157
- isLoading && /* @__PURE__ */ jsx(Spinner, {}),
158
- isLoading ? t("form.submitting") : t("form.continue")
159
- ] })
160
- ] }) });
269
+ fieldState.invalid && /* @__PURE__ */ jsx2(FieldError, { errors: [fieldState.error] })
270
+ ] })
271
+ }
272
+ ) }),
273
+ /* @__PURE__ */ jsx2("div", { className: "mt-4", children: /* @__PURE__ */ jsxs(Button, { type: "submit", className: "w-full", disabled: isSubmitting, children: [
274
+ isSubmitting && /* @__PURE__ */ jsx2(Spinner, {}),
275
+ isSubmitting ? t("form.submitting") : t("form.continue")
276
+ ] }) })
277
+ ]
278
+ }
279
+ );
161
280
  };
162
281
  export {
163
282
  SignIn
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/auth/sign-in.tsx","../../../src/utils/normalize-phone.ts"],"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 { Spinner } from '@mesob/ui/components/spinner';\nimport { IconEye, IconEyeOff } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { normalizePhone } from '../../utils/normalize-phone';\n\ntype SignInFormValues = {\n account: string;\n password: string;\n};\n\ntype SignInProps = {\n onSubmit: (\n values: SignInFormValues,\n step: 'identifier' | 'password',\n ) => Promise<void> | void;\n isLoading?: boolean;\n identifier?: string;\n step?: 'identifier' | 'password';\n onBack?: () => void;\n};\n\nexport const SignIn = ({\n onSubmit,\n isLoading = false,\n identifier = '',\n step = 'identifier',\n onBack: _onBack,\n}: SignInProps) => {\n const t = useTranslations('Auth.signIn');\n const [showPassword, setShowPassword] = useState(false);\n\n const identifierSchema = useMemo(\n () =>\n z.object({\n account: z\n .string()\n .trim()\n .min(1, { message: t('errors.requiredField') })\n .refine(\n (val) => {\n const isEmail = z.string().email().safeParse(val).success;\n const phoneRegex = /^(\\+2519|2519|09)\\d{8}$/;\n const isPhone = phoneRegex.test(val);\n return isEmail || isPhone;\n },\n {\n message: t('errors.invalidEmailOrPhone'),\n },\n ),\n }),\n [t],\n );\n\n const passwordSchema = useMemo(\n () =>\n z.object({\n password: z\n .string()\n .min(8, t('errors.passwordLength'))\n .max(128, t('errors.longPasswordError')),\n }),\n [t],\n );\n\n const identifierForm = useForm<{ account: string }>({\n resolver: zodResolver(identifierSchema),\n defaultValues: {\n account: identifier,\n },\n });\n\n const passwordForm = useForm<{ password: string }>({\n resolver: zodResolver(passwordSchema),\n defaultValues: {\n password: '',\n },\n });\n\n const handleIdentifierSubmit = identifierForm.handleSubmit(async (values) => {\n const phoneRegex = /^(\\+2519|2519|09)\\d{8}$/;\n const normalizedAccount = phoneRegex.test(values.account)\n ? normalizePhone(values.account)\n : values.account;\n await onSubmit({ account: normalizedAccount, password: '' }, 'identifier');\n });\n\n const handlePasswordSubmit = passwordForm.handleSubmit(async (values) => {\n await onSubmit(\n { account: identifier, password: values.password },\n 'password',\n );\n });\n\n if (step === 'password') {\n return (\n <Form {...passwordForm}>\n <form onSubmit={handlePasswordSubmit} className=\"space-y-4\">\n <div className=\"text-center\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {t('form.enterPasswordFor')}\n </p>\n <p className=\"font-bold\">{identifier}</p>\n </div>\n <FormField\n control={passwordForm.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 autoComplete=\"current-password\"\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 <IconEyeOff className=\"h-4 w-4\" />\n ) : (\n <IconEye className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\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\n return (\n <Form {...identifierForm}>\n <form onSubmit={handleIdentifierSubmit} className=\"space-y-4\">\n <FormField\n control={identifierForm.control}\n name=\"account\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{t('form.accountLabel')}</FormLabel>\n <FormControl>\n <Input\n type=\"text\"\n placeholder={t('form.accountPlaceholder')}\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading && <Spinner />}\n {isLoading ? t('form.submitting') : t('form.continue')}\n </Button>\n </form>\n </Form>\n );\n};\n","export function normalizePhone(phone: string): string {\n const cleaned = phone.trim().replace(/\\s/g, '');\n if (cleaned.startsWith('+2519')) {\n return cleaned;\n }\n if (cleaned.startsWith('2519')) {\n return `+${cleaned}`;\n }\n if (cleaned.startsWith('09')) {\n return `+251${cleaned.slice(1)}`;\n }\n if (cleaned.startsWith('9') && cleaned.length === 9) {\n return `+251${cleaned}`;\n }\n return cleaned;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/auth/sign-in.tsx","../../../src/lib/translations.ts","../../../src/provider.tsx","../../../src/hooks/use-translator.ts","../../../src/utils/normalize-phone.ts"],"sourcesContent":["'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { Button } from '@mesob/ui/components/button';\nimport {\n Field,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from '@mesob/ui/components/field';\nimport { Input } from '@mesob/ui/components/input';\nimport { Spinner } from '@mesob/ui/components/spinner';\nimport { IconEye, IconEyeOff } from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { useTranslator } from '../../hooks/use-translator';\nimport { useApi } from '../../provider';\nimport { normalizePhone } from '../../utils/normalize-phone';\n\ntype SignInFormValues = {\n identifier: string;\n password?: string;\n};\n\ntype SignInProps = {\n onSubmit: (\n values: SignInFormValues,\n step: 'identifier' | 'password',\n ) => Promise<void> | void;\n isLoading?: boolean;\n identifier?: string;\n step?: 'identifier' | 'password';\n onBack?: () => void;\n};\n\nconst identifierSchema = (t: (key: string) => string) =>\n z.object({\n identifier: z\n .string()\n .trim()\n .min(1, { message: t('errors.requiredField') })\n .refine(\n (val) => {\n const isEmail = z.email().safeParse(val).success;\n const phoneRegex = /^(\\+2519|2519|09)\\d{8}$/;\n const isPhone = phoneRegex.test(val);\n return isEmail || isPhone;\n },\n {\n message: t('errors.invalidEmailOrPhone'),\n },\n ),\n });\n\nconst passwordSchema = (t: (key: string) => string) =>\n z.object({\n password: z\n .string()\n .min(8, t('errors.passwordLength'))\n .max(128, t('errors.longPasswordError')),\n });\n\nexport const SignIn = ({\n onSubmit,\n isLoading = false,\n identifier: initialIdentifier = '',\n step = 'identifier',\n onBack: _onBack,\n}: SignInProps) => {\n const { hooks } = useApi();\n const t = useTranslator('Auth.signIn');\n const [showPassword, setShowPassword] = useState(false);\n const [currentIdentifier, setCurrentIdentifier] = useState(initialIdentifier);\n\n const identifierForm = useForm<{ identifier: string }>({\n resolver: zodResolver(identifierSchema(t)),\n defaultValues: {\n identifier: initialIdentifier,\n },\n mode: 'onChange',\n });\n\n const passwordForm = useForm<{ password: string }>({\n resolver: zodResolver(passwordSchema(t)),\n defaultValues: {\n password: '',\n },\n mode: 'onChange',\n });\n\n const signInMutation = hooks.useMutation('post', '/sign-in');\n\n useEffect(() => {\n if (initialIdentifier) {\n identifierForm.setValue('identifier', initialIdentifier);\n setCurrentIdentifier(initialIdentifier);\n }\n }, [initialIdentifier, identifierForm]);\n\n const handleIdentifierSubmit = async (values: { identifier: string }) => {\n const phoneRegex = /^(\\+2519|2519|09)\\d{8}$/;\n const normalizedIdentifier = phoneRegex.test(values.identifier)\n ? normalizePhone(values.identifier)\n : values.identifier;\n\n setCurrentIdentifier(normalizedIdentifier);\n identifierForm.setValue('identifier', normalizedIdentifier);\n await onSubmit(\n { identifier: normalizedIdentifier, password: '' },\n 'identifier',\n );\n };\n\n const handlePasswordSubmit = async (values: { password: string }) => {\n try {\n await signInMutation.mutateAsync({\n body: {\n identifier:\n currentIdentifier || identifierForm.getValues('identifier'),\n password: values.password,\n },\n });\n\n await onSubmit(\n {\n identifier:\n currentIdentifier || identifierForm.getValues('identifier'),\n password: values.password,\n },\n 'password',\n );\n } catch {\n await onSubmit(\n {\n identifier:\n currentIdentifier || identifierForm.getValues('identifier'),\n password: values.password,\n },\n 'password',\n );\n }\n };\n\n const isPasswordStep = step === 'password';\n const isSubmitting = isLoading || signInMutation.isPending;\n\n if (isPasswordStep) {\n return (\n <form\n id=\"sign-in-password-form\"\n onSubmit={passwordForm.handleSubmit(handlePasswordSubmit)}\n >\n <FieldGroup>\n <div className=\"text-center\">\n <p className=\"text-sm text-muted-foreground mb-2\">\n {t('form.enterPasswordFor')}\n </p>\n <p className=\"font-bold\">\n {currentIdentifier || identifierForm.getValues('identifier')}\n </p>\n </div>\n <Controller\n name=\"password\"\n control={passwordForm.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"sign-in-password\">\n {t('form.passwordLabel')}\n </FieldLabel>\n <div className=\"relative\">\n <Input\n {...field}\n id=\"sign-in-password\"\n type={showPassword ? 'text' : 'password'}\n placeholder={t('form.passwordPlaceholder')}\n autoComplete=\"current-password\"\n aria-invalid={fieldState.invalid}\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 <IconEyeOff className=\"h-4 w-4\" />\n ) : (\n <IconEye className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n {fieldState.invalid && (\n <FieldError errors={[fieldState.error]} />\n )}\n </Field>\n )}\n />\n </FieldGroup>\n <div className=\"mt-4\">\n <Button type=\"submit\" className=\"w-full\" disabled={isSubmitting}>\n {isSubmitting && <Spinner />}\n {isSubmitting ? t('form.submitting') : t('form.submit')}\n </Button>\n </div>\n </form>\n );\n }\n\n return (\n <form\n id=\"sign-in-identifier-form\"\n onSubmit={identifierForm.handleSubmit(handleIdentifierSubmit)}\n >\n <FieldGroup>\n <Controller\n name=\"identifier\"\n control={identifierForm.control}\n render={({ field, fieldState }) => (\n <Field data-invalid={fieldState.invalid}>\n <FieldLabel htmlFor=\"sign-in-identifier\">\n {t('form.accountLabel')}\n </FieldLabel>\n <Input\n {...field}\n id=\"sign-in-identifier\"\n type=\"text\"\n placeholder={t('form.accountPlaceholder')}\n autoComplete=\"username\"\n aria-invalid={fieldState.invalid}\n />\n {fieldState.invalid && <FieldError errors={[fieldState.error]} />}\n </Field>\n )}\n />\n </FieldGroup>\n <div className=\"mt-4\">\n <Button type=\"submit\" className=\"w-full\" disabled={isSubmitting}>\n {isSubmitting && <Spinner />}\n {isSubmitting ? t('form.submitting') : t('form.continue')}\n </Button>\n </div>\n </form>\n );\n};\n","type Messages = Record<string, unknown>;\n\nexport function createTranslator(messages: Messages, namespace?: string) {\n return (key: string, params?: Record<string, string | number>): string => {\n const fullKey = namespace ? `${namespace}.${key}` : key;\n const keys = fullKey.split('.');\n\n let value: unknown = messages;\n for (const k of keys) {\n if (value && typeof value === 'object' && value !== null) {\n value = (value as Record<string, unknown>)[k];\n } else {\n return fullKey;\n }\n }\n\n if (typeof value !== 'string') {\n return fullKey;\n }\n\n // Simple parameter replacement\n if (params) {\n return value.replace(/\\{(\\w+)\\}/g, (_, param) =>\n String(params[param] ?? `{${param}}`),\n );\n }\n\n return value;\n };\n}\n","'use client';\n\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { deepmerge } from 'deepmerge-ts';\nimport createFetchClient from 'openapi-fetch';\nimport createClient from 'openapi-react-query';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useEffect, useState } from 'react';\nimport type { paths } from './data/openapi';\nimport { createTranslator } from './lib/translations';\nimport {\n type AuthClientConfig,\n type AuthResponse,\n defaultAuthClientConfig,\n type Session,\n type User,\n} from './types';\nimport { createCustomFetch } from './utils/custom-fetch';\n\ntype OpenApiHooks = any;\n\ntype SessionState = {\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n error: Error | null;\n};\n\ntype SessionContextValue = SessionState & {\n refresh: () => Promise<void>;\n signOut: () => Promise<void>;\n};\n\ntype ApiContextValue = {\n hooks: OpenApiHooks;\n setAuth: (auth: AuthResponse) => void;\n clearAuth: () => void;\n refresh: () => Promise<void>;\n};\n\ntype ConfigContextValue = {\n config: AuthClientConfig;\n t: (key: string, params?: Record<string, string | number>) => string;\n};\n\nconst SessionContext = createContext<SessionContextValue | null>(null);\nconst ApiContext = createContext<ApiContextValue | null>(null);\nconst ConfigContext = createContext<ConfigContextValue | null>(null);\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 1000 * 60 * 5,\n gcTime: 1000 * 60 * 10,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nexport function useSession() {\n const context = useContext(SessionContext);\n if (!context) {\n throw new Error('useSession must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useApi() {\n const context = useContext(ApiContext);\n if (!context) {\n throw new Error('useApi must be used within MesobAuthProvider');\n }\n return context;\n}\n\nexport function useConfig() {\n const context = useContext(ConfigContext);\n if (!context) {\n throw new Error('useConfig must be used within MesobAuthProvider');\n }\n return context;\n}\n\ntype MesobAuthProviderProps = {\n config: AuthClientConfig;\n children: ReactNode;\n};\n\nexport function MesobAuthProvider({\n config,\n children,\n}: MesobAuthProviderProps) {\n const mergedConfig = deepmerge(\n { ...defaultAuthClientConfig } as Partial<AuthClientConfig>,\n config,\n ) as AuthClientConfig;\n\n const api = createFetchClient<paths>({\n baseUrl: mergedConfig.baseURL,\n fetch: createCustomFetch(mergedConfig),\n });\n\n const hooks = createClient(api);\n\n return (\n <QueryClientProvider client={queryClient}>\n <AuthStateProvider config={mergedConfig} hooks={hooks}>\n {children}\n </AuthStateProvider>\n </QueryClientProvider>\n );\n}\n\ntype AuthStateProviderProps = {\n config: AuthClientConfig;\n hooks: OpenApiHooks;\n children: ReactNode;\n};\n\nfunction AuthStateProvider({\n config,\n hooks,\n children,\n}: AuthStateProviderProps) {\n const [authState, setAuthState] = useState<{\n user: User | null;\n session: Session | null;\n isLoading: boolean;\n error: Error | null;\n }>({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n\n const {\n data: sessionData,\n isLoading: sessionLoading,\n error: sessionError,\n refetch,\n } = hooks.useQuery(\n 'get',\n '/session',\n {},\n {\n enabled: true,\n refetchOnMount: true,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n );\n\n useEffect(() => {\n if (sessionLoading) {\n setAuthState((prev) => ({ ...prev, isLoading: true }));\n return;\n }\n\n if (sessionError) {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: sessionError as Error,\n });\n return;\n }\n\n if (sessionData) {\n setAuthState({\n user: sessionData.user,\n session: sessionData.session,\n isLoading: false,\n error: null,\n });\n return;\n }\n\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n }, [sessionData, sessionLoading, sessionError]);\n\n const refresh = async () => {\n await refetch();\n };\n\n const setAuth = (auth: AuthResponse) => {\n setAuthState({\n user: auth.user,\n session: auth.session,\n isLoading: false,\n error: null,\n });\n };\n\n const clearAuth = () => {\n setAuthState({\n user: null,\n session: null,\n isLoading: false,\n error: null,\n });\n };\n\n const signOutMutation = hooks.useMutation('post', '/sign-out');\n\n const signOut = async () => {\n try {\n await signOutMutation.mutateAsync({});\n } finally {\n clearAuth();\n }\n };\n\n const t = createTranslator(config.messages || {});\n\n return (\n <ConfigContext.Provider value={{ config, t }}>\n <ApiContext.Provider value={{ hooks, setAuth, clearAuth, refresh }}>\n <SessionContext.Provider\n value={{\n user: authState.user,\n session: authState.session,\n isLoading: authState.isLoading,\n isAuthenticated: !!authState.user && !!authState.session,\n error: authState.error,\n refresh,\n signOut,\n }}\n >\n {children}\n </SessionContext.Provider>\n </ApiContext.Provider>\n </ConfigContext.Provider>\n );\n}\n","import { createTranslator } from '../lib/translations';\nimport { useConfig } from '../provider';\n\nexport function useTranslator(namespace?: string) {\n const { config } = useConfig();\n return createTranslator(config.messages || {}, namespace);\n}\n","export function normalizePhone(phone: string): string {\n const cleaned = phone.trim().replace(/\\s/g, '');\n if (cleaned.startsWith('+2519')) {\n return cleaned;\n }\n if (cleaned.startsWith('2519')) {\n return `+${cleaned}`;\n }\n if (cleaned.startsWith('09')) {\n return `+251${cleaned.slice(1)}`;\n }\n if (cleaned.startsWith('9') && cleaned.length === 9) {\n return `+251${cleaned}`;\n }\n return cleaned;\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","useState","useEffect"]}