@questpie/admin 0.0.1 → 1.0.1
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.
- package/README.md +439 -424
- package/dist/auth-layout-M8K8_q5R.mjs +181 -0
- package/dist/auth-layout-M8K8_q5R.mjs.map +1 -0
- package/dist/bulk-upload-dialog-D7w7W1Hl.mjs +273 -0
- package/dist/bulk-upload-dialog-D7w7W1Hl.mjs.map +1 -0
- package/dist/{components/ui/card.mjs → card-BKHjBQfw.mjs} +8 -8
- package/dist/card-BKHjBQfw.mjs.map +1 -0
- package/dist/client/styles/index.css +434 -0
- package/dist/client-DbpZKSgH.d.mts +13585 -0
- package/dist/client-DbpZKSgH.d.mts.map +1 -0
- package/dist/client-njX1rZmi.mjs +22612 -0
- package/dist/client-njX1rZmi.mjs.map +1 -0
- package/dist/client.d.mts +3 -0
- package/dist/client.mjs +13 -0
- package/dist/content-locales-provider-BXvuIgfg.mjs +1650 -0
- package/dist/content-locales-provider-BXvuIgfg.mjs.map +1 -0
- package/dist/dashboard-page-B4PGEdc2.mjs +2500 -0
- package/dist/dashboard-page-B4PGEdc2.mjs.map +1 -0
- package/dist/dashboard-page-mCY0pgZv.mjs +3 -0
- package/dist/dropzone-Do3awXKd.mjs +634 -0
- package/dist/dropzone-Do3awXKd.mjs.map +1 -0
- package/dist/{views/auth/forgot-password-form.mjs → forgot-password-page-Bcp-An4Y.mjs} +87 -14
- package/dist/forgot-password-page-Bcp-An4Y.mjs.map +1 -0
- package/dist/forgot-password-page-CEwsdLwn.mjs +3 -0
- package/dist/index-B9Xwk4hi.d.mts +2753 -0
- package/dist/index-B9Xwk4hi.d.mts.map +1 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +13 -0
- package/dist/login-page-BUnpCbCa.mjs +3 -0
- package/dist/login-page-CP4gA-dl.mjs +298 -0
- package/dist/login-page-CP4gA-dl.mjs.map +1 -0
- package/dist/preview-utils-BKQ9-TMa.mjs +65 -0
- package/dist/preview-utils-BKQ9-TMa.mjs.map +1 -0
- package/dist/{views/auth/reset-password-form.mjs → reset-password-page-BqfDmLxA.mjs} +111 -14
- package/dist/reset-password-page-BqfDmLxA.mjs.map +1 -0
- package/dist/reset-password-page-CufHz3h3.mjs +3 -0
- package/dist/runtime-6VZM878K.mjs +69 -0
- package/dist/runtime-6VZM878K.mjs.map +1 -0
- package/dist/saved-views.types-BMsz5mCy.d.mts +42 -0
- package/dist/saved-views.types-BMsz5mCy.d.mts.map +1 -0
- package/dist/server.d.mts +250 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.mjs +832 -0
- package/dist/server.mjs.map +1 -0
- package/dist/setup-page-BNNzt_Z6.mjs +3 -0
- package/dist/setup-page-YAP_fzqh.mjs +264 -0
- package/dist/setup-page-YAP_fzqh.mjs.map +1 -0
- package/dist/shared.d.mts +57 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +3 -0
- package/dist/{hooks/use-auth.mjs → use-auth-BoLmWtmU.mjs} +42 -30
- package/dist/use-auth-BoLmWtmU.mjs.map +1 -0
- package/package.json +48 -198
- package/.turbo/turbo-build.log +0 -108
- package/CHANGELOG.md +0 -10
- package/STATUS.md +0 -917
- package/VALIDATION.md +0 -602
- package/components.json +0 -24
- package/dist/__tests__/setup.mjs +0 -38
- package/dist/__tests__/test-utils.mjs +0 -45
- package/dist/__tests__/vitest.d.mjs +0 -3
- package/dist/components/admin-app.mjs +0 -69
- package/dist/components/fields/array-field.mjs +0 -190
- package/dist/components/fields/checkbox-field.mjs +0 -34
- package/dist/components/fields/custom-field.mjs +0 -32
- package/dist/components/fields/date-field.mjs +0 -41
- package/dist/components/fields/datetime-field.mjs +0 -42
- package/dist/components/fields/email-field.mjs +0 -37
- package/dist/components/fields/embedded-collection.mjs +0 -253
- package/dist/components/fields/field-types.mjs +0 -1
- package/dist/components/fields/field-utils.mjs +0 -10
- package/dist/components/fields/field-wrapper.mjs +0 -34
- package/dist/components/fields/index.mjs +0 -23
- package/dist/components/fields/json-field.mjs +0 -243
- package/dist/components/fields/locale-badge.mjs +0 -16
- package/dist/components/fields/number-field.mjs +0 -39
- package/dist/components/fields/password-field.mjs +0 -37
- package/dist/components/fields/relation-field.mjs +0 -104
- package/dist/components/fields/relation-picker.mjs +0 -229
- package/dist/components/fields/relation-select.mjs +0 -188
- package/dist/components/fields/rich-text-editor/index.mjs +0 -897
- package/dist/components/fields/select-field.mjs +0 -41
- package/dist/components/fields/switch-field.mjs +0 -34
- package/dist/components/fields/text-field.mjs +0 -38
- package/dist/components/fields/textarea-field.mjs +0 -38
- package/dist/components/index.mjs +0 -59
- package/dist/components/primitives/checkbox-input.mjs +0 -127
- package/dist/components/primitives/date-input.mjs +0 -303
- package/dist/components/primitives/index.mjs +0 -12
- package/dist/components/primitives/number-input.mjs +0 -104
- package/dist/components/primitives/select-input.mjs +0 -177
- package/dist/components/primitives/tag-input.mjs +0 -135
- package/dist/components/primitives/text-input.mjs +0 -39
- package/dist/components/primitives/textarea-input.mjs +0 -37
- package/dist/components/primitives/toggle-input.mjs +0 -31
- package/dist/components/primitives/types.mjs +0 -12
- package/dist/components/ui/accordion.mjs +0 -55
- package/dist/components/ui/avatar.mjs +0 -54
- package/dist/components/ui/badge.mjs +0 -34
- package/dist/components/ui/button.mjs +0 -48
- package/dist/components/ui/checkbox.mjs +0 -21
- package/dist/components/ui/combobox.mjs +0 -163
- package/dist/components/ui/dialog.mjs +0 -95
- package/dist/components/ui/dropdown-menu.mjs +0 -138
- package/dist/components/ui/field.mjs +0 -113
- package/dist/components/ui/input-group.mjs +0 -82
- package/dist/components/ui/input.mjs +0 -17
- package/dist/components/ui/label.mjs +0 -15
- package/dist/components/ui/popover.mjs +0 -56
- package/dist/components/ui/scroll-area.mjs +0 -38
- package/dist/components/ui/select.mjs +0 -100
- package/dist/components/ui/separator.mjs +0 -16
- package/dist/components/ui/sheet.mjs +0 -90
- package/dist/components/ui/sidebar.mjs +0 -387
- package/dist/components/ui/skeleton.mjs +0 -14
- package/dist/components/ui/spinner.mjs +0 -16
- package/dist/components/ui/switch.mjs +0 -22
- package/dist/components/ui/table.mjs +0 -68
- package/dist/components/ui/tabs.mjs +0 -48
- package/dist/components/ui/textarea.mjs +0 -15
- package/dist/components/ui/tooltip.mjs +0 -44
- package/dist/config/component-registry.mjs +0 -38
- package/dist/config/index.mjs +0 -129
- package/dist/hooks/admin-provider.mjs +0 -70
- package/dist/hooks/index.mjs +0 -7
- package/dist/hooks/store.mjs +0 -178
- package/dist/hooks/use-collection-db.mjs +0 -146
- package/dist/hooks/use-collection.mjs +0 -112
- package/dist/hooks/use-global.mjs +0 -46
- package/dist/hooks/use-mobile.mjs +0 -20
- package/dist/lib/utils.mjs +0 -10
- package/dist/styles/index.css +0 -336
- package/dist/styles/index.mjs +0 -1
- package/dist/utils/index.mjs +0 -9
- package/dist/views/auth/auth-layout.mjs +0 -52
- package/dist/views/auth/index.mjs +0 -6
- package/dist/views/auth/login-form.mjs +0 -156
- package/dist/views/collection/auto-form-fields.mjs +0 -525
- package/dist/views/collection/collection-form.mjs +0 -91
- package/dist/views/collection/collection-list.mjs +0 -76
- package/dist/views/collection/form-field.mjs +0 -42
- package/dist/views/collection/index.mjs +0 -6
- package/dist/views/common/index.mjs +0 -4
- package/dist/views/common/locale-switcher.mjs +0 -39
- package/dist/views/common/version-history.mjs +0 -272
- package/dist/views/index.mjs +0 -9
- package/dist/views/layout/admin-layout.mjs +0 -40
- package/dist/views/layout/admin-router.mjs +0 -95
- package/dist/views/layout/admin-sidebar.mjs +0 -63
- package/dist/views/layout/index.mjs +0 -5
- package/src/__tests__/setup.ts +0 -44
- package/src/__tests__/test-utils.tsx +0 -49
- package/src/__tests__/vitest.d.ts +0 -9
- package/src/components/admin-app.tsx +0 -221
- package/src/components/fields/array-field.tsx +0 -237
- package/src/components/fields/checkbox-field.tsx +0 -47
- package/src/components/fields/custom-field.tsx +0 -50
- package/src/components/fields/date-field.tsx +0 -65
- package/src/components/fields/datetime-field.tsx +0 -67
- package/src/components/fields/email-field.tsx +0 -51
- package/src/components/fields/embedded-collection.tsx +0 -315
- package/src/components/fields/field-types.ts +0 -162
- package/src/components/fields/field-utils.ts +0 -6
- package/src/components/fields/field-wrapper.tsx +0 -52
- package/src/components/fields/index.ts +0 -66
- package/src/components/fields/json-field.tsx +0 -440
- package/src/components/fields/locale-badge.tsx +0 -15
- package/src/components/fields/number-field.tsx +0 -57
- package/src/components/fields/password-field.tsx +0 -51
- package/src/components/fields/relation-field.tsx +0 -243
- package/src/components/fields/relation-picker.tsx +0 -402
- package/src/components/fields/relation-select.tsx +0 -327
- package/src/components/fields/rich-text-editor/index.tsx +0 -1337
- package/src/components/fields/select-field.tsx +0 -61
- package/src/components/fields/switch-field.tsx +0 -47
- package/src/components/fields/text-field.tsx +0 -55
- package/src/components/fields/textarea-field.tsx +0 -55
- package/src/components/index.ts +0 -40
- package/src/components/primitives/checkbox-input.tsx +0 -193
- package/src/components/primitives/date-input.tsx +0 -401
- package/src/components/primitives/index.ts +0 -24
- package/src/components/primitives/number-input.tsx +0 -132
- package/src/components/primitives/select-input.tsx +0 -296
- package/src/components/primitives/tag-input.tsx +0 -200
- package/src/components/primitives/text-input.tsx +0 -49
- package/src/components/primitives/textarea-input.tsx +0 -46
- package/src/components/primitives/toggle-input.tsx +0 -36
- package/src/components/primitives/types.ts +0 -235
- package/src/components/ui/accordion.tsx +0 -72
- package/src/components/ui/avatar.tsx +0 -106
- package/src/components/ui/badge.tsx +0 -48
- package/src/components/ui/button.tsx +0 -53
- package/src/components/ui/card.tsx +0 -94
- package/src/components/ui/checkbox.tsx +0 -27
- package/src/components/ui/combobox.tsx +0 -290
- package/src/components/ui/dialog.tsx +0 -151
- package/src/components/ui/dropdown-menu.tsx +0 -254
- package/src/components/ui/field.tsx +0 -227
- package/src/components/ui/input-group.tsx +0 -149
- package/src/components/ui/input.tsx +0 -20
- package/src/components/ui/label.tsx +0 -18
- package/src/components/ui/popover.tsx +0 -88
- package/src/components/ui/scroll-area.tsx +0 -53
- package/src/components/ui/select.tsx +0 -192
- package/src/components/ui/separator.tsx +0 -23
- package/src/components/ui/sheet.tsx +0 -127
- package/src/components/ui/sidebar.tsx +0 -723
- package/src/components/ui/skeleton.tsx +0 -13
- package/src/components/ui/spinner.tsx +0 -10
- package/src/components/ui/switch.tsx +0 -32
- package/src/components/ui/table.tsx +0 -99
- package/src/components/ui/tabs.tsx +0 -82
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/tooltip.tsx +0 -70
- package/src/config/component-registry.ts +0 -190
- package/src/config/index.ts +0 -1099
- package/src/hooks/README.md +0 -269
- package/src/hooks/admin-provider.tsx +0 -110
- package/src/hooks/index.ts +0 -41
- package/src/hooks/store.ts +0 -248
- package/src/hooks/use-auth.ts +0 -168
- package/src/hooks/use-collection-db.ts +0 -209
- package/src/hooks/use-collection.ts +0 -156
- package/src/hooks/use-global.ts +0 -69
- package/src/hooks/use-mobile.ts +0 -21
- package/src/lib/utils.ts +0 -6
- package/src/styles/index.css +0 -340
- package/src/utils/index.ts +0 -6
- package/src/views/auth/auth-layout.tsx +0 -77
- package/src/views/auth/forgot-password-form.tsx +0 -192
- package/src/views/auth/index.ts +0 -21
- package/src/views/auth/login-form.tsx +0 -229
- package/src/views/auth/reset-password-form.tsx +0 -232
- package/src/views/collection/auto-form-fields.tsx +0 -982
- package/src/views/collection/collection-form.tsx +0 -186
- package/src/views/collection/collection-list.tsx +0 -223
- package/src/views/collection/form-field.tsx +0 -52
- package/src/views/collection/index.ts +0 -15
- package/src/views/common/index.ts +0 -8
- package/src/views/common/locale-switcher.tsx +0 -45
- package/src/views/common/version-history.tsx +0 -406
- package/src/views/index.ts +0 -25
- package/src/views/layout/admin-layout.tsx +0 -117
- package/src/views/layout/admin-router.tsx +0 -206
- package/src/views/layout/admin-sidebar.tsx +0 -185
- package/src/views/layout/index.ts +0 -12
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
- package/tsdown.config.ts +0 -13
- package/vitest.config.ts +0 -29
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["getApp","payload: PreviewTokenPayload"],"sources":["../src/server/auth-helpers.ts","../src/server/adapters/tanstack.ts","../src/server/adapters/nextjs.ts","../src/server/modules/admin-preferences/collections/admin-preferences.collection.ts","../src/server/modules/admin-preferences/collections/saved-views.collection.ts","../src/server/modules/admin/functions/locales.ts","../src/server/modules/admin/functions/preview.ts","../src/server/modules/admin/functions/setup.ts","../src/server/modules/admin/index.ts"],"sourcesContent":["/**\n * SSR Auth Helpers\n *\n * Server-side utilities for authentication in SSR frameworks.\n * These helpers work with Better Auth to check session on the server.\n *\n * @example TanStack Router\n * ```ts\n * import { requireAdminAuth } from \"@questpie/admin/server\";\n *\n * export const Route = createFileRoute(\"/admin\")({\n * beforeLoad: async ({ context }) => {\n * const redirect = await requireAdminAuth({\n * request: context.request,\n * cms,\n * loginPath: \"/admin/login\",\n * });\n * if (redirect) throw redirect;\n * },\n * });\n * ```\n */\n\nimport type { Questpie } from \"questpie\";\n\n/**\n * Session object from Better Auth\n */\nexport interface AuthSession {\n user: {\n id: string;\n email: string;\n name?: string | null;\n role?: string | null;\n [key: string]: unknown;\n };\n session: {\n id: string;\n userId: string;\n expiresAt: Date;\n [key: string]: unknown;\n };\n}\n\n/**\n * Options for requireAdminAuth\n */\nexport interface RequireAdminAuthOptions {\n /**\n * The incoming request\n */\n request: Request;\n\n /**\n * The CMS instance with auth configured\n */\n cms: Questpie<any>;\n\n /**\n * Path to redirect to when not authenticated\n * @default \"/admin/login\"\n */\n loginPath?: string;\n\n /**\n * Required role for access\n * @default \"admin\"\n */\n requiredRole?: string;\n\n /**\n * Query parameter name for redirect URL\n * @default \"redirect\"\n */\n redirectParam?: string;\n}\n\n/**\n * Options for getAdminSession\n */\nexport interface GetAdminSessionOptions {\n /**\n * The incoming request\n */\n request: Request;\n\n /**\n * The CMS instance with auth configured\n */\n cms: Questpie<any>;\n}\n\n/**\n * Check if user is authenticated with required role on the server.\n * Returns a redirect Response if not authenticated, null if authenticated.\n *\n * Use this in server loaders/middleware to protect routes.\n *\n * @example TanStack Router\n * ```ts\n * export const Route = createFileRoute(\"/admin\")({\n * beforeLoad: async ({ context }) => {\n * const redirect = await requireAdminAuth({\n * request: context.request,\n * cms,\n * loginPath: \"/admin/login\",\n * });\n * if (redirect) throw redirect;\n * },\n * });\n * ```\n *\n * @example Next.js Middleware\n * ```ts\n * export async function middleware(request: NextRequest) {\n * const redirect = await requireAdminAuth({\n * request,\n * cms,\n * loginPath: \"/admin/login\",\n * });\n * if (redirect) return redirect;\n * return NextResponse.next();\n * }\n * ```\n */\nexport async function requireAdminAuth({\n request,\n cms,\n loginPath = \"/admin/login\",\n requiredRole = \"admin\",\n redirectParam = \"redirect\",\n}: RequireAdminAuthOptions): Promise<Response | null> {\n // Check if auth is configured\n if (!cms.auth) {\n console.warn(\"requireAdminAuth: Auth not configured on CMS instance\");\n return null;\n }\n\n try {\n // Get session from Better Auth\n const session = await cms.auth.api.getSession({\n headers: request.headers,\n });\n\n // No session - redirect to login\n if (!session || !session.user) {\n const currentUrl = new URL(request.url);\n const redirectUrl = new URL(loginPath, currentUrl.origin);\n redirectUrl.searchParams.set(redirectParam, currentUrl.pathname);\n return Response.redirect(redirectUrl.toString(), 302);\n }\n\n // Check role - cast to any because role is added by Better Auth admin plugin\n const userRole = (session.user as any).role;\n if (userRole !== requiredRole) {\n const currentUrl = new URL(request.url);\n const redirectUrl = new URL(loginPath, currentUrl.origin);\n redirectUrl.searchParams.set(redirectParam, currentUrl.pathname);\n return Response.redirect(redirectUrl.toString(), 302);\n }\n\n // Authenticated with correct role\n return null;\n } catch (error) {\n console.error(\"requireAdminAuth: Error checking session\", error);\n // On error, redirect to login for safety\n const currentUrl = new URL(request.url);\n const redirectUrl = new URL(loginPath, currentUrl.origin);\n return Response.redirect(redirectUrl.toString(), 302);\n }\n}\n\n/**\n * Get the current admin session on the server.\n * Returns null if not authenticated.\n *\n * Use this when you need access to the session data in server code.\n *\n * @example\n * ```ts\n * const session = await getAdminSession({ request, cms });\n * if (!session) {\n * return redirect(\"/admin/login\");\n * }\n * console.log(\"User:\", session.user.name);\n * ```\n */\nexport async function getAdminSession({\n request,\n cms,\n}: GetAdminSessionOptions): Promise<AuthSession | null> {\n // Check if auth is configured\n if (!cms.auth) {\n return null;\n }\n\n try {\n const session = await cms.auth.api.getSession({\n headers: request.headers,\n });\n\n if (!session || !session.user) {\n return null;\n }\n\n return session as AuthSession;\n } catch (error) {\n console.error(\"getAdminSession: Error getting session\", error);\n return null;\n }\n}\n\n/**\n * Check if the current user has admin role on the server.\n *\n * @example\n * ```ts\n * const isAdmin = await isAdminUser({ request, cms });\n * if (!isAdmin) {\n * return json({ error: \"Unauthorized\" }, { status: 403 });\n * }\n * ```\n */\nexport async function isAdminUser({\n request,\n cms,\n requiredRole = \"admin\",\n}: GetAdminSessionOptions & { requiredRole?: string }): Promise<boolean> {\n const session = await getAdminSession({ request, cms });\n // Cast to any to access role - it's added by Better Auth admin plugin\n return (session?.user as any)?.role === requiredRole;\n}\n","/**\n * TanStack Router Adapter\n *\n * Convenience helpers for TanStack Router/Start integration.\n *\n * @example\n * ```ts\n * import { createTanStackAuthGuard } from \"@questpie/admin/server/adapters/tanstack\";\n * import { cms } from \"~/questpie/server/cms\";\n *\n * export const Route = createFileRoute(\"/admin\")({\n * beforeLoad: createTanStackAuthGuard({ cms, loginPath: \"/admin/login\" }),\n * });\n * ```\n */\n\nimport type { Questpie } from \"questpie\";\nimport { requireAdminAuth } from \"../auth-helpers.js\";\n\n/**\n * Options for TanStack auth guard\n */\nexport interface TanStackAuthGuardOptions {\n /**\n * The CMS instance with auth configured\n */\n cms: Questpie<any>;\n\n /**\n * Path to redirect to when not authenticated\n * @default \"/admin/login\"\n */\n loginPath?: string;\n\n /**\n * Required role for access\n * @default \"admin\"\n */\n requiredRole?: string;\n\n /**\n * Query parameter name for redirect URL\n * @default \"redirect\"\n */\n redirectParam?: string;\n}\n\n/**\n * Context passed to TanStack Router beforeLoad\n */\nexport interface BeforeLoadContext {\n context: {\n request: Request;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n/**\n * Create a TanStack Router beforeLoad guard for admin authentication.\n *\n * Returns a function that can be used as beforeLoad in route definitions.\n * Throws a redirect Response when authentication fails.\n *\n * @example\n * ```ts\n * import { createFileRoute } from \"@tanstack/react-router\";\n * import { createTanStackAuthGuard } from \"@questpie/admin/server/adapters/tanstack\";\n * import { cms } from \"~/questpie/server/cms\";\n *\n * export const Route = createFileRoute(\"/admin\")({\n * beforeLoad: createTanStackAuthGuard({\n * cms,\n * loginPath: \"/admin/login\",\n * requiredRole: \"admin\",\n * }),\n * component: AdminLayout,\n * });\n * ```\n *\n * @example With custom context\n * ```ts\n * export const Route = createFileRoute(\"/admin\")({\n * beforeLoad: async (ctx) => {\n * // Run auth guard\n * await createTanStackAuthGuard({ cms })(ctx);\n *\n * // Add additional context\n * return { user: ctx.context.user };\n * },\n * });\n * ```\n */\nexport function createTanStackAuthGuard({\n cms,\n loginPath = \"/admin/login\",\n requiredRole = \"admin\",\n redirectParam = \"redirect\",\n}: TanStackAuthGuardOptions) {\n return async function beforeLoad({ context }: BeforeLoadContext) {\n const request = context.request;\n\n if (!request) {\n console.warn(\n \"createTanStackAuthGuard: No request in context. \" +\n \"Make sure you're using TanStack Start with SSR enabled.\",\n );\n return;\n }\n\n const redirect = await requireAdminAuth({\n request,\n cms,\n loginPath,\n requiredRole,\n redirectParam,\n });\n\n if (redirect) {\n // TanStack Router expects thrown Response for redirects\n throw redirect;\n }\n };\n}\n\n/**\n * Create a TanStack Router loader that injects the admin session into context.\n *\n * Use this when you need access to the session in your components.\n *\n * @example\n * ```ts\n * import { createTanStackSessionLoader } from \"@questpie/admin/server/adapters/tanstack\";\n *\n * export const Route = createFileRoute(\"/admin\")({\n * loader: createTanStackSessionLoader({ cms }),\n * component: AdminLayout,\n * });\n *\n * function AdminLayout() {\n * const { session } = Route.useLoaderData();\n * return <div>Hello {session?.user?.name}</div>;\n * }\n * ```\n */\nexport function createTanStackSessionLoader({ cms }: { cms: Questpie<any> }) {\n return async function loader({ context }: BeforeLoadContext) {\n const request = context.request;\n\n if (!request || !cms.auth) {\n return { session: null };\n }\n\n try {\n const session = await cms.auth.api.getSession({\n headers: request.headers,\n });\n\n return { session: session ?? null };\n } catch {\n return { session: null };\n }\n };\n}\n","/**\n * Next.js Adapter\n *\n * Convenience helpers for Next.js middleware integration.\n *\n * @example App Router middleware\n * ```ts\n * // middleware.ts\n * import { createNextAuthMiddleware } from \"@questpie/admin/server/adapters/nextjs\";\n * import { cms } from \"./questpie/server/cms\";\n *\n * export default createNextAuthMiddleware({\n * cms,\n * loginPath: \"/admin/login\",\n * protectedPaths: [\"/admin\"],\n * publicPaths: [\"/admin/login\", \"/admin/forgot-password\"],\n * });\n *\n * export const config = {\n * matcher: [\"/admin/:path*\"],\n * };\n * ```\n */\n\nimport type { Questpie } from \"questpie\";\nimport { getAdminSession, requireAdminAuth } from \"../auth-helpers.js\";\n\n/**\n * Options for Next.js auth middleware\n */\nexport interface NextAuthMiddlewareOptions {\n /**\n * The CMS instance with auth configured\n */\n cms: Questpie<any>;\n\n /**\n * Path to redirect to when not authenticated\n * @default \"/admin/login\"\n */\n loginPath?: string;\n\n /**\n * Required role for access\n * @default \"admin\"\n */\n requiredRole?: string;\n\n /**\n * Paths that require authentication (uses startsWith matching)\n * @default [\"/admin\"]\n */\n protectedPaths?: string[];\n\n /**\n * Paths within protectedPaths that should be publicly accessible\n * @default [\"/admin/login\", \"/admin/forgot-password\", \"/admin/reset-password\", \"/admin/accept-invite\"]\n */\n publicPaths?: string[];\n\n /**\n * Query parameter name for redirect URL\n * @default \"redirect\"\n */\n redirectParam?: string;\n}\n\n/**\n * Check if a path matches any of the given patterns\n */\nfunction pathMatches(pathname: string, patterns: string[]): boolean {\n return patterns.some(\n (pattern) =>\n pathname === pattern ||\n pathname.startsWith(`${pattern}/`) ||\n pathname.startsWith(pattern),\n );\n}\n\n/**\n * Create a Next.js middleware for admin authentication.\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { createNextAuthMiddleware } from \"@questpie/admin/server/adapters/nextjs\";\n * import { cms } from \"./questpie/server/cms\";\n *\n * export default createNextAuthMiddleware({\n * cms,\n * loginPath: \"/admin/login\",\n * });\n *\n * export const config = {\n * matcher: [\"/admin/:path*\"],\n * };\n * ```\n */\nexport function createNextAuthMiddleware({\n cms,\n loginPath = \"/admin/login\",\n requiredRole = \"admin\",\n protectedPaths = [\"/admin\"],\n publicPaths = [\n \"/admin/login\",\n \"/admin/forgot-password\",\n \"/admin/reset-password\",\n \"/admin/accept-invite\",\n ],\n redirectParam = \"redirect\",\n}: NextAuthMiddlewareOptions) {\n return async function middleware(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const pathname = url.pathname;\n\n // Check if this is a protected path\n const isProtected = pathMatches(pathname, protectedPaths);\n const isPublic = pathMatches(pathname, publicPaths);\n\n // If not protected or is public, continue\n if (!isProtected || isPublic) {\n // Return a \"continue\" response\n // In Next.js middleware, returning undefined or NextResponse.next() continues\n // Since we're using standard Response, we'll return a special header\n return new Response(null, {\n status: 200,\n headers: {\n \"x-middleware-next\": \"1\",\n },\n });\n }\n\n // Check authentication\n const redirect = await requireAdminAuth({\n request,\n cms,\n loginPath,\n requiredRole,\n redirectParam,\n });\n\n if (redirect) {\n return redirect;\n }\n\n // Authenticated - continue\n return new Response(null, {\n status: 200,\n headers: {\n \"x-middleware-next\": \"1\",\n },\n });\n };\n}\n\n/**\n * Get the admin session in a Next.js server component or API route.\n *\n * @example Server Component\n * ```tsx\n * // app/admin/layout.tsx\n * import { getNextAdminSession } from \"@questpie/admin/server/adapters/nextjs\";\n * import { headers } from \"next/headers\";\n * import { cms } from \"~/questpie/server/cms\";\n *\n * export default async function AdminLayout({ children }) {\n * const headersList = headers();\n * const session = await getNextAdminSession({\n * headers: headersList,\n * cms,\n * });\n *\n * if (!session) {\n * redirect(\"/admin/login\");\n * }\n *\n * return <div>{children}</div>;\n * }\n * ```\n *\n * @example API Route\n * ```ts\n * // app/api/admin/users/route.ts\n * import { getNextAdminSession } from \"@questpie/admin/server/adapters/nextjs\";\n * import { cms } from \"~/questpie/server/cms\";\n *\n * export async function GET(request: Request) {\n * const session = await getNextAdminSession({\n * headers: request.headers,\n * cms,\n * });\n *\n * if (!session || session.user.role !== \"admin\") {\n * return Response.json({ error: \"Unauthorized\" }, { status: 401 });\n * }\n *\n * // ... handle request\n * }\n * ```\n */\nexport async function getNextAdminSession({\n headers,\n cms,\n}: {\n headers: Headers;\n cms: Questpie<any>;\n}) {\n // Create a minimal request object for getAdminSession\n const request = new Request(\"http://localhost\", { headers });\n return getAdminSession({ request, cms });\n}\n","import { jsonb, uniqueIndex, varchar } from \"drizzle-orm/pg-core\";\nimport { q } from \"questpie\";\n\n/**\n * Admin Preferences Collection\n *\n * Stores user-specific preferences for admin UI state.\n * This includes view configurations (columns, filters, sort)\n * that persist across devices and sessions.\n *\n * Key format: \"viewState:{collectionName}\" for view state preferences\n *\n * @example\n * ```ts\n * import { adminModule } from \"@questpie/admin/server\";\n *\n * const cms = q({ name: \"my-app\" })\n * .use(adminModule)\n * .collections({ ... })\n * .build({ ... });\n *\n * // Access preferences\n * const prefs = await cms.api.collections.admin_preferences.findOne({\n * where: { userId: currentUser.id, key: \"viewState:posts\" }\n * });\n * ```\n */\nexport const adminPreferencesCollection = q\n\t.collection(\"admin_preferences\")\n\t.fields({\n\t\t// User who owns this preference\n\t\tuserId: varchar(\"user_id\", { length: 255 }).notNull(),\n\n\t\t// Preference key (e.g., \"viewState:posts\")\n\t\tkey: varchar(\"key\", { length: 255 }).notNull(),\n\n\t\t// Preference value (JSON)\n\t\tvalue: jsonb(\"value\").notNull(),\n\t})\n\t.options({\n\t\ttimestamps: true,\n\t})\n\t.indexes(({ table }) => [\n\t\t// Unique constraint on userId + key\n\t\t// Type assertion needed due to Drizzle ORM duplicate dependency resolution\n\t\tuniqueIndex(\"admin_preferences_user_key_idx\").on(\n\t\t\ttable.userId as any,\n\t\t\ttable.key as any,\n\t\t),\n\t]);\n","import { q } from \"questpie\";\nimport { boolean, jsonb, varchar } from \"drizzle-orm/pg-core\";\nimport type { ViewConfiguration } from \"../../../../shared/types/saved-views.types.js\";\n\n// Re-export types from shared\nexport type {\n FilterOperator,\n FilterRule,\n SortConfig,\n ViewConfiguration,\n} from \"../../../../shared/types/saved-views.types.js\";\n\n/**\n * Admin Saved Views Collection\n *\n * Stores user-specific view configurations for collection lists.\n * Each view can contain:\n * - Filter rules (field/operator/value combinations)\n * - Sort configuration (field/direction)\n * - Visible columns selection\n *\n * @example\n * ```ts\n * import { adminModule } from \"@questpie/admin/server\";\n *\n * const cms = q({ name: \"my-app\" })\n * .use(starterModule)\n * .use(adminModule)\n * .collections({ ... })\n * .build({ ... });\n *\n * // Access saved views\n * const views = await cms.api.collections.adminSavedViews.find({\n * where: { collectionName: \"posts\", userId: currentUser.id }\n * });\n * ```\n */\nexport const savedViewsCollection = q\n .collection(\"admin_saved_views\")\n .fields({\n // User who owns this saved view\n userId: varchar(\"user_id\", { length: 255 }).notNull(),\n\n // Target collection for this view\n collectionName: varchar(\"collection_name\", { length: 255 }).notNull(),\n\n // Display name for the view\n name: varchar(\"name\", { length: 255 }).notNull(),\n\n // View configuration (filters, sort, columns)\n configuration: jsonb(\"configuration\").notNull().$type<ViewConfiguration>(),\n\n // Whether this is the default view for the user/collection\n isDefault: boolean(\"is_default\").default(false).notNull(),\n })\n .options({\n timestamps: true,\n });\n","/**\n * Content Locales Functions\n *\n * Functions for retrieving available content locales from the CMS configuration.\n * Used by the admin panel to populate locale switchers and validate locale selection.\n */\n\nimport { fn, type Questpie } from \"questpie\";\nimport { z } from \"zod\";\n\n// ============================================================================\n// Type Helpers\n// ============================================================================\n\n/**\n * Helper to get typed CMS app from handler context.\n */\nfunction getApp(ctx: { app: unknown }): Questpie<any> {\n\treturn ctx.app as Questpie<any>;\n}\n\n// ============================================================================\n// Schema Definitions\n// ============================================================================\n\nconst getContentLocalesSchema = z.object({}).optional();\n\nconst getContentLocalesOutputSchema = z.object({\n\tlocales: z.array(\n\t\tz.object({\n\t\t\tcode: z.string(),\n\t\t\tlabel: z.string().optional(),\n\t\t\tfallback: z.boolean().optional(),\n\t\t\tflagCountryCode: z.string().optional(),\n\t\t}),\n\t),\n\tdefaultLocale: z.string(),\n\tfallbacks: z.record(z.string(), z.string()).optional(),\n});\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get available content locales from CMS configuration.\n *\n * Returns the list of available locales for content localization,\n * the default locale, and any fallback mappings.\n *\n * @example\n * ```ts\n * const result = await client.rpc.getContentLocales({});\n * // {\n * // locales: [\n * // { code: \"en\", label: \"English\", fallback: true },\n * // { code: \"sk\", label: \"Slovenčina\" },\n * // ],\n * // defaultLocale: \"en\",\n * // fallbacks: { \"en-GB\": \"en\" },\n * // }\n * ```\n */\nexport const getContentLocales = fn({\n\ttype: \"query\",\n\tschema: getContentLocalesSchema,\n\toutputSchema: getContentLocalesOutputSchema,\n\thandler: async (ctx) => {\n\t\tconst cms = getApp(ctx);\n\t\tconst localeConfig = cms.config.locale;\n\n\t\t// If no locale config, return sensible defaults\n\t\tif (!localeConfig) {\n\t\t\treturn {\n\t\t\t\tlocales: [{ code: \"en\", label: \"English\", fallback: true }],\n\t\t\t\tdefaultLocale: \"en\",\n\t\t\t};\n\t\t}\n\n\t\t// Resolve locales (can be async function)\n\t\tconst locales =\n\t\t\ttypeof localeConfig.locales === \"function\"\n\t\t\t\t? await localeConfig.locales()\n\t\t\t\t: localeConfig.locales;\n\n\t\treturn {\n\t\t\tlocales: locales.map(\n\t\t\t\t(l: {\n\t\t\t\t\tcode: string;\n\t\t\t\t\tlabel?: string;\n\t\t\t\t\tfallback?: boolean;\n\t\t\t\t\tflagCountryCode?: string;\n\t\t\t\t}) => ({\n\t\t\t\t\tcode: l.code,\n\t\t\t\t\tlabel: l.label,\n\t\t\t\t\tfallback: l.fallback,\n\t\t\t\t\tflagCountryCode: l.flagCountryCode,\n\t\t\t\t}),\n\t\t\t),\n\t\t\tdefaultLocale: localeConfig.defaultLocale,\n\t\t\tfallbacks: localeConfig.fallbacks,\n\t\t};\n\t},\n});\n\n// ============================================================================\n// Export Bundle\n// ============================================================================\n\n/**\n * Bundle of locale-related functions.\n */\nexport const localeFunctions = {\n\tgetContentLocales,\n} as const;\n","/**\n * Preview Functions - Server-side\n *\n * RPC functions for draft mode preview.\n * Handles token minting for secure, shareable preview links.\n *\n * Browser-safe utilities (isDraftMode, createDraftModeCookie, etc.) are in @questpie/admin/shared\n */\n\nimport { getPreviewSecret } from \"#questpie/admin/shared/preview-utils.js\";\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { fn } from \"questpie\";\nimport { z } from \"zod\";\n\n// ============================================================================\n// Token Utilities\n// ============================================================================\n\nfunction base64UrlEncode(input: string | Buffer): string {\n\tconst buffer = Buffer.isBuffer(input) ? input : Buffer.from(input);\n\treturn buffer\n\t\t.toString(\"base64\")\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(input: string): string {\n\tlet base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\tconst padding = base64.length % 4;\n\tif (padding) {\n\t\tbase64 += \"=\".repeat(4 - padding);\n\t}\n\treturn Buffer.from(base64, \"base64\").toString(\"utf8\");\n}\n\n// ============================================================================\n// Schema Definitions\n// ============================================================================\n\nconst mintPreviewTokenSchema = z.object({\n\tpath: z.string().min(1, \"Path is required\"),\n\tttlMs: z.number().positive().optional(),\n});\n\nconst mintPreviewTokenOutputSchema = z.object({\n\ttoken: z.string(),\n\texpiresAt: z.number(),\n});\n\nconst verifyPreviewTokenSchema = z.object({\n\ttoken: z.string(),\n});\n\nconst verifyPreviewTokenOutputSchema = z.object({\n\tvalid: z.boolean(),\n\tpath: z.string().optional(),\n\terror: z.string().optional(),\n});\n\n// ============================================================================\n// Preview Token Payload\n// ============================================================================\n\nexport interface PreviewTokenPayload {\n\tpath: string;\n\texp: number;\n}\n\n// ============================================================================\n// Functions Factory\n// ============================================================================\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n/**\n * Create preview-related RPC functions.\n *\n * @param secret - Secret key for signing tokens\n * @returns Object with preview functions\n */\nexport function createPreviewFunctions(secret: string) {\n\tconst signPayload = (payload: string): string => {\n\t\tconst signature = createHmac(\"sha256\", secret).update(payload).digest();\n\t\treturn base64UrlEncode(signature);\n\t};\n\n\t/**\n\t * Mint a preview token for draft mode access.\n\t *\n\t * Requires authenticated admin session.\n\t * Token contains the target path and expiration time.\n\t *\n\t * @example\n\t * ```ts\n\t * const { token } = await client.rpc.mintPreviewToken({\n\t * path: \"/pages/about\",\n\t * ttlMs: 30 * 60 * 1000, // 30 minutes\n\t * });\n\t * // Use: /api/preview?token=${token}\n\t * ```\n\t */\n\tconst mintPreviewToken = fn({\n\t\ttype: \"mutation\",\n\t\tschema: mintPreviewTokenSchema,\n\t\toutputSchema: mintPreviewTokenOutputSchema,\n\t\thandler: async ({ input, session }) => {\n\t\t\t// Require authenticated admin session\n\t\t\tif (!session) {\n\t\t\t\tthrow new Error(\"Unauthorized: Admin session required\");\n\t\t\t}\n\n\t\t\tconst { path, ttlMs = DEFAULT_TTL_MS } = input;\n\t\t\tconst expiresAt = Date.now() + ttlMs;\n\n\t\t\tconst payload: PreviewTokenPayload = { path, exp: expiresAt };\n\t\t\tconst payloadString = JSON.stringify(payload);\n\t\t\tconst encodedPayload = base64UrlEncode(payloadString);\n\t\t\tconst signature = signPayload(encodedPayload);\n\n\t\t\treturn {\n\t\t\t\ttoken: `${encodedPayload}.${signature}`,\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t},\n\t});\n\n\t/**\n\t * Verify a preview token.\n\t *\n\t * Used by the preview route to validate tokens before setting draft mode cookie.\n\t * Does not require authentication (token IS the authentication).\n\t *\n\t * @example\n\t * ```ts\n\t * const result = await client.rpc.verifyPreviewToken({ token });\n\t * if (result.valid) {\n\t * // Redirect to result.path with draft mode cookie\n\t * }\n\t * ```\n\t */\n\tconst verifyPreviewToken = fn({\n\t\ttype: \"query\",\n\t\tschema: verifyPreviewTokenSchema,\n\t\toutputSchema: verifyPreviewTokenOutputSchema,\n\t\thandler: async ({ input }) => {\n\t\t\tconst { token } = input;\n\n\t\t\tconst [encodedPayload, signature] = token.split(\".\");\n\t\t\tif (!encodedPayload || !signature) {\n\t\t\t\treturn { valid: false, error: \"Invalid token format\" };\n\t\t\t}\n\n\t\t\t// Verify signature\n\t\t\tconst expectedSignature = signPayload(encodedPayload);\n\t\t\tconst signatureBuffer = Uint8Array.from(Buffer.from(signature));\n\t\t\tconst expectedBuffer = Uint8Array.from(Buffer.from(expectedSignature));\n\n\t\t\tif (signatureBuffer.length !== expectedBuffer.length) {\n\t\t\t\treturn { valid: false, error: \"Invalid signature\" };\n\t\t\t}\n\n\t\t\tif (!timingSafeEqual(signatureBuffer, expectedBuffer)) {\n\t\t\t\treturn { valid: false, error: \"Invalid signature\" };\n\t\t\t}\n\n\t\t\t// Parse and validate payload\n\t\t\ttry {\n\t\t\t\tconst payload = JSON.parse(\n\t\t\t\t\tbase64UrlDecode(encodedPayload),\n\t\t\t\t) as PreviewTokenPayload;\n\n\t\t\t\tif (!payload?.exp || typeof payload.exp !== \"number\") {\n\t\t\t\t\treturn { valid: false, error: \"Invalid payload\" };\n\t\t\t\t}\n\n\t\t\t\tif (payload.exp < Date.now()) {\n\t\t\t\t\treturn { valid: false, error: \"Token expired\" };\n\t\t\t\t}\n\n\t\t\t\tif (!payload.path || typeof payload.path !== \"string\") {\n\t\t\t\t\treturn { valid: false, error: \"Invalid path\" };\n\t\t\t\t}\n\n\t\t\t\treturn { valid: true, path: payload.path };\n\t\t\t} catch {\n\t\t\t\treturn { valid: false, error: \"Invalid payload\" };\n\t\t\t}\n\t\t},\n\t});\n\n\treturn {\n\t\tmintPreviewToken,\n\t\tverifyPreviewToken,\n\t};\n}\n\n// ============================================================================\n// Standalone Token Verification (for route handlers)\n// ============================================================================\n\n/**\n * Verify a preview token without RPC.\n * Used directly in route handlers where RPC is not available.\n *\n * @param token - The preview token to verify\n * @param secret - The secret used to sign the token\n * @returns The payload if valid, null otherwise\n */\nexport function verifyPreviewTokenDirect(\n\ttoken: string,\n\tsecret: string,\n): PreviewTokenPayload | null {\n\tconst [encodedPayload, signature] = token.split(\".\");\n\tif (!encodedPayload || !signature) return null;\n\n\tconst expectedSignature = base64UrlEncode(\n\t\tcreateHmac(\"sha256\", secret).update(encodedPayload).digest(),\n\t);\n\n\tconst signatureBuffer = Uint8Array.from(Buffer.from(signature));\n\tconst expectedBuffer = Uint8Array.from(Buffer.from(expectedSignature));\n\n\tif (signatureBuffer.length !== expectedBuffer.length) return null;\n\tif (!timingSafeEqual(signatureBuffer, expectedBuffer)) return null;\n\n\ttry {\n\t\tconst payload = JSON.parse(\n\t\t\tbase64UrlDecode(encodedPayload),\n\t\t) as PreviewTokenPayload;\n\n\t\tif (!payload?.exp || typeof payload.exp !== \"number\") return null;\n\t\tif (payload.exp < Date.now()) return null;\n\t\tif (!payload.path || typeof payload.path !== \"string\") return null;\n\n\t\treturn payload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// ============================================================================\n// Factory & Helpers\n// ============================================================================\n\n/**\n * Create a preview token verifier with bound secret.\n * Use this in route handlers to avoid passing secret repeatedly.\n *\n * @param secret - The secret used to sign tokens (optional, uses env if not provided)\n * @returns A verify function that only needs the token\n *\n * @example\n * ```ts\n * // Create once at module level\n * const verifyPreviewToken = createPreviewTokenVerifier();\n *\n * // Use in route handler\n * const payload = verifyPreviewToken(token);\n * if (!payload) {\n * return new Response(\"Invalid token\", { status: 401 });\n * }\n * ```\n */\nexport function createPreviewTokenVerifier(secret?: string) {\n\tconst resolvedSecret = secret ?? getPreviewSecret();\n\n\treturn (token: string): PreviewTokenPayload | null => {\n\t\treturn verifyPreviewTokenDirect(token, resolvedSecret);\n\t};\n}\n\n// ============================================================================\n// Default Functions Bundle\n// ============================================================================\n\n/**\n * Default preview functions bundle with env-based secret.\n * Used by adminModule to register preview RPC functions.\n */\nexport const previewFunctions = createPreviewFunctions(getPreviewSecret());\n","/**\n * Setup Functions\n *\n * Built-in functions for bootstrapping the first admin user.\n * Solves the chicken-and-egg problem where invitation-based systems\n * need an existing admin to create the first invitation.\n */\n\nimport { eq, sql } from \"drizzle-orm\";\nimport { fn, type Questpie } from \"questpie\";\nimport { z } from \"zod\";\n\n// ============================================================================\n// Type Helpers\n// ============================================================================\n\n/**\n * Helper to get typed CMS app from handler context.\n * Used internally for better IDE support without affecting the public API.\n */\nfunction getApp(ctx: { app: unknown }): Questpie<any> {\n\treturn ctx.app as Questpie<any>;\n}\n\n// ============================================================================\n// Schema Definitions\n// ============================================================================\n\nconst isSetupRequiredSchema = z.object({});\n\nconst isSetupRequiredOutputSchema = z.object({\n\trequired: z.boolean(),\n});\n\nconst createFirstAdminSchema = z.object({\n\temail: z.string().email(\"Invalid email address\"),\n\tpassword: z.string().min(8, \"Password must be at least 8 characters\"),\n\tname: z.string().min(2, \"Name must be at least 2 characters\"),\n});\n\nconst createFirstAdminOutputSchema = z.object({\n\tsuccess: z.boolean(),\n\tuser: z\n\t\t.object({\n\t\t\tid: z.string(),\n\t\t\temail: z.string(),\n\t\t\tname: z.string(),\n\t\t})\n\t\t.optional(),\n\terror: z.string().optional(),\n});\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Check if setup is required (no users exist in the system).\n *\n * @example\n * ```ts\n * const result = await client.rpc.isSetupRequired({});\n * if (result.required) {\n * // Redirect to setup page\n * }\n * ```\n */\nexport const isSetupRequired = fn({\n\ttype: \"query\",\n\tschema: isSetupRequiredSchema,\n\toutputSchema: isSetupRequiredOutputSchema,\n\thandler: async (ctx) => {\n\t\tconst app = getApp(ctx);\n\t\tconst userCollection = app.getCollectionConfig(\"user\");\n\t\tconst result = await app.db\n\t\t\t.select({ count: sql<number>`count(*)::int` })\n\t\t\t.from(userCollection.table);\n\t\treturn { required: result[0].count === 0 };\n\t},\n});\n\n/**\n * Create the first admin user in the system.\n * This function only works when no users exist (setup mode).\n *\n * Security: Once any user exists, this function will refuse to create more users.\n * This prevents unauthorized admin creation after initial setup.\n *\n * @example\n * ```ts\n * const result = await client.rpc.createFirstAdmin({\n * email: \"admin@example.com\",\n * password: \"securepassword123\",\n * name: \"Admin User\",\n * });\n *\n * if (result.success) {\n * // Redirect to login page\n * } else {\n * console.error(result.error);\n * }\n * ```\n */\nexport const createFirstAdmin = fn({\n\ttype: \"mutation\",\n\tschema: createFirstAdminSchema,\n\toutputSchema: createFirstAdminOutputSchema,\n\thandler: async (ctx) => {\n\t\tconst app = getApp(ctx);\n\t\tconst input = ctx.input as z.infer<typeof createFirstAdminSchema>;\n\t\tconst userCollection = app.getCollectionConfig(\"user\");\n\n\t\t// Check if setup already completed (any users exist)\n\t\tconst checkResult = await app.db\n\t\t\t.select({ count: sql<number>`count(*)::int` })\n\t\t\t.from(userCollection.table);\n\n\t\tif (checkResult[0].count > 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: \"Setup already completed - users exist in the system\",\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\t// Create user via Better Auth signUp\n\t\t\tconst signUpResult = await app.auth.api.signUpEmail({\n\t\t\t\tbody: {\n\t\t\t\t\temail: input.email,\n\t\t\t\t\tpassword: input.password,\n\t\t\t\t\tname: input.name,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tif (!signUpResult.user) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: \"Failed to create user account\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Update role to admin and verify email (first user is always admin)\n\t\t\t// TODO: we can also use our builder pattern for admin functions for proper typesafety here !\n\t\t\tawait app.db\n\t\t\t\t.update(userCollection.table)\n\t\t\t\t.set({\n\t\t\t\t\trole: \"admin\",\n\t\t\t\t\temailVerified: true,\n\t\t\t\t} as any)\n\t\t\t\t.where(eq(userCollection.table.id, signUpResult.user.id));\n\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tuser: {\n\t\t\t\t\tid: signUpResult.user.id,\n\t\t\t\t\temail: signUpResult.user.email,\n\t\t\t\t\tname: signUpResult.user.name,\n\t\t\t\t},\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror:\n\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t? error.message\n\t\t\t\t\t\t: \"An unexpected error occurred\",\n\t\t\t};\n\t\t}\n\t},\n});\n\n// ============================================================================\n// Export Bundle\n// ============================================================================\n\n/**\n * Bundle of setup-related functions.\n */\nexport const setupFunctions = {\n\tisSetupRequired,\n\tcreateFirstAdmin,\n} as const;\n","/**\n * Admin Module\n *\n * Complete backend module for running the QuestPie admin panel.\n * This is the main entry point for setting up the admin backend.\n *\n * Includes:\n * - Auth collections (users, sessions, accounts, verifications, apikeys)\n * - Assets collection with file upload support\n * - Admin saved views collection (named view configurations)\n * - Admin preferences collection (user-specific view state)\n * - Setup functions for bootstrapping first admin\n * - Core auth options (Better Auth configuration)\n *\n * @example\n * ```ts\n * import { q } from \"questpie\";\n * import { adminModule } from \"@questpie/admin/server\";\n *\n * const cms = q({ name: \"my-app\" })\n * .use(adminModule)\n * .collections({\n * posts: postsCollection,\n * })\n * .build({\n * db: { url: process.env.DATABASE_URL },\n * storage: { driver: s3Driver(...) },\n * });\n * ```\n */\n\nimport { q, starterModule } from \"questpie\";\nimport { adminPreferencesCollection } from \"../admin-preferences/collections/admin-preferences.collection.js\";\nimport { savedViewsCollection } from \"../admin-preferences/collections/saved-views.collection.js\";\nimport { localeFunctions } from \"./functions/locales.js\";\nimport { previewFunctions } from \"./functions/preview.js\";\nimport { setupFunctions } from \"./functions/setup.js\";\n\n// Re-export admin preferences collection\nexport { adminPreferencesCollection } from \"../admin-preferences/collections/admin-preferences.collection.js\";\n// Re-export saved views types\nexport {\n\ttype FilterOperator,\n\ttype FilterRule,\n\ttype SortConfig,\n\tsavedViewsCollection,\n\ttype ViewConfiguration,\n} from \"../admin-preferences/collections/saved-views.collection.js\";\n// Re-export locale functions for individual use\nexport { getContentLocales, localeFunctions } from \"./functions/locales.js\";\n// Re-export server-only preview functions (crypto-based token verification)\n// For browser-safe utilities (isDraftMode, createDraftModeCookie, etc.), use @questpie/admin/shared\nexport {\n\tcreatePreviewFunctions,\n\tcreatePreviewTokenVerifier,\n\ttype PreviewTokenPayload,\n\tpreviewFunctions,\n\tverifyPreviewTokenDirect,\n} from \"./functions/preview.js\";\n// Re-export setup functions for individual use\nexport {\n\tcreateFirstAdmin,\n\tisSetupRequired,\n\tsetupFunctions,\n} from \"./functions/setup.js\";\n\n/**\n * Admin Module - the complete backend for QuestPie admin panel.\n *\n * This module provides everything needed to run the admin panel:\n * - User authentication (Better Auth) - from starterModule\n * - File uploads (assets) - from starterModule\n * - Saved views for collection filters (named configurations)\n * - User preferences (view state synced across devices)\n * - Setup flow for first admin creation\n *\n * @example\n * ```ts\n * import { q } from \"questpie\";\n * import { adminModule } from \"@questpie/admin/server\";\n *\n * const cms = q({ name: \"my-app\" })\n * .use(adminModule)\n * .collections({\n * posts: postsCollection,\n * })\n * .build({\n * db: { url: process.env.DATABASE_URL },\n * });\n * ```\n *\n * @example\n * ```ts\n * // Extend assets collection with custom fields\n * import { q, collection, varchar } from \"questpie\";\n * import { adminModule } from \"@questpie/admin/server\";\n *\n * const cms = q({ name: \"my-app\" })\n * .use(adminModule)\n * .collections({\n * // Override assets with additional fields\n * assets: adminModule.state.collections.assets.merge(\n * collection(\"assets\").fields({\n * folder: varchar(\"folder\", { length: 255 }),\n * tags: varchar(\"tags\", { length: 1000 }),\n * })\n * ),\n * })\n * .build({ ... });\n * ```\n */\nexport const adminModule = q({ name: \"questpie-admin\" })\n\t// Include all starterModule functionality (auth, assets)\n\t.use(starterModule)\n\t// Add admin-specific collections\n\t.collections({\n\t\tadmin_saved_views: savedViewsCollection,\n\t\tadmin_preferences: adminPreferencesCollection,\n\t})\n\t// Add setup, locale, and preview functions\n\t.functions({\n\t\t...setupFunctions,\n\t\t...localeFunctions,\n\t\t...previewFunctions,\n\t});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { a as selectBasePath, f as useAdminStore, g as cn, h as Button, l as selectNavigate, o as selectBrandName, s as selectClient } from "./content-locales-provider-BXvuIgfg.mjs";
|
|
2
|
+
import { a as FieldContent, c as FieldGroup, f as Input, i as Field, l as FieldLabel, n as Alert, r as AlertDescription, s as FieldError, t as AuthLayout } from "./auth-layout-M8K8_q5R.mjs";
|
|
3
|
+
import { Envelope, Lock, SpinnerGap, User, WarningCircle } from "@phosphor-icons/react";
|
|
4
|
+
import * as React$1 from "react";
|
|
5
|
+
import { useForm } from "react-hook-form";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/client/views/auth/setup-form.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Setup Form - create first admin account
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Setup form for creating the first admin account.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function SetupPage() {
|
|
18
|
+
* const [error, setError] = useState<string | null>(null)
|
|
19
|
+
*
|
|
20
|
+
* const handleSetup = async (values: SetupFormValues) => {
|
|
21
|
+
* const result = await client.rpc.createFirstAdmin({
|
|
22
|
+
* email: values.email,
|
|
23
|
+
* password: values.password,
|
|
24
|
+
* name: values.name,
|
|
25
|
+
* })
|
|
26
|
+
* if (!result.success) {
|
|
27
|
+
* setError(result.error)
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* return (
|
|
32
|
+
* <AuthLayout title="Welcome">
|
|
33
|
+
* <SetupForm onSubmit={handleSetup} error={error} />
|
|
34
|
+
* </AuthLayout>
|
|
35
|
+
* )
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function SetupForm({ onSubmit, defaultValues, className, error }) {
|
|
40
|
+
const { register, handleSubmit, watch, formState: { errors, isSubmitting } } = useForm({ defaultValues: {
|
|
41
|
+
name: "",
|
|
42
|
+
email: "",
|
|
43
|
+
password: "",
|
|
44
|
+
confirmPassword: "",
|
|
45
|
+
...defaultValues
|
|
46
|
+
} });
|
|
47
|
+
const password = watch("password");
|
|
48
|
+
return /* @__PURE__ */ jsxs("form", {
|
|
49
|
+
onSubmit: handleSubmit(async (values) => {
|
|
50
|
+
await onSubmit(values);
|
|
51
|
+
}),
|
|
52
|
+
className: cn("space-y-4", className),
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsxs(FieldGroup, { children: [
|
|
55
|
+
/* @__PURE__ */ jsxs(Field, {
|
|
56
|
+
"data-invalid": !!errors.name,
|
|
57
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
58
|
+
htmlFor: "name",
|
|
59
|
+
children: "Name"
|
|
60
|
+
}), /* @__PURE__ */ jsxs(FieldContent, { children: [/* @__PURE__ */ jsxs("div", {
|
|
61
|
+
className: "relative",
|
|
62
|
+
children: [/* @__PURE__ */ jsx(User, {
|
|
63
|
+
className: "text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2",
|
|
64
|
+
weight: "duotone"
|
|
65
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
66
|
+
id: "name",
|
|
67
|
+
type: "text",
|
|
68
|
+
placeholder: "Admin User",
|
|
69
|
+
className: "pl-8",
|
|
70
|
+
autoComplete: "name",
|
|
71
|
+
"aria-invalid": !!errors.name,
|
|
72
|
+
...register("name", {
|
|
73
|
+
required: "Name is required",
|
|
74
|
+
minLength: {
|
|
75
|
+
value: 2,
|
|
76
|
+
message: "Name must be at least 2 characters"
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
})]
|
|
80
|
+
}), /* @__PURE__ */ jsx(FieldError, { children: errors.name?.message })] })]
|
|
81
|
+
}),
|
|
82
|
+
/* @__PURE__ */ jsxs(Field, {
|
|
83
|
+
"data-invalid": !!errors.email,
|
|
84
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
85
|
+
htmlFor: "email",
|
|
86
|
+
children: "Email"
|
|
87
|
+
}), /* @__PURE__ */ jsxs(FieldContent, { children: [/* @__PURE__ */ jsxs("div", {
|
|
88
|
+
className: "relative",
|
|
89
|
+
children: [/* @__PURE__ */ jsx(Envelope, {
|
|
90
|
+
className: "text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2",
|
|
91
|
+
weight: "duotone"
|
|
92
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
93
|
+
id: "email",
|
|
94
|
+
type: "email",
|
|
95
|
+
placeholder: "admin@example.com",
|
|
96
|
+
className: "pl-8",
|
|
97
|
+
autoComplete: "email",
|
|
98
|
+
"aria-invalid": !!errors.email,
|
|
99
|
+
...register("email", {
|
|
100
|
+
required: "Email is required",
|
|
101
|
+
pattern: {
|
|
102
|
+
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
103
|
+
message: "Invalid email address"
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})]
|
|
107
|
+
}), /* @__PURE__ */ jsx(FieldError, { children: errors.email?.message })] })]
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ jsxs(Field, {
|
|
110
|
+
"data-invalid": !!errors.password,
|
|
111
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
112
|
+
htmlFor: "password",
|
|
113
|
+
children: "Password"
|
|
114
|
+
}), /* @__PURE__ */ jsxs(FieldContent, { children: [/* @__PURE__ */ jsxs("div", {
|
|
115
|
+
className: "relative",
|
|
116
|
+
children: [/* @__PURE__ */ jsx(Lock, {
|
|
117
|
+
className: "text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2",
|
|
118
|
+
weight: "duotone"
|
|
119
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
120
|
+
id: "password",
|
|
121
|
+
type: "password",
|
|
122
|
+
placeholder: "Enter a secure password",
|
|
123
|
+
className: "pl-8",
|
|
124
|
+
autoComplete: "new-password",
|
|
125
|
+
"aria-invalid": !!errors.password,
|
|
126
|
+
...register("password", {
|
|
127
|
+
required: "Password is required",
|
|
128
|
+
minLength: {
|
|
129
|
+
value: 8,
|
|
130
|
+
message: "Password must be at least 8 characters"
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})]
|
|
134
|
+
}), /* @__PURE__ */ jsx(FieldError, { children: errors.password?.message })] })]
|
|
135
|
+
}),
|
|
136
|
+
/* @__PURE__ */ jsxs(Field, {
|
|
137
|
+
"data-invalid": !!errors.confirmPassword,
|
|
138
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
139
|
+
htmlFor: "confirmPassword",
|
|
140
|
+
children: "Confirm Password"
|
|
141
|
+
}), /* @__PURE__ */ jsxs(FieldContent, { children: [/* @__PURE__ */ jsxs("div", {
|
|
142
|
+
className: "relative",
|
|
143
|
+
children: [/* @__PURE__ */ jsx(Lock, {
|
|
144
|
+
className: "text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2",
|
|
145
|
+
weight: "duotone"
|
|
146
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
147
|
+
id: "confirmPassword",
|
|
148
|
+
type: "password",
|
|
149
|
+
placeholder: "Confirm your password",
|
|
150
|
+
className: "pl-8",
|
|
151
|
+
autoComplete: "new-password",
|
|
152
|
+
"aria-invalid": !!errors.confirmPassword,
|
|
153
|
+
...register("confirmPassword", {
|
|
154
|
+
required: "Please confirm your password",
|
|
155
|
+
validate: (value) => value === password || "Passwords do not match"
|
|
156
|
+
})
|
|
157
|
+
})]
|
|
158
|
+
}), /* @__PURE__ */ jsx(FieldError, { children: errors.confirmPassword?.message })] })]
|
|
159
|
+
})
|
|
160
|
+
] }),
|
|
161
|
+
error && /* @__PURE__ */ jsxs(Alert, {
|
|
162
|
+
variant: "destructive",
|
|
163
|
+
children: [/* @__PURE__ */ jsx(WarningCircle, {}), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ jsx(Button, {
|
|
166
|
+
type: "submit",
|
|
167
|
+
className: "w-full",
|
|
168
|
+
size: "lg",
|
|
169
|
+
disabled: isSubmitting,
|
|
170
|
+
children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SpinnerGap, {
|
|
171
|
+
className: "animate-spin",
|
|
172
|
+
weight: "bold"
|
|
173
|
+
}), "Creating account..."] }) : "Create Admin Account"
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/client/views/pages/setup-page.tsx
|
|
181
|
+
/**
|
|
182
|
+
* Setup Page
|
|
183
|
+
*
|
|
184
|
+
* Default setup page for creating the first admin account.
|
|
185
|
+
* Uses AuthLayout and SetupForm, integrates with client from AdminProvider context.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Default setup page component.
|
|
189
|
+
*
|
|
190
|
+
* Uses client from AdminProvider to call createFirstAdmin RPC function.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```tsx
|
|
194
|
+
* // In your admin config
|
|
195
|
+
* const admin = qa<AppCMS>()
|
|
196
|
+
* .use(coreAdminModule)
|
|
197
|
+
* .pages({
|
|
198
|
+
* setup: page("setup", { component: SetupPage }).path("/setup"),
|
|
199
|
+
* })
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
function SetupPage({ title = "Welcome", description = "Create your admin account to get started", logo, redirectTo, loginPath, showLoginLink = true }) {
|
|
203
|
+
const client = useAdminStore(selectClient);
|
|
204
|
+
const navigate = useAdminStore(selectNavigate);
|
|
205
|
+
const basePath = useAdminStore(selectBasePath);
|
|
206
|
+
const brandName = useAdminStore(selectBrandName);
|
|
207
|
+
const [error, setError] = React$1.useState(null);
|
|
208
|
+
const handleSubmit = async (values) => {
|
|
209
|
+
setError(null);
|
|
210
|
+
try {
|
|
211
|
+
const result = await client.functions.createFirstAdmin({
|
|
212
|
+
email: values.email,
|
|
213
|
+
password: values.password,
|
|
214
|
+
name: values.name
|
|
215
|
+
});
|
|
216
|
+
if (!result.success) {
|
|
217
|
+
setError(result.error ?? "Failed to create admin account");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
navigate(redirectTo ?? `${basePath}/login`);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const handleLoginClick = () => {
|
|
226
|
+
navigate(loginPath ?? `${basePath}/login`);
|
|
227
|
+
};
|
|
228
|
+
return /* @__PURE__ */ jsx(AuthLayout, {
|
|
229
|
+
title,
|
|
230
|
+
description,
|
|
231
|
+
logo: logo ?? /* @__PURE__ */ jsx(DefaultLogo, { brandName }),
|
|
232
|
+
footer: showLoginLink && /* @__PURE__ */ jsxs("p", {
|
|
233
|
+
className: "text-muted-foreground text-center text-xs",
|
|
234
|
+
children: [
|
|
235
|
+
"Already have an account?",
|
|
236
|
+
" ",
|
|
237
|
+
/* @__PURE__ */ jsx("button", {
|
|
238
|
+
type: "button",
|
|
239
|
+
onClick: handleLoginClick,
|
|
240
|
+
className: "text-primary hover:underline",
|
|
241
|
+
children: "Sign in"
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
}),
|
|
245
|
+
children: /* @__PURE__ */ jsx(SetupForm, {
|
|
246
|
+
onSubmit: handleSubmit,
|
|
247
|
+
error
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function DefaultLogo({ brandName }) {
|
|
252
|
+
return /* @__PURE__ */ jsx("div", {
|
|
253
|
+
className: "text-center",
|
|
254
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
255
|
+
className: "text-xl font-bold",
|
|
256
|
+
children: brandName
|
|
257
|
+
})
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
var setup_page_default = SetupPage;
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
export { setup_page_default as n, SetupForm as r, SetupPage as t };
|
|
264
|
+
//# sourceMappingURL=setup-page-YAP_fzqh.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-page-YAP_fzqh.mjs","names":["React"],"sources":["../src/client/views/auth/setup-form.tsx","../src/client/views/pages/setup-page.tsx"],"sourcesContent":["/**\n * Setup Form - create first admin account\n */\n\nimport {\n Envelope,\n Lock,\n SpinnerGap,\n User,\n WarningCircle,\n} from \"@phosphor-icons/react\";\nimport * as React from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { Alert, AlertDescription } from \"../../components/ui/alert\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Field,\n FieldContent,\n FieldError,\n FieldGroup,\n FieldLabel,\n} from \"../../components/ui/field\";\nimport { Input } from \"../../components/ui/input\";\nimport { cn } from \"../../lib/utils\";\n\nexport type SetupFormValues = {\n name: string;\n email: string;\n password: string;\n confirmPassword: string;\n};\n\nexport type SetupFormProps = {\n /** Called when form is submitted with valid data */\n onSubmit: (values: SetupFormValues) => Promise<void>;\n /** Default values */\n defaultValues?: Partial<SetupFormValues>;\n /** Additional class name */\n className?: string;\n /** Error message from setup */\n error?: string | null;\n};\n\n/**\n * Setup form for creating the first admin account.\n *\n * @example\n * ```tsx\n * function SetupPage() {\n * const [error, setError] = useState<string | null>(null)\n *\n * const handleSetup = async (values: SetupFormValues) => {\n * const result = await client.rpc.createFirstAdmin({\n * email: values.email,\n * password: values.password,\n * name: values.name,\n * })\n * if (!result.success) {\n * setError(result.error)\n * }\n * }\n *\n * return (\n * <AuthLayout title=\"Welcome\">\n * <SetupForm onSubmit={handleSetup} error={error} />\n * </AuthLayout>\n * )\n * }\n * ```\n */\nexport function SetupForm({\n onSubmit,\n defaultValues,\n className,\n error,\n}: SetupFormProps) {\n const {\n register,\n handleSubmit,\n watch,\n formState: { errors, isSubmitting },\n } = useForm<SetupFormValues>({\n defaultValues: {\n name: \"\",\n email: \"\",\n password: \"\",\n confirmPassword: \"\",\n ...defaultValues,\n },\n });\n\n const password = watch(\"password\");\n\n const handleFormSubmit = handleSubmit(async (values) => {\n await onSubmit(values);\n });\n\n return (\n <form onSubmit={handleFormSubmit} className={cn(\"space-y-4\", className)}>\n <FieldGroup>\n {/* Name Field */}\n <Field data-invalid={!!errors.name}>\n <FieldLabel htmlFor=\"name\">Name</FieldLabel>\n <FieldContent>\n <div className=\"relative\">\n <User\n className=\"text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2\"\n weight=\"duotone\"\n />\n <Input\n id=\"name\"\n type=\"text\"\n placeholder=\"Admin User\"\n className=\"pl-8\"\n autoComplete=\"name\"\n aria-invalid={!!errors.name}\n {...register(\"name\", {\n required: \"Name is required\",\n minLength: {\n value: 2,\n message: \"Name must be at least 2 characters\",\n },\n })}\n />\n </div>\n <FieldError>{errors.name?.message}</FieldError>\n </FieldContent>\n </Field>\n\n {/* Email Field */}\n <Field data-invalid={!!errors.email}>\n <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n <FieldContent>\n <div className=\"relative\">\n <Envelope\n className=\"text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2\"\n weight=\"duotone\"\n />\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"admin@example.com\"\n className=\"pl-8\"\n autoComplete=\"email\"\n aria-invalid={!!errors.email}\n {...register(\"email\", {\n required: \"Email is required\",\n pattern: {\n value: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n message: \"Invalid email address\",\n },\n })}\n />\n </div>\n <FieldError>{errors.email?.message}</FieldError>\n </FieldContent>\n </Field>\n\n {/* Password Field */}\n <Field data-invalid={!!errors.password}>\n <FieldLabel htmlFor=\"password\">Password</FieldLabel>\n <FieldContent>\n <div className=\"relative\">\n <Lock\n className=\"text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2\"\n weight=\"duotone\"\n />\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"Enter a secure password\"\n className=\"pl-8\"\n autoComplete=\"new-password\"\n aria-invalid={!!errors.password}\n {...register(\"password\", {\n required: \"Password is required\",\n minLength: {\n value: 8,\n message: \"Password must be at least 8 characters\",\n },\n })}\n />\n </div>\n <FieldError>{errors.password?.message}</FieldError>\n </FieldContent>\n </Field>\n\n {/* Confirm Password Field */}\n <Field data-invalid={!!errors.confirmPassword}>\n <FieldLabel htmlFor=\"confirmPassword\">Confirm Password</FieldLabel>\n <FieldContent>\n <div className=\"relative\">\n <Lock\n className=\"text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2\"\n weight=\"duotone\"\n />\n <Input\n id=\"confirmPassword\"\n type=\"password\"\n placeholder=\"Confirm your password\"\n className=\"pl-8\"\n autoComplete=\"new-password\"\n aria-invalid={!!errors.confirmPassword}\n {...register(\"confirmPassword\", {\n required: \"Please confirm your password\",\n validate: (value) =>\n value === password || \"Passwords do not match\",\n })}\n />\n </div>\n <FieldError>{errors.confirmPassword?.message}</FieldError>\n </FieldContent>\n </Field>\n </FieldGroup>\n\n {/* Error Message */}\n {error && (\n <Alert variant=\"destructive\">\n <WarningCircle />\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n )}\n\n {/* Submit Button */}\n <Button\n type=\"submit\"\n className=\"w-full\"\n size=\"lg\"\n disabled={isSubmitting}\n >\n {isSubmitting ? (\n <>\n <SpinnerGap className=\"animate-spin\" weight=\"bold\" />\n Creating account...\n </>\n ) : (\n \"Create Admin Account\"\n )}\n </Button>\n </form>\n );\n}\n","/**\n * Setup Page\n *\n * Default setup page for creating the first admin account.\n * Uses AuthLayout and SetupForm, integrates with client from AdminProvider context.\n */\n\nimport * as React from \"react\";\nimport {\n selectBasePath,\n selectBrandName,\n selectClient,\n selectNavigate,\n useAdminStore,\n} from \"../../runtime/provider\";\nimport { AuthLayout } from \"../auth/auth-layout\";\nimport { SetupForm, type SetupFormValues } from \"../auth/setup-form\";\n\nexport interface SetupPageProps {\n /**\n * Title shown on the setup page\n * @default \"Welcome\"\n */\n title?: string;\n\n /**\n * Description shown below the title\n * @default \"Create your admin account to get started\"\n */\n description?: string;\n\n /**\n * Logo component to show above the form\n */\n logo?: React.ReactNode;\n\n /**\n * Path to redirect after successful setup\n * @default \"{basePath}/login\"\n */\n redirectTo?: string;\n\n /**\n * Path to login page (shown in footer)\n * @default \"{basePath}/login\"\n */\n loginPath?: string;\n\n /**\n * Show \"Already have an account?\" link\n * @default true\n */\n showLoginLink?: boolean;\n}\n\n/**\n * Default setup page component.\n *\n * Uses client from AdminProvider to call createFirstAdmin RPC function.\n *\n * @example\n * ```tsx\n * // In your admin config\n * const admin = qa<AppCMS>()\n * .use(coreAdminModule)\n * .pages({\n * setup: page(\"setup\", { component: SetupPage }).path(\"/setup\"),\n * })\n * ```\n */\nexport function SetupPage({\n title = \"Welcome\",\n description = \"Create your admin account to get started\",\n logo,\n redirectTo,\n loginPath,\n showLoginLink = true,\n}: SetupPageProps) {\n const client = useAdminStore(selectClient);\n const navigate = useAdminStore(selectNavigate);\n const basePath = useAdminStore(selectBasePath);\n const brandName = useAdminStore(selectBrandName);\n\n const [error, setError] = React.useState<string | null>(null);\n\n const handleSubmit = async (values: SetupFormValues) => {\n setError(null);\n\n try {\n const result = await (client as any).functions.createFirstAdmin({\n email: values.email,\n password: values.password,\n name: values.name,\n });\n\n if (!result.success) {\n setError(result.error ?? \"Failed to create admin account\");\n return;\n }\n\n // Redirect to login on success\n navigate(redirectTo ?? `${basePath}/login`);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"An error occurred\");\n }\n };\n\n const handleLoginClick = () => {\n navigate(loginPath ?? `${basePath}/login`);\n };\n\n return (\n <AuthLayout\n title={title}\n description={description}\n logo={logo ?? <DefaultLogo brandName={brandName} />}\n footer={\n showLoginLink && (\n <p className=\"text-muted-foreground text-center text-xs\">\n Already have an account?{\" \"}\n <button\n type=\"button\"\n onClick={handleLoginClick}\n className=\"text-primary hover:underline\"\n >\n Sign in\n </button>\n </p>\n )\n }\n >\n <SetupForm onSubmit={handleSubmit} error={error} />\n </AuthLayout>\n );\n}\n\nfunction DefaultLogo({ brandName }: { brandName: string }) {\n return (\n <div className=\"text-center\">\n <h1 className=\"text-xl font-bold\">{brandName}</h1>\n </div>\n );\n}\n\nexport default SetupPage;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,SAAgB,UAAU,EACxB,UACA,eACA,WACA,SACiB;CACjB,MAAM,EACJ,UACA,cACA,OACA,WAAW,EAAE,QAAQ,mBACnB,QAAyB,EAC3B,eAAe;EACb,MAAM;EACN,OAAO;EACP,UAAU;EACV,iBAAiB;EACjB,GAAG;EACJ,EACF,CAAC;CAEF,MAAM,WAAW,MAAM,WAAW;AAMlC,QACE,qBAAC;EAAK,UALiB,aAAa,OAAO,WAAW;AACtD,SAAM,SAAS,OAAO;IACtB;EAGkC,WAAW,GAAG,aAAa,UAAU;;GACrE,qBAAC;IAEC,qBAAC;KAAM,gBAAc,CAAC,CAAC,OAAO;gBAC5B,oBAAC;MAAW,SAAQ;gBAAO;OAAiB,EAC5C,qBAAC,2BACC,qBAAC;MAAI,WAAU;iBACb,oBAAC;OACC,WAAU;OACV,QAAO;QACP,EACF,oBAAC;OACC,IAAG;OACH,MAAK;OACL,aAAY;OACZ,WAAU;OACV,cAAa;OACb,gBAAc,CAAC,CAAC,OAAO;OACvB,GAAI,SAAS,QAAQ;QACnB,UAAU;QACV,WAAW;SACT,OAAO;SACP,SAAS;SACV;QACF,CAAC;QACF;OACE,EACN,oBAAC,wBAAY,OAAO,MAAM,UAAqB,IAClC;MACT;IAGR,qBAAC;KAAM,gBAAc,CAAC,CAAC,OAAO;gBAC5B,oBAAC;MAAW,SAAQ;gBAAQ;OAAkB,EAC9C,qBAAC,2BACC,qBAAC;MAAI,WAAU;iBACb,oBAAC;OACC,WAAU;OACV,QAAO;QACP,EACF,oBAAC;OACC,IAAG;OACH,MAAK;OACL,aAAY;OACZ,WAAU;OACV,cAAa;OACb,gBAAc,CAAC,CAAC,OAAO;OACvB,GAAI,SAAS,SAAS;QACpB,UAAU;QACV,SAAS;SACP,OAAO;SACP,SAAS;SACV;QACF,CAAC;QACF;OACE,EACN,oBAAC,wBAAY,OAAO,OAAO,UAAqB,IACnC;MACT;IAGR,qBAAC;KAAM,gBAAc,CAAC,CAAC,OAAO;gBAC5B,oBAAC;MAAW,SAAQ;gBAAW;OAAqB,EACpD,qBAAC,2BACC,qBAAC;MAAI,WAAU;iBACb,oBAAC;OACC,WAAU;OACV,QAAO;QACP,EACF,oBAAC;OACC,IAAG;OACH,MAAK;OACL,aAAY;OACZ,WAAU;OACV,cAAa;OACb,gBAAc,CAAC,CAAC,OAAO;OACvB,GAAI,SAAS,YAAY;QACvB,UAAU;QACV,WAAW;SACT,OAAO;SACP,SAAS;SACV;QACF,CAAC;QACF;OACE,EACN,oBAAC,wBAAY,OAAO,UAAU,UAAqB,IACtC;MACT;IAGR,qBAAC;KAAM,gBAAc,CAAC,CAAC,OAAO;gBAC5B,oBAAC;MAAW,SAAQ;gBAAkB;OAA6B,EACnE,qBAAC,2BACC,qBAAC;MAAI,WAAU;iBACb,oBAAC;OACC,WAAU;OACV,QAAO;QACP,EACF,oBAAC;OACC,IAAG;OACH,MAAK;OACL,aAAY;OACZ,WAAU;OACV,cAAa;OACb,gBAAc,CAAC,CAAC,OAAO;OACvB,GAAI,SAAS,mBAAmB;QAC9B,UAAU;QACV,WAAW,UACT,UAAU,YAAY;QACzB,CAAC;QACF;OACE,EACN,oBAAC,wBAAY,OAAO,iBAAiB,UAAqB,IAC7C;MACT;OACG;GAGZ,SACC,qBAAC;IAAM,SAAQ;eACb,oBAAC,kBAAgB,EACjB,oBAAC,8BAAkB,QAAyB;KACtC;GAIV,oBAAC;IACC,MAAK;IACL,WAAU;IACV,MAAK;IACL,UAAU;cAET,eACC,4CACE,oBAAC;KAAW,WAAU;KAAe,QAAO;MAAS,2BAEpD,GAEH;KAEK;;GACJ;;;;;;;;;;;;;;;;;;;;;;;;;;ACzKX,SAAgB,UAAU,EACxB,QAAQ,WACR,cAAc,4CACd,MACA,YACA,WACA,gBAAgB,QACC;CACjB,MAAM,SAAS,cAAc,aAAa;CAC1C,MAAM,WAAW,cAAc,eAAe;CAC9C,MAAM,WAAW,cAAc,eAAe;CAC9C,MAAM,YAAY,cAAc,gBAAgB;CAEhD,MAAM,CAAC,OAAO,YAAYA,QAAM,SAAwB,KAAK;CAE7D,MAAM,eAAe,OAAO,WAA4B;AACtD,WAAS,KAAK;AAEd,MAAI;GACF,MAAM,SAAS,MAAO,OAAe,UAAU,iBAAiB;IAC9D,OAAO,OAAO;IACd,UAAU,OAAO;IACjB,MAAM,OAAO;IACd,CAAC;AAEF,OAAI,CAAC,OAAO,SAAS;AACnB,aAAS,OAAO,SAAS,iCAAiC;AAC1D;;AAIF,YAAS,cAAc,GAAG,SAAS,QAAQ;WACpC,KAAK;AACZ,YAAS,eAAe,QAAQ,IAAI,UAAU,oBAAoB;;;CAItE,MAAM,yBAAyB;AAC7B,WAAS,aAAa,GAAG,SAAS,QAAQ;;AAG5C,QACE,oBAAC;EACQ;EACM;EACb,MAAM,QAAQ,oBAAC,eAAuB,YAAa;EACnD,QACE,iBACE,qBAAC;GAAE,WAAU;;IAA4C;IAC9B;IACzB,oBAAC;KACC,MAAK;KACL,SAAS;KACT,WAAU;eACX;MAEQ;;IACP;YAIR,oBAAC;GAAU,UAAU;GAAqB;IAAS;GACxC;;AAIjB,SAAS,YAAY,EAAE,aAAoC;AACzD,QACE,oBAAC;EAAI,WAAU;YACb,oBAAC;GAAG,WAAU;aAAqB;IAAe;GAC9C;;AAIV,yBAAe"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { i as ViewConfiguration, n as FilterRule, r as SortConfig, t as FilterOperator } from "./saved-views.types-BMsz5mCy.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/preview-utils.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Preview Utilities - Browser-safe
|
|
7
|
+
*
|
|
8
|
+
* Utilities that can run in both browser and server environments.
|
|
9
|
+
* No Node.js dependencies (crypto, etc.)
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Cookie name for draft mode.
|
|
13
|
+
* Set by /api/preview route, checked by page loaders.
|
|
14
|
+
*/
|
|
15
|
+
declare const DRAFT_MODE_COOKIE = "__draft_mode";
|
|
16
|
+
/**
|
|
17
|
+
* Check if draft mode is enabled from cookie header.
|
|
18
|
+
*
|
|
19
|
+
* @param cookieHeader - The Cookie header value from request
|
|
20
|
+
* @returns true if draft mode cookie is present and set to "true"
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const isDraft = isDraftMode(request.headers.get("cookie"));
|
|
25
|
+
* const page = await cms.pages.findOne({
|
|
26
|
+
* where: isDraft ? { slug } : { slug, isPublished: true }
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function isDraftMode(cookieHeader: string | null | undefined): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Create Set-Cookie header value for draft mode.
|
|
33
|
+
*
|
|
34
|
+
* @param enabled - Whether to enable or disable draft mode
|
|
35
|
+
* @param maxAge - Cookie max age in seconds (default: 1 hour)
|
|
36
|
+
* @returns Set-Cookie header value
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Enable draft mode
|
|
41
|
+
* headers.set("Set-Cookie", createDraftModeCookie(true));
|
|
42
|
+
*
|
|
43
|
+
* // Disable draft mode
|
|
44
|
+
* headers.set("Set-Cookie", createDraftModeCookie(false));
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function createDraftModeCookie(enabled: boolean, maxAge?: number): string;
|
|
48
|
+
/**
|
|
49
|
+
* Get preview secret from environment variables.
|
|
50
|
+
* Falls back to SECRET if PREVIEW_SECRET is not set.
|
|
51
|
+
*
|
|
52
|
+
* @returns The preview secret
|
|
53
|
+
*/
|
|
54
|
+
declare function getPreviewSecret(): string;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { DRAFT_MODE_COOKIE, type FilterOperator, type FilterRule, type SortConfig, type ViewConfiguration, createDraftModeCookie, getPreviewSecret, isDraftMode };
|
|
57
|
+
//# sourceMappingURL=shared.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.mts","names":[],"sources":["../src/shared/preview-utils.ts"],"sourcesContent":[],"mappings":";;;;;;;AAeA;AAoBA;AAqBA;AAcA;;;;cAvDa,iBAAA;;;;;;;;;;;;;;;iBAoBG,WAAA;;;;;;;;;;;;;;;;;iBAqBA,qBAAA;;;;;;;iBAcA,gBAAA,CAAA"}
|
package/dist/shared.mjs
ADDED
|
@@ -1,34 +1,7 @@
|
|
|
1
|
+
import { f as useAdminStore, i as selectAuthClient } from "./content-locales-provider-BXvuIgfg.mjs";
|
|
1
2
|
import { createAuthClient } from "better-auth/react";
|
|
2
3
|
|
|
3
|
-
//#region src/hooks/use-auth.ts
|
|
4
|
-
/**
|
|
5
|
-
* Auth client utilities for admin package
|
|
6
|
-
*
|
|
7
|
-
* Better Auth handles all auth state management internally.
|
|
8
|
-
* This module provides type-safe client creation helpers with
|
|
9
|
-
* full type inference from your CMS auth configuration.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* // In your app, create the auth client once:
|
|
14
|
-
* import { createAdminAuthClient } from '@questpie/admin/hooks'
|
|
15
|
-
* import type { cms } from './server/cms'
|
|
16
|
-
*
|
|
17
|
-
* export const authClient = createAdminAuthClient<typeof cms>({
|
|
18
|
-
* baseURL: 'http://localhost:3000'
|
|
19
|
-
* })
|
|
20
|
-
*
|
|
21
|
-
* // Then use the built-in hooks:
|
|
22
|
-
* function MyComponent() {
|
|
23
|
-
* const { data: session, isPending, error } = authClient.useSession()
|
|
24
|
-
*
|
|
25
|
-
* if (isPending) return <div>Loading...</div>
|
|
26
|
-
* if (!session) return <LoginForm />
|
|
27
|
-
*
|
|
28
|
-
* return <div>Welcome {session.user.name}</div>
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
4
|
+
//#region src/client/hooks/use-auth.ts
|
|
32
5
|
/**
|
|
33
6
|
* Create a type-safe Better Auth client for the admin UI
|
|
34
7
|
*
|
|
@@ -71,6 +44,45 @@ function createAdminAuthClient(options) {
|
|
|
71
44
|
}
|
|
72
45
|
});
|
|
73
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Hook to access the auth client from AdminProvider context.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* import { useAuthClient } from '@questpie/admin/hooks'
|
|
53
|
+
*
|
|
54
|
+
* function LoginPage() {
|
|
55
|
+
* const authClient = useAuthClient()
|
|
56
|
+
*
|
|
57
|
+
* const handleLogin = async (email: string, password: string) => {
|
|
58
|
+
* await authClient.signIn.email({ email, password })
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* return <LoginForm onSubmit={handleLogin} />
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function useAuthClient() {
|
|
66
|
+
const authClient = useAdminStore(selectAuthClient);
|
|
67
|
+
if (!authClient) throw new Error("useAuthClient: authClient is not provided. Make sure to pass authClient to AdminProvider.");
|
|
68
|
+
return authClient;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Hook to access the auth client from AdminProvider context.
|
|
72
|
+
* Returns null if authClient is not provided (safe version).
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* const authClient = useAuthClientSafe()
|
|
77
|
+
* if (!authClient) {
|
|
78
|
+
* return <div>Auth not configured</div>
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
function useAuthClientSafe() {
|
|
83
|
+
return useAdminStore(selectAuthClient);
|
|
84
|
+
}
|
|
74
85
|
|
|
75
86
|
//#endregion
|
|
76
|
-
export { createAdminAuthClient };
|
|
87
|
+
export { useAuthClient as n, useAuthClientSafe as r, createAdminAuthClient as t };
|
|
88
|
+
//# sourceMappingURL=use-auth-BoLmWtmU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-auth-BoLmWtmU.mjs","names":[],"sources":["../src/client/hooks/use-auth.ts"],"sourcesContent":["/**\n * Auth client utilities for admin package\n *\n * Better Auth handles all auth state management internally.\n * This module provides type-safe client creation helpers with\n * full type inference from your CMS auth configuration.\n *\n * @example\n * ```tsx\n * // In your app, create the auth client once:\n * import { createAdminAuthClient } from '@questpie/admin/hooks'\n * import type { cms } from './server/cms'\n *\n * export const authClient = createAdminAuthClient<typeof cms>({\n * baseURL: 'http://localhost:3000'\n * })\n *\n * // Then use the built-in hooks:\n * function MyComponent() {\n * const { data: session, isPending, error } = authClient.useSession()\n *\n * if (isPending) return <div>Loading...</div>\n * if (!session) return <LoginForm />\n *\n * return <div>Welcome {session.user.name}</div>\n * }\n * ```\n */\n\nimport type { BetterAuthOptions } from \"better-auth\";\nimport { createAuthClient } from \"better-auth/react\";\nimport type { Questpie } from \"questpie\";\n\n/**\n * Extract auth options type from Questpie instance\n */\ntype ExtractAuthOptions<T extends Questpie<any>> =\n T extends Questpie<infer TConfig>\n ? TConfig[\"auth\"] extends BetterAuthOptions\n ? TConfig[\"auth\"]\n : BetterAuthOptions\n : BetterAuthOptions;\n\n/**\n * Options for creating admin auth client\n */\nexport type AdminAuthClientOptions = {\n /**\n * Base URL of the CMS API\n * @example 'http://localhost:3000'\n */\n baseURL: string;\n\n /**\n * Base path for auth routes\n * @default '/cms/auth'\n */\n basePath?: string;\n\n /**\n * Custom fetch implementation\n */\n fetchOptions?: {\n credentials?: RequestCredentials;\n headers?: Record<string, string>;\n };\n};\n\n/**\n * Internal client options type that includes $InferAuth\n */\ntype InternalClientOptions<T extends Questpie<any>> = {\n baseURL: string;\n fetchOptions?: {\n credentials?: RequestCredentials;\n headers?: Record<string, string>;\n };\n $InferAuth: ExtractAuthOptions<T>;\n};\n\n/**\n * Create a type-safe Better Auth client for the admin UI\n *\n * The returned client includes full type inference from your CMS auth configuration:\n * - `useSession()` - React hook for session state (typed based on your user/session schema)\n * - `signIn` methods (email, social providers configured in CMS)\n * - `signOut` - Sign out the current user\n * - `signUp` - Register new users\n * - Plugin-specific hooks (e.g., `useListAccounts` for admin plugin)\n *\n * @example\n * ```tsx\n * import { createAdminAuthClient } from '@questpie/admin/hooks'\n * import type { cms } from './server/cms'\n *\n * // Create client with type inference from your CMS\n * export const authClient = createAdminAuthClient<typeof cms>({\n * baseURL: 'http://localhost:3000'\n * })\n *\n * // Session data is fully typed based on your CMS auth config\n * function App() {\n * const { data: session, isPending } = authClient.useSession()\n *\n * if (isPending) return <Loading />\n * if (!session) return <LoginPage authClient={authClient} />\n *\n * // session.user has all fields from your user schema\n * return <AdminDashboard user={session.user} />\n * }\n * ```\n */\nexport function createAdminAuthClient<T extends Questpie<any>>(\n options: AdminAuthClientOptions,\n): ReturnType<typeof createAuthClient<InternalClientOptions<T>>> {\n const basePath = options.basePath ?? \"/cms/auth\";\n\n return createAuthClient<InternalClientOptions<T>>({\n baseURL: `${options.baseURL}${basePath}`,\n fetchOptions: {\n credentials: options.fetchOptions?.credentials ?? \"include\",\n headers: options.fetchOptions?.headers,\n },\n } as InternalClientOptions<T>);\n}\n\n/**\n * Type helper to extract auth client type from CMS instance\n *\n * @example\n * ```tsx\n * import type { AdminAuthClient } from '@questpie/admin/hooks'\n * import type { cms } from './server/cms'\n *\n * type MyAuthClient = AdminAuthClient<typeof cms>\n * ```\n */\nexport type AdminAuthClient<T extends Questpie<any>> = ReturnType<\n typeof createAdminAuthClient<T>\n>;\n\n/**\n * Type helper to extract session type from auth client\n *\n * @example\n * ```tsx\n * import type { AdminSession } from '@questpie/admin/hooks'\n * import type { cms } from './server/cms'\n *\n * type MySession = AdminSession<typeof cms>\n * // Includes: { user: { id, email, name, role, ... }, session: { ... } }\n * ```\n */\nexport type AdminSession<T extends Questpie<any>> =\n AdminAuthClient<T>[\"$Infer\"][\"Session\"];\n\n/**\n * Type helper to extract user type from CMS auth configuration\n *\n * @example\n * ```tsx\n * import type { AdminUser } from '@questpie/admin/hooks'\n * import type { cms } from './server/cms'\n *\n * type MyUser = AdminUser<typeof cms>\n * ```\n */\nexport type AdminUser<T extends Questpie<any>> = AdminSession<T>[\"user\"];\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nimport { selectAuthClient, useAdminStore } from \"../runtime/provider\";\n\n/**\n * Hook to access the auth client from AdminProvider context.\n *\n * @example\n * ```tsx\n * import { useAuthClient } from '@questpie/admin/hooks'\n *\n * function LoginPage() {\n * const authClient = useAuthClient()\n *\n * const handleLogin = async (email: string, password: string) => {\n * await authClient.signIn.email({ email, password })\n * }\n *\n * return <LoginForm onSubmit={handleLogin} />\n * }\n * ```\n */\nexport function useAuthClient<T = any>(): T {\n const authClient = useAdminStore(selectAuthClient);\n if (!authClient) {\n throw new Error(\n \"useAuthClient: authClient is not provided. \" +\n \"Make sure to pass authClient to AdminProvider.\",\n );\n }\n return authClient as T;\n}\n\n/**\n * Hook to access the auth client from AdminProvider context.\n * Returns null if authClient is not provided (safe version).\n *\n * @example\n * ```tsx\n * const authClient = useAuthClientSafe()\n * if (!authClient) {\n * return <div>Auth not configured</div>\n * }\n * ```\n */\nexport function useAuthClientSafe<T = any>(): T | null {\n return useAdminStore(selectAuthClient) as T | null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgHA,SAAgB,sBACd,SAC+D;CAC/D,MAAM,WAAW,QAAQ,YAAY;AAErC,QAAO,iBAA2C;EAChD,SAAS,GAAG,QAAQ,UAAU;EAC9B,cAAc;GACZ,aAAa,QAAQ,cAAc,eAAe;GAClD,SAAS,QAAQ,cAAc;GAChC;EACF,CAA6B;;;;;;;;;;;;;;;;;;;;AAsEhC,SAAgB,gBAA4B;CAC1C,MAAM,aAAa,cAAc,iBAAiB;AAClD,KAAI,CAAC,WACH,OAAM,IAAI,MACR,4FAED;AAEH,QAAO;;;;;;;;;;;;;;AAeT,SAAgB,oBAAuC;AACrD,QAAO,cAAc,iBAAiB"}
|