@mesob/auth-react 0.3.5 → 0.4.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 (124) hide show
  1. package/dist/components/auth/forgot-password.js +5 -1
  2. package/dist/components/auth/forgot-password.js.map +1 -1
  3. package/dist/components/auth/reset-password-form.js +5 -1
  4. package/dist/components/auth/reset-password-form.js.map +1 -1
  5. package/dist/components/auth/set-password.d.ts +9 -0
  6. package/dist/components/auth/set-password.js +527 -0
  7. package/dist/components/auth/set-password.js.map +1 -0
  8. package/dist/components/auth/sign-in.js +22 -1
  9. package/dist/components/auth/sign-in.js.map +1 -1
  10. package/dist/components/auth/sign-up.js +7 -5
  11. package/dist/components/auth/sign-up.js.map +1 -1
  12. package/dist/components/auth/verify-email.js +5 -1
  13. package/dist/components/auth/verify-email.js.map +1 -1
  14. package/dist/components/auth/verify-phone.js +5 -1
  15. package/dist/components/auth/verify-phone.js.map +1 -1
  16. package/dist/components/authorization/deny.d.ts +11 -0
  17. package/dist/components/authorization/deny.js +52 -0
  18. package/dist/components/authorization/deny.js.map +1 -0
  19. package/dist/components/authorization/grant.d.ts +12 -0
  20. package/dist/components/authorization/grant.js +57 -0
  21. package/dist/components/authorization/grant.js.map +1 -0
  22. package/dist/components/error-boundary.d.ts +2 -2
  23. package/dist/components/iam/permission-selector.d.ts +19 -0
  24. package/dist/components/iam/permission-selector.js +122 -0
  25. package/dist/components/iam/permission-selector.js.map +1 -0
  26. package/dist/components/iam/permissions.js +12 -31
  27. package/dist/components/iam/permissions.js.map +1 -1
  28. package/dist/components/iam/role-detail-layout.d.ts +11 -0
  29. package/dist/components/iam/role-detail-layout.js +137 -0
  30. package/dist/components/iam/role-detail-layout.js.map +1 -0
  31. package/dist/components/iam/role-detail-page.d.ts +9 -0
  32. package/dist/components/iam/role-detail-page.js +229 -0
  33. package/dist/components/iam/role-detail-page.js.map +1 -0
  34. package/dist/components/iam/role-permissions-page.d.ts +8 -0
  35. package/dist/components/iam/role-permissions-page.js +397 -0
  36. package/dist/components/iam/role-permissions-page.js.map +1 -0
  37. package/dist/components/iam/roles.js +11 -8
  38. package/dist/components/iam/roles.js.map +1 -1
  39. package/dist/components/iam/users.js +1 -7
  40. package/dist/components/iam/users.js.map +1 -1
  41. package/dist/components/profile/account.js +110 -19
  42. package/dist/components/profile/account.js.map +1 -1
  43. package/dist/components/profile/change-profile.d.ts +2 -1
  44. package/dist/components/profile/change-profile.js +16 -8
  45. package/dist/components/profile/change-profile.js.map +1 -1
  46. package/dist/components/profile/security.js +51 -17
  47. package/dist/components/profile/security.js.map +1 -1
  48. package/dist/index.d.ts +9 -1
  49. package/dist/index.js +1813 -725
  50. package/dist/index.js.map +1 -1
  51. package/dist/pages/auth/forgot-password.d.ts +7 -0
  52. package/dist/pages/auth/forgot-password.js +784 -0
  53. package/dist/pages/auth/forgot-password.js.map +1 -0
  54. package/dist/pages/auth/layout.d.ts +8 -0
  55. package/dist/pages/auth/layout.js +562 -0
  56. package/dist/pages/auth/layout.js.map +1 -0
  57. package/dist/pages/auth/reset-password.d.ts +10 -0
  58. package/dist/pages/auth/reset-password.js +913 -0
  59. package/dist/pages/auth/reset-password.js.map +1 -0
  60. package/dist/pages/auth/set-password.d.ts +10 -0
  61. package/dist/pages/auth/set-password.js +946 -0
  62. package/dist/pages/auth/set-password.js.map +1 -0
  63. package/dist/pages/auth/sign-in.d.ts +10 -0
  64. package/dist/pages/auth/sign-in.js +984 -0
  65. package/dist/pages/auth/sign-in.js.map +1 -0
  66. package/dist/pages/auth/sign-up.d.ts +10 -0
  67. package/dist/pages/auth/sign-up.js +940 -0
  68. package/dist/pages/auth/sign-up.js.map +1 -0
  69. package/dist/pages/auth/verify-email.d.ts +10 -0
  70. package/dist/pages/auth/verify-email.js +950 -0
  71. package/dist/pages/auth/verify-email.js.map +1 -0
  72. package/dist/pages/auth/verify-phone.d.ts +10 -0
  73. package/dist/pages/auth/verify-phone.js +964 -0
  74. package/dist/pages/auth/verify-phone.js.map +1 -0
  75. package/dist/pages/iam/permissions.d.ts +5 -0
  76. package/dist/pages/iam/permissions.js +308 -0
  77. package/dist/pages/iam/permissions.js.map +1 -0
  78. package/dist/pages/iam/role-detail-layout.d.ts +12 -0
  79. package/dist/pages/iam/role-detail-layout.js +145 -0
  80. package/dist/pages/iam/role-detail-layout.js.map +1 -0
  81. package/dist/pages/iam/role-detail.d.ts +12 -0
  82. package/dist/pages/iam/role-detail.js +241 -0
  83. package/dist/pages/iam/role-detail.js.map +1 -0
  84. package/dist/pages/iam/role-permissions.d.ts +12 -0
  85. package/dist/pages/iam/role-permissions.js +409 -0
  86. package/dist/pages/iam/role-permissions.js.map +1 -0
  87. package/dist/pages/iam/role-users.d.ts +12 -0
  88. package/dist/pages/iam/role-users.js +825 -0
  89. package/dist/pages/iam/role-users.js.map +1 -0
  90. package/dist/pages/iam/roles.d.ts +5 -0
  91. package/dist/pages/iam/roles.js +684 -0
  92. package/dist/pages/iam/roles.js.map +1 -0
  93. package/dist/pages/iam/sessions.d.ts +5 -0
  94. package/dist/pages/iam/sessions.js +315 -0
  95. package/dist/pages/iam/sessions.js.map +1 -0
  96. package/dist/pages/iam/tenant-detail.d.ts +10 -0
  97. package/dist/pages/iam/tenant-detail.js +186 -0
  98. package/dist/pages/iam/tenant-detail.js.map +1 -0
  99. package/dist/pages/iam/tenants.d.ts +5 -0
  100. package/dist/pages/iam/tenants.js +610 -0
  101. package/dist/pages/iam/tenants.js.map +1 -0
  102. package/dist/pages/iam/user-activity.d.ts +10 -0
  103. package/dist/pages/iam/user-activity.js +850 -0
  104. package/dist/pages/iam/user-activity.js.map +1 -0
  105. package/dist/pages/iam/user-detail-layout.d.ts +12 -0
  106. package/dist/pages/iam/user-detail-layout.js +106 -0
  107. package/dist/pages/iam/user-detail-layout.js.map +1 -0
  108. package/dist/pages/iam/user-detail.d.ts +10 -0
  109. package/dist/pages/iam/user-detail.js +102 -0
  110. package/dist/pages/iam/user-detail.js.map +1 -0
  111. package/dist/pages/iam/users.d.ts +5 -0
  112. package/dist/pages/iam/users.js +1275 -0
  113. package/dist/pages/iam/users.js.map +1 -0
  114. package/dist/pages/profile/account.d.ts +5 -0
  115. package/dist/pages/profile/account.js +182 -0
  116. package/dist/pages/profile/account.js.map +1 -0
  117. package/dist/pages/profile/layout.d.ts +8 -0
  118. package/dist/pages/profile/layout.js +133 -0
  119. package/dist/pages/profile/layout.js.map +1 -0
  120. package/dist/pages/profile/security.d.ts +5 -0
  121. package/dist/pages/profile/security.js +1539 -0
  122. package/dist/pages/profile/security.js.map +1 -0
  123. package/dist/{types-vcfvnAzQ.d.ts → types-g9QcNRxT.d.ts} +13 -7
  124. package/package.json +102 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/profile/account.tsx","../../../src/provider.tsx","../../../src/utils/cookie.ts"],"sourcesContent":["'use client';\n\nimport { Separator } from '@mesob/ui/components';\nimport { useSession } from '../../provider';\n\nexport function Account() {\n const { user, isAuthenticated } = useSession();\n\n if (!(isAuthenticated && user)) {\n return <div>Sign in required</div>;\n }\n\n return (\n <div className=\"p-6 max-w-4xl\">\n <div className=\"mb-6\">\n <h2 className=\"text-lg font-semibold mb-4\">Profile details</h2>\n <Separator />\n </div>\n\n <div className=\"space-y-6 transition-all duration-300 ease-in-out\">\n {/* Profile Section */}\n <div className=\"flex flex-col md:flex-row items-start gap-4 md:gap-12 min-h-[3rem]\">\n <div className=\"w-full md:w-48 mt-4 shrink-0 h-fit\">\n <span className=\"font-medium text-sm\">Profile</span>\n </div>\n </div>\n\n <Separator />\n\n {/* Email addresses Section */}\n <div className=\"flex flex-col md:flex-row gap-4 md:gap-12 py-2\">\n <div className=\"w-full md:w-48 shrink-0\">\n <span className=\"font-medium text-sm\">Email addresses</span>\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"flex items-center justify-between group\">\n {user.email ? (\n <div className=\"flex items-center gap-3\">\n <span className=\"text-sm\">{user.email}</span>\n </div>\n ) : (\n <span className=\"text-sm text-muted-foreground\">\n No email address\n </span>\n )}\n </div>\n </div>\n </div>\n\n <Separator />\n\n {/* Phone numbers Section */}\n <div className=\"flex flex-col md:flex-row gap-4 md:gap-12 py-2\">\n <div className=\"w-full md:w-48 shrink-0\">\n <span className=\"font-medium text-sm\">Phone numbers</span>\n </div>\n <div className=\"flex-1 space-y-4\">\n {user.phone ? (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm\">{user.phone}</span>\n </div>\n ) : (\n <span className=\"text-sm text-muted-foreground\">\n No phone number\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\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"],"mappings":";;;AAEA,SAAS,iBAAiB;;;ACA1B,SAAS,aAAa,2BAA2B;AACjD,SAAS,iBAAiB;AAC1B,OAAO,uBAAuB;AAC9B,OAAO,kBAAkB;AAEzB,SAAS,eAAe,YAAY,SAAS,gBAAgB;;;ACL7D,IAAM,eACJ,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;;;AD4JvD;AA1FN,IAAM,iBAAiB,cAA0C,IAAI;AACrE,IAAM,aAAa,cAAsC,IAAI;AAC7D,IAAM,gBAAgB,cAAyC,IAAI;AAEnE,IAAM,cAAc,IAAI,YAAY;AAAA,EAClC,gBAAgB;AAAA,IACd,SAAS;AAAA,MACP,sBAAsB;AAAA,IACxB;AAAA,EACF;AACF,CAAC;AAUM,SAAS,aAAkC;AAChD,QAAM,UAAU,WAAW,cAAc;AACzC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACA,SAAO;AACT;;;ADtFW,gBAAAA,MAKL,YALK;AAJJ,SAAS,UAAU;AACxB,QAAM,EAAE,MAAM,gBAAgB,IAAI,WAAW;AAE7C,MAAI,EAAE,mBAAmB,OAAO;AAC9B,WAAO,gBAAAA,KAAC,SAAI,8BAAgB;AAAA,EAC9B;AAEA,SACE,qBAAC,SAAI,WAAU,iBACb;AAAA,yBAAC,SAAI,WAAU,QACb;AAAA,sBAAAA,KAAC,QAAG,WAAU,8BAA6B,6BAAe;AAAA,MAC1D,gBAAAA,KAAC,aAAU;AAAA,OACb;AAAA,IAEA,qBAAC,SAAI,WAAU,qDAEb;AAAA,sBAAAA,KAAC,SAAI,WAAU,sEACb,0BAAAA,KAAC,SAAI,WAAU,sCACb,0BAAAA,KAAC,UAAK,WAAU,uBAAsB,qBAAO,GAC/C,GACF;AAAA,MAEA,gBAAAA,KAAC,aAAU;AAAA,MAGX,qBAAC,SAAI,WAAU,kDACb;AAAA,wBAAAA,KAAC,SAAI,WAAU,2BACb,0BAAAA,KAAC,UAAK,WAAU,uBAAsB,6BAAe,GACvD;AAAA,QACA,gBAAAA,KAAC,SAAI,WAAU,oBACb,0BAAAA,KAAC,SAAI,WAAU,2CACZ,eAAK,QACJ,gBAAAA,KAAC,SAAI,WAAU,2BACb,0BAAAA,KAAC,UAAK,WAAU,WAAW,eAAK,OAAM,GACxC,IAEA,gBAAAA,KAAC,UAAK,WAAU,iCAAgC,8BAEhD,GAEJ,GACF;AAAA,SACF;AAAA,MAEA,gBAAAA,KAAC,aAAU;AAAA,MAGX,qBAAC,SAAI,WAAU,kDACb;AAAA,wBAAAA,KAAC,SAAI,WAAU,2BACb,0BAAAA,KAAC,UAAK,WAAU,uBAAsB,2BAAa,GACrD;AAAA,QACA,gBAAAA,KAAC,SAAI,WAAU,oBACZ,eAAK,QACJ,gBAAAA,KAAC,SAAI,WAAU,qCACb,0BAAAA,KAAC,UAAK,WAAU,WAAW,eAAK,OAAM,GACxC,IAEA,gBAAAA,KAAC,UAAK,WAAU,iCAAgC,6BAEhD,GAEJ;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;","names":["jsx"]}
