@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.
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-D7w7W1Hl.mjs +273 -0
  5. package/dist/bulk-upload-dialog-D7w7W1Hl.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-DbpZKSgH.d.mts +13585 -0
  10. package/dist/client-DbpZKSgH.d.mts.map +1 -0
  11. package/dist/client-njX1rZmi.mjs +22612 -0
  12. package/dist/client-njX1rZmi.mjs.map +1 -0
  13. package/dist/client.d.mts +3 -0
  14. package/dist/client.mjs +13 -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-mCY0pgZv.mjs +3 -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-CEwsdLwn.mjs +3 -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 +13 -0
  29. package/dist/login-page-BUnpCbCa.mjs +3 -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-CufHz3h3.mjs +3 -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-BNNzt_Z6.mjs +3 -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 -198
  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,1650 @@
1
+ import { DEFAULT_LOCALE, DEFAULT_LOCALE_CONFIG, validationMessagesEN } from "questpie/shared";
2
+ import * as React$1 from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useRef } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { Button } from "@base-ui/react/button";
6
+ import { cva } from "class-variance-authority";
7
+ import { clsx } from "clsx";
8
+ import { twMerge } from "tailwind-merge";
9
+ import { useQuery } from "@tanstack/react-query";
10
+ import { createStore, useStore } from "zustand";
11
+
12
+ //#region src/client/i18n/hooks.tsx
13
+ /**
14
+ * I18n React Hooks
15
+ *
16
+ * React context and hooks for i18n in the admin UI.
17
+ */
18
+ const I18nContext = createContext(null);
19
+ /**
20
+ * I18n Provider Component
21
+ *
22
+ * Provides i18n to all child components.
23
+ * The adapter is the source of truth for UI locale.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * import { I18nProvider, createSimpleI18n } from "@questpie/admin/i18n";
28
+ *
29
+ * const i18n = createSimpleI18n({ ... });
30
+ *
31
+ * <I18nProvider adapter={i18n}>
32
+ * <App />
33
+ * </I18nProvider>
34
+ * ```
35
+ */
36
+ function I18nProvider({ adapter, children }) {
37
+ const [, forceUpdate] = React$1.useReducer((x) => x + 1, 0);
38
+ React$1.useEffect(() => {
39
+ return adapter.onLocaleChange(() => {
40
+ forceUpdate();
41
+ });
42
+ }, [adapter]);
43
+ const contextValue = React$1.useMemo(() => ({
44
+ locale: adapter.locale,
45
+ locales: adapter.locales,
46
+ t: adapter.t.bind(adapter),
47
+ setLocale: adapter.setLocale.bind(adapter),
48
+ onLocaleChange: adapter.onLocaleChange.bind(adapter),
49
+ formatDate: adapter.formatDate.bind(adapter),
50
+ formatNumber: adapter.formatNumber.bind(adapter),
51
+ formatRelative: adapter.formatRelative?.bind(adapter),
52
+ getLocaleName: adapter.getLocaleName.bind(adapter),
53
+ isRTL: adapter.isRTL.bind(adapter)
54
+ }), [adapter, adapter.locale]);
55
+ return /* @__PURE__ */ jsx(I18nContext.Provider, {
56
+ value: contextValue,
57
+ children
58
+ });
59
+ }
60
+ /**
61
+ * Get the i18n adapter (throws if not in provider)
62
+ */
63
+ function useI18n() {
64
+ const adapter = useContext(I18nContext);
65
+ if (!adapter) throw new Error("useI18n must be used within I18nProvider");
66
+ return adapter;
67
+ }
68
+ /**
69
+ * Get i18n adapter or null (safe version)
70
+ */
71
+ function useSafeI18n() {
72
+ return useContext(I18nContext);
73
+ }
74
+ /**
75
+ * Main translation hook
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * function MyComponent() {
80
+ * const { t, locale, formatDate } = useTranslation();
81
+ * return <h1>{t("dashboard.title")}</h1>;
82
+ * }
83
+ * ```
84
+ */
85
+ function useTranslation() {
86
+ const adapter = useI18n();
87
+ return {
88
+ locale: adapter.locale,
89
+ locales: adapter.locales,
90
+ t: adapter.t,
91
+ setLocale: adapter.setLocale,
92
+ formatDate: adapter.formatDate,
93
+ formatNumber: adapter.formatNumber,
94
+ getLocaleName: adapter.getLocaleName,
95
+ isRTL: adapter.isRTL()
96
+ };
97
+ }
98
+ /**
99
+ * Check if an object is a locale map (has locale codes as keys)
100
+ */
101
+ function isLocaleMap(obj) {
102
+ if ("key" in obj) return false;
103
+ return Object.values(obj).every((v) => typeof v === "string");
104
+ }
105
+ /**
106
+ * Resolve locale map to string for given locale
107
+ */
108
+ function resolveLocaleMap(map, locale, fallback) {
109
+ if (map[locale]) return map[locale];
110
+ const lang = locale.split("-")[0];
111
+ if (lang && map[lang]) return map[lang];
112
+ if (map[DEFAULT_LOCALE]) return map[DEFAULT_LOCALE];
113
+ return Object.values(map)[0] ?? fallback;
114
+ }
115
+ /**
116
+ * Resolve I18nText to string
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * function Label({ text }: { text: I18nText }) {
121
+ * const resolve = useResolveText();
122
+ * return <span>{resolve(text)}</span>;
123
+ * }
124
+ * ```
125
+ */
126
+ function useResolveText() {
127
+ const adapter = useSafeI18n();
128
+ return useCallback((text, fallback = "", contextValues) => {
129
+ const resolveValue = (value) => {
130
+ if (value === void 0 || value === null) return fallback;
131
+ if (typeof value === "string") return value;
132
+ if (typeof value === "function") {
133
+ if (!adapter && !contextValues) return fallback;
134
+ const i18nCtx = adapter ? {
135
+ locale: adapter.locale,
136
+ t: adapter.t,
137
+ formatDate: adapter.formatDate,
138
+ formatNumber: adapter.formatNumber
139
+ } : void 0;
140
+ const ctx = {
141
+ ...contextValues ?? {},
142
+ ...i18nCtx ?? {}
143
+ };
144
+ try {
145
+ return resolveValue(value(ctx));
146
+ } catch (error) {
147
+ console.error("Failed to resolve dynamic text:", error);
148
+ return fallback;
149
+ }
150
+ }
151
+ if (typeof value === "object" && "key" in value && typeof value.key === "string") {
152
+ const keyObj = value;
153
+ if (!adapter) return keyObj.fallback ?? fallback;
154
+ const result = adapter.t(keyObj.key, keyObj.params);
155
+ return result === keyObj.key ? keyObj.fallback ?? result : result;
156
+ }
157
+ if (typeof value === "object" && isLocaleMap(value)) return resolveLocaleMap(value, adapter?.locale ?? DEFAULT_LOCALE, fallback);
158
+ return fallback;
159
+ };
160
+ return resolveValue(text);
161
+ }, [adapter]);
162
+ }
163
+ /**
164
+ * Resolve I18nText synchronously (without adapter)
165
+ * Only works with string and object types, not functions
166
+ *
167
+ * @param locale - Optional locale to use for locale maps (defaults to "en")
168
+ */
169
+ function resolveTextSync(text, fallback = "", locale = DEFAULT_LOCALE) {
170
+ if (text === void 0 || text === null) return fallback;
171
+ if (typeof text === "string") return text;
172
+ if (typeof text === "function") return fallback;
173
+ if (typeof text === "object" && "key" in text) return text.fallback ?? fallback;
174
+ if (typeof text === "object" && isLocaleMap(text)) return resolveLocaleMap(text, locale, fallback);
175
+ return fallback;
176
+ }
177
+
178
+ //#endregion
179
+ //#region src/client/lib/utils.ts
180
+ function cn(...inputs) {
181
+ return twMerge(clsx(inputs));
182
+ }
183
+ /**
184
+ * Format a collection name for display
185
+ *
186
+ * Converts camelCase/PascalCase to Title Case with spaces.
187
+ *
188
+ * @example
189
+ * formatCollectionName("blogPosts") // "Blog Posts"
190
+ * formatCollectionName("userSettings") // "User Settings"
191
+ */
192
+ function formatCollectionName(name) {
193
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
194
+ }
195
+
196
+ //#endregion
197
+ //#region src/client/components/ui/button.tsx
198
+ const buttonVariants = cva("focus-visible:border-ring cursor-pointer focus-visible:ring-ring/30 focus-visible:shadow-[0_0_10px_oklch(0.55_0.3_300_/_0.15)] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[2px] aria-invalid:ring-[2px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none", {
199
+ variants: {
200
+ variant: {
201
+ default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm hover:shadow-[0_0_15px_oklch(0.55_0.3_300_/_0.2)]",
202
+ outline: "border-input/80 bg-input/20 backdrop-blur-sm hover:bg-accent hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
203
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
204
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
205
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
206
+ link: "text-primary underline-offset-4 hover:underline"
207
+ },
208
+ size: {
209
+ default: "h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 [&_svg:not([class*='size-'])]:size-4",
210
+ xs: "h-6 gap-1 px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
211
+ sm: "h-7 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
212
+ lg: "h-10 gap-2 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 [&_svg:not([class*='size-'])]:size-4",
213
+ icon: "size-9 [&_svg:not([class*='size-'])]:size-4",
214
+ "icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
215
+ "icon-sm": "size-7 [&_svg:not([class*='size-'])]:size-3.5",
216
+ "icon-lg": "size-10 [&_svg:not([class*='size-'])]:size-5"
217
+ }
218
+ },
219
+ defaultVariants: {
220
+ variant: "default",
221
+ size: "default"
222
+ }
223
+ });
224
+ function Button$1({ className, variant = "default", size = "default", ...props }) {
225
+ return /* @__PURE__ */ jsx(Button, {
226
+ "data-slot": "button",
227
+ className: cn(buttonVariants({
228
+ variant,
229
+ size,
230
+ className
231
+ })),
232
+ ...props
233
+ });
234
+ }
235
+
236
+ //#endregion
237
+ //#region src/client/builder/admin.ts
238
+ /**
239
+ * Admin Runtime Class
240
+ *
241
+ * Wraps AdminBuilder state with runtime methods for accessing configuration.
242
+ * This is what gets passed to AdminProvider and used throughout the admin UI.
243
+ */
244
+ /**
245
+ * Admin runtime instance
246
+ *
247
+ * Provides methods to access admin configuration at runtime.
248
+ * Can be created from an AdminBuilder directly - no need for Admin.from().
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * import { Admin } from "@questpie/admin/builder";
253
+ *
254
+ * // Direct usage - pass builder to AdminLayoutProvider
255
+ * <AdminLayoutProvider admin={admin} ... />
256
+ *
257
+ * // Or create Admin instance explicitly if needed
258
+ * const adminInstance = Admin.from(adminBuilder);
259
+ * ```
260
+ */
261
+ var Admin = class Admin {
262
+ constructor(state) {
263
+ this.state = state;
264
+ }
265
+ /**
266
+ * Create Admin from an AdminBuilder or return existing Admin instance.
267
+ * This is called internally by AdminLayoutProvider - you don't need to call it manually.
268
+ *
269
+ * @deprecated Pass the builder directly to AdminLayoutProvider instead
270
+ */
271
+ static from(input) {
272
+ if (input instanceof Admin) return input;
273
+ return new Admin(input.state);
274
+ }
275
+ /**
276
+ * Normalize input to Admin instance (internal helper)
277
+ */
278
+ static normalize(input) {
279
+ if (input instanceof Admin) return input;
280
+ return new Admin(input.state);
281
+ }
282
+ /**
283
+ * Get all collection names
284
+ */
285
+ getCollectionNames() {
286
+ return Object.keys(this.state.collections ?? {});
287
+ }
288
+ /**
289
+ * Get all collection configurations.
290
+ * Automatically extracts `.state` from builders.
291
+ */
292
+ getCollections() {
293
+ const collections = this.state.collections ?? {};
294
+ const result = {};
295
+ for (const [name, config] of Object.entries(collections)) result[name] = config && typeof config === "object" && "state" in config ? config.state : config;
296
+ return result;
297
+ }
298
+ /**
299
+ * Get configuration for a specific collection.
300
+ * Automatically extracts `.state` from builder.
301
+ */
302
+ getCollectionConfig(name) {
303
+ const config = this.state.collections?.[name];
304
+ if (!config) return void 0;
305
+ return config && typeof config === "object" && "state" in config ? config.state : config;
306
+ }
307
+ /**
308
+ * Get all global names
309
+ */
310
+ getGlobalNames() {
311
+ return Object.keys(this.state.globals ?? {});
312
+ }
313
+ /**
314
+ * Get all global configurations.
315
+ * Automatically extracts `.state` from builders.
316
+ */
317
+ getGlobals() {
318
+ const globals = this.state.globals ?? {};
319
+ const result = {};
320
+ for (const [name, config] of Object.entries(globals)) result[name] = config && typeof config === "object" && "state" in config ? config.state : config;
321
+ return result;
322
+ }
323
+ /**
324
+ * Get configuration for a specific global.
325
+ * Automatically extracts `.state` from builder.
326
+ */
327
+ getGlobalConfig(name) {
328
+ const config = this.state.globals?.[name];
329
+ if (!config) return void 0;
330
+ return config && typeof config === "object" && "state" in config ? config.state : config;
331
+ }
332
+ /**
333
+ * Get all custom page configurations.
334
+ * Automatically extracts `.state` from builders.
335
+ */
336
+ getPages() {
337
+ const pages = this.state.pages ?? {};
338
+ const result = {};
339
+ for (const [name, config] of Object.entries(pages)) result[name] = config && typeof config === "object" && "state" in config ? config.state : config;
340
+ return result;
341
+ }
342
+ /**
343
+ * Get configuration for a specific page.
344
+ * Automatically extracts `.state` from builder.
345
+ */
346
+ getPageConfig(name) {
347
+ const config = this.state.pages?.[name];
348
+ if (!config) return void 0;
349
+ return config && typeof config === "object" && "state" in config ? config.state : config;
350
+ }
351
+ /**
352
+ * Get dashboard configuration
353
+ */
354
+ getDashboard() {
355
+ return this.state.dashboard ?? {};
356
+ }
357
+ /**
358
+ * Get sidebar configuration
359
+ */
360
+ getSidebar() {
361
+ return this.state.sidebar ?? {};
362
+ }
363
+ /**
364
+ * Get branding configuration
365
+ */
366
+ getBranding() {
367
+ return this.state.branding ?? {};
368
+ }
369
+ /**
370
+ * Get default views configuration
371
+ */
372
+ getDefaultViews() {
373
+ return this.state.defaultViews ?? {};
374
+ }
375
+ /**
376
+ * Get locale configuration
377
+ */
378
+ getLocale() {
379
+ return this.state.locale ?? DEFAULT_LOCALE_CONFIG;
380
+ }
381
+ /**
382
+ * Get available locales
383
+ */
384
+ getAvailableLocales() {
385
+ return this.getLocale().supported ?? [DEFAULT_LOCALE];
386
+ }
387
+ /**
388
+ * Get default locale
389
+ */
390
+ getDefaultLocale() {
391
+ return this.getLocale().default ?? DEFAULT_LOCALE;
392
+ }
393
+ /**
394
+ * Get human-readable label for a locale
395
+ */
396
+ getLocaleLabel(locale) {
397
+ return {
398
+ en: "English",
399
+ sk: "Slovencina",
400
+ cs: "Cestina",
401
+ de: "Deutsch",
402
+ fr: "Francais",
403
+ es: "Espanol",
404
+ it: "Italiano",
405
+ pt: "Portugues",
406
+ pl: "Polski",
407
+ nl: "Nederlands",
408
+ ru: "Russkij",
409
+ uk: "Ukrainska",
410
+ ja: "Nihongo",
411
+ ko: "Hangugeo",
412
+ zh: "Zhongwen"
413
+ }[locale] ?? locale.toUpperCase();
414
+ }
415
+ /**
416
+ * Get translations map from builder state
417
+ * Used by AdminProvider to initialize i18n
418
+ */
419
+ getTranslations() {
420
+ return this.state.translations ?? {};
421
+ }
422
+ /**
423
+ * Get all registered field definitions
424
+ */
425
+ getFields() {
426
+ return this.state.fields ?? {};
427
+ }
428
+ /**
429
+ * Get a specific field definition
430
+ */
431
+ getField(name) {
432
+ return this.state.fields?.[name];
433
+ }
434
+ /**
435
+ * Get all registered list view definitions
436
+ */
437
+ getListViews() {
438
+ return this.state.listViews ?? {};
439
+ }
440
+ /**
441
+ * Get all registered edit view definitions
442
+ */
443
+ getEditViews() {
444
+ return this.state.editViews ?? {};
445
+ }
446
+ /**
447
+ * Get all registered widget definitions
448
+ */
449
+ getWidgets() {
450
+ return this.state.widgets ?? {};
451
+ }
452
+ };
453
+
454
+ //#endregion
455
+ //#region src/client/i18n/messages/en.ts
456
+ /**
457
+ * English Admin UI Messages (Default)
458
+ */
459
+ const adminMessagesEN = {
460
+ "common.save": "Save",
461
+ "common.cancel": "Cancel",
462
+ "common.delete": "Delete",
463
+ "common.edit": "Edit",
464
+ "common.create": "Create",
465
+ "common.add": "Add",
466
+ "common.remove": "Remove",
467
+ "common.close": "Close",
468
+ "common.form": "Form",
469
+ "common.search": "Search",
470
+ "common.filter": "Filter",
471
+ "common.refresh": "Refresh",
472
+ "common.loading": "Loading...",
473
+ "common.confirm": "Confirm",
474
+ "common.back": "Back",
475
+ "common.next": "Next",
476
+ "common.previous": "Previous",
477
+ "common.actions": "Actions",
478
+ "common.more": "More",
479
+ "common.yes": "Yes",
480
+ "common.no": "No",
481
+ "common.ok": "OK",
482
+ "common.apply": "Apply",
483
+ "common.reset": "Reset",
484
+ "common.clear": "Clear",
485
+ "common.selectAll": "Select all",
486
+ "common.deselectAll": "Deselect all",
487
+ "common.duplicate": "Duplicate",
488
+ "common.copy": "Copy",
489
+ "common.paste": "Paste",
490
+ "common.upload": "Upload",
491
+ "common.download": "Download",
492
+ "common.preview": "Preview",
493
+ "common.view": "View",
494
+ "common.open": "Open",
495
+ "common.retry": "Retry",
496
+ "common.submit": "Submit",
497
+ "nav.dashboard": "Dashboard",
498
+ "nav.collections": "Collections",
499
+ "nav.globals": "Globals",
500
+ "nav.media": "Media",
501
+ "nav.settings": "Settings",
502
+ "nav.logout": "Log out",
503
+ "nav.home": "Home",
504
+ "nav.back": "Back",
505
+ "dashboard.title": "Dashboard",
506
+ "dashboard.welcome": "Welcome back",
507
+ "dashboard.recentActivity": "Recent Activity",
508
+ "dashboard.quickActions": "Quick Actions",
509
+ "collection.create": "Create {{name}}",
510
+ "collection.edit": "Edit {{name}}",
511
+ "collection.delete": "Delete {{name}}",
512
+ "collection.deleteConfirm": "Are you sure you want to delete this {{name}}?",
513
+ "collection.noItems": "No {{name}} found",
514
+ "collection.createFirst": "Create your first {{name}}",
515
+ "collection.itemCount": {
516
+ one: "{{count}} item",
517
+ other: "{{count}} items"
518
+ },
519
+ "collection.bulkDelete": "Delete selected",
520
+ "collection.bulkDeleteConfirm": "Are you sure you want to delete {{count}} items?",
521
+ "collection.bulkDeleteSuccess": {
522
+ one: "Successfully deleted {{count}} item",
523
+ other: "Successfully deleted {{count}} items"
524
+ },
525
+ "collection.bulkDeleteError": "Failed to delete items",
526
+ "collection.bulkDeletePartial": {
527
+ one: "Deleted {{success}} item, {{failed}} failed",
528
+ other: "Deleted {{success}} items, {{failed}} failed"
529
+ },
530
+ "collection.bulkActionFailed": "Bulk action failed",
531
+ "collection.selected": "{{count}} selected",
532
+ "collection.selectOnPage": "All on this page",
533
+ "collection.selectAllMatching": "All matching filter ({{count}})",
534
+ "collection.clearSelection": "Clear selection",
535
+ "collection.list": "{{name}} list",
536
+ "collection.new": "New {{name}}",
537
+ "collection.duplicateSuccess": "{{name}} duplicated successfully",
538
+ "collection.duplicateError": "Failed to duplicate {{name}}",
539
+ "relation.select": "Select {{name}}",
540
+ "relation.clear": "Clear selection",
541
+ "relation.search": "Search {{name}}...",
542
+ "relation.noResults": "No {{name}} found",
543
+ "relation.loading": "Loading...",
544
+ "relation.createNew": "Create new {{name}}",
545
+ "relation.selected": "{{count}} selected",
546
+ "relation.removeItem": "Remove {{name}}",
547
+ "relation.addItem": "Add {{name}}",
548
+ "relation.noneSelected": "No {{name}} selected",
549
+ "relation.noRelated": "No related items found",
550
+ "relation.saveFirst": "Save this item first to see related items.",
551
+ "array.empty": "No {{name}} added yet",
552
+ "array.addItem": "Add {{name}}",
553
+ "blocks.addAbove": "Add above",
554
+ "blocks.addBelow": "Add below",
555
+ "blocks.addChild": "Add child block",
556
+ "form.id": "ID",
557
+ "form.created": "Created",
558
+ "form.updated": "Updated",
559
+ "form.required": "This field is required",
560
+ "form.invalid": "Invalid value",
561
+ "form.saveChanges": "Save changes",
562
+ "form.unsavedChanges": "You have unsaved changes",
563
+ "form.discardChanges": "Discard changes",
564
+ "form.discardConfirm": "Are you sure you want to discard your changes? This action cannot be undone.",
565
+ "form.fieldRequired": "{{field}} is required",
566
+ "form.fieldInvalid": "{{field}} is invalid",
567
+ "form.maxLength": "Must be at most {{max}} characters",
568
+ "form.minLength": "Must be at least {{min}} characters",
569
+ "form.maxValue": "Must be at most {{max}}",
570
+ "form.minValue": "Must be at least {{min}}",
571
+ "form.pattern": "Invalid format",
572
+ "form.email": "Invalid email address",
573
+ "form.url": "Invalid URL",
574
+ "form.createSuccess": "{{name}} created successfully",
575
+ "form.createError": "Failed to create {{name}}",
576
+ "form.updateSuccess": "{{name}} updated successfully",
577
+ "form.updateError": "Failed to update {{name}}",
578
+ "form.deleteSuccess": "{{name}} deleted successfully",
579
+ "form.deleteError": "Failed to delete {{name}}",
580
+ "auth.login": "Log in",
581
+ "auth.logout": "Log out",
582
+ "auth.email": "Email",
583
+ "auth.password": "Password",
584
+ "auth.forgotPassword": "Forgot password?",
585
+ "auth.resetPassword": "Reset password",
586
+ "auth.signIn": "Sign in",
587
+ "auth.signOut": "Sign out",
588
+ "auth.signUp": "Sign up",
589
+ "auth.rememberMe": "Remember me",
590
+ "auth.invalidCredentials": "Invalid email or password",
591
+ "auth.sessionExpired": "Your session has expired. Please log in again.",
592
+ "auth.emailPlaceholder": "you@example.com",
593
+ "auth.passwordPlaceholder": "Enter your password",
594
+ "auth.signingIn": "Signing in...",
595
+ "auth.creatingAdmin": "Creating admin...",
596
+ "auth.name": "Name",
597
+ "auth.namePlaceholder": "Your name",
598
+ "auth.confirmPassword": "Confirm password",
599
+ "auth.confirmPasswordPlaceholder": "Confirm your password",
600
+ "auth.acceptInvite": "Accept invite",
601
+ "auth.acceptingInvite": "Accepting invite...",
602
+ "auth.dontHaveAccount": "Don't have an account?",
603
+ "auth.alreadyHaveAccount": "Already have an account?",
604
+ "auth.emailRequired": "Email is required",
605
+ "auth.passwordRequired": "Password is required",
606
+ "auth.passwordMinLength": "Password must be at least {{min}} characters",
607
+ "auth.nameRequired": "Name is required",
608
+ "auth.nameMinLength": "Name must be at least {{min}} characters",
609
+ "auth.invalidEmail": "Invalid email address",
610
+ "auth.passwordMismatch": "Passwords do not match",
611
+ "auth.newPassword": "New password",
612
+ "auth.newPasswordPlaceholder": "Enter new password",
613
+ "auth.sendResetLink": "Send reset link",
614
+ "auth.sendingResetLink": "Sending...",
615
+ "auth.resetLinkSent": "Password reset link sent to your email",
616
+ "auth.resettingPassword": "Resetting password...",
617
+ "auth.createFirstAdmin": "Create first admin",
618
+ "auth.setupTitle": "Setup",
619
+ "auth.setupDescription": "Create your first admin account to get started.",
620
+ "auth.profile": "Profile",
621
+ "auth.myAccount": "My account",
622
+ "error.notFound": "Not found",
623
+ "error.serverError": "Server error",
624
+ "error.networkError": "Network error. Please check your connection.",
625
+ "error.unauthorized": "You are not authorized to perform this action",
626
+ "error.forbidden": "Access denied",
627
+ "error.validation": "Validation failed",
628
+ "error.unknown": "An unknown error occurred",
629
+ "error.timeout": "Request timed out. Please try again.",
630
+ "error.conflict": "A conflict occurred. Please refresh and try again.",
631
+ "table.rowsPerPage": "Rows per page",
632
+ "table.of": "of",
633
+ "table.noResults": "No results",
634
+ "table.selectAll": "Select all",
635
+ "table.selectRow": "Select row",
636
+ "table.showing": "Showing {{from}} to {{to}} of {{total}}",
637
+ "table.page": "Page {{page}}",
638
+ "table.firstPage": "First page",
639
+ "table.lastPage": "Last page",
640
+ "table.nextPage": "Next page",
641
+ "table.previousPage": "Previous page",
642
+ "table.sortAsc": "Sort ascending",
643
+ "table.sortDesc": "Sort descending",
644
+ "table.columns": "Columns",
645
+ "table.hideColumn": "Hide column",
646
+ "table.showColumn": "Show column",
647
+ "upload.dropzone": "Drop files here or click to upload",
648
+ "upload.browse": "Browse files",
649
+ "upload.uploading": "Uploading...",
650
+ "upload.complete": "Upload complete",
651
+ "upload.error": "Upload failed",
652
+ "upload.maxSize": "File must be smaller than {{size}}",
653
+ "upload.invalidType": "Invalid file type. Allowed: {{types}}",
654
+ "upload.remove": "Remove file",
655
+ "upload.replace": "Replace file",
656
+ "upload.preview": "Preview",
657
+ "upload.noFile": "No file selected",
658
+ "upload.dragDrop": "Drag and drop files here",
659
+ "editor.bold": "Bold",
660
+ "editor.italic": "Italic",
661
+ "editor.underline": "Underline",
662
+ "editor.strikethrough": "Strikethrough",
663
+ "editor.heading": "Heading {{level}}",
664
+ "editor.link": "Insert link",
665
+ "editor.image": "Insert image",
666
+ "editor.list": "List",
667
+ "editor.orderedList": "Numbered list",
668
+ "editor.unorderedList": "Bullet list",
669
+ "editor.quote": "Quote",
670
+ "editor.code": "Code",
671
+ "editor.codeBlock": "Code block",
672
+ "editor.table": "Insert table",
673
+ "editor.undo": "Undo",
674
+ "editor.redo": "Redo",
675
+ "editor.alignLeft": "Align left",
676
+ "editor.alignCenter": "Align center",
677
+ "editor.alignRight": "Align right",
678
+ "editor.alignJustify": "Justify",
679
+ "editor.horizontalRule": "Horizontal rule",
680
+ "editor.addRowBefore": "Add row before",
681
+ "editor.addRowAfter": "Add row after",
682
+ "editor.addColumnBefore": "Add column before",
683
+ "editor.addColumnAfter": "Add column after",
684
+ "editor.deleteRow": "Delete row",
685
+ "editor.deleteColumn": "Delete column",
686
+ "editor.deleteTable": "Delete table",
687
+ "editor.toggleHeaderRow": "Toggle header row",
688
+ "editor.toggleHeaderColumn": "Toggle header column",
689
+ "editor.mergeCells": "Merge cells",
690
+ "editor.splitCell": "Split cell",
691
+ "editor.insertUrl": "Insert URL",
692
+ "editor.altText": "Alt text (optional)",
693
+ "editor.uploadFile": "Upload file",
694
+ "editor.chooseFile": "Choose file",
695
+ "editor.uploading": "Uploading...",
696
+ "editor.browseLibrary": "Browse library",
697
+ "toast.success": "Success",
698
+ "toast.error": "Error",
699
+ "toast.warning": "Warning",
700
+ "toast.info": "Info",
701
+ "toast.saving": "Saving...",
702
+ "toast.saveFailed": "Failed to save changes",
703
+ "toast.saveSuccess": "Changes saved successfully",
704
+ "toast.creating": "Creating...",
705
+ "toast.createSuccess": "Created successfully",
706
+ "toast.createFailed": "Failed to create",
707
+ "toast.deleting": "Deleting...",
708
+ "toast.deleteFailed": "Failed to delete",
709
+ "toast.deleteSuccess": "Deleted successfully",
710
+ "toast.loadFailed": "Failed to load data",
711
+ "toast.uploadFailed": "Failed to upload file",
712
+ "toast.uploadSuccess": "File uploaded successfully",
713
+ "toast.copySuccess": "Copied to clipboard",
714
+ "toast.copyFailed": "Failed to copy to clipboard",
715
+ "toast.idCopied": "ID copied to clipboard",
716
+ "toast.validationFailed": "Validation failed",
717
+ "toast.validationDescription": "Please check the form for errors",
718
+ "toast.created": "{{name}} created",
719
+ "toast.updated": "{{name}} updated",
720
+ "toast.resourceSaveFailed": "Failed to save {{name}}",
721
+ "toast.editComingSoon": "Edit functionality coming soon",
722
+ "toast.maxFilesWarning": "Only {{remaining}} more file(s) can be added (max {{max}})",
723
+ "toast.settingsSaveFailed": "Failed to save settings",
724
+ "toast.actionSuccess": "Action completed successfully",
725
+ "toast.actionFailed": "Action failed",
726
+ "toast.localeChangedUnsaved": "Content language changed",
727
+ "toast.localeChangedUnsavedDescription": "Your unsaved changes were replaced with content from the new language.",
728
+ "confirm.delete": "Are you sure you want to delete this? This action cannot be undone.",
729
+ "confirm.discard": "Are you sure you want to discard your changes? This action cannot be undone.",
730
+ "confirm.unsavedChanges": "You have unsaved changes. Are you sure you want to leave?",
731
+ "confirm.action": "Are you sure you want to continue?",
732
+ "confirm.irreversible": "This action cannot be undone.",
733
+ "confirm.localeChange": "Discard unsaved changes?",
734
+ "confirm.localeChangeDescription": "You have unsaved changes. Switching content language will discard your changes and load the content in the new language.",
735
+ "confirm.localeChangeStay": "Stay",
736
+ "confirm.localeChangeDiscard": "Discard & switch",
737
+ "status.draft": "Draft",
738
+ "status.published": "Published",
739
+ "status.archived": "Archived",
740
+ "status.pending": "Pending",
741
+ "status.active": "Active",
742
+ "status.inactive": "Inactive",
743
+ "date.today": "Today",
744
+ "date.yesterday": "Yesterday",
745
+ "date.tomorrow": "Tomorrow",
746
+ "date.selectDate": "Select date",
747
+ "date.selectTime": "Select time",
748
+ "date.clear": "Clear date",
749
+ "a11y.openMenu": "Open menu",
750
+ "a11y.closeMenu": "Close menu",
751
+ "a11y.expand": "Expand",
752
+ "a11y.collapse": "Collapse",
753
+ "a11y.loading": "Loading",
754
+ "a11y.required": "Required",
755
+ "a11y.optional": "Optional",
756
+ "a11y.selected": "Selected",
757
+ "a11y.notSelected": "Not selected",
758
+ "locale.language": "Language",
759
+ "locale.switchLanguage": "Switch language",
760
+ "locale.contentLanguage": "Content language",
761
+ "locale.uiLanguage": "Interface language",
762
+ "defaults.users.label": "Users",
763
+ "defaults.users.description": "Manage admin users and their roles",
764
+ "defaults.users.fields.name.label": "Name",
765
+ "defaults.users.fields.name.placeholder": "Enter user name",
766
+ "defaults.users.fields.email.label": "Email",
767
+ "defaults.users.fields.email.description": "Email address (read-only)",
768
+ "defaults.users.fields.role.label": "Role",
769
+ "defaults.users.fields.role.options.admin": "Admin",
770
+ "defaults.users.fields.role.options.user": "User",
771
+ "defaults.users.fields.emailVerified.label": "Email Verified",
772
+ "defaults.users.fields.emailVerified.description": "Whether the user has verified their email address",
773
+ "defaults.users.fields.banned.label": "Banned",
774
+ "defaults.users.fields.banned.description": "Prevent user from accessing the system",
775
+ "defaults.users.fields.banReason.label": "Ban Reason",
776
+ "defaults.users.fields.banReason.placeholder": "Enter reason for banning...",
777
+ "defaults.users.sections.basicInfo": "Basic Information",
778
+ "defaults.users.sections.permissions": "Permissions",
779
+ "defaults.users.sections.accessControl": "Access Control",
780
+ "defaults.users.actions.createUser.label": "Create User",
781
+ "defaults.users.actions.createUser.title": "Create User",
782
+ "defaults.users.actions.createUser.description": "Create a new user account with login credentials.",
783
+ "defaults.users.actions.createUser.fields.password.label": "Password",
784
+ "defaults.users.actions.createUser.fields.password.placeholder": "Enter password",
785
+ "defaults.users.actions.createUser.submit": "Create User",
786
+ "defaults.users.actions.createUser.success": "User {{email}} created successfully. Share the credentials with the user.",
787
+ "defaults.users.actions.createUser.errorNoAuth": "Auth client not configured. Cannot create user.",
788
+ "defaults.users.actions.resetPassword.label": "Reset Password",
789
+ "defaults.users.actions.resetPassword.title": "Reset Password",
790
+ "defaults.users.actions.resetPassword.description": "Set a new password for this user.",
791
+ "defaults.users.actions.resetPassword.fields.newPassword.label": "New Password",
792
+ "defaults.users.actions.resetPassword.fields.newPassword.placeholder": "Enter new password",
793
+ "defaults.users.actions.resetPassword.fields.confirmPassword.label": "Confirm Password",
794
+ "defaults.users.actions.resetPassword.fields.confirmPassword.placeholder": "Confirm new password",
795
+ "defaults.users.actions.resetPassword.submit": "Reset Password",
796
+ "defaults.users.actions.resetPassword.success": "Password reset successfully!",
797
+ "defaults.users.actions.resetPassword.errorMismatch": "Passwords do not match",
798
+ "defaults.users.actions.delete.label": "Delete User",
799
+ "defaults.assets.label": "Media",
800
+ "defaults.assets.description": "Manage uploaded files and images",
801
+ "defaults.assets.fields.preview.label": "Preview",
802
+ "defaults.assets.fields.filename.label": "Filename",
803
+ "defaults.assets.fields.filename.description": "Original filename of the uploaded file",
804
+ "defaults.assets.fields.mimeType.label": "Type",
805
+ "defaults.assets.fields.mimeType.description": "MIME type of the file",
806
+ "defaults.assets.fields.size.label": "Size (bytes)",
807
+ "defaults.assets.fields.size.description": "File size in bytes",
808
+ "defaults.assets.fields.alt.label": "Alt Text",
809
+ "defaults.assets.fields.alt.placeholder": "Describe the image for accessibility",
810
+ "defaults.assets.fields.alt.description": "Alternative text for screen readers",
811
+ "defaults.assets.fields.caption.label": "Caption",
812
+ "defaults.assets.fields.caption.placeholder": "Add a caption...",
813
+ "defaults.assets.fields.visibility.label": "Visibility",
814
+ "defaults.assets.fields.visibility.options.public": "Public",
815
+ "defaults.assets.fields.visibility.options.private": "Private",
816
+ "defaults.assets.fields.visibility.description": "Public files are accessible without authentication. Private files require a signed URL.",
817
+ "defaults.assets.sections.fileInfo": "File Information",
818
+ "defaults.assets.sections.metadata": "Metadata",
819
+ "defaults.assets.sections.metadata.description": "Add descriptive information for accessibility and SEO",
820
+ "defaults.assets.actions.upload.label": "Upload Files",
821
+ "defaults.sidebar.administration": "Administration",
822
+ "viewOptions.title": "View Options",
823
+ "viewOptions.columns": "Columns",
824
+ "viewOptions.filters": "Filters",
825
+ "viewOptions.savedViews": "Saved Views",
826
+ "viewOptions.apply": "Apply",
827
+ "viewOptions.reset": "Reset",
828
+ "viewOptions.saveCurrentConfig": "Save Current Configuration",
829
+ "viewOptions.viewNamePlaceholder": "View Name...",
830
+ "viewOptions.saveDescription": "Saves current columns, filters, and sort order.",
831
+ "viewOptions.noChangesToSave": "No filters or column changes to save.",
832
+ "viewOptions.noSavedViews": "No saved views yet.",
833
+ "viewOptions.filtersCount": {
834
+ one: "{{count}} filter",
835
+ other: "{{count}} filters"
836
+ },
837
+ "viewOptions.columnsCount": {
838
+ one: "{{count}} col",
839
+ other: "{{count}} cols"
840
+ },
841
+ "viewOptions.defaultView": "Default",
842
+ "viewOptions.columnsDragHint": "Drag to reorder, toggle to show/hide columns.",
843
+ "viewOptions.noFieldsAvailable": "No fields available.",
844
+ "viewOptions.filtersDescription": "Narrow down your results with custom rules.",
845
+ "viewOptions.filterNumber": "Filter #{{number}}",
846
+ "viewOptions.selectField": "Select field",
847
+ "viewOptions.selectOperator": "Select operator",
848
+ "viewOptions.valuePlaceholder": "Value...",
849
+ "viewOptions.noActiveFilters": "No active filters.",
850
+ "viewOptions.addFilter": "Add Filter",
851
+ "viewOptions.clearAll": "Clear All",
852
+ "viewOptions.activeFilters": {
853
+ one: "{{count}} filter active",
854
+ other: "{{count}} filters active"
855
+ },
856
+ "viewOptions.clearFilters": "Clear filters",
857
+ "filter.contains": "Contains",
858
+ "filter.notContains": "Does not contain",
859
+ "filter.equals": "Equals",
860
+ "filter.notEquals": "Does not equal",
861
+ "filter.startsWith": "Starts with",
862
+ "filter.endsWith": "Ends with",
863
+ "filter.greaterThan": "Greater than",
864
+ "filter.greaterThanOrEqual": "Greater than or equal",
865
+ "filter.lessThan": "Less than",
866
+ "filter.lessThanOrEqual": "Less than or equal",
867
+ "filter.in": "Is any of",
868
+ "filter.notIn": "Is none of",
869
+ "filter.some": "Has any",
870
+ "filter.every": "Has all",
871
+ "filter.none": "Has none",
872
+ "filter.isEmpty": "Is empty",
873
+ "filter.isNotEmpty": "Is not empty",
874
+ "preview.show": "Preview",
875
+ "preview.hide": "Hide Preview",
876
+ "preview.title": "Preview",
877
+ "preview.livePreview": "Live Preview",
878
+ "preview.fullscreen": "Fullscreen",
879
+ "preview.close": "Close preview",
880
+ "preview.loading": "Loading preview...",
881
+ "autosave.saving": "Saving...",
882
+ "autosave.saved": "Saved",
883
+ "autosave.unsavedChanges": "Unsaved changes",
884
+ "autosave.justNow": "just now",
885
+ "autosave.secondsAgo": {
886
+ one: "{{count}}s ago",
887
+ other: "{{count}}s ago"
888
+ },
889
+ "autosave.minutesAgo": {
890
+ one: "{{count}}m ago",
891
+ other: "{{count}}m ago"
892
+ },
893
+ "autosave.hoursAgo": {
894
+ one: "{{count}}h ago",
895
+ other: "{{count}}h ago"
896
+ },
897
+ "globalSearch.placeholder": "Search collections, globals, actions, records...",
898
+ "globalSearch.collections": "Collections",
899
+ "globalSearch.globals": "Globals",
900
+ "globalSearch.quickActions": "Quick Actions",
901
+ "globalSearch.records": "Records",
902
+ "globalSearch.createNew": "Create new {{name}}",
903
+ "globalSearch.noResults": "No results found",
904
+ "globalSearch.searching": "Searching...",
905
+ "globalSearch.navigate": "to navigate",
906
+ "globalSearch.select": "to select",
907
+ "collectionSearch.placeholder": "Search records...",
908
+ "collectionSearch.noResults": "No matching records found",
909
+ "collectionSearch.searching": "Searching..."
910
+ };
911
+
912
+ //#endregion
913
+ //#region src/client/i18n/messages.ts
914
+ /**
915
+ * Default Admin UI Messages
916
+ *
917
+ * Re-exports English messages as the default language.
918
+ * For other languages, import from "@questpie/admin/i18n/messages".
919
+ *
920
+ * @example
921
+ * ```ts
922
+ * // Default English (always bundled)
923
+ * import { adminMessages } from "@questpie/admin";
924
+ *
925
+ * // Add other languages
926
+ * import { adminMessagesSK } from "@questpie/admin/i18n/messages";
927
+ *
928
+ * const admin = qa()
929
+ * .translations({ sk: adminMessagesSK })
930
+ * .build();
931
+ * ```
932
+ */
933
+ /**
934
+ * Default English messages for admin UI
935
+ * This is re-exported from messages/en.ts for backwards compatibility
936
+ */
937
+ const adminMessages = adminMessagesEN;
938
+ /**
939
+ * Convert validation messages to SimpleMessages format
940
+ */
941
+ function flattenValidationMessages(messages) {
942
+ const result = {};
943
+ for (const [key, value] of Object.entries(messages)) if (typeof value === "string") result[key] = value;
944
+ else result[key] = {
945
+ one: value.one,
946
+ other: value.other
947
+ };
948
+ return result;
949
+ }
950
+ /**
951
+ * All admin messages including shared validation messages
952
+ */
953
+ const allAdminMessages = {
954
+ ...flattenValidationMessages(validationMessagesEN),
955
+ ...adminMessages
956
+ };
957
+
958
+ //#endregion
959
+ //#region src/client/i18n/intl-cache.ts
960
+ /**
961
+ * Intl Formatters Cache
962
+ *
963
+ * Caches Intl formatter instances to avoid creating new ones on every call.
964
+ * Intl formatters are expensive to create but cheap to reuse.
965
+ */
966
+ function getCacheKey(locale, options) {
967
+ if (!options || Object.keys(options).length === 0) return locale;
968
+ return `${locale}:${JSON.stringify(options)}`;
969
+ }
970
+ const dateTimeFormatCache = /* @__PURE__ */ new Map();
971
+ function getDateTimeFormat(locale, options) {
972
+ const key = getCacheKey(locale, options);
973
+ let formatter = dateTimeFormatCache.get(key);
974
+ if (!formatter) {
975
+ formatter = new Intl.DateTimeFormat(locale, options);
976
+ dateTimeFormatCache.set(key, formatter);
977
+ }
978
+ return formatter;
979
+ }
980
+ const numberFormatCache = /* @__PURE__ */ new Map();
981
+ function getNumberFormat(locale, options) {
982
+ const key = getCacheKey(locale, options);
983
+ let formatter = numberFormatCache.get(key);
984
+ if (!formatter) {
985
+ formatter = new Intl.NumberFormat(locale, options);
986
+ numberFormatCache.set(key, formatter);
987
+ }
988
+ return formatter;
989
+ }
990
+ const pluralRulesCache = /* @__PURE__ */ new Map();
991
+ function getPluralRules(locale, options) {
992
+ const key = getCacheKey(locale, options);
993
+ let rules = pluralRulesCache.get(key);
994
+ if (!rules) {
995
+ rules = new Intl.PluralRules(locale, options);
996
+ pluralRulesCache.set(key, rules);
997
+ }
998
+ return rules;
999
+ }
1000
+ const relativeTimeFormatCache = /* @__PURE__ */ new Map();
1001
+ function getRelativeTimeFormat(locale, options) {
1002
+ const key = getCacheKey(locale, options);
1003
+ let formatter = relativeTimeFormatCache.get(key);
1004
+ if (!formatter) {
1005
+ formatter = new Intl.RelativeTimeFormat(locale, options);
1006
+ relativeTimeFormatCache.set(key, formatter);
1007
+ }
1008
+ return formatter;
1009
+ }
1010
+ const displayNamesCache = /* @__PURE__ */ new Map();
1011
+ function getDisplayNames(locale, options) {
1012
+ const key = getCacheKey(locale, options);
1013
+ let displayNames = displayNamesCache.get(key);
1014
+ if (!displayNames) {
1015
+ displayNames = new Intl.DisplayNames([locale], options);
1016
+ displayNamesCache.set(key, displayNames);
1017
+ }
1018
+ return displayNames;
1019
+ }
1020
+
1021
+ //#endregion
1022
+ //#region src/client/i18n/simple.ts
1023
+ /**
1024
+ * Simple I18n Implementation
1025
+ *
1026
+ * A minimal i18n implementation for basic use cases.
1027
+ * For full i18n features (pluralization, ICU, etc.), use i18next or react-intl.
1028
+ *
1029
+ * Features:
1030
+ * - Basic key-value translation
1031
+ * - Simple interpolation ({{key}})
1032
+ * - Uses Intl APIs for formatting
1033
+ * - Basic pluralization via Intl.PluralRules
1034
+ */
1035
+ const RTL_LOCALES = new Set([
1036
+ "ar",
1037
+ "he",
1038
+ "fa",
1039
+ "ur",
1040
+ "ps",
1041
+ "sd",
1042
+ "yi"
1043
+ ]);
1044
+ /**
1045
+ * Create a simple i18n adapter
1046
+ *
1047
+ * @example
1048
+ * ```ts
1049
+ * const i18n = createSimpleI18n({
1050
+ * locale: "en",
1051
+ * locales: ["en", "de"],
1052
+ * messages: {
1053
+ * en: {
1054
+ * "common.save": "Save",
1055
+ * "items.count": { one: "{{count}} item", other: "{{count}} items" }
1056
+ * },
1057
+ * de: {
1058
+ * "common.save": "Speichern",
1059
+ * "items.count": { one: "{{count}} Artikel", other: "{{count}} Artikel" }
1060
+ * }
1061
+ * }
1062
+ * });
1063
+ * ```
1064
+ */
1065
+ function createSimpleI18n(options) {
1066
+ let currentLocale = options.locale;
1067
+ const { locales, messages, fallbackLocale = locales[0], onLocaleChange: onLocaleChangeCallback } = options;
1068
+ const listeners = /* @__PURE__ */ new Set();
1069
+ /**
1070
+ * Notify all listeners about locale change
1071
+ */
1072
+ function notifyListeners(locale) {
1073
+ onLocaleChangeCallback?.(locale);
1074
+ for (const listener of listeners) listener(locale);
1075
+ }
1076
+ /**
1077
+ * Get message for key in locale
1078
+ */
1079
+ function getMessage(key, locale) {
1080
+ return messages[locale]?.[key];
1081
+ }
1082
+ /**
1083
+ * Interpolate values into string
1084
+ */
1085
+ function interpolate(str, params) {
1086
+ if (!params) return str;
1087
+ return str.replace(/\{\{(\w+)\}\}/g, (_, key) => {
1088
+ const value = params[key];
1089
+ return value !== void 0 ? String(value) : `{{${key}}}`;
1090
+ });
1091
+ }
1092
+ /**
1093
+ * Check if value is plural messages
1094
+ */
1095
+ function isPluralMessages(value) {
1096
+ return typeof value === "object" && value !== null && "one" in value && "other" in value;
1097
+ }
1098
+ /**
1099
+ * Get plural form
1100
+ */
1101
+ function getPluralForm(forms, count, locale) {
1102
+ switch (getPluralRules(locale).select(count)) {
1103
+ case "zero": return forms.zero ?? forms.other;
1104
+ case "one": return forms.one;
1105
+ case "two": return forms.two ?? forms.other;
1106
+ case "few": return forms.few ?? forms.other;
1107
+ case "many": return forms.many ?? forms.other;
1108
+ default: return forms.other;
1109
+ }
1110
+ }
1111
+ return {
1112
+ get locale() {
1113
+ return currentLocale;
1114
+ },
1115
+ get locales() {
1116
+ return locales;
1117
+ },
1118
+ t(key, params) {
1119
+ let value = getMessage(key, currentLocale);
1120
+ if (value === void 0 && currentLocale !== fallbackLocale) value = getMessage(key, fallbackLocale);
1121
+ if (value === void 0) return key;
1122
+ if (isPluralMessages(value)) {
1123
+ const count = typeof params?.count === "number" ? params.count : 1;
1124
+ return interpolate(getPluralForm(value, count, currentLocale), params);
1125
+ }
1126
+ return interpolate(value, params);
1127
+ },
1128
+ setLocale(locale) {
1129
+ if (locales.includes(locale) && locale !== currentLocale) {
1130
+ currentLocale = locale;
1131
+ notifyListeners(locale);
1132
+ }
1133
+ },
1134
+ onLocaleChange(callback) {
1135
+ listeners.add(callback);
1136
+ return () => {
1137
+ listeners.delete(callback);
1138
+ };
1139
+ },
1140
+ formatDate(date, opts) {
1141
+ const d = typeof date === "number" ? new Date(date) : date;
1142
+ return getDateTimeFormat(currentLocale, opts).format(d);
1143
+ },
1144
+ formatNumber(value, opts) {
1145
+ return getNumberFormat(currentLocale, opts).format(value);
1146
+ },
1147
+ formatRelative(date) {
1148
+ const d = typeof date === "number" ? new Date(date) : date;
1149
+ const now = Date.now();
1150
+ const diff = d.getTime() - now;
1151
+ const diffSeconds = Math.round(diff / 1e3);
1152
+ const diffMinutes = Math.round(diff / 6e4);
1153
+ const diffHours = Math.round(diff / 36e5);
1154
+ const diffDays = Math.round(diff / 864e5);
1155
+ const rtf = getRelativeTimeFormat(currentLocale, { numeric: "auto" });
1156
+ if (Math.abs(diffSeconds) < 60) return rtf.format(diffSeconds, "second");
1157
+ if (Math.abs(diffMinutes) < 60) return rtf.format(diffMinutes, "minute");
1158
+ if (Math.abs(diffHours) < 24) return rtf.format(diffHours, "hour");
1159
+ return rtf.format(diffDays, "day");
1160
+ },
1161
+ getLocaleName(locale) {
1162
+ try {
1163
+ return getDisplayNames(currentLocale, { type: "language" }).of(locale) ?? locale;
1164
+ } catch {
1165
+ return locale;
1166
+ }
1167
+ },
1168
+ isRTL() {
1169
+ return RTL_LOCALES.has(currentLocale);
1170
+ }
1171
+ };
1172
+ }
1173
+
1174
+ //#endregion
1175
+ //#region src/client/runtime/routes.ts
1176
+ /**
1177
+ * Build type-safe routes from admin configuration
1178
+ *
1179
+ * @example
1180
+ * ```ts
1181
+ * import { buildRoutes } from "@questpie/admin/runtime";
1182
+ * import { appAdmin } from "./admin";
1183
+ *
1184
+ * const routes = buildRoutes(appAdmin);
1185
+ *
1186
+ * // Type-safe route generation
1187
+ * routes.dashboard(); // "/admin"
1188
+ * routes.collections.posts.list(); // "/admin/collections/posts"
1189
+ * routes.collections.posts.edit("123"); // "/admin/collections/posts/123/edit"
1190
+ * routes.globals.settings.edit(); // "/admin/globals/settings"
1191
+ * ```
1192
+ */
1193
+ function buildRoutes(admin, options = {}) {
1194
+ const { basePath = "/admin" } = options;
1195
+ const path = (...segments) => [basePath, ...segments].filter(Boolean).join("/");
1196
+ const collectionNames = admin.getCollectionNames();
1197
+ const collections = {};
1198
+ for (const name of collectionNames) collections[name] = {
1199
+ list: () => path("collections", name),
1200
+ create: () => path("collections", name, "create"),
1201
+ edit: (id) => path("collections", name, id, "edit"),
1202
+ view: (id) => path("collections", name, id)
1203
+ };
1204
+ const globalNames = admin.getGlobalNames();
1205
+ const globals = {};
1206
+ for (const name of globalNames) globals[name] = { edit: () => path("globals", name) };
1207
+ const pageConfigs = admin.getPages();
1208
+ const pages = {};
1209
+ for (const [name, config] of Object.entries(pageConfigs)) {
1210
+ const pagePath = config.path ?? name;
1211
+ pages[name] = { view: () => pagePath.startsWith("/") ? pagePath : path("pages", pagePath) };
1212
+ }
1213
+ return {
1214
+ dashboard: () => basePath,
1215
+ collections,
1216
+ globals,
1217
+ pages,
1218
+ auth: {
1219
+ login: () => path("auth", "login"),
1220
+ logout: () => path("auth", "logout"),
1221
+ forgotPassword: () => path("auth", "forgot-password"),
1222
+ resetPassword: (token) => path("auth", "reset-password", token)
1223
+ }
1224
+ };
1225
+ }
1226
+ /**
1227
+ * Build navigation structure from admin configuration
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * import { buildNavigation } from "@questpie/admin/runtime";
1232
+ * import { appAdmin } from "./admin";
1233
+ *
1234
+ * const navigation = buildNavigation(appAdmin);
1235
+ * // Returns grouped navigation items for sidebar rendering
1236
+ * ```
1237
+ */
1238
+ function buildNavigation(admin, options = {}) {
1239
+ const routes = buildRoutes(admin, options);
1240
+ const items = [];
1241
+ items.push({
1242
+ id: "dashboard",
1243
+ label: "Dashboard",
1244
+ href: routes.dashboard(),
1245
+ icon: void 0,
1246
+ type: "dashboard",
1247
+ order: -1e3
1248
+ });
1249
+ for (const [name, config] of Object.entries(admin.getCollections())) {
1250
+ const meta = config.meta ?? config;
1251
+ if (meta.hidden) continue;
1252
+ const collectionRoutes = routes.collections[name];
1253
+ if (!collectionRoutes) continue;
1254
+ items.push({
1255
+ id: `collection:${name}`,
1256
+ label: resolveLabel(meta.label, name),
1257
+ href: collectionRoutes.list(),
1258
+ icon: resolveIcon(meta.icon),
1259
+ group: meta.group,
1260
+ order: meta.order ?? 0,
1261
+ type: "collection"
1262
+ });
1263
+ }
1264
+ for (const [name, config] of Object.entries(admin.getGlobals())) {
1265
+ const meta = config.meta ?? config;
1266
+ if (meta.hidden) continue;
1267
+ const globalRoutes = routes.globals[name];
1268
+ if (!globalRoutes) continue;
1269
+ items.push({
1270
+ id: `global:${name}`,
1271
+ label: resolveLabel(meta.label, name),
1272
+ href: globalRoutes.edit(),
1273
+ icon: resolveIcon(meta.icon),
1274
+ group: meta.group,
1275
+ order: meta.order ?? 0,
1276
+ type: "global"
1277
+ });
1278
+ }
1279
+ for (const [name, config] of Object.entries(admin.getPages())) {
1280
+ if (config.showInNav === false) continue;
1281
+ items.push({
1282
+ id: `page:${name}`,
1283
+ label: resolveLabel(config.label, name),
1284
+ href: routes.pages[name].view(),
1285
+ icon: resolveIcon(config.icon),
1286
+ group: config.group,
1287
+ order: config.order ?? 0,
1288
+ type: "page"
1289
+ });
1290
+ }
1291
+ return groupNavigationItems(items, admin);
1292
+ }
1293
+ /**
1294
+ * Resolve label - passes through I18nText for runtime resolution
1295
+ */
1296
+ function resolveLabel(label, fallback) {
1297
+ if (typeof label === "string") return label;
1298
+ if (typeof label === "object" && label !== null) return label;
1299
+ return fallback.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
1300
+ }
1301
+ /**
1302
+ * Resolve icon to IconComponent
1303
+ * Only accepts React components, not strings
1304
+ */
1305
+ function resolveIcon(icon) {
1306
+ if (typeof icon === "function" || typeof icon === "object" && icon !== null) return icon;
1307
+ }
1308
+ /**
1309
+ * Group navigation items based on sidebar configuration
1310
+ */
1311
+ function groupNavigationItems(items, admin) {
1312
+ const sidebarConfig = admin.getSidebar();
1313
+ if (sidebarConfig.sections?.length) return sidebarConfig.sections.map((section) => ({
1314
+ id: section.id,
1315
+ label: resolveLabel(section.title, ""),
1316
+ icon: resolveIcon(section.icon),
1317
+ collapsed: section.collapsed,
1318
+ items: section.items.map((item) => {
1319
+ switch (item.type) {
1320
+ case "collection": {
1321
+ const found = items.find((i) => i.id === `collection:${item.collection}`);
1322
+ if (!found) return void 0;
1323
+ return {
1324
+ ...found,
1325
+ label: item.label ?? found.label,
1326
+ icon: resolveIcon(item.icon) ?? found.icon
1327
+ };
1328
+ }
1329
+ case "global": {
1330
+ const found = items.find((i) => i.id === `global:${item.global}`);
1331
+ if (!found) return void 0;
1332
+ return {
1333
+ ...found,
1334
+ label: item.label ?? found.label,
1335
+ icon: resolveIcon(item.icon) ?? found.icon
1336
+ };
1337
+ }
1338
+ case "page": {
1339
+ const found = items.find((i) => i.id === `page:${item.pageId}`);
1340
+ if (!found) return void 0;
1341
+ return {
1342
+ ...found,
1343
+ label: item.label ?? found.label,
1344
+ icon: resolveIcon(item.icon) ?? found.icon
1345
+ };
1346
+ }
1347
+ case "link": return {
1348
+ id: `link:${item.href}`,
1349
+ label: resolveLabel(item.label, ""),
1350
+ href: item.href,
1351
+ icon: resolveIcon(item.icon),
1352
+ type: "link",
1353
+ order: 0
1354
+ };
1355
+ case "divider": return { type: "divider" };
1356
+ default: return;
1357
+ }
1358
+ }).filter((i) => i !== void 0)
1359
+ }));
1360
+ if (sidebarConfig.groups?.length) return sidebarConfig.groups.map((group) => ({
1361
+ label: resolveLabel(group.label, ""),
1362
+ items: group.items.map((item) => {
1363
+ if (typeof item === "string") return items.find((i) => i.id === `collection:${item}` || i.id === `global:${item}` || i.id === `page:${item}` || i.id === item);
1364
+ return {
1365
+ id: item.id,
1366
+ label: resolveLabel(item.label, item.id),
1367
+ href: item.href ?? "#",
1368
+ icon: resolveIcon(item.icon),
1369
+ type: "link",
1370
+ order: item.order ?? 0
1371
+ };
1372
+ }).filter((i) => i !== void 0)
1373
+ }));
1374
+ const groups = /* @__PURE__ */ new Map();
1375
+ const sortedItems = [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
1376
+ for (const item of sortedItems) {
1377
+ const groupKey = item.group;
1378
+ if (!groups.has(groupKey)) groups.set(groupKey, []);
1379
+ groups.get(groupKey).push(item);
1380
+ }
1381
+ const result = [];
1382
+ const ungrouped = groups.get(void 0);
1383
+ if (ungrouped?.length) result.push({ items: ungrouped });
1384
+ for (const [label, groupItems] of groups) {
1385
+ if (label === void 0) continue;
1386
+ result.push({
1387
+ label,
1388
+ items: groupItems
1389
+ });
1390
+ }
1391
+ return result;
1392
+ }
1393
+
1394
+ //#endregion
1395
+ //#region src/client/runtime/provider.tsx
1396
+ /** Cookie for UI locale (admin interface language) */
1397
+ const UI_LOCALE_COOKIE = "questpie_ui_locale";
1398
+ /** Cookie for content locale (CMS content language) */
1399
+ const CONTENT_LOCALE_COOKIE = "questpie_content_locale";
1400
+ /** Cookie max age (1 year) */
1401
+ const LOCALE_COOKIE_MAX_AGE = 3600 * 24 * 365;
1402
+ const LEGACY_LOCALE_COOKIE = "questpie_locale";
1403
+ function getCookie(name) {
1404
+ if (typeof document === "undefined") return null;
1405
+ const match = document.cookie.match(/* @__PURE__ */ new RegExp(`${name}=([^;]+)`));
1406
+ return match ? match[1] : null;
1407
+ }
1408
+ function setCookie(name, value) {
1409
+ if (typeof document === "undefined") return;
1410
+ document.cookie = `${name}=${value}; path=/; max-age=${LOCALE_COOKIE_MAX_AGE}; SameSite=Lax`;
1411
+ }
1412
+ function getUiLocaleFromCookie() {
1413
+ return getCookie(UI_LOCALE_COOKIE) ?? getCookie(LEGACY_LOCALE_COOKIE);
1414
+ }
1415
+ function getContentLocaleFromCookie() {
1416
+ return getCookie(CONTENT_LOCALE_COOKIE) ?? getCookie(LEGACY_LOCALE_COOKIE);
1417
+ }
1418
+ function setUiLocaleCookie(locale) {
1419
+ setCookie(UI_LOCALE_COOKIE, locale);
1420
+ }
1421
+ function setContentLocaleCookie(locale) {
1422
+ setCookie(CONTENT_LOCALE_COOKIE, locale);
1423
+ }
1424
+ function createAdminStore({ admin, client, authClient, basePath, navigate, initialContentLocale }) {
1425
+ if (client && initialContentLocale && "setLocale" in client) client.setLocale(initialContentLocale);
1426
+ return createStore((set) => ({
1427
+ admin,
1428
+ client,
1429
+ authClient,
1430
+ basePath,
1431
+ navigate,
1432
+ contentLocale: initialContentLocale,
1433
+ setContentLocale: (newLocale) => {
1434
+ setContentLocaleCookie(newLocale);
1435
+ set({ contentLocale: newLocale });
1436
+ },
1437
+ navigation: buildNavigation(admin, { basePath }),
1438
+ brandName: resolveTextSync(admin.getBranding().name, "Admin")
1439
+ }));
1440
+ }
1441
+ const AdminStoreContext = createContext(null);
1442
+ /**
1443
+ * Merge admin messages with custom translations
1444
+ */
1445
+ function mergeMessages(baseMessages, customTranslations) {
1446
+ const result = { en: { ...baseMessages } };
1447
+ if (!customTranslations) return result;
1448
+ for (const [locale, messages] of Object.entries(customTranslations)) result[locale] = {
1449
+ ...result[locale] ?? {},
1450
+ ...messages
1451
+ };
1452
+ return result;
1453
+ }
1454
+ /**
1455
+ * Admin provider component
1456
+ *
1457
+ * Creates a scoped Zustand store for admin state management.
1458
+ * Use `useAdminStore(selector)` to access state with optimized re-renders.
1459
+ *
1460
+ * @example
1461
+ * ```tsx
1462
+ * import { AdminProvider, useAdminStore } from "@questpie/admin/runtime";
1463
+ *
1464
+ * function App() {
1465
+ * return (
1466
+ * <AdminProvider admin={admin} client={client}>
1467
+ * <MyComponent />
1468
+ * </AdminProvider>
1469
+ * );
1470
+ * }
1471
+ *
1472
+ * function MyComponent() {
1473
+ * // Only re-renders when locale changes
1474
+ * const locale = useAdminStore((s) => s.locale);
1475
+ * const setLocale = useAdminStore((s) => s.setLocale);
1476
+ * // ...
1477
+ * }
1478
+ * ```
1479
+ */
1480
+ function AdminProvider({ admin: adminInput, client, authClient, basePath = "/admin", navigate: navigateProp, initialUiLocale, initialContentLocale, i18nAdapter: customI18nAdapter, children }) {
1481
+ const admin = Admin.normalize(adminInput);
1482
+ const navigate = navigateProp ?? ((path) => {
1483
+ if (typeof window !== "undefined") window.location.href = path;
1484
+ });
1485
+ const localeConfig = admin.getLocale();
1486
+ const defaultLocale = localeConfig.default ?? DEFAULT_LOCALE;
1487
+ const resolvedUiLocale = initialUiLocale ?? getUiLocaleFromCookie() ?? defaultLocale;
1488
+ const resolvedContentLocale = initialContentLocale ?? getContentLocaleFromCookie() ?? defaultLocale;
1489
+ const storeRef = useRef(null);
1490
+ if (!storeRef.current) storeRef.current = createAdminStore({
1491
+ admin,
1492
+ client,
1493
+ authClient: authClient ?? null,
1494
+ basePath,
1495
+ navigate,
1496
+ initialContentLocale: resolvedContentLocale
1497
+ });
1498
+ useEffect(() => {
1499
+ if (storeRef.current) storeRef.current.setState({
1500
+ admin,
1501
+ navigation: buildNavigation(admin, { basePath }),
1502
+ brandName: resolveTextSync(admin.getBranding().name, "Admin")
1503
+ });
1504
+ }, [admin, basePath]);
1505
+ const i18nAdapterRef = useRef(null);
1506
+ if (!i18nAdapterRef.current) if (customI18nAdapter) i18nAdapterRef.current = customI18nAdapter;
1507
+ else {
1508
+ const messages = mergeMessages(adminMessages, admin.getTranslations());
1509
+ i18nAdapterRef.current = createSimpleI18n({
1510
+ locale: resolvedUiLocale,
1511
+ locales: localeConfig.supported ?? [DEFAULT_LOCALE],
1512
+ messages,
1513
+ fallbackLocale: defaultLocale,
1514
+ onLocaleChange: setUiLocaleCookie
1515
+ });
1516
+ }
1517
+ const contentLocale = useStore(storeRef.current, (s) => s.contentLocale);
1518
+ useEffect(() => {
1519
+ if (client && contentLocale && "setLocale" in client) client.setLocale(contentLocale);
1520
+ }, [client, contentLocale]);
1521
+ return /* @__PURE__ */ jsx(AdminStoreContext.Provider, {
1522
+ value: storeRef.current,
1523
+ children: /* @__PURE__ */ jsx(I18nProvider, {
1524
+ adapter: i18nAdapterRef.current,
1525
+ children: /* @__PURE__ */ jsx(ContentLocalesProvider, { children })
1526
+ })
1527
+ });
1528
+ }
1529
+ /**
1530
+ * Access admin store with a selector for optimized re-renders.
1531
+ *
1532
+ * @example
1533
+ * ```tsx
1534
+ * // Only re-renders when locale changes
1535
+ * const locale = useAdminStore((s) => s.locale);
1536
+ *
1537
+ * // Get multiple values (re-renders when any changes)
1538
+ * const { admin, client } = useAdminStore((s) => ({
1539
+ * admin: s.admin,
1540
+ * client: s.client,
1541
+ * }));
1542
+ *
1543
+ * // Get navigation (computed once)
1544
+ * const navigation = useAdminStore((s) => s.navigation);
1545
+ * ```
1546
+ */
1547
+ function useAdminStore(selector) {
1548
+ const store = useContext(AdminStoreContext);
1549
+ if (!store) throw new Error("useAdminStore must be used within AdminProvider. Wrap your app with <AdminProvider admin={admin} client={client}>");
1550
+ return useStore(store, selector);
1551
+ }
1552
+ /** Select admin instance */
1553
+ const selectAdmin = (s) => s.admin;
1554
+ /** Select client instance */
1555
+ const selectClient = (s) => s.client;
1556
+ /** Select auth client instance */
1557
+ const selectAuthClient = (s) => s.authClient;
1558
+ /** Select base path */
1559
+ const selectBasePath = (s) => s.basePath;
1560
+ /** Select navigate function */
1561
+ const selectNavigate = (s) => s.navigate;
1562
+ /** Select current content locale (CMS content language) */
1563
+ const selectContentLocale = (s) => s.contentLocale;
1564
+ /** Select setContentLocale function */
1565
+ const selectSetContentLocale = (s) => s.setContentLocale;
1566
+ /** Select navigation groups */
1567
+ const selectNavigation = (s) => s.navigation;
1568
+ /** Select brand name */
1569
+ const selectBrandName = (s) => s.brandName;
1570
+
1571
+ //#endregion
1572
+ //#region src/client/runtime/content-locales-provider.tsx
1573
+ /**
1574
+ * Content Locales Provider
1575
+ *
1576
+ * Fetches and provides content locales from the backend CMS configuration.
1577
+ * Content locales are different from UI locales - they define which languages
1578
+ * are available for content translation.
1579
+ */
1580
+ const ContentLocalesContext = createContext(null);
1581
+ const DEFAULT_LOCALES = {
1582
+ locales: [{
1583
+ code: DEFAULT_LOCALE,
1584
+ label: "English",
1585
+ fallback: true
1586
+ }],
1587
+ defaultLocale: DEFAULT_LOCALE
1588
+ };
1589
+ /**
1590
+ * Content Locales Provider
1591
+ *
1592
+ * Fetches content locales from the backend and provides them to the UI.
1593
+ * Must be used inside AdminProvider to access the client.
1594
+ *
1595
+ * @example
1596
+ * ```tsx
1597
+ * <AdminProvider admin={admin} client={client}>
1598
+ * <ContentLocalesProvider>
1599
+ * <App />
1600
+ * </ContentLocalesProvider>
1601
+ * </AdminProvider>
1602
+ * ```
1603
+ */
1604
+ function ContentLocalesProvider({ children }) {
1605
+ const client = useAdminStore(selectClient);
1606
+ const { data, isLoading, error } = useQuery({
1607
+ queryKey: ["cms", "contentLocales"],
1608
+ queryFn: async () => {
1609
+ try {
1610
+ return await client.functions.getContentLocales({});
1611
+ } catch {
1612
+ return DEFAULT_LOCALES;
1613
+ }
1614
+ },
1615
+ staleTime: 300 * 1e3,
1616
+ gcTime: 600 * 1e3
1617
+ });
1618
+ const localesData = data ?? DEFAULT_LOCALES;
1619
+ const value = {
1620
+ ...localesData,
1621
+ isLocalized: localesData.locales.length > 1,
1622
+ isLoading,
1623
+ error,
1624
+ getLocaleLabel: (code) => {
1625
+ return localesData.locales.find((l) => l.code === code)?.label ?? code.toUpperCase();
1626
+ },
1627
+ isValidLocale: (code) => {
1628
+ return localesData.locales.some((l) => l.code === code);
1629
+ },
1630
+ getFallbackLocale: (code) => {
1631
+ if (localesData.fallbacks?.[code]) return localesData.fallbacks[code];
1632
+ return localesData.defaultLocale;
1633
+ }
1634
+ };
1635
+ return /* @__PURE__ */ jsx(ContentLocalesContext.Provider, {
1636
+ value,
1637
+ children
1638
+ });
1639
+ }
1640
+ /**
1641
+ * Safely get content locales from context.
1642
+ * Returns null if not inside provider (useful for optional features).
1643
+ */
1644
+ function useSafeContentLocales() {
1645
+ return useContext(ContentLocalesContext);
1646
+ }
1647
+
1648
+ //#endregion
1649
+ export { formatCollectionName as _, selectBasePath as a, useTranslation as b, selectContentLocale as c, selectSetContentLocale as d, useAdminStore as f, cn as g, Button$1 as h, selectAuthClient as i, selectNavigate as l, Admin as m, AdminProvider as n, selectBrandName as o, adminMessagesEN as p, selectAdmin as r, selectClient as s, useSafeContentLocales as t, selectNavigation as u, useResolveText as v, useSafeI18n as y };
1650
+ //# sourceMappingURL=content-locales-provider-BXvuIgfg.mjs.map