@mesob/auth-react 0.3.4 → 0.3.5

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 (43) hide show
  1. package/dist/components/auth/auth-layout.d.ts +1 -1
  2. package/dist/components/auth/auth-layout.js +10 -2
  3. package/dist/components/auth/auth-layout.js.map +1 -1
  4. package/dist/components/auth/countdown.js +8 -6
  5. package/dist/components/auth/countdown.js.map +1 -1
  6. package/dist/components/auth/forgot-password.js +16 -18
  7. package/dist/components/auth/forgot-password.js.map +1 -1
  8. package/dist/components/auth/reset-password-form.js +17 -20
  9. package/dist/components/auth/reset-password-form.js.map +1 -1
  10. package/dist/components/auth/sign-in.js +23 -25
  11. package/dist/components/auth/sign-in.js.map +1 -1
  12. package/dist/components/auth/sign-up.js +18 -24
  13. package/dist/components/auth/sign-up.js.map +1 -1
  14. package/dist/components/auth/verification-form.js +24 -27
  15. package/dist/components/auth/verification-form.js.map +1 -1
  16. package/dist/components/auth/verify-email.js +35 -30
  17. package/dist/components/auth/verify-email.js.map +1 -1
  18. package/dist/components/auth/verify-phone.js +35 -30
  19. package/dist/components/auth/verify-phone.js.map +1 -1
  20. package/dist/components/error-boundary.d.ts +2 -2
  21. package/dist/components/iam/permissions.js +9 -2
  22. package/dist/components/iam/permissions.js.map +1 -1
  23. package/dist/components/iam/roles.js +9 -2
  24. package/dist/components/iam/roles.js.map +1 -1
  25. package/dist/components/iam/tenants.js +9 -2
  26. package/dist/components/iam/tenants.js.map +1 -1
  27. package/dist/components/iam/users.js +9 -2
  28. package/dist/components/iam/users.js.map +1 -1
  29. package/dist/components/profile/change-email-form.js +26 -29
  30. package/dist/components/profile/change-email-form.js.map +1 -1
  31. package/dist/components/profile/change-phone-form.js +26 -29
  32. package/dist/components/profile/change-phone-form.js.map +1 -1
  33. package/dist/components/profile/otp-verification-modal.js +24 -27
  34. package/dist/components/profile/otp-verification-modal.js.map +1 -1
  35. package/dist/components/profile/security.js +38 -41
  36. package/dist/components/profile/security.js.map +1 -1
  37. package/dist/components/profile/verify-change-email-form.js +24 -27
  38. package/dist/components/profile/verify-change-email-form.js.map +1 -1
  39. package/dist/components/profile/verify-change-phone-form.js +24 -27
  40. package/dist/components/profile/verify-change-phone-form.js.map +1 -1
  41. package/dist/index.js +147 -159
  42. package/dist/index.js.map +1 -1
  43. package/package.json +2 -2
@@ -203,20 +203,28 @@ var AuthLayout = ({
203
203
  logoImage
204
204
  }) => {
205
205
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
206
- /* @__PURE__ */ jsx2("div", { className: "flex size-8 mb-6 w-full items-center justify-center rounded-md", children: /* @__PURE__ */ jsx2("img", { src: logoImage || "", alt: "Mesob", width: 42, height: 42 }) }),
206
+ /* @__PURE__ */ jsx2("div", { className: "flex size-8 mb-6 w-full items-center justify-center rounded-md", children: /* @__PURE__ */ jsx2(
207
+ "img",
208
+ {
209
+ src: logoImage || "",
210
+ alt: title,
211
+ width: 42,
212
+ height: 42
213
+ }
214
+ ) }),
207
215
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
208
216
  /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
209
217
  description && /* @__PURE__ */ jsx2("p", { className: "mt-2 text-sm text-muted-foreground", children: description })
210
218
  ] }),
211
219
  children,
212
- /* @__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 }) })
220
+ footer && /* @__PURE__ */ jsx2("div", { className: "mt-2 w-full", children: /* @__PURE__ */ jsx2("div", { className: "w-full text-center text-sm text-muted-foreground", children: footer }) })
213
221
  ] });
214
222
  };
215
223
 
216
224
  // src/components/auth/verification-form.tsx
217
225
  import { zodResolver } from "@hookform/resolvers/zod";
218
226
  import {
219
- Button as Button2,
227
+ Button,
220
228
  Form,
221
229
  FormControl,
222
230
  FormField,
@@ -225,14 +233,13 @@ import {
225
233
  FormMessage,
226
234
  InputOTP,
227
235
  InputOTPGroup,
228
- InputOTPSlot,
229
- Spinner as Spinner2
236
+ InputOTPSlot
230
237
  } from "@mesob/ui/components";
231
238
  import { useForm } from "react-hook-form";
232
239
  import { z } from "zod";
233
240
 
234
241
  // src/components/auth/countdown.tsx
235
- import { Button, Spinner } from "@mesob/ui/components";
242
+ import { Spinner } from "@mesob/ui/components";
236
243
  import { useEffect, useState as useState2 } from "react";
237
244
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
238
245
  var Countdown = ({
@@ -268,17 +275,19 @@ var Countdown = ({
268
275
  setIsResending(false);
269
276
  }
270
277
  };
278
+ const busy = isResending || resending;
271
279
  if (seconds > 0) {
272
- return /* @__PURE__ */ jsx3(Button, { variant: "ghost", disabled: true, children: t("resendIn", { seconds }) });
280
+ return /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: t("resendIn", { seconds }) });
273
281
  }
274
282
  return /* @__PURE__ */ jsxs2(
275
- Button,
283
+ "button",
276
284
  {
277
- variant: "ghost",
285
+ type: "button",
278
286
  onClick: handleResend,
279
- disabled: isResending || resending,
287
+ disabled: busy,
288
+ className: "text-sm text-primary hover:underline disabled:opacity-50 flex items-center gap-1",
280
289
  children: [
281
- isResending || resending && /* @__PURE__ */ jsx3(Spinner, {}),
290
+ busy && /* @__PURE__ */ jsx3(Spinner, { className: "h-3 w-3" }),
282
291
  t("resend")
283
292
  ]
284
293
  }
@@ -298,13 +307,12 @@ var VerificationForm = ({
298
307
  const t = useTranslator("Auth.verification");
299
308
  const form = useForm({
300
309
  resolver: zodResolver(verificationSchema(t)),
301
- defaultValues: {
302
- code: ""
303
- }
310
+ defaultValues: { code: "" }
304
311
  });
305
312
  const handleSubmit = form.handleSubmit(async (values) => {
306
313
  await onSubmit(values);
307
314
  });
315
+ const codeLength = form.watch("code").length;
308
316
  return /* @__PURE__ */ jsx4(Form, { ...form, children: /* @__PURE__ */ jsxs3(
309
317
  "form",
310
318
  {
@@ -342,21 +350,18 @@ var VerificationForm = ({
342
350
  ] })
343
351
  }
344
352
  ),
345
- /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center", children: [
346
- /* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }),
347
- /* @__PURE__ */ jsxs3(
348
- Button2,
349
- {
350
- type: "submit",
351
- form: "verification-form",
352
- disabled: isLoading || form.watch("code").length !== 6,
353
- children: [
354
- isLoading && /* @__PURE__ */ jsx4(Spinner2, {}),
355
- t("form.confirm")
356
- ]
357
- }
358
- )
359
- ] })
353
+ /* @__PURE__ */ jsx4(
354
+ Button,
355
+ {
356
+ type: "submit",
357
+ form: "verification-form",
358
+ className: "w-full",
359
+ disabled: isLoading || codeLength !== 6,
360
+ loading: isLoading,
361
+ children: t("form.confirm")
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx4("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }) })
360
365
  ]
361
366
  }
362
367
  ) });