1
+ {"version":3,"sources":["../../../src/components/profile/account.tsx","../../../src/provider.tsx","../../../src/utils/cookie.ts","../../../src/components/profile/change-profile.tsx"],"sourcesContent":["'use client';\n\nimport { Badge, Card, CardContent, Separator } from '@mesob/ui/components';\nimport { useSession } from '../../provider';\nimport { ChangeProfile } from './change-profile';\n\nexport function Account() {\n const { user, isAuthenticated } = useSession();\n\n if (!(isAuthenticated && user)) {\n return (\n <div className=\"rounded-[1.75rem] border border-dashed border-border bg-muted/30 p-8 text-sm text-muted-foreground\">\n Sign in required.\n </div>\n );\n }\n\n const contactItems = [\n {\n label: 'Email',\n value: user.email ?? 'No email address',\n status: user.emailVerified ? 'Verified' : 'Needs verification',\n },\n {\n label: 'Phone',\n value: user.phone ?? 'No phone number',\n status: user.phoneVerified ? 'Verified' : 'Needs verification',\n },\n ];\n\n return (\n <div className=\"mx-auto flex w-full max-w-6xl flex-col gap-6\">\n <div className=\"relative overflow-hidden rounded-[2rem] border border-border/60 bg-gradient-to-br from-background via-background to-primary/5 p-6 shadow-sm\">\n <div className=\"absolute inset-y-0 right-0 w-1/3 bg-[radial-gradient(circle_at_top,_hsl(var(--primary)/0.16),_transparent_60%)]\" />\n <div className=\"relative flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"outline\">Profile console</Badge>\n <Badge variant=\"secondary\">Live session data</Badge>\n </div>\n <div className=\"max-w-2xl space-y-2\">\n <h2 className=\"text-2xl font-semibold tracking-tight\">\n Account details, contact state, and verification status.\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n Keep the identity surface minimal here. Contact updates and\n sensitive changes stay in security.\n </p>\n </div>\n <ChangeProfile user={user} />\n </div>\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-[1.5fr_0.9fr]\">\n <Card className=\"rounded-[1.75rem] border-border/60 shadow-sm\">\n <CardContent className=\"space-y-5 p-6\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium text-muted-foreground\">\n Contact channels\n </div>\n <div className=\"text-lg font-semibold\">Primary profile data</div>\n </div>\n <Separator />\n <div className=\"space-y-4\">\n {contactItems.map((item) => (\n <div\n key={item.label}\n className=\"flex flex-col gap-3 rounded-2xl border border-border/60 bg-muted/20 p-4 md:flex-row md:items-center md:justify-between\"\n >\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">{item.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {item.value}\n </div>\n </div>\n <Badge variant=\"outline\">{item.status}</Badge>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n\n <Card className=\"rounded-[1.75rem] border-border/60 shadow-sm\">\n <CardContent className=\"space-y-5 p-6\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium text-muted-foreground\">\n Snapshot\n </div>\n <div className=\"text-lg font-semibold\">\n Session-facing identity\n </div>\n </div>\n <Separator />\n <div className=\"grid gap-3\">\n <div className=\"rounded-2xl bg-primary/[0.06] p-4\">\n <div className=\"text-xs uppercase tracking-[0.18em] text-muted-foreground\">\n Full name\n </div>\n <div className=\"mt-2 text-base font-semibold\">\n {user.fullName}\n </div>\n </div>\n <div className=\"rounded-2xl bg-muted/25 p-4\">\n <div className=\"text-xs uppercase tracking-[0.18em] text-muted-foreground\">\n Last sign in\n </div>\n <div className=\"mt-2 text-base font-semibold\">\n {user.lastSignInAt\n ? new Date(user.lastSignInAt).toLocaleString()\n : 'No activity recorded'}\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\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","import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n Badge,\n Button,\n} from '@mesob/ui/components';\nimport { useState } from 'react';\nimport type { User } from '../../types';\n\ntype Props = {\n user: User;\n};\n\nexport const ChangeProfile = ({ user }: Props) => {\n const [isEditing, setIsEditing] = useState(false);\n const initials =\n user.fullName\n ?.split(' ')\n .map((part) => part[0])\n .join('')\n .toUpperCase()\n .slice(0, 2) || 'U';\n\n return (\n <div className=\"w-full rounded-[1.5rem] border border-border/60 bg-background/80 p-5 shadow-sm backdrop-blur\">\n <div className=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex items-center gap-4\">\n <Avatar className=\"h-14 w-14 ring-4 ring-primary/10\">\n <AvatarImage src={user.image || ''} alt={user.fullName || ''} />\n <AvatarFallback>{initials}</AvatarFallback>\n </Avatar>\n <div className=\"space-y-1\">\n <div className=\"text-base font-semibold\">{user.fullName}</div>\n <div className=\"flex flex-wrap gap-2 text-xs text-muted-foreground\">\n <Badge variant=\"outline\">\n {user.emailVerified ? 'Email verified' : 'Email unverified'}\n </Badge>\n <Badge variant=\"outline\">\n {user.phoneVerified ? 'Phone verified' : 'Phone unverified'}\n </Badge>\n </div>\n </div>\n </div>\n <Button\n variant=\"secondary\"\n className=\"rounded-full px-5 text-primary hover:text-primary/80\"\n onClick={() => setIsEditing((v) => !v)}\n >\n {isEditing ? 'Hide editor' : 'Update profile'}\n </Button>\n </div>\n {isEditing && (\n <div className=\"mt-4 rounded-2xl border border-dashed border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground\">\n Profile editing is not wired yet. Contact and password flows below are\n live.\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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","names":["Badge","useState","jsx","jsx","jsxs","Badge"]}
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { U as User } from '../../types-vcfvnAzQ.js';
2
+ import { U as User } from '../../types-g9QcNRxT.js';
3
+ import '@mesob/common';
3
4
 
4
5
  type Props = {
5
6
  user: User;
@@ -3,32 +3,40 @@ import {
3
3
  Avatar,
4
4
  AvatarFallback,
5
5
  AvatarImage,
6
+ Badge,
6
7
  Button
7
8
  } from "@mesob/ui/components";
8
9
  import { useState } from "react";
9
10
  import { jsx, jsxs } from "react/jsx-runtime";
10
11
  var ChangeProfile = ({ user }) => {
11
12
  const [isEditing, setIsEditing] = useState(false);
12
- return /* @__PURE__ */ jsxs("div", { className: "flex-1 w-full", children: [
13
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-1", children: [
13
+ const initials = user.fullName?.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2) || "U";
14
+ return /* @__PURE__ */ jsxs("div", { className: "w-full rounded-[1.5rem] border border-border/60 bg-background/80 p-5 shadow-sm backdrop-blur", children: [
15
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
14
16
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
15
- /* @__PURE__ */ jsxs(Avatar, { className: "h-12 w-12", children: [
17
+ /* @__PURE__ */ jsxs(Avatar, { className: "h-14 w-14 ring-4 ring-primary/10", children: [
16
18
  /* @__PURE__ */ jsx(AvatarImage, { src: user.image || "", alt: user.fullName || "" }),
17
- /* @__PURE__ */ jsx(AvatarFallback, { children: user.fullName ? user.fullName.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : "U" })
19
+ /* @__PURE__ */ jsx(AvatarFallback, { children: initials })
18
20
  ] }),
19
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: user.fullName })
21
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
22
+ /* @__PURE__ */ jsx("div", { className: "text-base font-semibold", children: user.fullName }),
23
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 text-xs text-muted-foreground", children: [
24
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: user.emailVerified ? "Email verified" : "Email unverified" }),
25
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: user.phoneVerified ? "Phone verified" : "Phone unverified" })
26
+ ] })
27
+ ] })
20
28
  ] }),
