@questpie/admin 0.0.1 → 1.0.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 (250) hide show
  1. package/README.md +439 -424
  2. package/dist/auth-layout-M8K8_q5R.mjs +181 -0
  3. package/dist/auth-layout-M8K8_q5R.mjs.map +1 -0
  4. package/dist/bulk-upload-dialog-h7zXD78Y.mjs +274 -0
  5. package/dist/bulk-upload-dialog-h7zXD78Y.mjs.map +1 -0
  6. package/dist/{components/ui/card.mjs → card-BKHjBQfw.mjs} +8 -8
  7. package/dist/card-BKHjBQfw.mjs.map +1 -0
  8. package/dist/client/styles/index.css +434 -0
  9. package/dist/client-BCGpkAz6.mjs +22635 -0
  10. package/dist/client-BCGpkAz6.mjs.map +1 -0
  11. package/dist/client-CcWZbkBP.d.mts +13585 -0
  12. package/dist/client-CcWZbkBP.d.mts.map +1 -0
  13. package/dist/client.d.mts +3 -0
  14. package/dist/client.mjs +14 -0
  15. package/dist/content-locales-provider-BXvuIgfg.mjs +1650 -0
  16. package/dist/content-locales-provider-BXvuIgfg.mjs.map +1 -0
  17. package/dist/dashboard-page-B4PGEdc2.mjs +2500 -0
  18. package/dist/dashboard-page-B4PGEdc2.mjs.map +1 -0
  19. package/dist/dashboard-page-CVlyR40m.mjs +6 -0
  20. package/dist/dropzone-Do3awXKd.mjs +634 -0
  21. package/dist/dropzone-Do3awXKd.mjs.map +1 -0
  22. package/dist/{views/auth/forgot-password-form.mjs → forgot-password-page-Bcp-An4Y.mjs} +87 -14
  23. package/dist/forgot-password-page-Bcp-An4Y.mjs.map +1 -0
  24. package/dist/forgot-password-page-CIILVhfo.mjs +7 -0
  25. package/dist/index-B9Xwk4hi.d.mts +2753 -0
  26. package/dist/index-B9Xwk4hi.d.mts.map +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +14 -0
  29. package/dist/login-page-8K7fo0qK.mjs +7 -0
  30. package/dist/login-page-CP4gA-dl.mjs +298 -0
  31. package/dist/login-page-CP4gA-dl.mjs.map +1 -0
  32. package/dist/preview-utils-BKQ9-TMa.mjs +65 -0
  33. package/dist/preview-utils-BKQ9-TMa.mjs.map +1 -0
  34. package/dist/{views/auth/reset-password-form.mjs → reset-password-page-BqfDmLxA.mjs} +111 -14
  35. package/dist/reset-password-page-BqfDmLxA.mjs.map +1 -0
  36. package/dist/reset-password-page-DLATv0xQ.mjs +7 -0
  37. package/dist/runtime-6VZM878K.mjs +69 -0
  38. package/dist/runtime-6VZM878K.mjs.map +1 -0
  39. package/dist/saved-views.types-BMsz5mCy.d.mts +42 -0
  40. package/dist/saved-views.types-BMsz5mCy.d.mts.map +1 -0
  41. package/dist/server.d.mts +250 -0
  42. package/dist/server.d.mts.map +1 -0
  43. package/dist/server.mjs +832 -0
  44. package/dist/server.mjs.map +1 -0
  45. package/dist/setup-page-CMZ5P_OE.mjs +6 -0
  46. package/dist/setup-page-YAP_fzqh.mjs +264 -0
  47. package/dist/setup-page-YAP_fzqh.mjs.map +1 -0
  48. package/dist/shared.d.mts +57 -0
  49. package/dist/shared.d.mts.map +1 -0
  50. package/dist/shared.mjs +3 -0
  51. package/dist/{hooks/use-auth.mjs → use-auth-BoLmWtmU.mjs} +42 -30
  52. package/dist/use-auth-BoLmWtmU.mjs.map +1 -0
  53. package/package.json +48 -197
  54. package/.turbo/turbo-build.log +0 -108
  55. package/CHANGELOG.md +0 -10
  56. package/STATUS.md +0 -917
  57. package/VALIDATION.md +0 -602
  58. package/components.json +0 -24
  59. package/dist/__tests__/setup.mjs +0 -38
  60. package/dist/__tests__/test-utils.mjs +0 -45
  61. package/dist/__tests__/vitest.d.mjs +0 -3
  62. package/dist/components/admin-app.mjs +0 -69
  63. package/dist/components/fields/array-field.mjs +0 -190
  64. package/dist/components/fields/checkbox-field.mjs +0 -34
  65. package/dist/components/fields/custom-field.mjs +0 -32
  66. package/dist/components/fields/date-field.mjs +0 -41
  67. package/dist/components/fields/datetime-field.mjs +0 -42
  68. package/dist/components/fields/email-field.mjs +0 -37
  69. package/dist/components/fields/embedded-collection.mjs +0 -253
  70. package/dist/components/fields/field-types.mjs +0 -1
  71. package/dist/components/fields/field-utils.mjs +0 -10
  72. package/dist/components/fields/field-wrapper.mjs +0 -34
  73. package/dist/components/fields/index.mjs +0 -23
  74. package/dist/components/fields/json-field.mjs +0 -243
  75. package/dist/components/fields/locale-badge.mjs +0 -16
  76. package/dist/components/fields/number-field.mjs +0 -39
  77. package/dist/components/fields/password-field.mjs +0 -37
  78. package/dist/components/fields/relation-field.mjs +0 -104
  79. package/dist/components/fields/relation-picker.mjs +0 -229
  80. package/dist/components/fields/relation-select.mjs +0 -188
  81. package/dist/components/fields/rich-text-editor/index.mjs +0 -897
  82. package/dist/components/fields/select-field.mjs +0 -41
  83. package/dist/components/fields/switch-field.mjs +0 -34
  84. package/dist/components/fields/text-field.mjs +0 -38
  85. package/dist/components/fields/textarea-field.mjs +0 -38
  86. package/dist/components/index.mjs +0 -59
  87. package/dist/components/primitives/checkbox-input.mjs +0 -127
  88. package/dist/components/primitives/date-input.mjs +0 -303
  89. package/dist/components/primitives/index.mjs +0 -12
  90. package/dist/components/primitives/number-input.mjs +0 -104
  91. package/dist/components/primitives/select-input.mjs +0 -177
  92. package/dist/components/primitives/tag-input.mjs +0 -135
  93. package/dist/components/primitives/text-input.mjs +0 -39
  94. package/dist/components/primitives/textarea-input.mjs +0 -37
  95. package/dist/components/primitives/toggle-input.mjs +0 -31
  96. package/dist/components/primitives/types.mjs +0 -12
  97. package/dist/components/ui/accordion.mjs +0 -55
  98. package/dist/components/ui/avatar.mjs +0 -54
  99. package/dist/components/ui/badge.mjs +0 -34
  100. package/dist/components/ui/button.mjs +0 -48
  101. package/dist/components/ui/checkbox.mjs +0 -21
  102. package/dist/components/ui/combobox.mjs +0 -163
  103. package/dist/components/ui/dialog.mjs +0 -95
  104. package/dist/components/ui/dropdown-menu.mjs +0 -138
  105. package/dist/components/ui/field.mjs +0 -113
  106. package/dist/components/ui/input-group.mjs +0 -82
  107. package/dist/components/ui/input.mjs +0 -17
  108. package/dist/components/ui/label.mjs +0 -15
  109. package/dist/components/ui/popover.mjs +0 -56
  110. package/dist/components/ui/scroll-area.mjs +0 -38
  111. package/dist/components/ui/select.mjs +0 -100
  112. package/dist/components/ui/separator.mjs +0 -16
  113. package/dist/components/ui/sheet.mjs +0 -90
  114. package/dist/components/ui/sidebar.mjs +0 -387
  115. package/dist/components/ui/skeleton.mjs +0 -14
  116. package/dist/components/ui/spinner.mjs +0 -16
  117. package/dist/components/ui/switch.mjs +0 -22
  118. package/dist/components/ui/table.mjs +0 -68
  119. package/dist/components/ui/tabs.mjs +0 -48
  120. package/dist/components/ui/textarea.mjs +0 -15
  121. package/dist/components/ui/tooltip.mjs +0 -44
  122. package/dist/config/component-registry.mjs +0 -38
  123. package/dist/config/index.mjs +0 -129
  124. package/dist/hooks/admin-provider.mjs +0 -70
  125. package/dist/hooks/index.mjs +0 -7
  126. package/dist/hooks/store.mjs +0 -178
  127. package/dist/hooks/use-collection-db.mjs +0 -146
  128. package/dist/hooks/use-collection.mjs +0 -112
  129. package/dist/hooks/use-global.mjs +0 -46
  130. package/dist/hooks/use-mobile.mjs +0 -20
  131. package/dist/lib/utils.mjs +0 -10
  132. package/dist/styles/index.css +0 -336
  133. package/dist/styles/index.mjs +0 -1
  134. package/dist/utils/index.mjs +0 -9
  135. package/dist/views/auth/auth-layout.mjs +0 -52
  136. package/dist/views/auth/index.mjs +0 -6
  137. package/dist/views/auth/login-form.mjs +0 -156
  138. package/dist/views/collection/auto-form-fields.mjs +0 -525
  139. package/dist/views/collection/collection-form.mjs +0 -91
  140. package/dist/views/collection/collection-list.mjs +0 -76
  141. package/dist/views/collection/form-field.mjs +0 -42
  142. package/dist/views/collection/index.mjs +0 -6
  143. package/dist/views/common/index.mjs +0 -4
  144. package/dist/views/common/locale-switcher.mjs +0 -39
  145. package/dist/views/common/version-history.mjs +0 -272
  146. package/dist/views/index.mjs +0 -9
  147. package/dist/views/layout/admin-layout.mjs +0 -40
  148. package/dist/views/layout/admin-router.mjs +0 -95
  149. package/dist/views/layout/admin-sidebar.mjs +0 -63
  150. package/dist/views/layout/index.mjs +0 -5
  151. package/src/__tests__/setup.ts +0 -44
  152. package/src/__tests__/test-utils.tsx +0 -49
  153. package/src/__tests__/vitest.d.ts +0 -9
  154. package/src/components/admin-app.tsx +0 -221
  155. package/src/components/fields/array-field.tsx +0 -237
  156. package/src/components/fields/checkbox-field.tsx +0 -47
  157. package/src/components/fields/custom-field.tsx +0 -50
  158. package/src/components/fields/date-field.tsx +0 -65
  159. package/src/components/fields/datetime-field.tsx +0 -67
  160. package/src/components/fields/email-field.tsx +0 -51
  161. package/src/components/fields/embedded-collection.tsx +0 -315
  162. package/src/components/fields/field-types.ts +0 -162
  163. package/src/components/fields/field-utils.ts +0 -6
  164. package/src/components/fields/field-wrapper.tsx +0 -52
  165. package/src/components/fields/index.ts +0 -66
  166. package/src/components/fields/json-field.tsx +0 -440
  167. package/src/components/fields/locale-badge.tsx +0 -15
  168. package/src/components/fields/number-field.tsx +0 -57
  169. package/src/components/fields/password-field.tsx +0 -51
  170. package/src/components/fields/relation-field.tsx +0 -243
  171. package/src/components/fields/relation-picker.tsx +0 -402
  172. package/src/components/fields/relation-select.tsx +0 -327
  173. package/src/components/fields/rich-text-editor/index.tsx +0 -1337
  174. package/src/components/fields/select-field.tsx +0 -61
  175. package/src/components/fields/switch-field.tsx +0 -47
  176. package/src/components/fields/text-field.tsx +0 -55
  177. package/src/components/fields/textarea-field.tsx +0 -55
  178. package/src/components/index.ts +0 -40
  179. package/src/components/primitives/checkbox-input.tsx +0 -193
  180. package/src/components/primitives/date-input.tsx +0 -401
  181. package/src/components/primitives/index.ts +0 -24
  182. package/src/components/primitives/number-input.tsx +0 -132
  183. package/src/components/primitives/select-input.tsx +0 -296
  184. package/src/components/primitives/tag-input.tsx +0 -200
  185. package/src/components/primitives/text-input.tsx +0 -49
  186. package/src/components/primitives/textarea-input.tsx +0 -46
  187. package/src/components/primitives/toggle-input.tsx +0 -36
  188. package/src/components/primitives/types.ts +0 -235
  189. package/src/components/ui/accordion.tsx +0 -72
  190. package/src/components/ui/avatar.tsx +0 -106
  191. package/src/components/ui/badge.tsx +0 -48
  192. package/src/components/ui/button.tsx +0 -53
  193. package/src/components/ui/card.tsx +0 -94
  194. package/src/components/ui/checkbox.tsx +0 -27
  195. package/src/components/ui/combobox.tsx +0 -290
  196. package/src/components/ui/dialog.tsx +0 -151
  197. package/src/components/ui/dropdown-menu.tsx +0 -254
  198. package/src/components/ui/field.tsx +0 -227
  199. package/src/components/ui/input-group.tsx +0 -149
  200. package/src/components/ui/input.tsx +0 -20
  201. package/src/components/ui/label.tsx +0 -18
  202. package/src/components/ui/popover.tsx +0 -88
  203. package/src/components/ui/scroll-area.tsx +0 -53
  204. package/src/components/ui/select.tsx +0 -192
  205. package/src/components/ui/separator.tsx +0 -23
  206. package/src/components/ui/sheet.tsx +0 -127
  207. package/src/components/ui/sidebar.tsx +0 -723
  208. package/src/components/ui/skeleton.tsx +0 -13
  209. package/src/components/ui/spinner.tsx +0 -10
  210. package/src/components/ui/switch.tsx +0 -32
  211. package/src/components/ui/table.tsx +0 -99
  212. package/src/components/ui/tabs.tsx +0 -82
  213. package/src/components/ui/textarea.tsx +0 -18
  214. package/src/components/ui/tooltip.tsx +0 -70
  215. package/src/config/component-registry.ts +0 -190
  216. package/src/config/index.ts +0 -1099
  217. package/src/hooks/README.md +0 -269
  218. package/src/hooks/admin-provider.tsx +0 -110
  219. package/src/hooks/index.ts +0 -41
  220. package/src/hooks/store.ts +0 -248
  221. package/src/hooks/use-auth.ts +0 -168
  222. package/src/hooks/use-collection-db.ts +0 -209
  223. package/src/hooks/use-collection.ts +0 -156
  224. package/src/hooks/use-global.ts +0 -69
  225. package/src/hooks/use-mobile.ts +0 -21
  226. package/src/lib/utils.ts +0 -6
  227. package/src/styles/index.css +0 -340
  228. package/src/utils/index.ts +0 -6
  229. package/src/views/auth/auth-layout.tsx +0 -77
  230. package/src/views/auth/forgot-password-form.tsx +0 -192
  231. package/src/views/auth/index.ts +0 -21
  232. package/src/views/auth/login-form.tsx +0 -229
  233. package/src/views/auth/reset-password-form.tsx +0 -232
  234. package/src/views/collection/auto-form-fields.tsx +0 -982
  235. package/src/views/collection/collection-form.tsx +0 -186
  236. package/src/views/collection/collection-list.tsx +0 -223
  237. package/src/views/collection/form-field.tsx +0 -52
  238. package/src/views/collection/index.ts +0 -15
  239. package/src/views/common/index.ts +0 -8
  240. package/src/views/common/locale-switcher.tsx +0 -45
  241. package/src/views/common/version-history.tsx +0 -406
  242. package/src/views/index.ts +0 -25
  243. package/src/views/layout/admin-layout.tsx +0 -117
  244. package/src/views/layout/admin-router.tsx +0 -206
  245. package/src/views/layout/admin-sidebar.tsx +0 -185
  246. package/src/views/layout/index.ts +0 -12
  247. package/tsconfig.json +0 -13
  248. package/tsconfig.tsbuildinfo +0 -1
  249. package/tsdown.config.ts +0 -13
  250. package/vitest.config.ts +0 -29
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropzone-Do3awXKd.mjs","names":["React","Dialog","DialogPrimitive","Drawer","DrawerPrimitive","React","Drawer","DialogPrimitive","React","valid: File[]","errors: ValidationError[]","parts: string[]"],"sources":["../src/client/hooks/use-media-query.ts","../src/client/components/ui/dialog.tsx","../src/client/components/ui/drawer.tsx","../src/client/components/ui/responsive-dialog.tsx","../src/client/hooks/use-upload.ts","../src/client/components/primitives/dropzone.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Hook to detect media query matches\n *\n * @example\n * ```tsx\n * const isMobile = useMediaQuery(\"(max-width: 768px)\");\n * const isDesktop = useMediaQuery(\"(min-width: 769px)\");\n * const prefersDark = useMediaQuery(\"(prefers-color-scheme: dark)\");\n * ```\n */\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState<boolean>(() => {\n // Check if we're on the server\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(query).matches;\n });\n\n React.useEffect(() => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const mediaQuery = window.matchMedia(query);\n\n // Set initial value\n setMatches(mediaQuery.matches);\n\n // Create listener\n const listener = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n // Add listener (using modern API with fallback)\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener(\"change\", listener);\n } else {\n // Fallback for older browsers\n mediaQuery.addListener(listener);\n }\n\n // Cleanup\n return () => {\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener(\"change\", listener);\n } else {\n mediaQuery.removeListener(listener);\n }\n };\n }, [query]);\n\n return matches;\n}\n\n/**\n * Predefined breakpoints matching Tailwind CSS defaults\n */\nexport const breakpoints = {\n sm: \"(min-width: 640px)\",\n md: \"(min-width: 768px)\",\n lg: \"(min-width: 1024px)\",\n xl: \"(min-width: 1280px)\",\n \"2xl\": \"(min-width: 1536px)\",\n} as const;\n\n/**\n * Hook to check if viewport is mobile (below md breakpoint)\n */\nexport function useIsMobile(): boolean {\n return !useMediaQuery(breakpoints.md);\n}\n\n/**\n * Hook to check if viewport is desktop (md breakpoint and above)\n */\nexport function useIsDesktop(): boolean {\n return useMediaQuery(breakpoints.md);\n}\n","\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { XIcon } from \"@phosphor-icons/react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: DialogPrimitive.Backdrop.Props) {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogPrimitive.Popup.Props & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n \"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none\",\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n render={\n <Button\n variant=\"ghost\"\n className=\"absolute top-2 right-2\"\n size=\"icon-sm\"\n />\n }\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\"gap-1 flex flex-col\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n className,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n \"gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n Close\n </DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\"text-sm font-medium\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: DialogPrimitive.Description.Props) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\n \"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","\"use client\";\n\nimport type * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\n\nimport { cn } from \"#questpie/admin/client/lib/utils\";\n\nfunction Drawer({\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) {\n\treturn <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />;\n}\n\nfunction DrawerTrigger({\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {\n\treturn <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\nfunction DrawerPortal({\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {\n\treturn <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />;\n}\n\nfunction DrawerClose({\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Close>) {\n\treturn <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\nfunction DrawerOverlay({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {\n\treturn (\n\t\t<DrawerPrimitive.Overlay\n\t\t\tdata-slot=\"drawer-overlay\"\n\t\t\tclassName={cn(\n\t\t\t\t\"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DrawerContent({\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Content>) {\n\treturn (\n\t\t<DrawerPortal data-slot=\"drawer-portal\">\n\t\t\t<DrawerOverlay />\n\t\t\t<DrawerPrimitive.Content\n\t\t\t\tdata-slot=\"drawer-content\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"before:bg-background px-4 pb-4 relative flex h-auto flex-col bg-transparent text-xs/relaxed before:absolute before:inset-2 before:-z-10 before:rounded-xl data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=left]:sm:max-w-sm data-[vaul-drawer-direction=right]:sm:max-w-sm group/drawer-content fixed z-50\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t<div className=\"bg-muted mx-auto mt-4 hidden h-1.5 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n\t\t\t\t{children}\n\t\t\t</DrawerPrimitive.Content>\n\t\t</DrawerPortal>\n\t);\n}\n\nfunction DrawerHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"drawer-header\"\n\t\t\tclassName={cn(\n\t\t\t\t\"gap-1 pt-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:text-left flex flex-col\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DrawerFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n\treturn (\n\t\t<div\n\t\t\tdata-slot=\"drawer-footer\"\n\t\t\tclassName={cn(\"gap-2 py-4 mt-auto flex flex-col\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DrawerTitle({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Title>) {\n\treturn (\n\t\t<DrawerPrimitive.Title\n\t\t\tdata-slot=\"drawer-title\"\n\t\t\tclassName={cn(\"text-foreground text-sm font-medium\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction DrawerDescription({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof DrawerPrimitive.Description>) {\n\treturn (\n\t\t<DrawerPrimitive.Description\n\t\t\tdata-slot=\"drawer-description\"\n\t\t\tclassName={cn(\"text-muted-foreground text-xs/relaxed\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport {\n\tDrawer,\n\tDrawerPortal,\n\tDrawerOverlay,\n\tDrawerTrigger,\n\tDrawerClose,\n\tDrawerContent,\n\tDrawerHeader,\n\tDrawerFooter,\n\tDrawerTitle,\n\tDrawerDescription,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { cn } from \"#questpie/admin/client/lib/utils\";\nimport { useIsMobile } from \"#questpie/admin/client/hooks/use-media-query\";\nimport { DialogContent, DialogFooter, DialogHeader } from \"./dialog\";\nimport {\n\tDrawer,\n\tDrawerClose,\n\tDrawerContent,\n\tDrawerDescription,\n\tDrawerFooter,\n\tDrawerHeader,\n\tDrawerTitle,\n\tDrawerTrigger,\n} from \"./drawer\";\n\n/**\n * ResponsiveDialog - Uses Dialog on desktop, fullscreen Drawer on mobile\n *\n * @example\n * ```tsx\n * <ResponsiveDialog>\n * <ResponsiveDialogTrigger asChild>\n * <Button>Open</Button>\n * </ResponsiveDialogTrigger>\n * <ResponsiveDialogContent>\n * <ResponsiveDialogHeader>\n * <ResponsiveDialogTitle>Title</ResponsiveDialogTitle>\n * <ResponsiveDialogDescription>Description</ResponsiveDialogDescription>\n * </ResponsiveDialogHeader>\n * <div>Content here</div>\n * <ResponsiveDialogFooter>\n * <ResponsiveDialogClose asChild>\n * <Button variant=\"outline\">Cancel</Button>\n * </ResponsiveDialogClose>\n * <Button>Save</Button>\n * </ResponsiveDialogFooter>\n * </ResponsiveDialogContent>\n * </ResponsiveDialog>\n * ```\n */\n\ninterface ResponsiveDialogContextValue {\n\tisMobile: boolean;\n}\n\nconst ResponsiveDialogContext =\n\tReact.createContext<ResponsiveDialogContextValue | null>(null);\n\nfunction useResponsiveDialog() {\n\tconst context = React.useContext(ResponsiveDialogContext);\n\tif (!context) {\n\t\tthrow new Error(\n\t\t\t\"ResponsiveDialog components must be used within ResponsiveDialog\",\n\t\t);\n\t}\n\treturn context;\n}\n\nexport interface ResponsiveDialogProps {\n\tchildren: React.ReactNode;\n\topen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n}\n\nfunction ResponsiveDialog({\n\tchildren,\n\topen,\n\tonOpenChange,\n}: ResponsiveDialogProps) {\n\tconst isMobile = useIsMobile();\n\n\tconst contextValue = React.useMemo(() => ({ isMobile }), [isMobile]);\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<ResponsiveDialogContext.Provider value={contextValue}>\n\t\t\t\t<Drawer open={open} onOpenChange={onOpenChange}>\n\t\t\t\t\t{children}\n\t\t\t\t</Drawer>\n\t\t\t</ResponsiveDialogContext.Provider>\n\t\t);\n\t}\n\n\treturn (\n\t\t<ResponsiveDialogContext.Provider value={contextValue}>\n\t\t\t<DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n\t\t\t\t{children}\n\t\t\t</DialogPrimitive.Root>\n\t\t</ResponsiveDialogContext.Provider>\n\t);\n}\n\nfunction ResponsiveDialogTrigger({\n\tchildren,\n\tasChild = false,\n\tclassName,\n\t...props\n}: {\n\tchildren: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n} & Omit<React.ComponentProps<\"button\">, \"className\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<DrawerTrigger asChild={asChild} className={className} {...props}>\n\t\t\t\t{children}\n\t\t\t</DrawerTrigger>\n\t\t);\n\t}\n\n\treturn (\n\t\t<DialogPrimitive.Trigger\n\t\t\tclassName={className}\n\t\t\trender={asChild ? (children as React.ReactElement) : undefined}\n\t\t\t{...props}\n\t\t>\n\t\t\t{!asChild ? children : undefined}\n\t\t</DialogPrimitive.Trigger>\n\t);\n}\n\nexport interface ResponsiveDialogContentProps {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n}\n\nfunction ResponsiveDialogContent({\n\tchildren,\n\tclassName,\n}: ResponsiveDialogContentProps) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<DrawerContent className={cn(\"max-h-[96vh]\", className)}>\n\t\t\t\t{children}\n\t\t\t</DrawerContent>\n\t\t);\n\t}\n\n\treturn <DialogContent className={className}>{children}</DialogContent>;\n}\n\nfunction ResponsiveDialogHeader({\n\tclassName,\n\t...props\n}: React.ComponentProps<\"div\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn <DrawerHeader className={className} {...props} />;\n\t}\n\n\treturn <DialogHeader className={className} {...props} />;\n}\n\nfunction ResponsiveDialogTitle({\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<\"h2\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<DrawerTitle className={className} {...props}>\n\t\t\t\t{children}\n\t\t\t</DrawerTitle>\n\t\t);\n\t}\n\n\treturn (\n\t\t<DialogPrimitive.Title\n\t\t\tdata-slot=\"responsive-dialog-title\"\n\t\t\tclassName={cn(\"text-sm font-medium\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</DialogPrimitive.Title>\n\t);\n}\n\nfunction ResponsiveDialogDescription({\n\tclassName,\n\tchildren,\n\t...props\n}: React.ComponentProps<\"p\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<DrawerDescription className={className} {...props}>\n\t\t\t\t{children}\n\t\t\t</DrawerDescription>\n\t\t);\n\t}\n\n\treturn (\n\t\t<DialogPrimitive.Description\n\t\t\tdata-slot=\"responsive-dialog-description\"\n\t\t\tclassName={cn(\"text-muted-foreground text-xs/relaxed\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</DialogPrimitive.Description>\n\t);\n}\n\nfunction ResponsiveDialogFooter({\n\tclassName,\n\t...props\n}: React.ComponentProps<\"div\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn <DrawerFooter className={className} {...props} />;\n\t}\n\n\treturn <DialogFooter className={className} {...props} />;\n}\n\nfunction ResponsiveDialogClose({\n\tchildren,\n\tasChild = false,\n\tclassName,\n\t...props\n}: {\n\tchildren: React.ReactNode;\n\tasChild?: boolean;\n\tclassName?: string;\n} & Omit<React.ComponentProps<\"button\">, \"className\">) {\n\tconst { isMobile } = useResponsiveDialog();\n\n\tif (isMobile) {\n\t\treturn (\n\t\t\t<DrawerClose asChild={asChild} className={className} {...props}>\n\t\t\t\t{children}\n\t\t\t</DrawerClose>\n\t\t);\n\t}\n\n\treturn (\n\t\t<DialogPrimitive.Close\n\t\t\tclassName={className}\n\t\t\trender={asChild ? (children as React.ReactElement) : undefined}\n\t\t\t{...props}\n\t\t>\n\t\t\t{!asChild ? children : undefined}\n\t\t</DialogPrimitive.Close>\n\t);\n}\n\nexport {\n\tResponsiveDialog,\n\tResponsiveDialogTrigger,\n\tResponsiveDialogContent,\n\tResponsiveDialogHeader,\n\tResponsiveDialogTitle,\n\tResponsiveDialogDescription,\n\tResponsiveDialogFooter,\n\tResponsiveDialogClose,\n\tuseResponsiveDialog,\n};\n","/**\n * useUpload Hook\n *\n * Handles file uploads to the CMS with progress tracking.\n * Uses the QuestpieClient's upload method which uses XMLHttpRequest for progress.\n *\n * @example\n * ```tsx\n * const { upload, uploadMany, isUploading, progress } = useUpload();\n *\n * // Single file upload\n * const asset = await upload(file);\n *\n * // Multiple files upload\n * const assets = await uploadMany(files, {\n * onProgress: (p) => console.log(`${p}%`),\n * });\n * ```\n */\n\nimport { useCallback, useState } from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { selectClient, useAdminStore } from \"../runtime\";\n\n/**\n * Upload error with additional context\n */\nexport class UploadError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly response?: unknown,\n ) {\n super(message);\n this.name = \"UploadError\";\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Asset record returned from upload\n */\nexport interface Asset {\n id: string;\n key: string;\n filename: string;\n mimeType: string;\n size: number;\n visibility: \"public\" | \"private\";\n url?: string;\n width?: number | null;\n height?: number | null;\n alt?: string | null;\n caption?: string | null;\n createdAt?: string;\n updatedAt?: string;\n}\n\n/**\n * Options for upload operations\n */\nexport interface UploadOptions {\n /**\n * Target collection for upload (must have .upload() enabled)\n * @default \"assets\"\n */\n collection?: string;\n\n /**\n * Progress callback (0-100)\n */\n onProgress?: (progress: number) => void;\n\n /**\n * Abort signal for cancellation\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for uploadMany operation\n */\nexport interface UploadManyOptions extends UploadOptions {\n /**\n * Progress callback receives overall progress (0-100)\n * and optionally individual file progress\n */\n onProgress?: (progress: number, fileIndex?: number) => void;\n}\n\n/**\n * Return type for useUpload hook\n */\nexport interface UseUploadReturn {\n /**\n * Upload a single file\n */\n upload: (file: File, options?: UploadOptions) => Promise<Asset>;\n\n /**\n * Upload multiple files sequentially\n */\n uploadMany: (files: File[], options?: UploadManyOptions) => Promise<Asset[]>;\n\n /**\n * Whether an upload is currently in progress\n */\n isUploading: boolean;\n\n /**\n * Current upload progress (0-100)\n */\n progress: number;\n\n /**\n * Current error, if any\n */\n error: Error | null;\n\n /**\n * Reset state (clear error, progress)\n */\n reset: () => void;\n}\n\n// ============================================================================\n// Hook Implementation\n// ============================================================================\n\n/**\n * Hook for uploading files to the CMS\n *\n * Uses the QuestpieClient's built-in upload method which provides\n * progress tracking via XMLHttpRequest.\n */\nexport function useUpload(): UseUploadReturn {\n const client = useAdminStore(selectClient);\n const queryClient = useQueryClient();\n\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Upload a single file\n */\n const upload = useCallback(\n async (file: File, options: UploadOptions = {}): Promise<Asset> => {\n const { collection = \"assets\", onProgress, signal } = options;\n\n setIsUploading(true);\n setProgress(0);\n setError(null);\n\n try {\n // Get the collection API from client\n const collectionApi = (client.collections as any)[collection];\n\n if (!collectionApi?.upload) {\n throw new Error(\n `Collection \"${collection}\" does not support uploads. Make sure .upload() is enabled on the collection.`,\n );\n }\n\n const result = await collectionApi.upload(file, {\n signal,\n onProgress: (p: number) => {\n setProgress(p);\n onProgress?.(p);\n },\n });\n\n // Invalidate collection queries to refresh lists\n queryClient.invalidateQueries({\n queryKey: [\"questpie\", \"collections\", collection],\n });\n\n return result as Asset;\n } catch (err) {\n const uploadError =\n err instanceof Error ? err : new Error(\"Upload failed\");\n setError(uploadError);\n throw uploadError;\n } finally {\n setIsUploading(false);\n }\n },\n [client, queryClient],\n );\n\n /**\n * Upload multiple files sequentially\n */\n const uploadMany = useCallback(\n async (\n files: File[],\n options: UploadManyOptions = {},\n ): Promise<Asset[]> => {\n const { collection = \"assets\", onProgress, signal } = options;\n\n if (files.length === 0) {\n return [];\n }\n\n setIsUploading(true);\n setProgress(0);\n setError(null);\n\n try {\n // Get the collection API from client\n const collectionApi = (client.collections as any)[collection];\n\n if (!collectionApi?.uploadMany) {\n throw new Error(\n `Collection \"${collection}\" does not support uploads. Make sure .upload() is enabled on the collection.`,\n );\n }\n\n const results = await collectionApi.uploadMany(files, {\n signal,\n onProgress: (p: number, fileIndex?: number) => {\n setProgress(p);\n onProgress?.(p, fileIndex);\n },\n });\n\n // Invalidate collection queries\n queryClient.invalidateQueries({\n queryKey: [\"questpie\", \"collections\", collection],\n });\n\n setProgress(100);\n return results as Asset[];\n } catch (err) {\n const uploadError =\n err instanceof Error ? err : new Error(\"Upload failed\");\n setError(uploadError);\n throw uploadError;\n } finally {\n setIsUploading(false);\n }\n },\n [client, queryClient],\n );\n\n /**\n * Reset hook state\n */\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n return {\n upload,\n uploadMany,\n isUploading,\n progress,\n error,\n reset,\n };\n}\n","/**\n * Dropzone Primitive\n *\n * A reusable drag-and-drop area for file uploads.\n * Supports file type filtering, size validation, and visual feedback.\n *\n * @example\n * ```tsx\n * <Dropzone\n * onDrop={(files) => handleUpload(files)}\n * accept={[\"image/*\", \"application/pdf\"]}\n * maxSize={5_000_000}\n * />\n * ```\n */\n\nimport * as React from \"react\";\nimport { CloudArrowUp, SpinnerGap } from \"@phosphor-icons/react\";\nimport { cn } from \"../../lib/utils\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DropzoneProps {\n /**\n * Called when files are dropped or selected\n */\n onDrop: (files: File[]) => void;\n\n /**\n * Accepted file types (MIME types or extensions)\n * @example [\"image/*\", \"application/pdf\", \".doc\"]\n */\n accept?: string[];\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Whether multiple files can be selected\n * @default false\n */\n multiple?: boolean;\n\n /**\n * Disable the dropzone\n */\n disabled?: boolean;\n\n /**\n * Show loading state (e.g., during upload)\n */\n loading?: boolean;\n\n /**\n * Current upload progress (0-100)\n */\n progress?: number;\n\n /**\n * Custom label text\n * @default \"Drop files here or click to browse\"\n */\n label?: string;\n\n /**\n * Helper text shown below the label\n */\n hint?: string;\n\n /**\n * Error message to display\n */\n error?: string;\n\n /**\n * Additional className\n */\n className?: string;\n\n /**\n * Children to render inside the dropzone (custom content)\n */\n children?: React.ReactNode;\n\n /**\n * Callback when validation fails\n */\n onValidationError?: (errors: ValidationError[]) => void;\n}\n\nexport interface ValidationError {\n file: File;\n type: \"type\" | \"size\";\n message: string;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Check if a file matches accepted types\n */\nfunction matchesAccept(file: File, accept?: string[]): boolean {\n if (!accept || accept.length === 0) return true;\n\n const mimeType = file.type.toLowerCase();\n const fileName = file.name.toLowerCase();\n\n return accept.some((pattern) => {\n const normalizedPattern = pattern.toLowerCase();\n\n // Handle wildcard MIME types like \"image/*\"\n if (normalizedPattern.endsWith(\"/*\")) {\n const baseType = normalizedPattern.slice(0, -2);\n return mimeType.startsWith(`${baseType}/`);\n }\n\n // Handle extensions like \".pdf\"\n if (normalizedPattern.startsWith(\".\")) {\n return fileName.endsWith(normalizedPattern);\n }\n\n // Handle exact MIME types\n return mimeType === normalizedPattern;\n });\n}\n\n/**\n * Format file size for display\n */\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function Dropzone({\n onDrop,\n accept,\n maxSize,\n multiple = false,\n disabled = false,\n loading = false,\n progress,\n label = \"Drop files here or click to browse\",\n hint,\n error,\n className,\n children,\n onValidationError,\n}: DropzoneProps) {\n const [isDragging, setIsDragging] = React.useState(false);\n const inputRef = React.useRef<HTMLInputElement>(null);\n const dragCounterRef = React.useRef(0);\n\n /**\n * Validate files and return valid ones + errors\n */\n const validateFiles = React.useCallback(\n (files: File[]): { valid: File[]; errors: ValidationError[] } => {\n const valid: File[] = [];\n const errors: ValidationError[] = [];\n\n for (const file of files) {\n // Check type\n if (!matchesAccept(file, accept)) {\n errors.push({\n file,\n type: \"type\",\n message: `\"${file.name}\" is not an accepted file type`,\n });\n continue;\n }\n\n // Check size\n if (maxSize && file.size > maxSize) {\n errors.push({\n file,\n type: \"size\",\n message: `\"${file.name}\" exceeds maximum size of ${formatFileSize(maxSize)}`,\n });\n continue;\n }\n\n valid.push(file);\n }\n\n return { valid, errors };\n },\n [accept, maxSize],\n );\n\n /**\n * Handle file selection\n */\n const handleFiles = React.useCallback(\n (files: FileList | File[]) => {\n if (disabled || loading) return;\n\n const fileArray = Array.from(files);\n const filesToProcess = multiple ? fileArray : fileArray.slice(0, 1);\n\n const { valid, errors } = validateFiles(filesToProcess);\n\n if (errors.length > 0) {\n onValidationError?.(errors);\n }\n\n if (valid.length > 0) {\n onDrop(valid);\n }\n },\n [disabled, loading, multiple, validateFiles, onDrop, onValidationError],\n );\n\n /**\n * Handle drag events\n */\n const handleDragEnter = React.useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounterRef.current += 1;\n if (e.dataTransfer.items.length > 0 && !disabled && !loading) {\n setIsDragging(true);\n }\n },\n [disabled, loading],\n );\n\n const handleDragLeave = React.useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounterRef.current -= 1;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n }, []);\n\n const handleDragOver = React.useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n }, []);\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n dragCounterRef.current = 0;\n setIsDragging(false);\n\n if (e.dataTransfer.files.length > 0) {\n handleFiles(e.dataTransfer.files);\n }\n },\n [handleFiles],\n );\n\n /**\n * Handle click to open file dialog\n */\n const handleClick = React.useCallback(() => {\n if (disabled || loading) return;\n inputRef.current?.click();\n }, [disabled, loading]);\n\n /**\n * Handle file input change\n */\n const handleInputChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (e.target.files && e.target.files.length > 0) {\n handleFiles(e.target.files);\n }\n // Reset input so same file can be selected again\n e.target.value = \"\";\n },\n [handleFiles],\n );\n\n /**\n * Build accept string for input\n */\n const acceptString = accept?.join(\",\") || undefined;\n\n /**\n * Build hint text\n */\n const hintText = React.useMemo(() => {\n if (hint) return hint;\n\n const parts: string[] = [];\n\n if (accept && accept.length > 0) {\n // Simplify accept types for display\n const types = accept\n .map((t) => {\n if (t.startsWith(\"image/\")) return \"Images\";\n if (t.startsWith(\"video/\")) return \"Videos\";\n if (t.startsWith(\"audio/\")) return \"Audio\";\n if (t === \"application/pdf\") return \"PDF\";\n if (t.startsWith(\".\")) return t.toUpperCase();\n return t;\n })\n .filter((v, i, a) => a.indexOf(v) === i); // unique\n parts.push(types.join(\", \"));\n }\n\n if (maxSize) {\n parts.push(`Max ${formatFileSize(maxSize)}`);\n }\n\n return parts.length > 0 ? parts.join(\" • \") : undefined;\n }, [hint, accept, maxSize]);\n\n return (\n <div\n role=\"button\"\n tabIndex={disabled || loading ? -1 : 0}\n onClick={handleClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleClick();\n }\n }}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n className={cn(\n \"relative flex min-h-[120px] cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-2 border-dashed p-6 text-center transition-colors\",\n \"border-border/60 bg-muted/30 hover:border-border hover:bg-muted/50\",\n isDragging && \"border-primary bg-primary/5\",\n error && \"border-destructive/50 bg-destructive/5\",\n (disabled || loading) && \"pointer-events-none opacity-60\",\n className,\n )}\n aria-disabled={disabled || loading}\n data-dragging={isDragging || undefined}\n >\n {/* Hidden file input */}\n <input\n ref={inputRef}\n type=\"file\"\n accept={acceptString}\n multiple={multiple}\n onChange={handleInputChange}\n className=\"sr-only\"\n disabled={disabled || loading}\n tabIndex={-1}\n />\n\n {/* Custom children or default content */}\n {children || (\n <>\n {/* Icon / Loading */}\n <div className=\"flex items-center justify-center\">\n {loading ? (\n <div className=\"relative\">\n <SpinnerGap\n className=\"text-muted-foreground size-10 animate-spin\"\n weight=\"regular\"\n />\n {typeof progress === \"number\" && (\n <span className=\"text-muted-foreground absolute inset-0 flex items-center justify-center text-xs font-medium\">\n {progress}%\n </span>\n )}\n </div>\n ) : (\n <CloudArrowUp\n className={cn(\n \"size-10 transition-colors\",\n isDragging ? \"text-primary\" : \"text-muted-foreground\",\n )}\n weight=\"regular\"\n />\n )}\n </div>\n\n {/* Label */}\n <div className=\"space-y-1\">\n <p\n className={cn(\n \"text-sm font-medium\",\n isDragging ? \"text-primary\" : \"text-foreground\",\n )}\n >\n {loading ? \"Uploading...\" : label}\n </p>\n\n {/* Hint */}\n {hintText && !loading && (\n <p className=\"text-muted-foreground text-xs\">{hintText}</p>\n )}\n\n {/* Progress bar */}\n {loading && typeof progress === \"number\" && (\n <div className=\"bg-muted mx-auto mt-2 h-1.5 w-32 overflow-hidden rounded-full\">\n <div\n className=\"bg-primary h-full rounded-full transition-all duration-300\"\n style={{ width: `${progress}%` }}\n />\n </div>\n )}\n </div>\n </>\n )}\n\n {/* Error message */}\n {error && (\n <p className=\"text-destructive absolute bottom-2 left-0 right-0 text-center text-xs\">\n {error}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,13 +1,12 @@
1
+ import { a as selectBasePath, f as useAdminStore, g as cn, h as Button, l as selectNavigate, o as selectBrandName } 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 { n as useAuthClient } from "./use-auth-BoLmWtmU.mjs";
4
+ import { CheckCircle, Envelope, SpinnerGap, WarningCircle } from "@phosphor-icons/react";
1
5
  import * as React$1 from "react";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Button } from "../../components/ui/button";
4
- import { Field, FieldContent, FieldError, FieldGroup, FieldLabel } from "../../components/ui/field";
5
- import { Input } from "../../components/ui/input";
6
- import { cn } from "../../lib/utils";
7
- import { CheckCircle, Envelope, SpinnerGap } from "@phosphor-icons/react";
8
6
  import { useForm } from "react-hook-form";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
8
 
10
- //#region src/views/auth/forgot-password-form.tsx
9
+ //#region src/client/views/auth/forgot-password-form.tsx
11
10
  /**
12
11
  * Forgot Password Form - request password reset email
13
12
  */
@@ -53,7 +52,7 @@ function ForgotPasswordForm({ onSubmit, onBackToLoginClick, defaultValues, class
53
52
  className: cn("space-y-4 text-center", className),
54
53
  children: [
55
54
  /* @__PURE__ */ jsx("div", {
56
- className: "bg-primary/10 mx-auto flex size-12 items-center justify-center rounded-full",
55
+ className: "bg-primary/10 mx-auto flex size-12 items-center justify-center",
57
56
  children: /* @__PURE__ */ jsx(CheckCircle, {
58
57
  className: "text-primary size-6",
59
58
  weight: "duotone"
@@ -113,9 +112,9 @@ function ForgotPasswordForm({ onSubmit, onBackToLoginClick, defaultValues, class
113
112
  })]
114
113
  }), /* @__PURE__ */ jsx(FieldError, { children: errors.email?.message })] })]
115
114
  }) }),