@@ -483,7 +488,7 @@ var VerifyEmail = ({
483
488
  return /* @__PURE__ */ jsxs4(
484
489
  AuthLayout,
485
490
  {
486
- title: t("email.title"),
491
+ title: config.ui.name,
487
492
  description: t("email.description"),
488
493
  logoImage,
489
494
  footer: Link ? /* @__PURE__ */ jsx5(Link, { href: signInLink, className: "text-primary hover:underline", children: footer("backToSignIn") }) : /* @__PURE__ */ jsx5(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/auth/verify-email.tsx","../../../src/hooks/use-translator.ts","../../../src/lib/translations.ts","../../../src/provider.tsx","../../../src/utils/cookie.ts","../../../src/constants/auth.error.codes.ts","../../../src/utils/handle-error.ts","../../../src/components/auth/auth-layout.tsx","../../../src/components/auth/verification-form.tsx","../../../src/components/auth/countdown.tsx"],"sourcesContent":["'use client';\n\nimport { Alert, AlertDescription, AlertTitle } from '@mesob/ui/components';\nimport { useMesob } from '@mesob/ui/providers';\nimport { IconAlertCircle } from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport { toast } from 'sonner';\nimport { useTranslator } from '../../hooks/use-translator';\nimport { useApi, useConfig } from '../../provider';\nimport type { AuthErrorContent } from '../../utils/handle-error';\nimport { handleError } from '../../utils/handle-error';\nimport { AuthLayout } from './auth-layout';\nimport { VerificationForm } from './verification-form';\n\ntype VerifyEmailProps = {\n verificationId: string;\n email: string;\n redirectUrl?: string;\n};\n\nexport const VerifyEmail = ({\n verificationId,\n email,\n redirectUrl,\n}: VerifyEmailProps) => {\n const { hooks, setAuth } = useApi();\n const { config } = useConfig();\n const mesob = useMesob();\n const t = useTranslator('Auth.verification');\n const common = useTranslator('Common');\n const footer = useTranslator('Auth.forgotPassword.footer');\n const Link = mesob?.navigation?.Link;\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthErrorContent | null>(null);\n\n const verifyEmailMutation = hooks.useMutation(\n 'post',\n '/email/verification/confirm',\n );\n const requestEmailMutation = hooks.useMutation(\n 'post',\n '/email/verification/request',\n );\n\n const signInLink = config.navigation?.links?.signIn || '/auth/sign-in';\n const onNavigate =\n config.navigation?.onNavigate ||\n ((path: string) => {\n if (typeof window !== 'undefined') {\n window.location.href = path;\n }\n });\n const logoImage = config.ui.logoImage;\n const defaultRedirect =\n redirectUrl || config.navigation?.defaultRedirectUrl || '/';\n\n useEffect(() => {\n if (error) {\n toast.error(error.title || 'Error', {\n description: error.description,\n });\n }\n }, [error]);\n\n const handleSubmit = async (values: { code: string }) => {\n if (!verificationId) {\n setError({\n title: t('errors.fallback'),\n description: t('errors.missingVerificationId'),\n });\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const res = await verifyEmailMutation.mutateAsync({\n body: {\n verificationId,\n code: values.code,\n },\n });\n if ('user' in res && 'session' in res) {\n setAuth(res);\n }\n onNavigate(defaultRedirect);\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 res = await requestEmailMutation.mutateAsync({\n body: {\n email,\n },\n });\n if (res.verificationId) {\n onNavigate(\n `/auth/verify-email?verificationId=${res.verificationId}&email=${encodeURIComponent(email)}`,\n );\n } else {\n setError({\n title: t('errors.fallback'),\n description: t('errors.resendFailed'),\n });\n }\n } catch (err) {\n handleError(err, setError, t);\n }\n };\n\n if (!verificationId) {\n return (\n <AuthLayout\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 </AuthLayout>\n );\n }\n\n let errorContent: AuthErrorContent | null = null;\n if (error) {\n if (typeof error === 'string') {\n errorContent = { title: 'Error', description: error };\n } else {\n errorContent = error;\n }\n }\n\n return (\n <AuthLayout\n title={t('email.title')}\n description={t('email.description')}\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={\n isLoading ||\n verifyEmailMutation.isPending ||\n requestEmailMutation.isPending\n }\n error={error}\n />\n {errorContent && (\n <Alert variant=\"destructive\" className=\"mt-4\">\n <IconAlertCircle className=\"h-4 w-4\" />\n <AlertTitle>{errorContent.title}</AlertTitle>\n <AlertDescription>{errorContent.description}</AlertDescription>\n </Alert>\n )}\n </AuthLayout>\n );\n};\n","import { useMesob } from '@mesob/ui/providers';\nimport { createTranslator } from '../lib/translations';\nimport { useConfig } from '../provider';\n\nexport function useTranslator(namespace?: string) {\n const mesob = useMesob();\n const { config } = useConfig();\n\n if (mesob?.t) {\n return (key: string, params?: Record<string, string | number>): string => {\n const fullKey = namespace ? `${namespace}.${key}` : key;\n return mesob.t?.(fullKey, params) ?? fullKey;\n };\n }\n\n return createTranslator(config.messages || {}, namespace);\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, useMemo, 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 { getSessionCookieName } from './utils/cookie';\nimport { createCustomFetch } from './utils/custom-fetch';\n\n// biome-ignore lint/suspicious/noExplicitAny: OpenAPI hooks type\ntype OpenApiHooks = any;\n\n// --- Utility: Check if running on server ---\nfunction isServer(): boolean {\n return typeof document === 'undefined';\n}\n\n/**\n * @deprecated Cookie is httpOnly and cannot be read client-side.\n * Use `useSession().isAuthenticated` instead.\n * This function always returns false on client.\n */\nexport function hasAuthCookie(_cookieName: string): boolean {\n // Cookie is httpOnly, can't check client-side\n // Always return false - use useSession() for auth status\n return false;\n}\n\n// --- Types ---\nexport type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\ntype AuthState = {\n user: User | null;\n session: Session | null;\n status: AuthStatus;\n error: Error | null;\n};\n\ntype SessionContextValue = AuthState & {\n isLoading: boolean;\n isAuthenticated: boolean;\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 cookieName: string;\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 refetchOnWindowFocus: false,\n },\n },\n});\n\n// --- Hooks ---\n\n/**\n * Get session state including user, session, and auth status.\n * - `status`: 'loading' | 'authenticated' | 'unauthenticated'\n * - `isLoading`: true while fetching session\n * - `isAuthenticated`: true if user and session exist\n */\nexport function useSession(): SessionContextValue {\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(): ApiContextValue {\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(): ConfigContextValue {\n const context = useContext(ConfigContext);\n if (!context) {\n throw new Error('useConfig must be used within MesobAuthProvider');\n }\n return context;\n}\n\n/**\n * @deprecated Cookie is httpOnly, can't be checked client-side.\n * Use `useSession().isAuthenticated` instead.\n */\nexport function useHasAuthCookie(): boolean {\n const { status } = useSession();\n return status === 'authenticated' || status === 'loading';\n}\n\n// --- Provider ---\n\ntype MesobAuthProviderProps = {\n config: AuthClientConfig;\n children: ReactNode;\n};\n\nexport function MesobAuthProvider({\n config,\n children,\n}: MesobAuthProviderProps) {\n const mergedConfig = useMemo(\n () =>\n deepmerge(\n { ...defaultAuthClientConfig } as Partial<AuthClientConfig>,\n config,\n ) as AuthClientConfig,\n [config],\n );\n\n const api = useMemo(\n () =>\n createFetchClient<paths>({\n baseUrl: mergedConfig.baseURL,\n fetch: createCustomFetch(mergedConfig),\n }),\n [mergedConfig],\n );\n\n const hooks = useMemo(() => createClient(api), [api]);\n const cookieName = useMemo(\n () => getSessionCookieName(mergedConfig),\n [mergedConfig],\n );\n\n return (\n <QueryClientProvider client={queryClient}>\n <AuthStateProvider\n config={mergedConfig}\n hooks={hooks}\n cookieName={cookieName}\n >\n {children}\n </AuthStateProvider>\n </QueryClientProvider>\n );\n}\n\ntype AuthStateProviderProps = {\n config: AuthClientConfig;\n hooks: OpenApiHooks;\n cookieName: string;\n children: ReactNode;\n};\n\nfunction AuthStateProvider({\n config,\n hooks,\n cookieName,\n children,\n}: AuthStateProviderProps) {\n // Manual override for sign-out / sign-in\n const [override, setOverride] = useState<AuthState | null>(null);\n\n // Always fetch session - cookie is httpOnly, can't check client-side\n // Server will read the cookie and return user/session if valid\n const {\n data: sessionData,\n isLoading,\n isFetched,\n error: sessionError,\n refetch,\n } = hooks.useQuery(\n 'get',\n '/session',\n {},\n {\n enabled: !(override || isServer()),\n refetchOnMount: false,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n gcTime: 0,\n staleTime: 0,\n },\n );\n\n // Derive state directly - no useEffect\n const user = override?.user ?? sessionData?.user ?? null;\n const session = override?.session ?? sessionData?.session ?? null;\n const error = override?.error ?? (sessionError as Error | null);\n\n // Check error status code\n const errorStatus = (() => {\n if (!sessionError) {\n return null;\n }\n const err = sessionError as { status?: number };\n return err.status ?? null;\n })();\n\n // Check if error is a network/connection error\n const isNetworkError = (() => {\n if (!sessionError) {\n return false;\n }\n const error = sessionError as Error & { cause?: unknown; data?: unknown };\n const errorMessage =\n error.message || String(error) || JSON.stringify(error);\n // Network errors: TypeError, DOMException, or fetch failures\n if (\n error instanceof TypeError ||\n error instanceof DOMException ||\n error.name === 'TypeError' ||\n errorMessage.includes('Failed to fetch') ||\n errorMessage.includes('ERR_CONNECTION_REFUSED') ||\n errorMessage.includes('NetworkError') ||\n errorMessage.includes('Network request failed') ||\n errorMessage.includes('fetch failed')\n ) {\n return true;\n }\n // Check error cause\n if (error.cause) {\n const causeStr = String(error.cause);\n if (\n causeStr.includes('Failed to fetch') ||\n causeStr.includes('ERR_CONNECTION_REFUSED') ||\n causeStr.includes('NetworkError')\n ) {\n return true;\n }\n }\n return false;\n })();\n\n // Compute status\n // biome-ignore lint: Status determination requires multiple checks\n const status: AuthStatus = (() => {\n if (override) {\n return override.status;\n }\n if (isServer()) {\n return 'loading';\n }\n if (user && session) {\n return 'authenticated';\n }\n // Check for network errors or auth errors first - allow auth page to show\n if (isNetworkError || errorStatus === 401) {\n return 'unauthenticated';\n }\n // If we have an error but it's not a network error, still check loading state\n if (sessionError && !isNetworkError && errorStatus !== 401) {\n if (errorStatus && errorStatus >= 500) {\n return 'authenticated';\n }\n // Other errors mean unauthenticated\n if (isFetched) {\n return 'unauthenticated';\n }\n }\n if (isLoading || !isFetched) {\n return 'loading';\n }\n if (isFetched && !user && !session) {\n return 'unauthenticated';\n }\n return 'unauthenticated';\n })();\n\n const signOutMutation = hooks.useMutation('post', '/sign-out');\n const t = createTranslator(config.messages || {});\n\n const setAuth = (auth: AuthResponse) => {\n setOverride({\n user: auth.user,\n session: auth.session,\n status: 'authenticated',\n error: null,\n });\n };\n\n const clearAuth = () => {\n setOverride({\n user: null,\n session: null,\n status: 'unauthenticated',\n error: null,\n });\n };\n\n const refresh = async () => {\n setOverride(null);\n await refetch();\n };\n\n const signOut = async () => {\n try {\n await signOutMutation.mutateAsync({});\n } finally {\n clearAuth();\n }\n };\n\n return (\n <ConfigContext.Provider value={{ config, cookieName, t }}>\n <ApiContext.Provider value={{ hooks, setAuth, clearAuth, refresh }}>\n <SessionContext.Provider\n value={{\n user,\n session,\n status,\n error,\n isLoading: status === 'loading',\n isAuthenticated: status === 'authenticated',\n refresh,\n signOut,\n }}\n >\n {children}\n </SessionContext.Provider>\n </ApiContext.Provider>\n </ConfigContext.Provider>\n );\n}\n","import type { AuthClientConfig } from '../types';\n\nconst isProduction =\n typeof process !== 'undefined' && process.env.NODE_ENV === 'production';\n\nexport const getSessionCookieName = (config: AuthClientConfig): string => {\n const prefix = config.cookiePrefix || '';\n const baseName = 'session_token';\n if (prefix) {\n return `${prefix}_${baseName}`;\n }\n return isProduction ? '__Host-session_token' : baseName;\n};\n","export const AUTH_ERROR_MAPPING: Record<\n string,\n { title: string; description: string }\n> = {\n USER_NOT_FOUND: {\n title: 'Account Not Found',\n description:\n 'We could not find an account with that identifier. Please check your spelling or sign up.',\n },\n INVALID_PASSWORD: {\n title: 'Invalid Password',\n description: 'The password you entered is incorrect. Please try again.',\n },\n USER_EXISTS: {\n title: 'Account Already Exists',\n description:\n 'An account with this identifier already exists. Please sign in instead.',\n },\n VERIFICATION_EXPIRED: {\n title: 'Verification Expired',\n description:\n 'The verification code or link has expired. Please request a new one.',\n },\n VERIFICATION_MISMATCH: {\n title: 'Invalid Code',\n description:\n 'The verification code you entered is invalid. Please double-check and try again.',\n },\n VERIFICATION_NOT_FOUND: {\n title: 'Verification Not Found',\n description:\n 'We could not find a pending verification request. Please restart the process.',\n },\n TOO_MANY_ATTEMPTS: {\n title: 'Too Many Attempts',\n description:\n 'You have made too many requests recently. Please wait a moment before trying again.',\n },\n REQUIRES_VERIFICATION: {\n title: 'Verification Required',\n description:\n 'You need to verify your account before you can continue. Please check your email or phone.',\n },\n UNAUTHORIZED: {\n title: 'Unauthorized',\n description:\n 'You are not authorized to perform this action. Please sign in again.',\n },\n ACCESS_DENIED: {\n title: 'Access Denied',\n description:\n 'You do not have permission to access this resource. Please contact support if you believe this is an error.',\n },\n HAS_NO_PASSWORD: {\n title: 'No Password Set',\n description:\n 'Your account does not have a password set (e.g. social login). Please sign in with your provider or reset your password.',\n },\n};\n\nexport const validCodes = Object.keys(AUTH_ERROR_MAPPING);\n","import { AUTH_ERROR_MAPPING, validCodes } from '../constants/auth.error.codes';\nimport type { AuthError } from '../types';\n\nexport type AuthErrorContent = {\n title: string;\n description: string;\n};\n\ntype TranslatorFunction = (\n key: string,\n params?: Record<string, string | number>,\n) => string;\n\n// Type guard to check if error is an AuthError\nfunction isAuthError(err: unknown): err is AuthError {\n return (\n typeof err === 'object' &&\n err !== null &&\n 'message' in err &&\n typeof (err as { message: unknown }).message === 'string'\n );\n}\n\nfunction extractErrorCode(err: AuthError): string {\n if (err.code && validCodes.includes(err.code)) {\n return err.code;\n }\n if (err.message) {\n const messageUpper = err.message.toUpperCase().trim();\n if (validCodes.includes(messageUpper)) {\n return messageUpper;\n }\n }\n return '';\n}\n\nfunction sanitizeErrorMessage(message: string): string {\n const lowerMessage = message.toLowerCase();\n const isDatabaseError =\n lowerMessage.includes('failed query') ||\n lowerMessage.includes('select') ||\n lowerMessage.includes('insert') ||\n lowerMessage.includes('update') ||\n lowerMessage.includes('delete') ||\n lowerMessage.includes('from') ||\n lowerMessage.includes('where') ||\n lowerMessage.includes('limit') ||\n lowerMessage.includes('params:') ||\n lowerMessage.includes('query') ||\n message.includes('\"iam\".') ||\n message.includes('\"tenants\"') ||\n message.includes('\"users\"') ||\n message.includes('\"sessions\"') ||\n message.includes('\"accounts\"') ||\n lowerMessage.includes('relation') ||\n lowerMessage.includes('column') ||\n lowerMessage.includes('syntax error') ||\n lowerMessage.includes('database') ||\n lowerMessage.includes('postgres') ||\n lowerMessage.includes('sql');\n\n if (isDatabaseError) {\n return 'An error occurred while processing your request';\n }\n\n return message;\n}\n\nfunction handleAuthError(\n err: AuthError,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) {\n const errorCode = extractErrorCode(err);\n\n if (errorCode && AUTH_ERROR_MAPPING[errorCode]) {\n const mapping = AUTH_ERROR_MAPPING[errorCode];\n setError({\n title: mapping.title,\n description: mapping.description,\n });\n return;\n }\n\n const sanitizedMessage = sanitizeErrorMessage(\n err.message || t('errors.fallback'),\n );\n setError({\n title: t('errors.fallback'),\n description: sanitizedMessage,\n });\n}\n\nfunction handleGenericError(\n err: unknown,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) {\n const rawMessage = err instanceof Error ? err.message : t('errors.fallback');\n const sanitizedMessage = sanitizeErrorMessage(rawMessage);\n setError({\n title: 'Error',\n description: sanitizedMessage,\n });\n}\n\nexport const handleError = (\n err: unknown,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) => {\n if (isAuthError(err)) {\n handleAuthError(err, setError, t);\n } else {\n handleGenericError(err, setError, t);\n }\n};\n","'use client';\n\nimport type { ReactNode } from 'react';\n\ntype AuthLayoutProps = {\n title: string;\n description?: string;\n children: ReactNode;\n footer?: ReactNode;\n logoImage?: string;\n};\n\nexport const AuthLayout = ({\n title,\n description,\n children,\n footer,\n logoImage,\n}: AuthLayoutProps) => {\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=\"Mesob\" 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 <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 {\n Button,\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n Spinner,\n} from '@mesob/ui/components';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { useTranslator } from '../../hooks/use-translator';\nimport type { AuthErrorContent } from '../../utils/handle-error';\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?: AuthErrorContent | string | null;\n};\n\nconst verificationSchema = (t: (key: string) => string) =>\n z.object({\n code: z.string().length(6, t('form.codeLength')),\n });\n\nexport const VerificationForm = ({\n onSubmit,\n onResend,\n isLoading = false,\n}: VerificationFormProps) => {\n const t = useTranslator('Auth.verification');\n const form = useForm<VerificationFormValues>({\n resolver: zodResolver(verificationSchema(t)),\n defaultValues: {\n code: '',\n },\n });\n\n const handleSubmit = form.handleSubmit(async (values) => {\n await onSubmit(values);\n });\n\n return (\n <Form {...form}>\n <form\n id=\"verification-form\"\n onSubmit={handleSubmit}\n className=\"space-y-4\"\n >\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 <InputOTP\n maxLength={6}\n required\n value={field.value ?? ''}\n onChange={field.onChange}\n onBlur={field.onBlur}\n containerClassName=\"gap-4 justify-center mb-2 flex items-center\"\n >\n <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\">\n <InputOTPSlot className=\"h-12\" index={0} />\n <InputOTPSlot className=\"h-12\" index={1} />\n <InputOTPSlot className=\"h-12\" index={2} />\n <InputOTPSlot className=\"h-12\" index={3} />\n <InputOTPSlot className=\"h-12\" index={4} />\n <InputOTPSlot className=\"h-12\" index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <div className=\"flex justify-between items-center\">\n <Countdown onResend={onResend} resending={isLoading} />\n <Button\n type=\"submit\"\n form=\"verification-form\"\n disabled={isLoading || form.watch('code').length !== 6}\n >\n {isLoading && <Spinner />}\n {t('form.confirm')}\n </Button>\n </div>\n </form>\n </Form>\n );\n};\n","'use client';\n\nimport { Button, Spinner } from '@mesob/ui/components';\nimport { useEffect, useState } from 'react';\nimport { useTranslator } from '../../hooks/use-translator';\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 = useTranslator('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 && <Spinner />)}\n {t('resend')}\n </Button>\n );\n};\n"],"mappings":"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","names":["useMesob","useEffect","useState","jsx","Button","Spinner","useState","jsx","jsxs","useState","jsx","jsxs","Button","Spinner","jsx","jsxs","useMesob","useState","useEffect"]}
1
+ {"version":3,"sources":["../../../src/components/auth/verify-email.tsx","../../../src/hooks/use-translator.ts","../../../src/lib/translations.ts","../../../src/provider.tsx","../../../src/utils/cookie.ts","../../../src/constants/auth.error.codes.ts","../../../src/utils/handle-error.ts","../../../src/components/auth/auth-layout.tsx","../../../src/components/auth/verification-form.tsx","../../../src/components/auth/countdown.tsx"],"sourcesContent":["'use client';\n\nimport { Alert, AlertDescription, AlertTitle } from '@mesob/ui/components';\nimport { useMesob } from '@mesob/ui/providers';\nimport { IconAlertCircle } from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport { toast } from 'sonner';\nimport { useTranslator } from '../../hooks/use-translator';\nimport { useApi, useConfig } from '../../provider';\nimport type { AuthErrorContent } from '../../utils/handle-error';\nimport { handleError } from '../../utils/handle-error';\nimport { AuthLayout } from './auth-layout';\nimport { VerificationForm } from './verification-form';\n\ntype VerifyEmailProps = {\n verificationId: string;\n email: string;\n redirectUrl?: string;\n};\n\nexport const VerifyEmail = ({\n verificationId,\n email,\n redirectUrl,\n}: VerifyEmailProps) => {\n const { hooks, setAuth } = useApi();\n const { config } = useConfig();\n const mesob = useMesob();\n const t = useTranslator('Auth.verification');\n const common = useTranslator('Common');\n const footer = useTranslator('Auth.forgotPassword.footer');\n const Link = mesob?.navigation?.Link;\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthErrorContent | null>(null);\n\n const verifyEmailMutation = hooks.useMutation(\n 'post',\n '/email/verification/confirm',\n );\n const requestEmailMutation = hooks.useMutation(\n 'post',\n '/email/verification/request',\n );\n\n const signInLink = config.navigation?.links?.signIn || '/auth/sign-in';\n const onNavigate =\n config.navigation?.onNavigate ||\n ((path: string) => {\n if (typeof window !== 'undefined') {\n window.location.href = path;\n }\n });\n const logoImage = config.ui.logoImage;\n const defaultRedirect =\n redirectUrl || config.navigation?.defaultRedirectUrl || '/';\n\n useEffect(() => {\n if (error) {\n toast.error(error.title || 'Error', {\n description: error.description,\n });\n }\n }, [error]);\n\n const handleSubmit = async (values: { code: string }) => {\n if (!verificationId) {\n setError({\n title: t('errors.fallback'),\n description: t('errors.missingVerificationId'),\n });\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const res = await verifyEmailMutation.mutateAsync({\n body: {\n verificationId,\n code: values.code,\n },\n });\n if ('user' in res && 'session' in res) {\n setAuth(res);\n }\n onNavigate(defaultRedirect);\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 res = await requestEmailMutation.mutateAsync({\n body: {\n email,\n },\n });\n if (res.verificationId) {\n onNavigate(\n `/auth/verify-email?verificationId=${res.verificationId}&email=${encodeURIComponent(email)}`,\n );\n } else {\n setError({\n title: t('errors.fallback'),\n description: t('errors.resendFailed'),\n });\n }\n } catch (err) {\n handleError(err, setError, t);\n }\n };\n\n if (!verificationId) {\n return (\n <AuthLayout\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 </AuthLayout>\n );\n }\n\n let errorContent: AuthErrorContent | null = null;\n if (error) {\n if (typeof error === 'string') {\n errorContent = { title: 'Error', description: error };\n } else {\n errorContent = error;\n }\n }\n\n return (\n <AuthLayout\n title={config.ui.name}\n description={t('email.description')}\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={\n isLoading ||\n verifyEmailMutation.isPending ||\n requestEmailMutation.isPending\n }\n error={error}\n />\n {errorContent && (\n <Alert variant=\"destructive\" className=\"mt-4\">\n <IconAlertCircle className=\"h-4 w-4\" />\n <AlertTitle>{errorContent.title}</AlertTitle>\n <AlertDescription>{errorContent.description}</AlertDescription>\n </Alert>\n )}\n </AuthLayout>\n );\n};\n","import { useMesob } from '@mesob/ui/providers';\nimport { createTranslator } from '../lib/translations';\nimport { useConfig } from '../provider';\n\nexport function useTranslator(namespace?: string) {\n const mesob = useMesob();\n const { config } = useConfig();\n\n if (mesob?.t) {\n return (key: string, params?: Record<string, string | number>): string => {\n const fullKey = namespace ? `${namespace}.${key}` : key;\n return mesob.t?.(fullKey, params) ?? fullKey;\n };\n }\n\n return createTranslator(config.messages || {}, namespace);\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, useMemo, 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 { getSessionCookieName } from './utils/cookie';\nimport { createCustomFetch } from './utils/custom-fetch';\n\n// biome-ignore lint/suspicious/noExplicitAny: OpenAPI hooks type\ntype OpenApiHooks = any;\n\n// --- Utility: Check if running on server ---\nfunction isServer(): boolean {\n return typeof document === 'undefined';\n}\n\n/**\n * @deprecated Cookie is httpOnly and cannot be read client-side.\n * Use `useSession().isAuthenticated` instead.\n * This function always returns false on client.\n */\nexport function hasAuthCookie(_cookieName: string): boolean {\n // Cookie is httpOnly, can't check client-side\n // Always return false - use useSession() for auth status\n return false;\n}\n\n// --- Types ---\nexport type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\ntype AuthState = {\n user: User | null;\n session: Session | null;\n status: AuthStatus;\n error: Error | null;\n};\n\ntype SessionContextValue = AuthState & {\n isLoading: boolean;\n isAuthenticated: boolean;\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 cookieName: string;\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 refetchOnWindowFocus: false,\n },\n },\n});\n\n// --- Hooks ---\n\n/**\n * Get session state including user, session, and auth status.\n * - `status`: 'loading' | 'authenticated' | 'unauthenticated'\n * - `isLoading`: true while fetching session\n * - `isAuthenticated`: true if user and session exist\n */\nexport function useSession(): SessionContextValue {\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(): ApiContextValue {\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(): ConfigContextValue {\n const context = useContext(ConfigContext);\n if (!context) {\n throw new Error('useConfig must be used within MesobAuthProvider');\n }\n return context;\n}\n\n/**\n * @deprecated Cookie is httpOnly, can't be checked client-side.\n * Use `useSession().isAuthenticated` instead.\n */\nexport function useHasAuthCookie(): boolean {\n const { status } = useSession();\n return status === 'authenticated' || status === 'loading';\n}\n\n// --- Provider ---\n\ntype MesobAuthProviderProps = {\n config: AuthClientConfig;\n children: ReactNode;\n};\n\nexport function MesobAuthProvider({\n config,\n children,\n}: MesobAuthProviderProps) {\n const mergedConfig = useMemo(\n () =>\n deepmerge(\n { ...defaultAuthClientConfig } as Partial<AuthClientConfig>,\n config,\n ) as AuthClientConfig,\n [config],\n );\n\n const api = useMemo(\n () =>\n createFetchClient<paths>({\n baseUrl: mergedConfig.baseURL,\n fetch: createCustomFetch(mergedConfig),\n }),\n [mergedConfig],\n );\n\n const hooks = useMemo(() => createClient(api), [api]);\n const cookieName = useMemo(\n () => getSessionCookieName(mergedConfig),\n [mergedConfig],\n );\n\n return (\n <QueryClientProvider client={queryClient}>\n <AuthStateProvider\n config={mergedConfig}\n hooks={hooks}\n cookieName={cookieName}\n >\n {children}\n </AuthStateProvider>\n </QueryClientProvider>\n );\n}\n\ntype AuthStateProviderProps = {\n config: AuthClientConfig;\n hooks: OpenApiHooks;\n cookieName: string;\n children: ReactNode;\n};\n\nfunction AuthStateProvider({\n config,\n hooks,\n cookieName,\n children,\n}: AuthStateProviderProps) {\n // Manual override for sign-out / sign-in\n const [override, setOverride] = useState<AuthState | null>(null);\n\n // Always fetch session - cookie is httpOnly, can't check client-side\n // Server will read the cookie and return user/session if valid\n const {\n data: sessionData,\n isLoading,\n isFetched,\n error: sessionError,\n refetch,\n } = hooks.useQuery(\n 'get',\n '/session',\n {},\n {\n enabled: !(override || isServer()),\n refetchOnMount: false,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n gcTime: 0,\n staleTime: 0,\n },\n );\n\n // Derive state directly - no useEffect\n const user = override?.user ?? sessionData?.user ?? null;\n const session = override?.session ?? sessionData?.session ?? null;\n const error = override?.error ?? (sessionError as Error | null);\n\n // Check error status code\n const errorStatus = (() => {\n if (!sessionError) {\n return null;\n }\n const err = sessionError as { status?: number };\n return err.status ?? null;\n })();\n\n // Check if error is a network/connection error\n const isNetworkError = (() => {\n if (!sessionError) {\n return false;\n }\n const error = sessionError as Error & { cause?: unknown; data?: unknown };\n const errorMessage =\n error.message || String(error) || JSON.stringify(error);\n // Network errors: TypeError, DOMException, or fetch failures\n if (\n error instanceof TypeError ||\n error instanceof DOMException ||\n error.name === 'TypeError' ||\n errorMessage.includes('Failed to fetch') ||\n errorMessage.includes('ERR_CONNECTION_REFUSED') ||\n errorMessage.includes('NetworkError') ||\n errorMessage.includes('Network request failed') ||\n errorMessage.includes('fetch failed')\n ) {\n return true;\n }\n // Check error cause\n if (error.cause) {\n const causeStr = String(error.cause);\n if (\n causeStr.includes('Failed to fetch') ||\n causeStr.includes('ERR_CONNECTION_REFUSED') ||\n causeStr.includes('NetworkError')\n ) {\n return true;\n }\n }\n return false;\n })();\n\n // Compute status\n // biome-ignore lint: Status determination requires multiple checks\n const status: AuthStatus = (() => {\n if (override) {\n return override.status;\n }\n if (isServer()) {\n return 'loading';\n }\n if (user && session) {\n return 'authenticated';\n }\n // Check for network errors or auth errors first - allow auth page to show\n if (isNetworkError || errorStatus === 401) {\n return 'unauthenticated';\n }\n // If we have an error but it's not a network error, still check loading state\n if (sessionError && !isNetworkError && errorStatus !== 401) {\n if (errorStatus && errorStatus >= 500) {\n return 'authenticated';\n }\n // Other errors mean unauthenticated\n if (isFetched) {\n return 'unauthenticated';\n }\n }\n if (isLoading || !isFetched) {\n return 'loading';\n }\n if (isFetched && !user && !session) {\n return 'unauthenticated';\n }\n return 'unauthenticated';\n })();\n\n const signOutMutation = hooks.useMutation('post', '/sign-out');\n const t = createTranslator(config.messages || {});\n\n const setAuth = (auth: AuthResponse) => {\n setOverride({\n user: auth.user,\n session: auth.session,\n status: 'authenticated',\n error: null,\n });\n };\n\n const clearAuth = () => {\n setOverride({\n user: null,\n session: null,\n status: 'unauthenticated',\n error: null,\n });\n };\n\n const refresh = async () => {\n setOverride(null);\n await refetch();\n };\n\n const signOut = async () => {\n try {\n await signOutMutation.mutateAsync({});\n } finally {\n clearAuth();\n }\n };\n\n return (\n <ConfigContext.Provider value={{ config, cookieName, t }}>\n <ApiContext.Provider value={{ hooks, setAuth, clearAuth, refresh }}>\n <SessionContext.Provider\n value={{\n user,\n session,\n status,\n error,\n isLoading: status === 'loading',\n isAuthenticated: status === 'authenticated',\n refresh,\n signOut,\n }}\n >\n {children}\n </SessionContext.Provider>\n </ApiContext.Provider>\n </ConfigContext.Provider>\n );\n}\n","import type { AuthClientConfig } from '../types';\n\nconst isProduction =\n typeof process !== 'undefined' && process.env.NODE_ENV === 'production';\n\nexport const getSessionCookieName = (config: AuthClientConfig): string => {\n const prefix = config.cookiePrefix || '';\n const baseName = 'session_token';\n if (prefix) {\n return `${prefix}_${baseName}`;\n }\n return isProduction ? '__Host-session_token' : baseName;\n};\n","export const AUTH_ERROR_MAPPING: Record<\n string,\n { title: string; description: string }\n> = {\n USER_NOT_FOUND: {\n title: 'Account Not Found',\n description:\n 'We could not find an account with that identifier. Please check your spelling or sign up.',\n },\n INVALID_PASSWORD: {\n title: 'Invalid Password',\n description: 'The password you entered is incorrect. Please try again.',\n },\n USER_EXISTS: {\n title: 'Account Already Exists',\n description:\n 'An account with this identifier already exists. Please sign in instead.',\n },\n VERIFICATION_EXPIRED: {\n title: 'Verification Expired',\n description:\n 'The verification code or link has expired. Please request a new one.',\n },\n VERIFICATION_MISMATCH: {\n title: 'Invalid Code',\n description:\n 'The verification code you entered is invalid. Please double-check and try again.',\n },\n VERIFICATION_NOT_FOUND: {\n title: 'Verification Not Found',\n description:\n 'We could not find a pending verification request. Please restart the process.',\n },\n TOO_MANY_ATTEMPTS: {\n title: 'Too Many Attempts',\n description:\n 'You have made too many requests recently. Please wait a moment before trying again.',\n },\n REQUIRES_VERIFICATION: {\n title: 'Verification Required',\n description:\n 'You need to verify your account before you can continue. Please check your email or phone.',\n },\n UNAUTHORIZED: {\n title: 'Unauthorized',\n description:\n 'You are not authorized to perform this action. Please sign in again.',\n },\n ACCESS_DENIED: {\n title: 'Access Denied',\n description:\n 'You do not have permission to access this resource. Please contact support if you believe this is an error.',\n },\n HAS_NO_PASSWORD: {\n title: 'No Password Set',\n description:\n 'Your account does not have a password set (e.g. social login). Please sign in with your provider or reset your password.',\n },\n};\n\nexport const validCodes = Object.keys(AUTH_ERROR_MAPPING);\n","import { AUTH_ERROR_MAPPING, validCodes } from '../constants/auth.error.codes';\nimport type { AuthError } from '../types';\n\nexport type AuthErrorContent = {\n title: string;\n description: string;\n};\n\ntype TranslatorFunction = (\n key: string,\n params?: Record<string, string | number>,\n) => string;\n\n// Type guard to check if error is an AuthError\nfunction isAuthError(err: unknown): err is AuthError {\n return (\n typeof err === 'object' &&\n err !== null &&\n 'message' in err &&\n typeof (err as { message: unknown }).message === 'string'\n );\n}\n\nfunction extractErrorCode(err: AuthError): string {\n if (err.code && validCodes.includes(err.code)) {\n return err.code;\n }\n if (err.message) {\n const messageUpper = err.message.toUpperCase().trim();\n if (validCodes.includes(messageUpper)) {\n return messageUpper;\n }\n }\n return '';\n}\n\nfunction sanitizeErrorMessage(message: string): string {\n const lowerMessage = message.toLowerCase();\n const isDatabaseError =\n lowerMessage.includes('failed query') ||\n lowerMessage.includes('select') ||\n lowerMessage.includes('insert') ||\n lowerMessage.includes('update') ||\n lowerMessage.includes('delete') ||\n lowerMessage.includes('from') ||\n lowerMessage.includes('where') ||\n lowerMessage.includes('limit') ||\n lowerMessage.includes('params:') ||\n lowerMessage.includes('query') ||\n message.includes('\"iam\".') ||\n message.includes('\"tenants\"') ||\n message.includes('\"users\"') ||\n message.includes('\"sessions\"') ||\n message.includes('\"accounts\"') ||\n lowerMessage.includes('relation') ||\n lowerMessage.includes('column') ||\n lowerMessage.includes('syntax error') ||\n lowerMessage.includes('database') ||\n lowerMessage.includes('postgres') ||\n lowerMessage.includes('sql');\n\n if (isDatabaseError) {\n return 'An error occurred while processing your request';\n }\n\n return message;\n}\n\nfunction handleAuthError(\n err: AuthError,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) {\n const errorCode = extractErrorCode(err);\n\n if (errorCode && AUTH_ERROR_MAPPING[errorCode]) {\n const mapping = AUTH_ERROR_MAPPING[errorCode];\n setError({\n title: mapping.title,\n description: mapping.description,\n });\n return;\n }\n\n const sanitizedMessage = sanitizeErrorMessage(\n err.message || t('errors.fallback'),\n );\n setError({\n title: t('errors.fallback'),\n description: sanitizedMessage,\n });\n}\n\nfunction handleGenericError(\n err: unknown,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) {\n const rawMessage = err instanceof Error ? err.message : t('errors.fallback');\n const sanitizedMessage = sanitizeErrorMessage(rawMessage);\n setError({\n title: 'Error',\n description: sanitizedMessage,\n });\n}\n\nexport const handleError = (\n err: unknown,\n setError: (error: AuthErrorContent | null) => void,\n t: TranslatorFunction,\n) => {\n if (isAuthError(err)) {\n handleAuthError(err, setError, t);\n } else {\n handleGenericError(err, setError, t);\n }\n};\n","'use client';\n\nimport type { ReactNode } from 'react';\n\ntype AuthLayoutProps = {\n title: ReactNode;\n description?: string;\n children: ReactNode;\n footer?: ReactNode;\n logoImage?: string;\n};\n\nexport const AuthLayout = ({\n title,\n description,\n children,\n footer,\n logoImage,\n}: AuthLayoutProps) => {\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\n src={logoImage || ''}\n alt={title as string}\n width={42}\n height={42}\n />\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 {footer && (\n <div className=\"mt-2 w-full\">\n <div className=\"w-full text-center text-sm text-muted-foreground\">\n {footer}\n </div>\n </div>\n )}\n </div>\n );\n};\n","'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport {\n Button,\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n InputOTP,\n InputOTPGroup,\n InputOTPSlot,\n} from '@mesob/ui/components';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { useTranslator } from '../../hooks/use-translator';\nimport type { AuthErrorContent } from '../../utils/handle-error';\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?: AuthErrorContent | string | null;\n};\n\nconst verificationSchema = (t: (key: string) => string) =>\n z.object({\n code: z.string().length(6, t('form.codeLength')),\n });\n\nexport const VerificationForm = ({\n onSubmit,\n onResend,\n isLoading = false,\n}: VerificationFormProps) => {\n const t = useTranslator('Auth.verification');\n const form = useForm<VerificationFormValues>({\n resolver: zodResolver(verificationSchema(t)),\n defaultValues: { code: '' },\n });\n\n const handleSubmit = form.handleSubmit(async (values) => {\n await onSubmit(values);\n });\n\n const codeLength = form.watch('code').length;\n\n return (\n <Form {...form}>\n <form\n id=\"verification-form\"\n onSubmit={handleSubmit}\n className=\"space-y-4\"\n >\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 <InputOTP\n maxLength={6}\n required\n value={field.value ?? ''}\n onChange={field.onChange}\n onBlur={field.onBlur}\n containerClassName=\"gap-4 justify-center mb-2 flex items-center\"\n >\n <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\">\n <InputOTPSlot className=\"h-12\" index={0} />\n <InputOTPSlot className=\"h-12\" index={1} />\n <InputOTPSlot className=\"h-12\" index={2} />\n <InputOTPSlot className=\"h-12\" index={3} />\n <InputOTPSlot className=\"h-12\" index={4} />\n <InputOTPSlot className=\"h-12\" index={5} />\n </InputOTPGroup>\n </InputOTP>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button\n type=\"submit\"\n form=\"verification-form\"\n className=\"w-full\"\n disabled={isLoading || codeLength !== 6}\n loading={isLoading}\n >\n {t('form.confirm')}\n </Button>\n <div className=\"flex justify-center\">\n <Countdown onResend={onResend} resending={isLoading} />\n </div>\n </form>\n </Form>\n );\n};\n","'use client';\n\nimport { Spinner } from '@mesob/ui/components';\nimport { useEffect, useState } from 'react';\nimport { useTranslator } from '../../hooks/use-translator';\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 = useTranslator('Common');\n const [seconds, setSeconds] = useState(initialSeconds);\n const [isResending, setIsResending] = useState(false);\n\n useEffect(() => {\n if (seconds <= 0) {\n return;\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 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 // handled by parent\n } finally {\n setIsResending(false);\n }\n };\n\n const busy = isResending || resending;\n\n if (seconds > 0) {\n return (\n <p className=\"text-sm text-muted-foreground\">\n {t('resendIn', { seconds })}\n </p>\n );\n }\n\n return (\n <button\n type=\"button\"\n onClick={handleResend}\n disabled={busy}\n className=\"text-sm text-primary hover:underline disabled:opacity-50 flex items-center gap-1\"\n >\n {busy && <Spinner className=\"h-3 w-3\" />}\n {t('resend')}\n </button>\n );\n};\n"],"mappings":"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","names":["useMesob","useEffect","useState","jsx","useState","jsx","jsxs","useState","jsx","jsxs","jsx","jsxs","useMesob","useState","useEffect"]}
@@ -203,20 +203,28 @@ var AuthLayout = ({
203
203
  logoImage
204
204
  }) => {
205
205
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
206
- /* @__PURE__ */ jsx2("div", { className: "flex size-8 mb-6 w-full items-center justify-center rounded-md", children: /* @__PURE__ */ jsx2("img", { src: logoImage || "", alt: "Mesob", width: 42, height: 42 }) }),
206
+ /* @__PURE__ */ jsx2("div", { className: "flex size-8 mb-6 w-full items-center justify-center rounded-md", children: /* @__PURE__ */ jsx2(
207
+ "img",
208
+ {
209
+ src: logoImage || "",
210
+ alt: title,
211
+ width: 42,
212
+ height: 42
213
+ }
214
+ ) }),
207
215
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
208
216
  /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
209
217
  description && /* @__PURE__ */ jsx2("p", { className: "mt-2 text-sm text-muted-foreground", children: description })
210
218
  ] }),
211
219
  children,
212
- /* @__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 }) })
220
+ footer && /* @__PURE__ */ jsx2("div", { className: "mt-2 w-full", children: /* @__PURE__ */ jsx2("div", { className: "w-full text-center text-sm text-muted-foreground", children: footer }) })
213
221
  ] });
214
222
  };
215
223
 
216
224
  // src/components/auth/verification-form.tsx
217
225
  import { zodResolver } from "@hookform/resolvers/zod";
218
226
  import {
219
- Button as Button2,
227
+ Button,
220
228
  Form,
221
229
  FormControl,
222
230
  FormField,
@@ -225,14 +233,13 @@ import {
225
233
  FormMessage,
226
234
  InputOTP,
227
235
  InputOTPGroup,
228
- InputOTPSlot,
229
- Spinner as Spinner2
236
+ InputOTPSlot
230
237
  } from "@mesob/ui/components";
231
238
  import { useForm } from "react-hook-form";
232
239
  import { z } from "zod";
233
240
 
234
241
  // src/components/auth/countdown.tsx
235
- import { Button, Spinner } from "@mesob/ui/components";
242
+ import { Spinner } from "@mesob/ui/components";
236
243
  import { useEffect, useState as useState2 } from "react";
237
244
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
238
245
  var Countdown = ({
@@ -268,17 +275,19 @@ var Countdown = ({
268
275
  setIsResending(false);
269
276
  }
270
277
  };
278
+ const busy = isResending || resending;
271
279
  if (seconds > 0) {
272
- return /* @__PURE__ */ jsx3(Button, { variant: "ghost", disabled: true, children: t("resendIn", { seconds }) });
280
+ return /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: t("resendIn", { seconds }) });
273
281
  }
274
282
  return /* @__PURE__ */ jsxs2(
275
- Button,
283
+ "button",
276
284
  {
277
- variant: "ghost",
285
+ type: "button",
278
286
  onClick: handleResend,
279
- disabled: isResending || resending,
287
+ disabled: busy,
288
+ className: "text-sm text-primary hover:underline disabled:opacity-50 flex items-center gap-1",
280
289
  children: [
281
- isResending || resending && /* @__PURE__ */ jsx3(Spinner, {}),
290
+ busy && /* @__PURE__ */ jsx3(Spinner, { className: "h-3 w-3" }),
282
291
  t("resend")
283
292
  ]
284
293
  }
@@ -298,13 +307,12 @@ var VerificationForm = ({
298
307
  const t = useTranslator("Auth.verification");
299
308
  const form = useForm({
300
309
  resolver: zodResolver(verificationSchema(t)),
301
- defaultValues: {
302
- code: ""
303
- }
310
+ defaultValues: { code: "" }
304
311
  });
305
312
  const handleSubmit = form.handleSubmit(async (values) => {
306
313
  await onSubmit(values);
307
314
  });
315
+ const codeLength = form.watch("code").length;
308
316
  return /* @__PURE__ */ jsx4(Form, { ...form, children: /* @__PURE__ */ jsxs3(
309
317
  "form",
310
318
  {
@@ -342,21 +350,18 @@ var VerificationForm = ({
342
350
  ] })
343
351
  }
344
352
  ),
345
- /* @__PURE__ */ jsxs3("div", { className: "flex justify-between items-center", children: [
346
- /* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }),
347
- /* @__PURE__ */ jsxs3(
348
- Button2,
349
- {
350
- type: "submit",
351
- form: "verification-form",
352
- disabled: isLoading || form.watch("code").length !== 6,
353
- children: [
354
- isLoading && /* @__PURE__ */ jsx4(Spinner2, {}),
355
- t("form.confirm")
356
- ]
357
- }
358
- )
359
- ] })
353
+ /* @__PURE__ */ jsx4(
354
+ Button,
355
+ {
356
+ type: "submit",
357
+ form: "verification-form",
358
+ className: "w-full",
359
+ disabled: isLoading || codeLength !== 6,
360
+ loading: isLoading,
361
+ children: t("form.confirm")
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx4("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx4(Countdown, { onResend, resending: isLoading }) })
360
365
  ]
361
366
  }
362
367
  ) });
@@ -486,7 +491,7 @@ var VerifyPhone = ({
486
491
  return /* @__PURE__ */ jsxs4(
487
492
  AuthLayout,
488
493
  {
489
- title: t("phone.title"),
494
+ title: config.ui.name,
490
495
  description: t("phone.description", {
491
496
  target: phone || t("phone.missingPhone")
492
497
  }),