21
29
  /* @__PURE__ */ jsx(
22
30
  Button,
23
31
  {
24
32
  variant: "secondary",
25
- className: "text-primary hover:text-primary/80",
33
+ className: "rounded-full px-5 text-primary hover:text-primary/80",
26
34
  onClick: () => setIsEditing((v) => !v),
27
- children: "Update profile"
35
+ children: isEditing ? "Hide editor" : "Update profile"
28
36
  }
29
37
  )
30
38
  ] }),
31
- isEditing && /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "Profile editing currently unavailable." })
39
+ isEditing && /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-2xl border border-dashed border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground", children: "Profile editing is not wired yet. Contact and password flows below are live." })
32
40
  ] });
33
41
  };
34
42
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/profile/change-profile.tsx"],"sourcesContent":["import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n Button,\n} from '@mesob/ui/components';\nimport { useState } from 'react';\nimport type { User } from 'src/types';\n\ntype Props = {\n user: User;\n};\n\nexport const ChangeProfile = ({ user }: Props) => {\n const [isEditing, setIsEditing] = useState(false);\n\n return (\n <div className=\"flex-1 w-full\">\n <div className=\"flex items-center justify-between py-1\">\n <div className=\"flex items-center gap-4\">\n <Avatar className=\"h-12 w-12\">\n <AvatarImage src={user.image || ''} alt={user.fullName || ''} />\n <AvatarFallback>\n {user.fullName\n ? user.fullName\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n : 'U'}\n </AvatarFallback>\n </Avatar>\n <span className=\"font-medium\">{user.fullName}</span>\n </div>\n <Button\n variant=\"secondary\"\n className=\"text-primary hover:text-primary/80\"\n onClick={() => setIsEditing((v) => !v)}\n >\n Update profile\n </Button>\n </div>\n {isEditing && (\n <div className=\"mt-2 text-sm text-muted-foreground\">\n Profile editing currently unavailable.\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AAcf,SACE,KADF;AAPH,IAAM,gBAAgB,CAAC,EAAE,KAAK,MAAa;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,SACE,qBAAC,SAAI,WAAU,iBACb;AAAA,yBAAC,SAAI,WAAU,0CACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,6BAAC,UAAO,WAAU,aAChB;AAAA,8BAAC,eAAY,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,YAAY,IAAI;AAAA,UAC9D,oBAAC,kBACE,eAAK,WACF,KAAK,SACF,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EACf,KAAK,EAAE,EACP,YAAY,EACZ,MAAM,GAAG,CAAC,IACb,KACN;AAAA,WACF;AAAA,QACA,oBAAC,UAAK,WAAU,eAAe,eAAK,UAAS;AAAA,SAC/C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,WAAU;AAAA,UACV,SAAS,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;AAAA,UACtC;AAAA;AAAA,MAED;AAAA,OACF;AAAA,IACC,aACC,oBAAC,SAAI,WAAU,sCAAqC,oDAEpD;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/profile/change-profile.tsx"],"sourcesContent":["import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n Badge,\n Button,\n} from '@mesob/ui/components';\nimport { useState } from 'react';\nimport type { User } from '../../types';\n\ntype Props = {\n user: User;\n};\n\nexport const ChangeProfile = ({ user }: Props) => {\n const [isEditing, setIsEditing] = useState(false);\n const initials =\n user.fullName\n ?.split(' ')\n .map((part) => part[0])\n .join('')\n .toUpperCase()\n .slice(0, 2) || 'U';\n\n return (\n <div className=\"w-full rounded-[1.5rem] border border-border/60 bg-background/80 p-5 shadow-sm backdrop-blur\">\n <div className=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex items-center gap-4\">\n <Avatar className=\"h-14 w-14 ring-4 ring-primary/10\">\n <AvatarImage src={user.image || ''} alt={user.fullName || ''} />\n <AvatarFallback>{initials}</AvatarFallback>\n </Avatar>\n <div className=\"space-y-1\">\n <div className=\"text-base font-semibold\">{user.fullName}</div>\n <div className=\"flex flex-wrap gap-2 text-xs text-muted-foreground\">\n <Badge variant=\"outline\">\n {user.emailVerified ? 'Email verified' : 'Email unverified'}\n </Badge>\n <Badge variant=\"outline\">\n {user.phoneVerified ? 'Phone verified' : 'Phone unverified'}\n </Badge>\n </div>\n </div>\n </div>\n <Button\n variant=\"secondary\"\n className=\"rounded-full px-5 text-primary hover:text-primary/80\"\n onClick={() => setIsEditing((v) => !v)}\n >\n {isEditing ? 'Hide editor' : 'Update profile'}\n </Button>\n </div>\n {isEditing && (\n <div className=\"mt-4 rounded-2xl border border-dashed border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground\">\n Profile editing is not wired yet. Contact and password flows below are\n live.\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AAqBf,SACE,KADF;AAdH,IAAM,gBAAgB,CAAC,EAAE,KAAK,MAAa;AAChD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAChD,QAAM,WACJ,KAAK,UACD,MAAM,GAAG,EACV,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,EACrB,KAAK,EAAE,EACP,YAAY,EACZ,MAAM,GAAG,CAAC,KAAK;AAEpB,SACE,qBAAC,SAAI,WAAU,gGACb;AAAA,yBAAC,SAAI,WAAU,sEACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,6BAAC,UAAO,WAAU,oCAChB;AAAA,8BAAC,eAAY,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,YAAY,IAAI;AAAA,UAC9D,oBAAC,kBAAgB,oBAAS;AAAA,WAC5B;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAI,WAAU,2BAA2B,eAAK,UAAS;AAAA,UACxD,qBAAC,SAAI,WAAU,sDACb;AAAA,gCAAC,SAAM,SAAQ,WACZ,eAAK,gBAAgB,mBAAmB,oBAC3C;AAAA,YACA,oBAAC,SAAM,SAAQ,WACZ,eAAK,gBAAgB,mBAAmB,oBAC3C;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,WAAU;AAAA,UACV,SAAS,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;AAAA,UAEpC,sBAAY,gBAAgB;AAAA;AAAA,MAC/B;AAAA,OACF;AAAA,IACC,aACC,oBAAC,SAAI,WAAU,2GAA0G,0FAGzH;AAAA,KAEJ;AAEJ;","names":[]}
@@ -1,14 +1,7 @@
1
1
  "use client";
2
2
 
3
- // src/components/profile/change-email-form.tsx
4
- import {
5
- Button as Button3,
6
- Collapsible,
7
- CollapsibleContent,
8
- CollapsibleTrigger
9
- } from "@mesob/ui/components";
10
- import { IconChevronDown } from "@tabler/icons-react";
11
- import { useState as useState5 } from "react";
3
+ // src/components/profile/security.tsx
4
+ import { Badge, Card, CardContent, Separator } from "@mesob/ui/components";
12
5
 
13
6
  // src/provider.tsx
14
7
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -80,6 +73,16 @@ function useConfig() {
80
73
  return context;
81
74
  }
82
75
 
76
+ // src/components/profile/change-email-form.tsx
77
+ import {
78
+ Button as Button3,
79
+ Collapsible,
80
+ CollapsibleContent,
81
+ CollapsibleTrigger
82
+ } from "@mesob/ui/components";
83
+ import { IconChevronDown } from "@tabler/icons-react";
84
+ import { useState as useState5 } from "react";
85
+
83
86
  // src/components/profile/request-change-email-form.tsx
84
87
  import { zodResolver } from "@hookform/resolvers/zod";
85
88
  import { Button, Input, Label, Spinner } from "@mesob/ui/components";
@@ -1462,15 +1465,46 @@ function ChangePhoneForm() {
1462
1465
  // src/components/profile/security.tsx
1463
1466
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1464
1467
  function Security() {
1465
- return /* @__PURE__ */ jsxs9("div", { className: "p-6 space-y-6", children: [
1466
- /* @__PURE__ */ jsxs9("div", { children: [
1467
- /* @__PURE__ */ jsx12("h1", { className: "text-2xl font-semibold", children: "Security" }),
1468
- /* @__PURE__ */ jsx12("p", { className: "text-muted-foreground", children: "Manage your security settings" })
1468
+ const { user } = useSession();
1469
+ return /* @__PURE__ */ jsxs9("div", { className: "mx-auto flex w-full max-w-6xl flex-col gap-6", children: [
1470
+ /* @__PURE__ */ jsxs9("div", { className: "relative overflow-hidden rounded-[2rem] border border-border/60 bg-gradient-to-br from-background via-background to-amber-500/5 p-6 shadow-sm", children: [
1471
+ /* @__PURE__ */ jsx12("div", { className: "absolute left-0 top-0 h-32 w-32 rounded-full bg-primary/10 blur-3xl" }),
1472
+ /* @__PURE__ */ jsxs9("div", { className: "relative flex flex-col gap-4", children: [
1473
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap gap-2", children: [
1474
+ /* @__PURE__ */ jsx12(Badge, { variant: "outline", children: "Security center" }),
1475
+ /* @__PURE__ */ jsx12(Badge, { variant: "secondary", children: user?.emailVerified || user?.phoneVerified ? "Recovery methods available" : "Add a recovery method" })
1476
+ ] }),
1477
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
1478
+ /* @__PURE__ */ jsx12("h1", { className: "text-2xl font-semibold tracking-tight", children: "Security" }),
1479
+ /* @__PURE__ */ jsx12("p", { className: "max-w-2xl text-sm text-muted-foreground", children: "Password, email, and phone updates all live here. Keep recovery channels current so account recovery stays predictable." })
1480
+ ] })
1481
+ ] })
1469
1482
  ] }),
1470
- /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
1471
- /* @__PURE__ */ jsx12(ChangePasswordForm, {}),
1472
- /* @__PURE__ */ jsx12(ChangeEmailForm, {}),
1473
- /* @__PURE__ */ jsx12(ChangePhoneForm, {})
1483
+ /* @__PURE__ */ jsxs9("div", { className: "grid gap-4 lg:grid-cols-[0.85fr_1.4fr]", children: [
1484
+ /* @__PURE__ */ jsx12(Card, { className: "rounded-[1.75rem] border-border/60 shadow-sm", children: /* @__PURE__ */ jsxs9(CardContent, { className: "space-y-5 p-6", children: [
1485
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-1", children: [
1486
+ /* @__PURE__ */ jsx12("div", { className: "text-sm font-medium text-muted-foreground", children: "Verification state" }),
1487
+ /* @__PURE__ */ jsx12("div", { className: "text-lg font-semibold", children: "Recovery readiness" })
1488
+ ] }),
1489
+ /* @__PURE__ */ jsx12(Separator, {}),
1490
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-3", children: [
1491
+ /* @__PURE__ */ jsxs9("div", { className: "rounded-2xl border border-border/60 bg-muted/20 p-4", children: [
1492
+ /* @__PURE__ */ jsx12("div", { className: "text-sm font-medium", children: "Email" }),
1493
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 text-sm text-muted-foreground", children: user?.email ?? "No email added" }),
1494
+ /* @__PURE__ */ jsx12("div", { className: "mt-3", children: /* @__PURE__ */ jsx12(Badge, { variant: "outline", children: user?.emailVerified ? "Verified" : "Unverified" }) })
1495
+ ] }),
1496
+ /* @__PURE__ */ jsxs9("div", { className: "rounded-2xl border border-border/60 bg-muted/20 p-4", children: [
1497
+ /* @__PURE__ */ jsx12("div", { className: "text-sm font-medium", children: "Phone" }),
1498
+ /* @__PURE__ */ jsx12("div", { className: "mt-1 text-sm text-muted-foreground", children: user?.phone ?? "No phone added" }),
1499
+ /* @__PURE__ */ jsx12("div", { className: "mt-3", children: /* @__PURE__ */ jsx12(Badge, { variant: "outline", children: user?.phoneVerified ? "Verified" : "Unverified" }) })
1500
+ ] })
1501
+ ] })
1502
+ ] }) }),
1503
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
1504
+ /* @__PURE__ */ jsx12(ChangePasswordForm, {}),
1505
+ /* @__PURE__ */ jsx12(ChangeEmailForm, {}),
1506
+ /* @__PURE__ */ jsx12(ChangePhoneForm, {})
1507
+ ] })
1474
1508
  ] })
1475
1509
  ] });
1476
1510
  }