116
- error && /* @__PURE__ */ jsx("div", {
117
- className: "bg-destructive/10 text-destructive rounded-md p-3 text-xs",
118
- children: error
115
+ error && /* @__PURE__ */ jsxs(Alert, {
116
+ variant: "destructive",
117
+ children: [/* @__PURE__ */ jsx(WarningCircle, {}), /* @__PURE__ */ jsx(AlertDescription, { children: error })]
119
118
  }),
120
119
  /* @__PURE__ */ jsx(Button, {
121
120
  type: "submit",
@@ -132,10 +131,12 @@ function ForgotPasswordForm({ onSubmit, onBackToLoginClick, defaultValues, class
132
131
  children: [
133
132
  "Remember your password?",
134
133
  " ",
135
- /* @__PURE__ */ jsx("button", {
134
+ /* @__PURE__ */ jsx(Button, {
136
135
  type: "button",
136
+ variant: "link",
137
+ size: "sm",
137
138
  onClick: onBackToLoginClick,
138
- className: "text-primary hover:underline",
139
+ className: "h-auto p-0 text-xs",
139
140
  children: "Back to login"
140
141
  })
141
142
  ]
@@ -145,4 +146,76 @@ function ForgotPasswordForm({ onSubmit, onBackToLoginClick, defaultValues, class
145
146
  }
146
147
 
147
148
  //#endregion
148
- export { ForgotPasswordForm };
149
+ //#region src/client/views/pages/forgot-password-page.tsx
150
+ /**
151
+ * Forgot Password Page
152
+ *
153
+ * Default forgot password page that uses AuthLayout and ForgotPasswordForm.
154
+ * Integrates with authClient from AdminProvider context.
155
+ */
156
+ /**
157
+ * Default forgot password page component.
158
+ *
159
+ * Uses authClient from AdminProvider to handle password reset requests.
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * // In your admin config
164
+ * const admin = qa<AppCMS>()
165
+ * .use(coreAdminModule)
166
+ * .pages({
167
+ * forgotPassword: page("forgot-password", { component: ForgotPasswordPage })
168
+ * .path("/forgot-password"),
169
+ * })
170
+ * ```
171
+ */
172
+ function ForgotPasswordPage({ title = "Forgot password", description = "Enter your email to receive a password reset link", logo, loginPath, resetPasswordRedirectUrl }) {
173
+ const authClient = useAuthClient();
174
+ const navigate = useAdminStore(selectNavigate);
175
+ const basePath = useAdminStore(selectBasePath);
176
+ const brandName = useAdminStore(selectBrandName);
177
+ const [error, setError] = React$1.useState(null);
178
+ const handleSubmit = async (values) => {
179
+ setError(null);
180
+ try {
181
+ const redirectUrl = resetPasswordRedirectUrl ?? `${typeof window !== "undefined" ? window.location.origin : ""}${basePath}/reset-password`;
182
+ const result = await authClient.forgetPassword({
183
+ email: values.email,
184
+ redirectTo: redirectUrl
185
+ });
186
+ if (result.error) {
187
+ setError(result.error.message || "Failed to send reset email");
188
+ return;
189
+ }
190
+ } catch (err) {
191
+ setError(err instanceof Error ? err.message : "An error occurred");
192
+ }
193
+ };
194
+ const handleBackToLoginClick = () => {
195
+ navigate(loginPath ?? `${basePath}/login`);
196
+ };
197
+ return /* @__PURE__ */ jsx(AuthLayout, {
198
+ title,
199
+ description,
200
+ logo: logo ?? /* @__PURE__ */ jsx(DefaultLogo, { brandName }),
201
+ children: /* @__PURE__ */ jsx(ForgotPasswordForm, {
202
+ onSubmit: handleSubmit,
203
+ onBackToLoginClick: handleBackToLoginClick,
204
+ error
205
+ })
206
+ });
207
+ }
208
+ function DefaultLogo({ brandName }) {
209
+ return /* @__PURE__ */ jsx("div", {
210
+ className: "text-center",
211
+ children: /* @__PURE__ */ jsx("h1", {
212
+ className: "text-xl font-bold",
213
+ children: brandName
214
+ })
215
+ });
216
+ }
217
+ var forgot_password_page_default = ForgotPasswordPage;
218
+
219
+ //#endregion
220
+ export { forgot_password_page_default as n, ForgotPasswordForm as r, ForgotPasswordPage as t };
221
+ //# sourceMappingURL=forgot-password-page-Bcp-An4Y.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forgot-password-page-Bcp-An4Y.mjs","names":["React","React"],"sources":["../src/client/views/auth/forgot-password-form.tsx","../src/client/views/pages/forgot-password-page.tsx"],"sourcesContent":["/**\n * Forgot Password Form - request password reset email\n */\n\nimport {\n CheckCircle,\n Envelope,\n SpinnerGap,\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 ForgotPasswordFormValues = {\n email: string;\n};\n\nexport type ForgotPasswordFormProps = {\n /** Called when form is submitted with valid data */\n onSubmit: (values: ForgotPasswordFormValues) => Promise<void>;\n /** Called when back to login link is clicked */\n onBackToLoginClick?: () => void;\n /** Default values */\n defaultValues?: Partial<ForgotPasswordFormValues>;\n /** Additional class name */\n className?: string;\n /** Error message from auth */\n error?: string | null;\n};\n\n/**\n * Forgot password form with email field\n *\n * @example\n * ```tsx\n * const authClient = createAdminAuthClient<typeof cms>({ baseURL: '...' })\n *\n * function ForgotPasswordPage() {\n * const [error, setError] = useState<string | null>(null)\n *\n * const handleSubmit = async (values: ForgotPasswordFormValues) => {\n * const result = await authClient.forgetPassword({\n * email: values.email,\n * redirectTo: '/reset-password',\n * })\n * if (result.error) {\n * setError(result.error.message)\n * }\n * }\n *\n * return (\n * <AuthLayout title=\"Forgot password\">\n * <ForgotPasswordForm onSubmit={handleSubmit} error={error} />\n * </AuthLayout>\n * )\n * }\n * ```\n */\nexport function ForgotPasswordForm({\n onSubmit,\n onBackToLoginClick,\n defaultValues,\n className,\n error,\n}: ForgotPasswordFormProps) {\n const [isSuccess, setIsSuccess] = React.useState(false);\n\n const {\n register,\n handleSubmit,\n formState: { errors, isSubmitting },\n } = useForm<ForgotPasswordFormValues>({\n defaultValues: {\n email: \"\",\n ...defaultValues,\n },\n });\n\n const handleFormSubmit = handleSubmit(async (values) => {\n await onSubmit(values);\n if (!error) {\n setIsSuccess(true);\n }\n });\n\n // Success state\n if (isSuccess) {\n return (\n <div className={cn(\"space-y-4 text-center\", className)}>\n <div className=\"bg-primary/10 mx-auto flex size-12 items-center justify-center\">\n <CheckCircle className=\"text-primary size-6\" weight=\"duotone\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-sm font-medium\">Check your email</h3>\n <p className=\"text-muted-foreground text-xs\">\n We've sent a password reset link to your email address. Please check\n your inbox and follow the instructions.\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n className=\"w-full\"\n onClick={onBackToLoginClick}\n >\n Back to login\n </Button>\n </div>\n );\n }\n\n return (\n <form onSubmit={handleFormSubmit} className={cn(\"space-y-4\", className)}>\n <p className=\"text-muted-foreground text-xs\">\n Enter your email address and we'll send you a link to reset your\n password.\n </p>\n\n <FieldGroup>\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=\"you@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 </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 Sending...\n </>\n ) : (\n \"Send reset link\"\n )}\n </Button>\n\n {/* Back to Login Link */}\n <p className=\"text-muted-foreground text-center text-xs\">\n Remember your password?{\" \"}\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n onClick={onBackToLoginClick}\n className=\"h-auto p-0 text-xs\"\n >\n Back to login\n </Button>\n </p>\n </form>\n );\n}\n","/**\n * Forgot Password Page\n *\n * Default forgot password page that uses AuthLayout and ForgotPasswordForm.\n * Integrates with authClient from AdminProvider context.\n */\n\nimport * as React from \"react\";\nimport { useAuthClient } from \"../../hooks/use-auth\";\nimport {\n selectBasePath,\n selectBrandName,\n selectNavigate,\n useAdminStore,\n} from \"../../runtime/provider\";\nimport { AuthLayout } from \"../auth/auth-layout\";\nimport {\n ForgotPasswordForm,\n type ForgotPasswordFormValues,\n} from \"../auth/forgot-password-form\";\n\nexport interface ForgotPasswordPageProps {\n /**\n * Title shown on the page\n * @default \"Forgot password\"\n */\n title?: string;\n\n /**\n * Description shown below the title\n * @default \"Enter your email to receive a password reset link\"\n */\n description?: string;\n\n /**\n * Logo component to show above the form\n */\n logo?: React.ReactNode;\n\n /**\n * Path to login page\n * @default \"{basePath}/login\"\n */\n loginPath?: string;\n\n /**\n * URL to redirect to after password reset (included in email)\n * @default \"{window.origin}{basePath}/reset-password\"\n */\n resetPasswordRedirectUrl?: string;\n}\n\n/**\n * Default forgot password page component.\n *\n * Uses authClient from AdminProvider to handle password reset requests.\n *\n * @example\n * ```tsx\n * // In your admin config\n * const admin = qa<AppCMS>()\n * .use(coreAdminModule)\n * .pages({\n * forgotPassword: page(\"forgot-password\", { component: ForgotPasswordPage })\n * .path(\"/forgot-password\"),\n * })\n * ```\n */\nexport function ForgotPasswordPage({\n title = \"Forgot password\",\n description = \"Enter your email to receive a password reset link\",\n logo,\n loginPath,\n resetPasswordRedirectUrl,\n}: ForgotPasswordPageProps) {\n const authClient = useAuthClient();\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: ForgotPasswordFormValues) => {\n setError(null);\n\n try {\n const redirectUrl =\n resetPasswordRedirectUrl ??\n `${typeof window !== \"undefined\" ? window.location.origin : \"\"}${basePath}/reset-password`;\n\n const result = await authClient.forgetPassword({\n email: values.email,\n redirectTo: redirectUrl,\n });\n\n if (result.error) {\n setError(result.error.message || \"Failed to send reset email\");\n return;\n }\n\n // Success is handled by the form (shows success message)\n } catch (err) {\n setError(err instanceof Error ? err.message : \"An error occurred\");\n }\n };\n\n const handleBackToLoginClick = () => {\n navigate(loginPath ?? `${basePath}/login`);\n };\n\n return (\n <AuthLayout\n title={title}\n description={description}\n logo={logo ?? <DefaultLogo brandName={brandName} />}\n >\n <ForgotPasswordForm\n onSubmit={handleSubmit}\n onBackToLoginClick={handleBackToLoginClick}\n error={error}\n />\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 ForgotPasswordPage;\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import "./content-locales-provider-BXvuIgfg.mjs";
2
+ import "./auth-layout-M8K8_q5R.mjs";
3
+ import "./use-auth-BoLmWtmU.mjs";
4
+ import "./card-BKHjBQfw.mjs";
5
+ import { n as forgot_password_page_default, t as ForgotPasswordPage } from "./forgot-password-page-Bcp-An4Y.mjs";
6
+
7
+ export { ForgotPasswordPage, forgot_password_page_default as default };