@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/pages/iam/users.tsx","../../../src/lib/query-options.ts","../../../src/provider.tsx","../../../src/utils/cookie.ts","../../../src/pages/iam/users/_components/bulk-invite-user-form.tsx","../../../src/pages/iam/shared/navigation.tsx","../../../src/pages/iam/shared/page-helpers.tsx","../../../src/pages/iam/users/_components/invite-user-shared.tsx","../../../src/pages/iam/users/_components/invite-user-form.tsx","../../../src/pages/iam/users/_components/users-list.tsx","../../../src/pages/iam/users/_components/user-card.tsx","../../../src/pages/iam/users/_components/user-form.tsx"],"sourcesContent":["'use client';\n\nimport {\n EntityDrawerTrigger,\n EntityFilter,\n EntityHeader,\n EntitySearch,\n EntitySort,\n EntityViewToggle,\n PageBody,\n PageContainer,\n useBreadcrumbs,\n useEntityPagination,\n useEntityParams,\n} from '@mesob/ui/components';\nimport { IconUsers } from '@tabler/icons-react';\nimport { useState } from 'react';\nimport type { paths } from '../../data/openapi';\nimport { defaultEntityQueryOptions } from '../../lib/query-options';\nimport { useApi } from '../../provider';\nimport { BulkInviteUserForm } from './users/_components/bulk-invite-user-form';\nimport { InviteUserForm } from './users/_components/invite-user-form';\nimport { UsersList } from './users/_components/users-list';\n\nexport default function UsersPage() {\n const { hooks } = useApi();\n useBreadcrumbs({\n items: [\n { label: 'Home', href: '/dashboard' },\n { label: 'IAM', href: '/iam/users' },\n { label: 'Users' },\n ],\n });\n const [inviteOpen, setInviteOpen] = useState(false);\n const [bulkInviteOpen, setBulkInviteOpen] = useState(false);\n\n const { queryConfig, params, setParams } = useEntityParams({\n searchKey: 'search',\n searchParamName: 'handle',\n });\n const usersQuery = queryConfig as {\n params: {\n query: NonNullable<paths['/users']['get']['parameters']['query']>;\n };\n };\n\n const { data, isPending, isFetching } = hooks.useQuery(\n 'get',\n '/users',\n usersQuery,\n defaultEntityQueryOptions,\n );\n\n const isLoading = isPending || isFetching;\n const users = data?.users ?? [];\n const { total, pageCount } = useEntityPagination({\n items: users,\n total: data?.total,\n pageSize: params.pageSize,\n });\n\n return (\n <PageContainer className=\"flex flex-1 flex-col gap-4 p-4 pt-0\">\n <PageBody className=\"px-0\">\n <EntityHeader\n icon={<IconUsers className=\"h-5 w-5\" />}\n title=\"Users\"\n actions={\n <div className=\"flex items-center gap-2\">\n <EntityDrawerTrigger\n mode=\"new\"\n entity=\"User\"\n label=\"Invite user\"\n open={inviteOpen}\n onOpenChange={setInviteOpen}\n >\n {(open, onClose) => (\n <InviteUserForm open={open} onClose={onClose} />\n )}\n </EntityDrawerTrigger>\n <EntityDrawerTrigger\n mode=\"new\"\n entity=\"User\"\n label=\"Bulk invite\"\n variant=\"outline\"\n open={bulkInviteOpen}\n onOpenChange={setBulkInviteOpen}\n >\n {(open, onClose) => (\n <BulkInviteUserForm open={open} onClose={onClose} />\n )}\n </EntityDrawerTrigger>\n </div>\n }\n search={\n <EntitySearch paramKey=\"search\" placeholder=\"Search users...\" />\n }\n filter={\n <EntityFilter\n options={[\n { label: 'All', value: '' },\n { label: 'Email Verified', value: 'emailVerified' },\n { label: 'Phone Verified', value: 'phoneVerified' },\n { label: 'Not Verified', value: 'notVerified' },\n ]}\n placeholder=\"Filter\"\n />\n }\n sort={\n <EntitySort\n options={[\n { label: 'Created', value: 'createdAt' },\n { label: 'Updated', value: 'updatedAt' },\n { label: 'Name', value: 'fullName' },\n { label: 'Last Sign In', value: 'lastSignInAt' },\n ]}\n />\n }\n view={<EntityViewToggle views={['table', 'card']} />}\n />\n <UsersList\n data={users}\n isLoading={isLoading}\n view={(params.view || 'table') as 'table' | 'card'}\n pageIndex={params.page - 1}\n pageSize={params.pageSize}\n pageCount={pageCount}\n totalRows={total}\n onCreateNew={() => setInviteOpen(true)}\n onPageChange={(p) => setParams({ page: p + 1 })}\n onPageSizeChange={(size) => setParams({ pageSize: size, page: 1 })}\n />\n </PageBody>\n </PageContainer>\n );\n}\n","import { keepPreviousData } from '@tanstack/react-query';\n\nexport const defaultEntityQueryOptions = {\n refetchOnMount: false,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n staleTime: 60 * 1000,\n gcTime: 5 * 60 * 1000,\n placeholderData: keepPreviousData,\n retry: 1,\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","'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EntityDrawer,\n EntityFormActions,\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n Input,\n Textarea,\n} from '@mesob/ui/components';\nimport { IconDownload, IconUsers } from '@tabler/icons-react';\nimport { useQueryClient } from '@tanstack/react-query';\nimport type { ChangeEvent } from 'react';\nimport { useEffect, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { toast } from 'sonner';\nimport { z } from 'zod';\nimport { authApi$ } from '../../shared/page-helpers';\nimport type {\n BulkInviteFailure,\n BulkInviteResponse,\n} from './invite-user-shared';\nimport {\n downloadInviteTemplate,\n InviteResultCard,\n inviteTemplateCsv,\n parseBulkInviteCsv,\n} from './invite-user-shared';\n\nconst schema = z.object({\n sourceFile: z.string().optional(),\n csvText: z.string().trim().min(1, 'CSV text is required'),\n});\n\ntype FormData = z.infer<typeof schema>;\n\ntype BulkInviteUserFormProps = {\n open: boolean;\n onClose: () => void;\n};\n\nconst defaults: FormData = {\n sourceFile: '',\n csvText: '',\n};\n\nexport function BulkInviteUserForm({ open, onClose }: BulkInviteUserFormProps) {\n const qc = useQueryClient();\n const [invited, setInvited] = useState<BulkInviteResponse['invited']>([]);\n const [failed, setFailed] = useState<BulkInviteFailure[]>([]);\n\n const form = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: defaults,\n });\n const { control, formState, getValues, reset, setValue } = form;\n\n const inviteBulk = authApi$.useMutation('post', '/users/invite/bulk', {\n onSuccess: async () => {\n await qc.invalidateQueries({ queryKey: ['get', '/users'] });\n },\n });\n\n useEffect(() => {\n if (open) {\n return;\n }\n\n reset(defaults);\n setInvited([]);\n setFailed([]);\n }, [open, reset]);\n\n const handleFileChange = async (\n event: ChangeEvent<HTMLInputElement>,\n onChange: (value: string) => void,\n ) => {\n const file = event.target.files?.[0];\n\n if (!file) {\n return;\n }\n\n try {\n const text = await file.text();\n onChange(file.name);\n setValue('csvText', text, {\n shouldDirty: true,\n shouldValidate: true,\n });\n toast.success(`Loaded ${file.name}`);\n } catch (error) {\n toast.error(\n error instanceof Error ? error.message : 'Failed to read CSV file',\n );\n } finally {\n event.target.value = '';\n }\n };\n\n const onSubmit = form.handleSubmit(async (values) => {\n try {\n const result = await inviteBulk.mutateAsync({\n body: {\n users: parseBulkInviteCsv(values.csvText),\n },\n });\n\n setInvited(result.invited);\n setFailed(result.failed);\n toast.success(\n `Bulk invite finished: ${result.invited.length} invited, ${result.failed.length} failed`,\n );\n } catch (error) {\n toast.error(\n error instanceof Error ? error.message : 'Failed to bulk invite users',\n );\n }\n });\n\n return (\n <EntityDrawer\n title=\"Bulk invite users\"\n open={open}\n onClose={onClose}\n isDirty={formState.isDirty}\n size=\"xl\"\n form={\n <Form {...form}>\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground\">\n Download the CSV template, fill it with your users, then upload\n or paste the CSV below.\n </p>\n <Button\n variant=\"outline\"\n onClick={downloadInviteTemplate}\n leftIcon={<IconDownload className=\"size-4\" />}\n >\n Download template\n </Button>\n </div>\n\n <FormField\n control={control}\n name=\"sourceFile\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Upload CSV</FormLabel>\n <FormControl>\n <Input\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) =>\n handleFileChange(event, field.onChange)\n }\n />\n </FormControl>\n {field.value ? (\n <p className=\"text-sm text-muted-foreground\">\n Loaded file: {field.value}\n </p>\n ) : null}\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"csvText\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>CSV textarea</FormLabel>\n <FormControl>\n <Textarea\n className=\"min-h-[240px] font-mono text-xs\"\n placeholder={inviteTemplateCsv}\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {(invited.length > 0 || failed.length > 0) && (\n <>\n <Card className=\"border-border/60\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <IconUsers className=\"size-4\" />\n Invited\n <Badge>{invited.length}</Badge>\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {invited.length ? (\n invited.map((result, index) => (\n <InviteResultCard\n key={`${result.user.id}-${index}`}\n result={result}\n index={index}\n />\n ))\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Successful invites will show here.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card className=\"border-border/60\">\n <CardHeader>\n <CardTitle className=\"text-base\">\n Failed rows\n <Badge className=\"ml-2\" variant=\"destructive\">\n {failed.length}\n </Badge>\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {failed.length ? (\n failed.map((failure) => (\n <div\n key={`${failure.index}-${failure.identifier}`}\n className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-4\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span className=\"font-medium\">\n row {failure.index + 1}\n </span>\n {failure.identifier ? (\n <Badge variant=\"outline\">\n {failure.identifier}\n </Badge>\n ) : null}\n </div>\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {failure.error}\n </p>\n </div>\n ))\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Failed invites will show here.\n </p>\n )}\n </CardContent>\n </Card>\n </>\n )}\n </form>\n </Form>\n }\n actions={\n <EntityFormActions\n mode=\"new\"\n onSubmit={onSubmit}\n onReset={() => {\n reset(defaults);\n setInvited([]);\n setFailed([]);\n }}\n isSubmitting={inviteBulk.isPending}\n submitLabel=\"Invite\"\n disabled={!getValues('csvText').trim()}\n />\n }\n />\n );\n}\n","'use client';\n\nimport { useMesob } from '@mesob/ui/providers';\nimport type { ReactNode } from 'react';\nimport { useConfig } from '../../../provider';\n\ntype AppLinkProps = React.ComponentProps<'a'> & {\n href: string;\n children: ReactNode;\n};\n\nexport function AppLink({ href, children, ...props }: AppLinkProps) {\n const mesob = useMesob();\n const Link = mesob?.linkComponent ?? mesob?.navigation?.Link;\n const locale = mesob?.locale;\n\n if (Link) {\n return (\n <Link href={href} {...(locale ? ({ locale } as object) : {})} {...props}>\n {children}\n </Link>\n );\n }\n\n return (\n <a href={href} {...props}>\n {children}\n </a>\n );\n}\n\nexport function useNavigate() {\n const { config } = useConfig();\n\n return (href: string) => {\n if (config.navigation?.onNavigate) {\n config.navigation.onNavigate(href);\n return;\n }\n\n if (typeof window !== 'undefined') {\n window.location.href = href;\n }\n };\n}\n","'use client';\n\nimport type { ComponentProps } from 'react';\nimport { useApi } from '../../../provider';\nimport { AppLink, useNavigate } from './navigation';\n\nexport { defaultEntityQueryOptions } from '../../../lib/query-options';\n\ntype HookArgs = [string, string, ...unknown[]];\n\nexport const authApi$ = {\n useQuery(...args: HookArgs) {\n const { hooks } = useApi();\n\n return hooks.useQuery(...args);\n },\n useMutation(...args: HookArgs) {\n const { hooks } = useApi();\n\n return hooks.useMutation(...args);\n },\n};\n\nexport function Link(props: ComponentProps<typeof AppLink>) {\n return <AppLink {...props} />;\n}\n\nexport function useRouter() {\n const navigate = useNavigate();\n\n return {\n push: navigate,\n };\n}\n","'use client';\n\nimport {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@mesob/ui/components';\nimport type { paths } from '../../../../data/openapi';\n\ntype InviteUserPayload = NonNullable<\n NonNullable<\n paths['/users/invite']['post']['requestBody']\n >['content']['application/json']\n>;\n\ntype InviteResult =\n paths['/users/invite']['post']['responses'][201]['content']['application/json'];\n\ntype BulkInviteResponse =\n paths['/users/invite/bulk']['post']['responses'][200]['content']['application/json'];\n\ntype BulkInviteFailure = BulkInviteResponse['failed'][number];\n\nconst inviteTemplateCsv = `fullName,email,phone,emailVerified,phoneVerified,sendVia,password\nLulit Demo,lulit@example.com,,true,false,email,\nAbel Demo,,+251911223344,false,true,sms|email,TempPass123!\nMarta Demo,marta@example.com,+251922334455,true,true,email|sms,`;\n\nconst defaultInviteUserValues = {\n fullName: '',\n email: '',\n phone: '',\n password: '',\n emailVerified: false,\n phoneVerified: false,\n viaEmail: false,\n viaSms: false,\n};\n\nfunction buildInviteUserPayload(\n values: typeof defaultInviteUserValues,\n): InviteUserPayload {\n return {\n fullName: values.fullName.trim(),\n email: values.email.trim() || undefined,\n phone: values.phone.trim() || undefined,\n password: values.password.trim() || undefined,\n emailVerified: values.emailVerified,\n phoneVerified: values.phoneVerified,\n sendVia: [\n values.viaEmail ? 'email' : null,\n values.viaSms ? 'sms' : null,\n ].filter(Boolean) as InviteUserPayload['sendVia'],\n };\n}\n\nfunction parseCsvLine(line: string) {\n const cells: string[] = [];\n let current = '';\n let quoted = false;\n\n for (let i = 0; i < line.length; i += 1) {\n const char = line[i];\n const next = line[i + 1];\n\n if (char === '\"' && quoted && next === '\"') {\n current += '\"';\n i += 1;\n continue;\n }\n\n if (char === '\"') {\n quoted = !quoted;\n continue;\n }\n\n if (char === ',' && !quoted) {\n cells.push(current.trim());\n current = '';\n continue;\n }\n\n current += char;\n }\n\n cells.push(current.trim());\n return cells;\n}\n\nfunction parseBoolean(value: string | undefined) {\n if (!value) {\n return false;\n }\n\n return ['true', '1', 'yes', 'y'].includes(value.trim().toLowerCase());\n}\n\nfunction parseSendVia(value: string | undefined) {\n if (!value) {\n return [];\n }\n\n return value\n .split('|')\n .map((item) => item.trim().toLowerCase())\n .filter(\n (item): item is NonNullable<InviteUserPayload['sendVia']>[number] =>\n item === 'email' || item === 'sms',\n );\n}\n\nfunction parseBulkInviteCsv(text: string): InviteUserPayload[] {\n const lines = text\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean);\n\n if (!lines.length) {\n throw new Error('Paste at least one CSV row.');\n }\n\n const header = parseCsvLine(lines[0]).map((cell) => cell.toLowerCase());\n const hasHeader = header.includes('fullname');\n const rows = hasHeader ? lines.slice(1) : lines;\n\n if (!rows.length) {\n throw new Error('CSV only has a header row.');\n }\n\n const keys = hasHeader\n ? header\n : [\n 'fullName',\n 'email',\n 'phone',\n 'emailVerified',\n 'phoneVerified',\n 'sendVia',\n 'password',\n ];\n\n return rows.map((line, index) => {\n const values = parseCsvLine(line);\n const record = Object.fromEntries(\n keys.map((key, valueIndex) => [key, values[valueIndex] ?? '']),\n );\n const fullName = String(\n record.fullname || record.fullName || record.name || '',\n ).trim();\n const email = String(record.email || '').trim();\n const phone = String(record.phone || '').trim();\n\n if (!fullName) {\n throw new Error(`Row ${index + 1}: fullName is required.`);\n }\n\n if (!(email || phone)) {\n throw new Error(`Row ${index + 1}: email or phone is required.`);\n }\n\n return {\n fullName,\n email: email || undefined,\n phone: phone || undefined,\n password: String(record.password || '').trim() || undefined,\n emailVerified: parseBoolean(\n String(record.emailverified || record.emailVerified || ''),\n ),\n phoneVerified: parseBoolean(\n String(record.phoneverified || record.phoneVerified || ''),\n ),\n sendVia: parseSendVia(String(record.sendvia || record.sendVia || '')),\n };\n });\n}\n\nfunction downloadInviteTemplate() {\n const blob = new Blob([inviteTemplateCsv], {\n type: 'text/csv;charset=utf-8',\n });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n\n link.href = url;\n link.download = 'users-bulk-invite-template.csv';\n link.click();\n URL.revokeObjectURL(url);\n}\n\nfunction DeliveryBadges({ delivery }: { delivery: InviteResult['delivery'] }) {\n return (\n <div className=\"flex flex-wrap gap-2\">\n {delivery.email ? (\n <Badge\n variant={delivery.email === 'failed' ? 'destructive' : 'outline'}\n >\n email: {delivery.email}\n </Badge>\n ) : null}\n {delivery.sms ? (\n <Badge variant={delivery.sms === 'failed' ? 'destructive' : 'outline'}>\n sms: {delivery.sms}\n </Badge>\n ) : null}\n {delivery.email || delivery.sms ? null : (\n <Badge variant=\"secondary\">no delivery</Badge>\n )}\n </div>\n );\n}\n\nfunction InviteResultCard({\n result,\n index,\n}: {\n result: InviteResult;\n index?: number;\n}) {\n return (\n <Card className=\"border-border/60 bg-background/70\">\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <CardTitle className=\"text-base\">{result.user.fullName}</CardTitle>\n <CardDescription>\n {result.user.email || result.user.phone || 'No identifier'}\n </CardDescription>\n </div>\n <div className=\"flex gap-2\">\n {typeof index === 'number' ? (\n <Badge variant=\"outline\">row {index + 1}</Badge>\n ) : null}\n <Badge variant={result.hasPassword ? 'secondary' : 'default'}>\n {result.hasPassword ? 'password set' : 'set-password flow'}\n </Badge>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex flex-wrap gap-2\">\n <Badge variant={result.user.emailVerified ? 'default' : 'secondary'}>\n email {result.user.emailVerified ? 'verified' : 'pending'}\n </Badge>\n <Badge variant={result.user.phoneVerified ? 'default' : 'secondary'}>\n phone {result.user.phoneVerified ? 'verified' : 'pending'}\n </Badge>\n </div>\n <DeliveryBadges delivery={result.delivery} />\n {result.inviteUrl ? (\n <p className=\"rounded-md border border-dashed border-border/70 bg-muted/40 px-3 py-2 font-mono text-xs break-all\">\n {result.inviteUrl}\n </p>\n ) : null}\n </CardContent>\n </Card>\n );\n}\n\nexport {\n buildInviteUserPayload,\n defaultInviteUserValues,\n downloadInviteTemplate,\n inviteTemplateCsv,\n InviteResultCard,\n parseBulkInviteCsv,\n};\nexport type { BulkInviteFailure, BulkInviteResponse, InviteResult };\n","'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport {\n Checkbox,\n EntityDrawer,\n EntityFormActions,\n Form,\n FormControl,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n Input,\n Stack,\n} from '@mesob/ui/components';\nimport { useQueryClient } from '@tanstack/react-query';\nimport type { ComponentProps } from 'react';\nimport { useEffect, useState } from 'react';\nimport { useForm, useWatch } from 'react-hook-form';\nimport { toast } from 'sonner';\nimport { z } from 'zod';\nimport { authApi$ } from '../../shared/page-helpers';\nimport {\n buildInviteUserPayload,\n defaultInviteUserValues,\n InviteResultCard,\n} from './invite-user-shared';\n\nconst schema = z\n .object({\n fullName: z.string().trim().min(1, 'Full name is required'),\n email: z.string().email('Invalid email').or(z.literal('')),\n phone: z.string(),\n password: z.string(),\n emailVerified: z.boolean(),\n phoneVerified: z.boolean(),\n viaEmail: z.boolean(),\n viaSms: z.boolean(),\n })\n .superRefine((value, ctx) => {\n if (!(value.email.trim() || value.phone.trim())) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['email'],\n message: 'Email or phone is required',\n });\n }\n });\n\ntype FormData = z.infer<typeof schema>;\n\ntype InviteUserFormProps = {\n open: boolean;\n onClose: () => void;\n};\n\nexport function InviteUserForm({ open, onClose }: InviteUserFormProps) {\n const qc = useQueryClient();\n const [result, setResult] = useState<\n ComponentProps<typeof InviteResultCard>['result'] | null\n >(null);\n\n const form = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: defaultInviteUserValues,\n });\n const { control, formState, reset, setValue } = form;\n const email = useWatch({ control, name: 'email' }) ?? '';\n const phone = useWatch({ control, name: 'phone' }) ?? '';\n const hasEmail = email.trim().length > 0;\n const hasPhone = phone.trim().length > 0;\n\n const inviteUser = authApi$.useMutation('post', '/users/invite', {\n onSuccess: async () => {\n await qc.invalidateQueries({ queryKey: ['get', '/users'] });\n },\n });\n\n useEffect(() => {\n if (!hasEmail) {\n setValue('emailVerified', false, { shouldDirty: false });\n setValue('viaEmail', false, { shouldDirty: false });\n }\n }, [hasEmail, setValue]);\n\n useEffect(() => {\n if (!hasPhone) {\n setValue('phoneVerified', false, { shouldDirty: false });\n setValue('viaSms', false, { shouldDirty: false });\n }\n }, [hasPhone, setValue]);\n\n useEffect(() => {\n if (open) {\n return;\n }\n\n reset(defaultInviteUserValues);\n setResult(null);\n }, [open, reset]);\n\n const onSubmit = form.handleSubmit(async (values) => {\n try {\n const inviteResult = await inviteUser.mutateAsync({\n body: buildInviteUserPayload(values),\n });\n\n setResult(inviteResult);\n reset(defaultInviteUserValues);\n toast.success('User invited');\n } catch (error) {\n toast.error(\n error instanceof Error ? error.message : 'Failed to invite user',\n );\n }\n });\n\n return (\n <EntityDrawer\n title=\"Invite user\"\n open={open}\n onClose={onClose}\n isDirty={formState.isDirty}\n size=\"lg\"\n form={\n <Form {...form}>\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <Stack>\n <FormField\n control={control}\n name=\"fullName\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n Full name <span className=\"text-destructive\">*</span>\n </FormLabel>\n <FormControl>\n <Input placeholder=\"Aster Bekele\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email</FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder=\"aster@example.com\"\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"phone\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Phone</FormLabel>\n <FormControl>\n <Input placeholder=\"+251911223344\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"password\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Password</FormLabel>\n <FormControl>\n <Input\n type=\"password\"\n placeholder=\"Optional\"\n {...field}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"emailVerified\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex items-start gap-3\">\n <FormControl>\n <Checkbox\n checked={field.value}\n disabled={!hasEmail}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n />\n </FormControl>\n <div className=\"grid gap-1\">\n <FormLabel>Email verified</FormLabel>\n <FormMessage />\n </div>\n </div>\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"phoneVerified\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex items-start gap-3\">\n <FormControl>\n <Checkbox\n checked={field.value}\n disabled={!hasPhone}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n />\n </FormControl>\n <div className=\"grid gap-1\">\n <FormLabel>Phone verified</FormLabel>\n <FormMessage />\n </div>\n </div>\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"viaEmail\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex items-start gap-3\">\n <FormControl>\n <Checkbox\n checked={field.value}\n disabled={!hasEmail}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n />\n </FormControl>\n <div className=\"grid gap-1\">\n <FormLabel>Send email invite</FormLabel>\n <FormMessage />\n </div>\n </div>\n </FormItem>\n )}\n />\n\n <FormField\n control={control}\n name=\"viaSms\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex items-start gap-3\">\n <FormControl>\n <Checkbox\n checked={field.value}\n disabled={!hasPhone}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n />\n </FormControl>\n <div className=\"grid gap-1\">\n <FormLabel>Send SMS invite</FormLabel>\n <FormMessage />\n </div>\n </div>\n </FormItem>\n )}\n />\n\n {result ? <InviteResultCard result={result} /> : null}\n </Stack>\n </form>\n </Form>\n }\n actions={\n <EntityFormActions\n mode=\"new\"\n onSubmit={onSubmit}\n onReset={() => {\n reset(defaultInviteUserValues);\n setResult(null);\n }}\n isSubmitting={inviteUser.isPending}\n submitLabel=\"Invite\"\n />\n }\n />\n );\n}\n","'use client';\n\nimport {\n Badge,\n DataTableAction,\n DataTablePagination,\n DisplayTable,\n EntityEmptyState,\n EntityLoadingState,\n Tbody,\n Td,\n Th,\n Thead,\n Tr,\n} from '@mesob/ui/components';\nimport { IconCalendar, IconUsers } from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Link } from '../../shared/page-helpers';\nimport { UserCard } from './user-card';\nimport { UserForm } from './user-form';\nimport type { User } from './users-data';\n\nconst TABLE_COLUMN_COUNT = 4;\n\ntype UsersListProps = {\n data: User[];\n isLoading?: boolean;\n view: 'table' | 'card';\n pageIndex: number;\n pageSize: number;\n pageCount: number;\n totalRows: number;\n onPageChange: (page: number) => void;\n onPageSizeChange: (size: number) => void;\n onCreateNew?: () => void;\n};\n\nexport function UsersList({\n data,\n isLoading,\n view,\n pageIndex,\n pageSize,\n pageCount,\n totalRows,\n onPageChange,\n onPageSizeChange,\n onCreateNew,\n}: UsersListProps) {\n const [editingUserId, setEditingUserId] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <EntityLoadingState\n view={view}\n rowCount={pageSize}\n columnCount={TABLE_COLUMN_COUNT}\n cardCount={pageSize}\n />\n );\n }\n if (totalRows === 0) {\n return (\n <EntityEmptyState\n icon={IconUsers}\n entityName=\"user\"\n title=\"No users yet\"\n description=\"Invite your first user to get started.\"\n actionLabel=\"Invite user\"\n onAction={onCreateNew}\n />\n );\n }\n if (view === 'table') {\n return (\n <div className=\"space-y-4\">\n {editingUserId && (\n <UserForm\n mode=\"edit\"\n userId={editingUserId}\n open\n onClose={() => setEditingUserId(null)}\n />\n )}\n <DisplayTable withTableBorder>\n <Thead>\n <Tr>\n <Th className=\"w-1/4\">Name</Th>\n <Th className=\"w-1/4\">Contact</Th>\n <Th className=\"w-1/4\">Last sign in</Th>\n <Th className=\"w-[50px]\" />\n </Tr>\n </Thead>\n <Tbody>\n {data.map((user) => (\n <Tr key={user.id} className=\"group\">\n <Td>\n <Link\n href={`/iam/users/${user.id}`}\n className=\"block text-left font-medium hover:text-primary hover:underline cursor-pointer\"\n >\n <p>{user.fullName}</p>\n </Link>\n </Td>\n <Td>\n <div className=\"space-y-1\">\n {user.email && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm\">{user.email}</span>\n {user.emailVerified && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Verified\n </Badge>\n )}\n </div>\n )}\n {user.phone && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm\">{user.phone}</span>\n {user.phoneVerified && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Verified\n </Badge>\n )}\n </div>\n )}\n {!(user.email || user.phone) && (\n <span className=\"text-muted-foreground\">—</span>\n )}\n </div>\n </Td>\n <Td>\n <div className=\"flex items-center gap-1 text-muted-foreground\">\n <IconCalendar className=\"h-4 w-4\" />\n {user.lastSignInAt\n ? new Date(user.lastSignInAt).toLocaleDateString()\n : 'Never'}\n </div>\n </Td>\n <Td>\n <DataTableAction onClick={() => setEditingUserId(user.id)} />\n </Td>\n </Tr>\n ))}\n </Tbody>\n </DisplayTable>\n <DataTablePagination\n pageIndex={pageIndex}\n pageSize={pageSize}\n pageCount={pageCount}\n totalRows={totalRows}\n onPageChange={onPageChange}\n onPageSizeChange={onPageSizeChange}\n />\n </div>\n );\n }\n return (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {data.map((user) => (\n <UserCard key={user.id} user={user} />\n ))}\n </div>\n <DataTablePagination\n pageIndex={pageIndex}\n pageSize={pageSize}\n pageCount={pageCount}\n totalRows={totalRows}\n onPageChange={onPageChange}\n onPageSizeChange={onPageSizeChange}\n />\n </div>\n );\n}\n","'use client';\n\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuTrigger,\n} from '@mesob/ui/components';\nimport { IconDots, IconPencil } from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Link } from '../../shared/page-helpers';\nimport { UserForm } from './user-form';\nimport type { User } from './users-data';\n\ntype UserCardProps = { user: User };\n\nexport function UserCard({ user }: UserCardProps) {\n const [editOpen, setEditOpen] = useState(false);\n return (\n <>\n <Card className=\"group hover:shadow-md transition-shadow\">\n <CardHeader className=\"pb-2\">\n <div className=\"flex items-start justify-between\">\n <Link\n href={`/iam/users/${user.id}`}\n className=\"text-left font-semibold hover:text-primary hover:underline\"\n >\n {user.fullName}\n </Link>\n <DropdownMenu>\n <DropdownMenuTrigger\n render={\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity\"\n />\n }\n >\n <IconDots className=\"h-4 w-4\" />\n </DropdownMenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuContent>\n <DropdownMenuItem onClick={() => setEditOpen(true)}>\n <IconPencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenuPortal>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {user.email && (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm\">{user.email}</span>\n {user.emailVerified && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Verified\n </Badge>\n )}\n </div>\n )}\n <p className=\"text-xs text-muted-foreground\">\n Last sign in{' '}\n {user.lastSignInAt\n ? new Date(user.lastSignInAt).toLocaleDateString()\n : 'never'}\n </p>\n </CardContent>\n </Card>\n {editOpen && (\n <UserForm\n mode=\"edit\"\n userId={user.id}\n open={editOpen}\n onClose={() => setEditOpen(false)}\n />\n )}\n </>\n );\n}\n","'use client';\n\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport {\n EntityDrawer,\n EntityFormActions,\n Input,\n Label,\n Skeleton,\n} from '@mesob/ui/components';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { useEffect } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { authApi$ } from '../../shared/page-helpers';\n\nconst schema = z.object({\n fullName: z.string().min(1, 'Name is required'),\n email: z.string().email().optional().or(z.literal('')),\n phone: z.string().optional(),\n});\n\ntype FormData = z.infer<typeof schema>;\n\nconst defaults: FormData = {\n fullName: '',\n email: '',\n phone: '',\n};\n\ntype UserFormProps = {\n mode: 'new' | 'edit';\n userId?: string;\n open: boolean;\n onClose: () => void;\n onSuccess?: () => void;\n};\n\nexport function UserForm({\n mode,\n userId,\n open,\n onClose,\n onSuccess,\n}: UserFormProps) {\n const qc = useQueryClient();\n const { data, isLoading } = authApi$.useQuery(\n 'get',\n '/users/{id}',\n { params: { path: { id: userId ?? '' } } },\n { enabled: mode === 'edit' && !!userId },\n );\n const create = authApi$.useMutation('post', '/users', {\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: ['get', '/users'] });\n },\n });\n const update = authApi$.useMutation('put', '/users/{id}', {\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: ['get', '/users'] });\n if (userId) {\n qc.invalidateQueries({ queryKey: ['get', '/users/{id}'] });\n }\n },\n });\n const del = authApi$.useMutation('delete', '/users/{id}', {\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: ['get', '/users'] });\n },\n });\n\n const form = useForm<FormData>({\n resolver: zodResolver(schema),\n defaultValues: defaults,\n });\n\n const { reset, formState } = form;\n\n useEffect(() => {\n if (!open) {\n return;\n }\n if (mode === 'edit' && data?.user && !isLoading) {\n const u = data.user;\n reset({\n fullName: u.fullName,\n email: u.email ?? '',\n phone: u.phone ?? '',\n });\n } else {\n reset(defaults);\n }\n }, [mode, data, open, isLoading, reset]);\n\n const onSubmit = form.handleSubmit(async (d) => {\n const payload = {\n fullName: d.fullName,\n email: d.email || undefined,\n phone: d.phone || undefined,\n };\n if (mode === 'new') {\n await create.mutateAsync({ body: payload });\n } else if (userId) {\n await update.mutateAsync({\n params: { path: { id: userId } },\n body: payload,\n });\n }\n onSuccess?.();\n onClose();\n });\n\n const onDelete = async () => {\n if (!userId) {\n return;\n }\n await del.mutateAsync({ params: { path: { id: userId } } });\n onSuccess?.();\n onClose();\n };\n\n const isSubmitting = create.isPending || update.isPending;\n\n return (\n <EntityDrawer\n title={mode === 'new' ? 'New user' : 'Edit user'}\n open={open}\n onClose={onClose}\n isDirty={formState.isDirty}\n form={\n isLoading ? (\n <FormSkeleton />\n ) : (\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"fullName\">\n Full name <span className=\"text-destructive\">*</span>\n </Label>\n <Input\n id=\"fullName\"\n placeholder=\"Full name\"\n {...form.register('fullName')}\n />\n {formState.errors.fullName && (\n <p className=\"text-sm text-destructive\">\n {formState.errors.fullName.message}\n </p>\n )}\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"Email\"\n {...form.register('email')}\n />\n {formState.errors.email && (\n <p className=\"text-sm text-destructive\">\n {formState.errors.email.message}\n </p>\n )}\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"phone\">Phone</Label>\n <Input\n id=\"phone\"\n placeholder=\"Phone\"\n {...form.register('phone')}\n />\n </div>\n </form>\n )\n }\n actions={\n <EntityFormActions\n mode={mode}\n onSubmit={onSubmit}\n onReset={mode === 'new' ? () => reset(defaults) : undefined}\n onDelete={mode === 'edit' ? onDelete : undefined}\n isSubmitting={isSubmitting}\n isDeleting={del.isPending}\n disabled={isLoading}\n itemName=\"user\"\n />\n }\n />\n );\n}\n\nfunction FormSkeleton() {\n return (\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Skeleton className=\"h-4 w-20\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n <div className=\"space-y-2\">\n <Skeleton className=\"h-4 w-16\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n <div className=\"space-y-2\">\n <Skeleton className=\"h-4 w-14\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n <div className=\"space-y-2\">\n <Skeleton className=\"h-4 w-16\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["IconUsers","useState","Badge","Card","CardContent","CardHeader","CardTitle","useState","jsx","Link","jsx","jsx","jsx","jsxs","useState","Card","CardHeader","CardTitle","Badge","CardContent","zodResolver","EntityDrawer","EntityFormActions","Form","FormControl","FormField","FormItem","FormLabel","FormMessage","Input","useQueryClient","useEffect","useState","useForm","toast","z","jsx","jsxs","schema","z","useQueryClient","useState","useForm","zodResolver","useEffect","toast","EntityDrawer","Form","FormField","FormItem","FormLabel","FormControl","Input","FormMessage","EntityFormActions","Badge","IconUsers","useState","Badge","Button","Card","CardContent","CardHeader","useState","zodResolver","EntityDrawer","EntityFormActions","Input","useQueryClient","useEffect","useForm","z","jsx","jsxs","schema","z","defaults","useQueryClient","useForm","zodResolver","useEffect","EntityDrawer","Input","EntityFormActions","Fragment","jsx","jsxs","useState","Card","CardHeader","Button","CardContent","Badge","jsx","jsxs","useState","IconUsers","Badge","jsx","jsxs","useState","IconUsers"]}
@@ -0,0 +1,5 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function ProfileAccountPage(): react_jsx_runtime.JSX.Element;
4
+
5
+ export { ProfileAccountPage as default };
@@ -0,0 +1,182 @@
1
+ "use client";
2
+
3
+ // src/pages/profile/account.tsx
4
+ import {
5
+ PageBody,
6
+ PageContainer,
7
+ PageTitle,
8
+ useBreadcrumbs
9
+ } from "@mesob/ui/components";
10
+ import { IconUser } from "@tabler/icons-react";
11
+
12
+ // src/components/profile/account.tsx
13
+ import { Badge as Badge2, Card, CardContent, Separator } from "@mesob/ui/components";
14
+
15
+ // src/provider.tsx
16
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
17
+ import { deepmerge } from "deepmerge-ts";
18
+ import createFetchClient from "openapi-fetch";
19
+ import createClient from "openapi-react-query";
20
+ import { createContext, useContext, useMemo, useState } from "react";
21
+
22
+ // src/utils/cookie.ts
23
+ var isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
24
+
25
+ // src/provider.tsx
26
+ import { jsx } from "react/jsx-runtime";
27
+ var SessionContext = createContext(null);
28
+ var ApiContext = createContext(null);
29
+ var ConfigContext = createContext(null);
30
+ var queryClient = new QueryClient({
31
+ defaultOptions: {
32
+ queries: {
33
+ refetchOnWindowFocus: false
34
+ }
35
+ }
36
+ });
37
+ function useSession() {
38
+ const context = useContext(SessionContext);
39
+ if (!context) {
40
+ throw new Error("useSession must be used within MesobAuthProvider");
41
+ }
42
+ return context;
43
+ }
44
+
45
+ // src/components/profile/change-profile.tsx
46
+ import {
47
+ Avatar,
48
+ AvatarFallback,
49
+ AvatarImage,
50
+ Badge,
51
+ Button
52
+ } from "@mesob/ui/components";
53
+ import { useState as useState2 } from "react";
54
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
55
+ var ChangeProfile = ({ user }) => {
56
+ const [isEditing, setIsEditing] = useState2(false);
57
+ const initials = user.fullName?.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2) || "U";
58
+ return /* @__PURE__ */ jsxs("div", { className: "w-full rounded-[1.5rem] border border-border/60 bg-background/80 p-5 shadow-sm backdrop-blur", children: [
59
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
60
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
61
+ /* @__PURE__ */ jsxs(Avatar, { className: "h-14 w-14 ring-4 ring-primary/10", children: [
62
+ /* @__PURE__ */ jsx2(AvatarImage, { src: user.image || "", alt: user.fullName || "" }),
63
+ /* @__PURE__ */ jsx2(AvatarFallback, { children: initials })
64
+ ] }),
65
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
66
+ /* @__PURE__ */ jsx2("div", { className: "text-base font-semibold", children: user.fullName }),
67
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 text-xs text-muted-foreground", children: [
68
+ /* @__PURE__ */ jsx2(Badge, { variant: "outline", children: user.emailVerified ? "Email verified" : "Email unverified" }),
69
+ /* @__PURE__ */ jsx2(Badge, { variant: "outline", children: user.phoneVerified ? "Phone verified" : "Phone unverified" })
70
+ ] })
71
+ ] })
72
+ ] }),
73
+ /* @__PURE__ */ jsx2(
74
+ Button,
75
+ {
76
+ variant: "secondary",
77
+ className: "rounded-full px-5 text-primary hover:text-primary/80",
78
+ onClick: () => setIsEditing((v) => !v),
79
+ children: isEditing ? "Hide editor" : "Update profile"
80
+ }
81
+ )
82
+ ] }),
83
+ isEditing && /* @__PURE__ */ jsx2("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." })
84
+ ] });
85
+ };
86
+
87
+ // src/components/profile/account.tsx
88
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
89
+ function Account() {
90
+ const { user, isAuthenticated } = useSession();
91
+ if (!(isAuthenticated && user)) {
92
+ return /* @__PURE__ */ jsx3("div", { className: "rounded-[1.75rem] border border-dashed border-border bg-muted/30 p-8 text-sm text-muted-foreground", children: "Sign in required." });
93
+ }
94
+ const contactItems = [
95
+ {
96
+ label: "Email",
97
+ value: user.email ?? "No email address",
98
+ status: user.emailVerified ? "Verified" : "Needs verification"
99
+ },
100
+ {
101
+ label: "Phone",
102
+ value: user.phone ?? "No phone number",
103
+ status: user.phoneVerified ? "Verified" : "Needs verification"
104
+ }
105
+ ];
106
+ return /* @__PURE__ */ jsxs2("div", { className: "mx-auto flex w-full max-w-6xl flex-col gap-6", children: [
107
+ /* @__PURE__ */ jsxs2("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", children: [
108
+ /* @__PURE__ */ jsx3("div", { className: "absolute inset-y-0 right-0 w-1/3 bg-[radial-gradient(circle_at_top,_hsl(var(--primary)/0.16),_transparent_60%)]" }),
109
+ /* @__PURE__ */ jsxs2("div", { className: "relative flex flex-col gap-5", children: [
110
+ /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
111
+ /* @__PURE__ */ jsx3(Badge2, { variant: "outline", children: "Profile console" }),
112
+ /* @__PURE__ */ jsx3(Badge2, { variant: "secondary", children: "Live session data" })
113
+ ] }),
114
+ /* @__PURE__ */ jsxs2("div", { className: "max-w-2xl space-y-2", children: [
115
+ /* @__PURE__ */ jsx3("h2", { className: "text-2xl font-semibold tracking-tight", children: "Account details, contact state, and verification status." }),
116
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: "Keep the identity surface minimal here. Contact updates and sensitive changes stay in security." })
117
+ ] }),
118
+ /* @__PURE__ */ jsx3(ChangeProfile, { user })
119
+ ] })
120
+ ] }),
121
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-4 lg:grid-cols-[1.5fr_0.9fr]", children: [
122
+ /* @__PURE__ */ jsx3(Card, { className: "rounded-[1.75rem] border-border/60 shadow-sm", children: /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-5 p-6", children: [
123
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
124
+ /* @__PURE__ */ jsx3("div", { className: "text-sm font-medium text-muted-foreground", children: "Contact channels" }),
125
+ /* @__PURE__ */ jsx3("div", { className: "text-lg font-semibold", children: "Primary profile data" })
126
+ ] }),
127
+ /* @__PURE__ */ jsx3(Separator, {}),
128
+ /* @__PURE__ */ jsx3("div", { className: "space-y-4", children: contactItems.map((item) => /* @__PURE__ */ jsxs2(
129
+ "div",
130
+ {
131
+ 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",
132
+ children: [
133
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
134
+ /* @__PURE__ */ jsx3("div", { className: "text-sm font-medium", children: item.label }),
135
+ /* @__PURE__ */ jsx3("div", { className: "text-sm text-muted-foreground", children: item.value })
136
+ ] }),
137
+ /* @__PURE__ */ jsx3(Badge2, { variant: "outline", children: item.status })
138
+ ]
139
+ },
140
+ item.label
141
+ )) })
142
+ ] }) }),
143
+ /* @__PURE__ */ jsx3(Card, { className: "rounded-[1.75rem] border-border/60 shadow-sm", children: /* @__PURE__ */ jsxs2(CardContent, { className: "space-y-5 p-6", children: [
144
+ /* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
145
+ /* @__PURE__ */ jsx3("div", { className: "text-sm font-medium text-muted-foreground", children: "Snapshot" }),
146
+ /* @__PURE__ */ jsx3("div", { className: "text-lg font-semibold", children: "Session-facing identity" })
147
+ ] }),
148
+ /* @__PURE__ */ jsx3(Separator, {}),
149
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-3", children: [
150
+ /* @__PURE__ */ jsxs2("div", { className: "rounded-2xl bg-primary/[0.06] p-4", children: [
151
+ /* @__PURE__ */ jsx3("div", { className: "text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Full name" }),
152
+ /* @__PURE__ */ jsx3("div", { className: "mt-2 text-base font-semibold", children: user.fullName })
153
+ ] }),
154
+ /* @__PURE__ */ jsxs2("div", { className: "rounded-2xl bg-muted/25 p-4", children: [
155
+ /* @__PURE__ */ jsx3("div", { className: "text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Last sign in" }),
156
+ /* @__PURE__ */ jsx3("div", { className: "mt-2 text-base font-semibold", children: user.lastSignInAt ? new Date(user.lastSignInAt).toLocaleString() : "No activity recorded" })
157
+ ] })
158
+ ] })
159
+ ] }) })
160
+ ] })
161
+ ] });
162
+ }
163
+
164
+ // src/pages/profile/account.tsx
165
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
166
+ function ProfileAccountPage() {
167
+ useBreadcrumbs({
168
+ items: [
169
+ { label: "Home", href: "/dashboard" },
170
+ { label: "Profile", href: "/profile/account" },
171
+ { label: "Account" }
172
+ ]
173
+ });
174
+ return /* @__PURE__ */ jsxs3(PageContainer, { className: "flex flex-1 flex-col overflow-auto p-4 pt-0 lg:p-6 lg:pt-0", children: [
175
+ /* @__PURE__ */ jsx4(PageTitle, { icon: /* @__PURE__ */ jsx4(IconUser, { className: "size-5" }), children: "Account" }),
176
+ /* @__PURE__ */ jsx4(PageBody, { className: "px-0 pb-6", children: /* @__PURE__ */ jsx4(Account, {}) })
177
+ ] });
178
+ }
179
+ export {
180
+ ProfileAccountPage as default
181
+ };
182
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/pages/profile/account.tsx","../../../src/components/profile/account.tsx","../../../src/provider.tsx","../../../src/utils/cookie.ts","../../../src/components/profile/change-profile.tsx"],"sourcesContent":["'use client';\n\nimport {\n PageBody,\n PageContainer,\n PageTitle,\n useBreadcrumbs,\n} from '@mesob/ui/components';\nimport { IconUser } from '@tabler/icons-react';\nimport { Account } from '../../components/profile/account';\n\nexport default function ProfileAccountPage() {\n useBreadcrumbs({\n items: [\n { label: 'Home', href: '/dashboard' },\n { label: 'Profile', href: '/profile/account' },\n { label: 'Account' },\n ],\n });\n\n return (\n <PageContainer className=\"flex flex-1 flex-col overflow-auto p-4 pt-0 lg:p-6 lg:pt-0\">\n <PageTitle icon={<IconUser className=\"size-5\" />}>Account</PageTitle>\n <PageBody className=\"px-0 pb-6\">\n <Account />\n </PageBody>\n </PageContainer>\n );\n}\n","'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":";;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;;;ACNzB,SAAS,SAAAA,QAAO,MAAM,aAAa,iBAAiB;;;ACApD,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;;;AE/FA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAAC,iBAAgB;AAqBf,SACE,OAAAC,MADF;AAdH,IAAM,gBAAgB,CAAC,EAAE,KAAK,MAAa;AAChD,QAAM,CAAC,WAAW,YAAY,IAAID,UAAS,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,0BAAAC,KAAC,eAAY,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,YAAY,IAAI;AAAA,UAC9D,gBAAAA,KAAC,kBAAgB,oBAAS;AAAA,WAC5B;AAAA,QACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAAA,KAAC,SAAI,WAAU,2BAA2B,eAAK,UAAS;AAAA,UACxD,qBAAC,SAAI,WAAU,sDACb;AAAA,4BAAAA,KAAC,SAAM,SAAQ,WACZ,eAAK,gBAAgB,mBAAmB,oBAC3C;AAAA,YACA,gBAAAA,KAAC,SAAM,SAAQ,WACZ,eAAK,gBAAgB,mBAAmB,oBAC3C;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,MACA,gBAAAA;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,gBAAAA,KAAC,SAAI,WAAU,2GAA0G,0FAGzH;AAAA,KAEJ;AAEJ;;;AHjDM,gBAAAC,MAwBI,QAAAC,aAxBJ;AALC,SAAS,UAAU;AACxB,QAAM,EAAE,MAAM,gBAAgB,IAAI,WAAW;AAE7C,MAAI,EAAE,mBAAmB,OAAO;AAC9B,WACE,gBAAAD,KAAC,SAAI,WAAU,sGAAqG,+BAEpH;AAAA,EAEJ;AAEA,QAAM,eAAe;AAAA,IACnB;AAAA,MACE,OAAO;AAAA,MACP,OAAO,KAAK,SAAS;AAAA,MACrB,QAAQ,KAAK,gBAAgB,aAAa;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,OAAO,KAAK,SAAS;AAAA,MACrB,QAAQ,KAAK,gBAAgB,aAAa;AAAA,IAC5C;AAAA,EACF;AAEA,SACE,gBAAAC,MAAC,SAAI,WAAU,gDACb;AAAA,oBAAAA,MAAC,SAAI,WAAU,+IACb;AAAA,sBAAAD,KAAC,SAAI,WAAU,mHAAkH;AAAA,MACjI,gBAAAC,MAAC,SAAI,WAAU,gCACb;AAAA,wBAAAA,MAAC,SAAI,WAAU,qCACb;AAAA,0BAAAD,KAACE,QAAA,EAAM,SAAQ,WAAU,6BAAe;AAAA,UACxC,gBAAAF,KAACE,QAAA,EAAM,SAAQ,aAAY,+BAAiB;AAAA,WAC9C;AAAA,QACA,gBAAAD,MAAC,SAAI,WAAU,uBACb;AAAA,0BAAAD,KAAC,QAAG,WAAU,yCAAwC,sEAEtD;AAAA,UACA,gBAAAA,KAAC,OAAE,WAAU,iCAAgC,6GAG7C;AAAA,WACF;AAAA,QACA,gBAAAA,KAAC,iBAAc,MAAY;AAAA,SAC7B;AAAA,OACF;AAAA,IAEA,gBAAAC,MAAC,SAAI,WAAU,yCACb;AAAA,sBAAAD,KAAC,QAAK,WAAU,gDACd,0BAAAC,MAAC,eAAY,WAAU,iBACrB;AAAA,wBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,0BAAAD,KAAC,SAAI,WAAU,6CAA4C,8BAE3D;AAAA,UACA,gBAAAA,KAAC,SAAI,WAAU,yBAAwB,kCAAoB;AAAA,WAC7D;AAAA,QACA,gBAAAA,KAAC,aAAU;AAAA,QACX,gBAAAA,KAAC,SAAI,WAAU,aACZ,uBAAa,IAAI,CAAC,SACjB,gBAAAC;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAEV;AAAA,8BAAAA,MAAC,SAAI,WAAU,aACb;AAAA,gCAAAD,KAAC,SAAI,WAAU,uBAAuB,eAAK,OAAM;AAAA,gBACjD,gBAAAA,KAAC,SAAI,WAAU,iCACZ,eAAK,OACR;AAAA,iBACF;AAAA,cACA,gBAAAA,KAACE,QAAA,EAAM,SAAQ,WAAW,eAAK,QAAO;AAAA;AAAA;AAAA,UATjC,KAAK;AAAA,QAUZ,CACD,GACH;AAAA,SACF,GACF;AAAA,MAEA,gBAAAF,KAAC,QAAK,WAAU,gDACd,0BAAAC,MAAC,eAAY,WAAU,iBACrB;AAAA,wBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,0BAAAD,KAAC,SAAI,WAAU,6CAA4C,sBAE3D;AAAA,UACA,gBAAAA,KAAC,SAAI,WAAU,yBAAwB,qCAEvC;AAAA,WACF;AAAA,QACA,gBAAAA,KAAC,aAAU;AAAA,QACX,gBAAAC,MAAC,SAAI,WAAU,cACb;AAAA,0BAAAA,MAAC,SAAI,WAAU,qCACb;AAAA,4BAAAD,KAAC,SAAI,WAAU,6DAA4D,uBAE3E;AAAA,YACA,gBAAAA,KAAC,SAAI,WAAU,gCACZ,eAAK,UACR;AAAA,aACF;AAAA,UACA,gBAAAC,MAAC,SAAI,WAAU,+BACb;AAAA,4BAAAD,KAAC,SAAI,WAAU,6DAA4D,0BAE3E;AAAA,YACA,gBAAAA,KAAC,SAAI,WAAU,gCACZ,eAAK,eACF,IAAI,KAAK,KAAK,YAAY,EAAE,eAAe,IAC3C,wBACN;AAAA,aACF;AAAA,WACF;AAAA,SACF,GACF;AAAA,OACF;AAAA,KACF;AAEJ;;;ADhGI,SACmB,OAAAG,MADnB,QAAAC,aAAA;AAVW,SAAR,qBAAsC;AAC3C,iBAAe;AAAA,IACb,OAAO;AAAA,MACL,EAAE,OAAO,QAAQ,MAAM,aAAa;AAAA,MACpC,EAAE,OAAO,WAAW,MAAM,mBAAmB;AAAA,MAC7C,EAAE,OAAO,UAAU;AAAA,IACrB;AAAA,EACF,CAAC;AAED,SACE,gBAAAA,MAAC,iBAAc,WAAU,8DACvB;AAAA,oBAAAD,KAAC,aAAU,MAAM,gBAAAA,KAAC,YAAS,WAAU,UAAS,GAAI,qBAAO;AAAA,IACzD,gBAAAA,KAAC,YAAS,WAAU,aAClB,0BAAAA,KAAC,WAAQ,GACX;AAAA,KACF;AAEJ;","names":["Badge","useState","jsx","jsx","jsxs","Badge","jsx","jsxs"]}
@@ -0,0 +1,8 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ declare function ProfileLayout({ children }: {
5
+ children: ReactNode;
6
+ }): react_jsx_runtime.JSX.Element;
7
+
8
+ export { ProfileLayout as default };
@@ -0,0 +1,133 @@
1
+ "use client";
2
+
3
+ // src/pages/profile/_components/profile-sidebar.tsx
4
+ import { Avatar, AvatarFallback, AvatarImage } from "@mesob/ui/components";
5
+ import { cn } from "@mesob/ui/lib/utils";
6
+ import { IconLock, IconUser } from "@tabler/icons-react";
7
+ import { useMemo as useMemo2 } from "react";
8
+
9
+ // src/provider.tsx
10
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
11
+ import { deepmerge } from "deepmerge-ts";
12
+ import createFetchClient from "openapi-fetch";
13
+ import createClient from "openapi-react-query";
14
+ import { createContext, useContext, useMemo, useState } from "react";
15
+
16
+ // src/utils/cookie.ts
17
+ var isProduction = typeof process !== "undefined" && process.env.NODE_ENV === "production";
18
+
19
+ // src/provider.tsx
20
+ import { jsx } from "react/jsx-runtime";
21
+ var SessionContext = createContext(null);
22
+ var ApiContext = createContext(null);
23
+ var ConfigContext = createContext(null);
24
+ var queryClient = new QueryClient({
25
+ defaultOptions: {
26
+ queries: {
27
+ refetchOnWindowFocus: false
28
+ }
29
+ }
30
+ });
31
+ function useSession() {
32
+ const context = useContext(SessionContext);
33
+ if (!context) {
34
+ throw new Error("useSession must be used within MesobAuthProvider");
35
+ }
36
+ return context;
37
+ }
38
+
39
+ // src/pages/iam/shared/navigation.tsx
40
+ import { useMesob } from "@mesob/ui/providers";
41
+ import { jsx as jsx2 } from "react/jsx-runtime";
42
+ function AppLink({ href, children, ...props }) {
43
+ const mesob = useMesob();
44
+ const Link = mesob?.linkComponent ?? mesob?.navigation?.Link;
45
+ const locale = mesob?.locale;
46
+ if (Link) {
47
+ return /* @__PURE__ */ jsx2(Link, { href, ...locale ? { locale } : {}, ...props, children });
48
+ }
49
+ return /* @__PURE__ */ jsx2("a", { href, ...props, children });
50
+ }
51
+
52
+ // src/pages/profile/_components/profile-sidebar.tsx
53
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
54
+ var profileNavItems = [
55
+ {
56
+ title: "Account",
57
+ href: "/profile/account",
58
+ icon: IconUser,
59
+ caption: "Identity and contact"
60
+ },
61
+ {
62
+ title: "Security",
63
+ href: "/profile/security",
64
+ icon: IconLock,
65
+ caption: "Password and recovery"
66
+ }
67
+ ];
68
+ function ProfileSidebar() {
69
+ const { user } = useSession();
70
+ const pathname = useMemo2(() => {
71
+ if (typeof window === "undefined") {
72
+ return "";
73
+ }
74
+ return window.location.pathname;
75
+ }, []);
76
+ const initials = user?.fullName?.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2) || "U";
77
+ return /* @__PURE__ */ jsx3("aside", { className: "w-full border-b border-border/60 bg-gradient-to-b from-muted/30 via-background to-background lg:w-80 lg:border-b-0 lg:border-r", children: /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-6 p-4 lg:p-5", children: [
78
+ /* @__PURE__ */ jsx3("div", { className: "rounded-[1.75rem] border border-border/60 bg-background/85 p-5 shadow-sm backdrop-blur", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
79
+ /* @__PURE__ */ jsxs(Avatar, { className: "h-12 w-12 ring-4 ring-primary/10", children: [
80
+ /* @__PURE__ */ jsx3(AvatarImage, { src: user?.image || "", alt: user?.fullName || "" }),
81
+ /* @__PURE__ */ jsx3(AvatarFallback, { children: initials })
82
+ ] }),
83
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
84
+ /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-medium text-muted-foreground", children: "Profile workspace" }),
85
+ /* @__PURE__ */ jsx3("div", { className: "truncate text-base font-semibold", children: user?.fullName ?? "Account" })
86
+ ] })
87
+ ] }) }),
88
+ /* @__PURE__ */ jsx3("nav", { className: "grid gap-2", children: profileNavItems.map((item) => {
89
+ const Icon = item.icon;
90
+ const isActive = pathname === item.href;
91
+ return /* @__PURE__ */ jsx3(
92
+ AppLink,
93
+ {
94
+ href: item.href,
95
+ className: cn(
96
+ "group rounded-[1.5rem] border px-4 py-4 transition-all",
97
+ isActive ? "border-primary/30 bg-primary/[0.08] shadow-sm" : "border-border/60 bg-background/70 hover:border-primary/20 hover:bg-muted/30"
98
+ ),
99
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
100
+ /* @__PURE__ */ jsx3(
101
+ "div",
102
+ {
103
+ className: cn(
104
+ "rounded-2xl p-2 transition-colors",
105
+ isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground group-hover:bg-primary/10 group-hover:text-foreground"
106
+ ),
107
+ children: /* @__PURE__ */ jsx3(Icon, { className: "size-4" })
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
111
+ /* @__PURE__ */ jsx3("div", { className: "font-medium", children: item.title }),
112
+ /* @__PURE__ */ jsx3("div", { className: "text-sm text-muted-foreground", children: item.caption })
113
+ ] })
114
+ ] })
115
+ },
116
+ item.href
117
+ );
118
+ }) })
119
+ ] }) });
120
+ }
121
+
122
+ // src/pages/profile/layout.tsx
123
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
124
+ function ProfileLayout({ children }) {
125
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col overflow-hidden lg:flex-row", children: [
126
+ /* @__PURE__ */ jsx4(ProfileSidebar, {}),
127
+ /* @__PURE__ */ jsx4("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden bg-[linear-gradient(180deg,hsl(var(--muted)/0.18),transparent_18rem)]", children })
128
+ ] });
129
+ }
130
+ export {
131
+ ProfileLayout as default
132
+ };
133
+ //# sourceMappingURL=layout.js.map