@open-mercato/core 0.4.6-develop-c2b70de148 → 0.4.6-develop-38209f5fc2

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 (30) hide show
  1. package/dist/modules/auth/frontend/login.js +13 -2
  2. package/dist/modules/auth/frontend/login.js.map +2 -2
  3. package/dist/modules/catalog/components/products/ProductCategorizeSection.js +1 -0
  4. package/dist/modules/catalog/components/products/ProductCategorizeSection.js.map +2 -2
  5. package/dist/modules/data_sync/lib/sync-engine.js +8 -2
  6. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  7. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +1 -1
  8. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +1 -1
  9. package/dist/modules/feature_toggles/components/formConfig.js +1 -0
  10. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  11. package/dist/modules/feature_toggles/components/overrideFormConfig.js +1 -0
  12. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  13. package/dist/modules/integrations/backend/integrations/filters.js +40 -0
  14. package/dist/modules/integrations/backend/integrations/filters.js.map +7 -0
  15. package/dist/modules/integrations/backend/integrations/page.js +34 -22
  16. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  17. package/package.json +2 -2
  18. package/src/modules/auth/frontend/login-injection.ts +2 -0
  19. package/src/modules/auth/frontend/login.tsx +15 -2
  20. package/src/modules/catalog/components/products/ProductCategorizeSection.tsx +1 -0
  21. package/src/modules/data_sync/lib/sync-engine.ts +8 -2
  22. package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +1 -1
  23. package/src/modules/feature_toggles/components/formConfig.tsx +1 -0
  24. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +1 -0
  25. package/src/modules/integrations/backend/integrations/filters.ts +39 -0
  26. package/src/modules/integrations/backend/integrations/page.tsx +51 -37
  27. package/src/modules/integrations/i18n/de.json +88 -11
  28. package/src/modules/integrations/i18n/en.json +79 -2
  29. package/src/modules/integrations/i18n/es.json +96 -19
  30. package/src/modules/integrations/i18n/pl.json +105 -28
@@ -81,12 +81,17 @@ function LoginPage() {
81
81
  const [error, setError] = useState(null);
82
82
  const [submitting, setSubmitting] = useState(false);
83
83
  const [authOverride, setAuthOverride] = useState(null);
84
+ const [authOverridePending, setAuthOverridePending] = useState(false);
85
+ const [clientReady, setClientReady] = useState(false);
84
86
  const [email, setEmail] = useState("");
85
87
  const [tenantId, setTenantId] = useState(null);
86
88
  const [tenantName, setTenantName] = useState(null);
87
89
  const [tenantLoading, setTenantLoading] = useState(false);
88
90
  const [tenantInvalid, setTenantInvalid] = useState(null);
89
91
  const showTenantInvalid = tenantId != null && tenantInvalid === tenantId;
92
+ useEffect(() => {
93
+ setClientReady(true);
94
+ }, []);
90
95
  useEffect(() => {
91
96
  const tenantParam = (searchParams.get("tenant") || "").trim();
92
97
  if (tenantParam) {
@@ -152,6 +157,9 @@ function LoginPage() {
152
157
  }
153
158
  async function onSubmit(e) {
154
159
  e.preventDefault();
160
+ if (!clientReady || authOverridePending) {
161
+ return;
162
+ }
155
163
  setError(null);
156
164
  if (authOverride) {
157
165
  authOverride.onSubmit();
@@ -229,15 +237,17 @@ function LoginPage() {
229
237
  tenantId,
230
238
  searchParams,
231
239
  setAuthOverride,
240
+ setAuthOverridePending,
232
241
  setError
233
242
  }), [email, tenantId, searchParams]);
243
+ const formReady = clientReady && !authOverridePending;
234
244
  return /* @__PURE__ */ jsx("div", { className: "min-h-svh flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(Card, { className: "w-full max-w-sm", children: [
235
245
  /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-col items-center gap-4 text-center p-10", children: [
236
246
  /* @__PURE__ */ jsx(Image, { alt: translate("auth.login.logoAlt", "Open Mercato logo"), src: "/open-mercato.svg", width: 150, height: 150, priority: true }),
237
247
  /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: translate("auth.login.brandName", "Open Mercato") }),
238
248
  /* @__PURE__ */ jsx(CardDescription, { children: translate("auth.login.subtitle", "Access your workspace") })
239
249
  ] }),
240
- /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { className: "grid gap-3", onSubmit, noValidate: true, children: [
250
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("form", { className: "grid gap-3", onSubmit, noValidate: true, "data-auth-ready": formReady ? "1" : "0", children: [
241
251
  tenantId ? /* @__PURE__ */ jsx("input", { type: "hidden", name: "tenantId", value: tenantId }) : null,
242
252
  !!translatedRoles.length && /* @__PURE__ */ jsx(Notice, { compact: true, className: "text-center", children: translate(
243
253
  translatedRoles.length > 1 ? "auth.login.requireRolesMessage" : "auth.login.requireRoleMessage",
@@ -273,6 +283,7 @@ function LoginPage() {
273
283
  type: "email",
274
284
  required: true,
275
285
  "aria-invalid": !!error,
286
+ onChange: (e) => setEmail(e.target.value),
276
287
  onBlur: (e) => setEmail(e.target.value)
277
288
  }
278
289
  )
@@ -292,7 +303,7 @@ function LoginPage() {
292
303
  /* @__PURE__ */ jsx("input", { type: "checkbox", name: "remember", className: "accent-foreground" }),
293
304
  /* @__PURE__ */ jsx("span", { children: translate("auth.login.rememberMe", "Remember me") })
294
305
  ] }),
295
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled: submitting, className: "h-10 mt-2", children: submitting ? translate("auth.login.loading", "Loading...") : authOverride ? authOverride.providerLabel : translate("auth.signIn", "Sign in") }),
306
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: submitting || !formReady, className: "h-10 mt-2", children: submitting ? translate("auth.login.loading", "Loading...") : authOverride ? authOverride.providerLabel : translate("auth.signIn", "Sign in") }),
296
307
  !authOverride?.hideForgotPassword && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground mt-2", children: /* @__PURE__ */ jsx(Link, { className: "underline", href: "/reset", children: translate("auth.login.forgotPassword", "Forgot password?") }) })
297
308
  ] }) })
298
309
  ] }) });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/frontend/login.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n const message = translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null)\n clearAllOperations()\n if (data && data.redirect) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setError,\n }), [email, tenantId, searchParams])\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Notice compact className=\"text-center\">\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </Notice>\n )}\n {!!translatedFeatures.length && (\n <Notice compact className=\"text-center\">\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </Notice>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n aria-invalid={!!error}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required={!authOverride} aria-invalid={!!error} />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Notice } from '@open-mercato/ui/primitives/Notice'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [authOverridePending, setAuthOverridePending] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n const message = translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n if (!clientReady || authOverridePending) {\n return\n }\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null)\n clearAllOperations()\n if (data && data.redirect) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setAuthOverridePending,\n setError,\n }), [email, tenantId, searchParams])\n\n const formReady = clientReady && !authOverridePending\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Notice compact className=\"text-center\">\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </Notice>\n )}\n {!!translatedFeatures.length && (\n <Notice compact className=\"text-center\">\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </Notice>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n aria-invalid={!!error}\n onChange={(e) => setEmail(e.target.value)}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required={!authOverride} aria-invalid={!!error} />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting || !formReady} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": ";AA4QQ,SACE,KADF;AA3QR,SAAS,aAAa,WAAW,SAAS,gBAAgB;AAC1D,OAAO,WAAW;AAClB,OAAO,UAAU;AACjB,SAAS,WAAW,uBAAuB;AAC3C,SAAS,MAAM,aAAa,YAAY,uBAAuB;AAC/D,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC,SAAS,eAAe;AACxB,SAAS,SAAS;AAClB,SAAS,cAAc;AACvB,SAAS,qBAAqB;AAG9B,MAAM,iBAAiB;AACvB,MAAM,0BAA0B,KAAK,KAAK,KAAK;AAE/C,SAAS,mBAAmB;AAC1B,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,UAAU,SAAS,OAAO,MAAM,GAAG;AACzC,aAAW,SAAS,SAAS;AAC3B,UAAM,CAAC,MAAM,GAAG,IAAI,IAAI,MAAM,KAAK,EAAE,MAAM,GAAG;AAC9C,QAAI,SAAS,eAAgB,QAAO,mBAAmB,KAAK,KAAK,GAAG,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAe;AACtC,MAAI,OAAO,aAAa,YAAa;AACrC,WAAS,SAAS,GAAG,cAAc,IAAI,mBAAmB,KAAK,CAAC,qBAAqB,uBAAuB;AAC9G;AAEA,SAAS,oBAAoB;AAC3B,MAAI,OAAO,aAAa,YAAa;AACrC,WAAS,SAAS,GAAG,cAAc;AACrC;AAEA,SAAS,oBAAoB,SAAiC;AAC5D,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,oBAAoB,KAAK;AAC1C,UAAI,SAAU,QAAO;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAM,SAAS;AACf,UAAM,aAAwB;AAAA,MAC5B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AACA,eAAW,aAAa,YAAY;AAClC,YAAM,WAAW,oBAAoB,SAAS;AAC9C,UAAI,SAAU,QAAO;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,OAAwB;AACnD,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG;AAC1D;AAEe,SAAR,YAA6B;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,YAAY;AAAA,IAChB,CAAC,KAAa,UAAkB,WAC9B,sBAAsB,GAAG,KAAK,UAAU,MAAM;AAAA,IAChD,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AACrC,QAAM,eAAe,aAAa,IAAI,aAAa,KAAK,aAAa,IAAI,MAAM,KAAK,IAAI,KAAK;AAC7F,QAAM,kBAAkB,aAAa,IAAI,gBAAgB,KAAK,IAAI,KAAK;AACvE,QAAM,gBAAgB,cAAc,YAAY,MAAM,GAAG,EAAE,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI,CAAC;AAC3G,QAAM,mBAAmB,iBAAiB,eAAe,MAAM,GAAG,EAAE,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI,CAAC;AACpH,QAAM,kBAAkB,cAAc,IAAI,CAAC,SAAS,UAAU,cAAc,IAAI,IAAI,IAAI,CAAC;AACzF,QAAM,qBAAqB,iBAAiB,IAAI,CAAC,YAAY,UAAU,YAAY,OAAO,IAAI,OAAO,CAAC;AACtG,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,cAAc,eAAe,IAAI,SAA8B,IAAI;AAC1E,QAAM,CAAC,qBAAqB,sBAAsB,IAAI,SAAS,KAAK;AACpE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AACxD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAwB,IAAI;AACtE,QAAM,oBAAoB,YAAY,QAAQ,kBAAkB;AAEhE,YAAU,MAAM;AACd,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,UAAM,eAAe,aAAa,IAAI,QAAQ,KAAK,IAAI,KAAK;AAC5D,QAAI,aAAa;AACf,kBAAY,WAAW;AACvB,aAAO,aAAa,QAAQ,gBAAgB,WAAW;AACvD,sBAAgB,WAAW;AAC3B;AAAA,IACF;AACA,UAAM,eAAe,OAAO,aAAa,QAAQ,cAAc,KAAK,iBAAiB;AACrF,QAAI,cAAc;AAChB,kBAAY,YAAY;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,YAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb,oBAAc,IAAI;AAClB,uBAAiB,IAAI;AACrB;AAAA,IACF;AACA,QAAI,kBAAkB,UAAU;AAC9B,oBAAc,IAAI;AAClB,uBAAiB,KAAK;AACtB;AAAA,IACF;AACA,QAAI,SAAS;AACb,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AACrB;AAAA,MACE,0CAA0C,mBAAmB,QAAQ,CAAC;AAAA,IACxE,EACG,KAAK,CAAC,EAAE,OAAO,MAAM;AACpB,UAAI,CAAC,OAAQ;AACb,UAAI,QAAQ,MAAM,OAAO,QAAQ;AAC/B,sBAAc,OAAO,OAAO,IAAI;AAChC;AAAA,MACF;AACA,YAAM,UAAU,UAAU,mCAAmC,6DAA6D;AAC1H,oBAAc,IAAI;AAClB,uBAAiB,QAAQ;AACzB,eAAS,IAAI;AAAA,IACf,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,OAAQ;AACb,oBAAc,IAAI;AAClB,uBAAiB,QAAQ;AACzB,eAAS,IAAI;AAAA,IACf,CAAC,EACA,QAAQ,MAAM;AACb,UAAI,OAAQ,kBAAiB,KAAK;AAAA,IACpC,CAAC;AACH,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,SAAS,CAAC;AAExB,WAAS,oBAAoB;AAC3B,WAAO,aAAa,WAAW,cAAc;AAC7C,sBAAkB;AAClB,gBAAY,IAAI;AAChB,kBAAc,IAAI;AAClB,qBAAiB,IAAI;AACrB,UAAM,SAAS,IAAI,gBAAgB,YAAY;AAC/C,WAAO,OAAO,QAAQ;AACtB,aAAS,IAAI;AACb,UAAM,QAAQ,OAAO,SAAS;AAC9B,WAAO,QAAQ,QAAQ,UAAU,KAAK,KAAK,QAAQ;AAAA,EACrD;AAEA,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,QAAI,CAAC,eAAe,qBAAqB;AACvC;AAAA,IACF;AACA,aAAS,IAAI;AACb,QAAI,cAAc;AAChB,mBAAa,SAAS;AACtB;AAAA,IACF;AACA,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,OAAO,IAAI,SAAS,EAAE,aAAa;AACzC,UAAI,cAAc,OAAQ,MAAK,IAAI,eAAe,cAAc,KAAK,GAAG,CAAC;AACzE,YAAM,MAAM,MAAM,MAAM,mBAAmB,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACzE,UAAI,IAAI,YAAY;AAClB,2BAAmB;AAEnB,eAAO,QAAQ,IAAI,GAAG;AACtB;AAAA,MACF;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,YAAY,MAAM;AACtB,cAAI,IAAI,WAAW,KAAK;AACtB,mBAAO;AAAA,cACL;AAAA,cACA;AAAA,YACF;AAAA,UACF;AACA,cAAI,IAAI,WAAW,OAAO,IAAI,WAAW,KAAK;AAC5C,mBAAO,UAAU,wCAAwC,2BAA2B;AAAA,UACtF;AACA,iBAAO,UAAU,6BAA6B,sCAAsC;AAAA,QACtF,GAAG;AACH,cAAM,SAAS,IAAI,MAAM;AACzB,YAAI,eAAe;AACnB,cAAM,cAAc,IAAI,QAAQ,IAAI,cAAc,KAAK;AACvD,YAAI,YAAY,SAAS,kBAAkB,GAAG;AAC5C,cAAI;AACF,kBAAMA,QAAO,MAAM,IAAI,KAAK;AAC5B,2BAAe,oBAAoBA,KAAI,KAAK;AAAA,UAC9C,QAAQ;AACN,gBAAI;AACF,oBAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,oBAAM,UAAU,KAAK,KAAK;AAC1B,kBAAI,WAAW,CAAC,oBAAoB,OAAO,GAAG;AAC5C,+BAAe;AAAA,cACjB;AAAA,YACF,QAAQ;AACN,6BAAe;AAAA,YACjB;AAAA,UACF;AAAA,QACF,OAAO;AACL,cAAI;AACF,kBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,kBAAM,UAAU,KAAK,KAAK;AAC1B,gBAAI,WAAW,CAAC,oBAAoB,OAAO,GAAG;AAC5C,6BAAe;AAAA,YACjB;AAAA,UACF,QAAQ;AACN,2BAAe;AAAA,UACjB;AAAA,QACF;AACA,iBAAS,gBAAgB,QAAQ;AACjC;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC9C,yBAAmB;AACnB,UAAI,QAAQ,KAAK,UAAU;AACzB,eAAO,QAAQ,KAAK,QAAQ;AAAA,MAC9B;AAAA,IACF,SAAS,KAAc;AAErB,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,eAAS,WAAW,UAAU,6BAA6B,sCAAsC,CAAC;AAAA,IACpG,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,mBAAmB,QAAgC,OAAO;AAAA,IAC9D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,CAAC,OAAO,UAAU,YAAY,CAAC;AAEnC,QAAM,YAAY,eAAe,CAAC;AAElC,SACE,oBAAC,SAAI,WAAU,kDACb,+BAAC,QAAK,WAAU,mBACd;AAAA,yBAAC,cAAW,WAAU,qDACpB;AAAA,0BAAC,SAAM,KAAK,UAAU,sBAAsB,mBAAmB,GAAG,KAAI,qBAAoB,OAAO,KAAK,QAAQ,KAAK,UAAQ,MAAC;AAAA,MAC5H,oBAAC,QAAG,WAAU,0BAA0B,oBAAU,wBAAwB,cAAc,GAAE;AAAA,MAC1F,oBAAC,mBAAiB,oBAAU,uBAAuB,uBAAuB,GAAE;AAAA,OAC9E;AAAA,IACA,oBAAC,eACC,+BAAC,UAAK,WAAU,cAAa,UAAoB,YAAU,MAAC,mBAAiB,YAAY,MAAM,KAC5F;AAAA,iBACC,oBAAC,WAAM,MAAK,UAAS,MAAK,YAAW,OAAO,UAAU,IACpD;AAAA,MACH,CAAC,CAAC,gBAAgB,UACjB,oBAAC,UAAO,SAAO,MAAC,WAAU,eACvB;AAAA,QACC,gBAAgB,SAAS,IAAI,mCAAmC;AAAA,QAChE,gBAAgB,SAAS,IACrB,wDACA;AAAA,QACJ,EAAE,OAAO,gBAAgB,KAAK,IAAI,EAAE;AAAA,MACtC,GACF;AAAA,MAED,CAAC,CAAC,mBAAmB,UACpB,oBAAC,UAAO,SAAO,MAAC,WAAU,eACvB,oBAAU,4BAA4B,yFAAyF;AAAA,QAC9H,SAAS,mBAAmB,KAAK,IAAI;AAAA,MACvC,CAAC,GACH;AAAA,MAED,oBACC,qBAAC,SAAI,WAAU,yFACb;AAAA,4BAAC,SAAI,WAAU,eAAe,oBAAU,mCAAmC,6DAA6D,GAAE;AAAA,QAC1I,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,WAAU,oCAAmC,SAAS,mBACtG;AAAA,8BAAC,KAAE,WAAU,eAAc,eAAY,QAAO;AAAA,UAC7C,UAAU,0BAA0B,OAAO;AAAA,WAC9C;AAAA,SACF,IACE,WACF,qBAAC,SAAI,WAAU,qGACb;AAAA,4BAAC,SAAI,WAAU,eACZ,0BACG,UAAU,4BAA4B,2BAA2B,IACjE,UAAU,2BAA2B,yCAAyC;AAAA,UAC5E,QAAQ,cAAc;AAAA,QACxB,CAAC,GACP;AAAA,QACA,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,WAAU,4CAA2C,SAAS,mBAC9G;AAAA,8BAAC,KAAE,WAAU,eAAc,eAAY,QAAO;AAAA,UAC7C,UAAU,0BAA0B,OAAO;AAAA,WAC9C;AAAA,SACF,IACE;AAAA,MACH,SAAS,CAAC,qBACT,oBAAC,SAAI,WAAU,yFAAwF,MAAK,SAAQ,aAAU,UAC3H,iBACH;AAAA,MAEF,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,SAAS,YAAE,YAAY,GAAE;AAAA,QACxC;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,gBAAc,CAAC,CAAC;AAAA,YAChB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,QAAQ,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA;AAAA,QACxC;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,QAAO;AAAA,UACP,SAAS;AAAA;AAAA,MACX;AAAA,MACC,cAAc,eAAe,OAC5B,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,YAAY,YAAE,eAAe,GAAE;AAAA,QAC9C,oBAAC,SAAM,IAAG,YAAW,MAAK,YAAW,MAAK,YAAW,UAAU,CAAC,cAAc,gBAAc,CAAC,CAAC,OAAO;AAAA,SACvG;AAAA,MAED,CAAC,cAAc,kBAAkB,CAAC,cAAc,gBAC/C,qBAAC,WAAM,WAAU,yDACf;AAAA,4BAAC,WAAM,MAAK,YAAW,MAAK,YAAW,WAAU,qBAAoB;AAAA,QACrE,oBAAC,UAAM,oBAAU,yBAAyB,aAAa,GAAE;AAAA,SAC3D;AAAA,MAEF,oBAAC,UAAO,MAAK,UAAS,UAAU,cAAc,CAAC,WAAW,WAAU,aACjE,uBACG,UAAU,sBAAsB,YAAY,IAC5C,eACE,aAAa,gBACb,UAAU,eAAe,SAAS,GAC1C;AAAA,MACC,CAAC,cAAc,sBACd,oBAAC,SAAI,WAAU,sCACb,8BAAC,QAAK,WAAU,aAAY,MAAK,UAC9B,oBAAU,6BAA6B,kBAAkB,GAC5D,GACF;AAAA,OAEJ,GACF;AAAA,KACF,GACF;AAEJ;",
6
6
  "names": ["data"]
7
7
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import * as React from "react";
3
4
  import { TagsInput } from "@open-mercato/ui/backend/inputs/TagsInput";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/catalog/components/products/ProductCategorizeSection.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { ProductFormValues } from './productForm'\n\nexport type ProductCategorizePickerOption = {\n value: string\n label: string\n description?: string | null\n}\n\nconst formatCategoryLabel = (name: string | null | undefined, fallback: string, parentName?: string | null) => {\n const base = typeof name === 'string' && name.trim().length ? name.trim() : fallback\n const parent = typeof parentName === 'string' && parentName.trim().length ? parentName.trim() : null\n return parent ? `${base} / ${parent}` : base\n}\n\ntype ProductCategorizeSectionProps = {\n values: ProductFormValues\n setValue: (id: string, value: unknown) => void\n errors: Record<string, string>\n initialCategoryOptions?: ProductCategorizePickerOption[]\n initialChannelOptions?: ProductCategorizePickerOption[]\n initialTagOptions?: ProductCategorizePickerOption[]\n}\n\nexport function ProductCategorizeSection({\n values,\n setValue,\n errors,\n initialCategoryOptions,\n initialChannelOptions,\n initialTagOptions,\n}: ProductCategorizeSectionProps) {\n const t = useT()\n const [categoryOptionsMap, setCategoryOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n const [channelOptionsMap, setChannelOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n const [tagOptionsMap, setTagOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n\n const registerPickerOptions = React.useCallback(\n (\n setter: React.Dispatch<React.SetStateAction<Record<string, ProductCategorizePickerOption>>>,\n options: ProductCategorizePickerOption[],\n ) => {\n setter((prev) => {\n const next = { ...prev }\n options.forEach((option) => {\n if (option.value) next[option.value] = option\n })\n return next\n })\n },\n [],\n )\n\n const categorySuggestions = React.useMemo(() => Object.values(categoryOptionsMap), [categoryOptionsMap])\n const channelSuggestions = React.useMemo(() => Object.values(channelOptionsMap), [channelOptionsMap])\n const tagSuggestions = React.useMemo(() => Object.values(tagOptionsMap), [tagOptionsMap])\n\n React.useEffect(() => {\n if (initialCategoryOptions?.length) {\n registerPickerOptions(setCategoryOptionsMap, initialCategoryOptions)\n }\n }, [initialCategoryOptions, registerPickerOptions])\n\n React.useEffect(() => {\n if (initialChannelOptions?.length) {\n registerPickerOptions(setChannelOptionsMap, initialChannelOptions)\n }\n }, [initialChannelOptions, registerPickerOptions])\n\n React.useEffect(() => {\n if (initialTagOptions?.length) {\n registerPickerOptions(setTagOptionsMap, initialTagOptions)\n }\n }, [initialTagOptions, registerPickerOptions])\n\n const resolveCategoryLabel = React.useCallback(\n (id: string) => categoryOptionsMap[id]?.label ?? id,\n [categoryOptionsMap],\n )\n const resolveCategoryDescription = React.useCallback(\n (id: string) => categoryOptionsMap[id]?.description ?? null,\n [categoryOptionsMap],\n )\n const resolveChannelLabel = React.useCallback(\n (id: string) => channelOptionsMap[id]?.label ?? id,\n [channelOptionsMap],\n )\n const resolveChannelDescription = React.useCallback(\n (id: string) => channelOptionsMap[id]?.description ?? null,\n [channelOptionsMap],\n )\n const resolveTagLabel = React.useCallback((id: string) => tagOptionsMap[id]?.label ?? id, [tagOptionsMap])\n\n const loadCategorySuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '200', view: 'manage' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; parentName?: string | null }> }>(\n `/api/catalog/categories?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.categoriesLoadError', 'Failed to load categories') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const parentName =\n typeof entry.parentName === 'string' && entry.parentName.trim().length ? entry.parentName : null\n const label = formatCategoryLabel(\n typeof entry.name === 'string' ? entry.name : null,\n value,\n parentName,\n )\n const description =\n parentName && !label.toLowerCase().includes(parentName.toLowerCase()) ? parentName : null\n return { value, label, description }\n })\n .filter(\n (\n option: { value: string; label: string; description: string | null } | null,\n ): option is { value: string; label: string; description: string | null } => !!option,\n )\n registerPickerOptions(setCategoryOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n const loadChannelSuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '100', isActive: 'true' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; code?: string }> }>(\n `/api/sales/channels?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.channelsLoadError', 'Failed to load channels') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label =\n typeof entry.name === 'string' && entry.name.trim().length\n ? entry.name\n : typeof entry.code === 'string' && entry.code.trim().length\n ? entry.code\n : value\n const description = typeof entry.code === 'string' && entry.code.trim().length ? entry.code : null\n return { value, label, description }\n })\n .filter(\n (\n option: { value: string; label: string; description: string | null } | null,\n ): option is { value: string; label: string; description: string | null } => !!option,\n )\n registerPickerOptions(setChannelOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n const loadTagSuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ label?: string }> }>(\n `/api/catalog/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.tagsLoadError', 'Failed to load tags') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const rawLabel = typeof entry.label === 'string' ? entry.label.trim() : ''\n if (!rawLabel) return null\n return { value: rawLabel, label: rawLabel }\n })\n .filter(\n (option: { value: string; label: string } | null): option is { value: string; label: string } => !!option,\n )\n registerPickerOptions(setTagOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n return (\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.categoriesLabel', 'Categories')}</Label>\n <TagsInput\n value={Array.isArray(values.categoryIds) ? values.categoryIds : []}\n onChange={(next) => setValue('categoryIds', next)}\n suggestions={categorySuggestions}\n loadSuggestions={loadCategorySuggestions}\n allowCustomValues={false}\n resolveLabel={resolveCategoryLabel}\n resolveDescription={resolveCategoryDescription}\n placeholder={t('catalog.products.create.organize.categoriesPlaceholder', 'Search categories')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.categoriesHelp', 'Assign products to one or more taxonomy nodes.')}\n </p>\n {errors.categoryIds ? <p className=\"text-xs text-red-600\">{errors.categoryIds}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.channelsLabel', 'Sales channels')}</Label>\n <TagsInput\n value={Array.isArray(values.channelIds) ? values.channelIds : []}\n onChange={(next) => setValue('channelIds', next)}\n suggestions={channelSuggestions}\n loadSuggestions={loadChannelSuggestions}\n allowCustomValues={false}\n resolveLabel={resolveChannelLabel}\n resolveDescription={resolveChannelDescription}\n placeholder={t('catalog.products.create.organize.channelsPlaceholder', 'Pick channels')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.channelsHelp', 'Selected channels will receive default offers for this product.')}\n </p>\n {errors.channelIds ? <p className=\"text-xs text-red-600\">{errors.channelIds}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.tagsLabel', 'Tags')}</Label>\n <TagsInput\n value={Array.isArray(values.tags) ? values.tags : []}\n onChange={(next) => setValue('tags', next)}\n suggestions={tagSuggestions}\n loadSuggestions={loadTagSuggestions}\n resolveLabel={resolveTagLabel}\n placeholder={t('catalog.products.create.organize.tagsPlaceholder', 'Add tag and press Enter')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.tagsHelp', 'Describe products with shared labels to build quick filters.')}\n </p>\n {errors.tags ? <p className=\"text-xs text-red-600\">{errors.tags}</p> : null}\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { ProductFormValues } from './productForm'\n\nexport type ProductCategorizePickerOption = {\n value: string\n label: string\n description?: string | null\n}\n\nconst formatCategoryLabel = (name: string | null | undefined, fallback: string, parentName?: string | null) => {\n const base = typeof name === 'string' && name.trim().length ? name.trim() : fallback\n const parent = typeof parentName === 'string' && parentName.trim().length ? parentName.trim() : null\n return parent ? `${base} / ${parent}` : base\n}\n\ntype ProductCategorizeSectionProps = {\n values: ProductFormValues\n setValue: (id: string, value: unknown) => void\n errors: Record<string, string>\n initialCategoryOptions?: ProductCategorizePickerOption[]\n initialChannelOptions?: ProductCategorizePickerOption[]\n initialTagOptions?: ProductCategorizePickerOption[]\n}\n\nexport function ProductCategorizeSection({\n values,\n setValue,\n errors,\n initialCategoryOptions,\n initialChannelOptions,\n initialTagOptions,\n}: ProductCategorizeSectionProps) {\n const t = useT()\n const [categoryOptionsMap, setCategoryOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n const [channelOptionsMap, setChannelOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n const [tagOptionsMap, setTagOptionsMap] = React.useState<Record<string, ProductCategorizePickerOption>>({})\n\n const registerPickerOptions = React.useCallback(\n (\n setter: React.Dispatch<React.SetStateAction<Record<string, ProductCategorizePickerOption>>>,\n options: ProductCategorizePickerOption[],\n ) => {\n setter((prev) => {\n const next = { ...prev }\n options.forEach((option) => {\n if (option.value) next[option.value] = option\n })\n return next\n })\n },\n [],\n )\n\n const categorySuggestions = React.useMemo(() => Object.values(categoryOptionsMap), [categoryOptionsMap])\n const channelSuggestions = React.useMemo(() => Object.values(channelOptionsMap), [channelOptionsMap])\n const tagSuggestions = React.useMemo(() => Object.values(tagOptionsMap), [tagOptionsMap])\n\n React.useEffect(() => {\n if (initialCategoryOptions?.length) {\n registerPickerOptions(setCategoryOptionsMap, initialCategoryOptions)\n }\n }, [initialCategoryOptions, registerPickerOptions])\n\n React.useEffect(() => {\n if (initialChannelOptions?.length) {\n registerPickerOptions(setChannelOptionsMap, initialChannelOptions)\n }\n }, [initialChannelOptions, registerPickerOptions])\n\n React.useEffect(() => {\n if (initialTagOptions?.length) {\n registerPickerOptions(setTagOptionsMap, initialTagOptions)\n }\n }, [initialTagOptions, registerPickerOptions])\n\n const resolveCategoryLabel = React.useCallback(\n (id: string) => categoryOptionsMap[id]?.label ?? id,\n [categoryOptionsMap],\n )\n const resolveCategoryDescription = React.useCallback(\n (id: string) => categoryOptionsMap[id]?.description ?? null,\n [categoryOptionsMap],\n )\n const resolveChannelLabel = React.useCallback(\n (id: string) => channelOptionsMap[id]?.label ?? id,\n [channelOptionsMap],\n )\n const resolveChannelDescription = React.useCallback(\n (id: string) => channelOptionsMap[id]?.description ?? null,\n [channelOptionsMap],\n )\n const resolveTagLabel = React.useCallback((id: string) => tagOptionsMap[id]?.label ?? id, [tagOptionsMap])\n\n const loadCategorySuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '200', view: 'manage' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; parentName?: string | null }> }>(\n `/api/catalog/categories?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.categoriesLoadError', 'Failed to load categories') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const parentName =\n typeof entry.parentName === 'string' && entry.parentName.trim().length ? entry.parentName : null\n const label = formatCategoryLabel(\n typeof entry.name === 'string' ? entry.name : null,\n value,\n parentName,\n )\n const description =\n parentName && !label.toLowerCase().includes(parentName.toLowerCase()) ? parentName : null\n return { value, label, description }\n })\n .filter(\n (\n option: { value: string; label: string; description: string | null } | null,\n ): option is { value: string; label: string; description: string | null } => !!option,\n )\n registerPickerOptions(setCategoryOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n const loadChannelSuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '100', isActive: 'true' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ id?: string; name?: string; code?: string }> }>(\n `/api/sales/channels?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.channelsLoadError', 'Failed to load channels') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const value = typeof entry.id === 'string' ? entry.id : null\n if (!value) return null\n const label =\n typeof entry.name === 'string' && entry.name.trim().length\n ? entry.name\n : typeof entry.code === 'string' && entry.code.trim().length\n ? entry.code\n : value\n const description = typeof entry.code === 'string' && entry.code.trim().length ? entry.code : null\n return { value, label, description }\n })\n .filter(\n (\n option: { value: string; label: string; description: string | null } | null,\n ): option is { value: string; label: string; description: string | null } => !!option,\n )\n registerPickerOptions(setChannelOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n const loadTagSuggestions = React.useCallback(\n async (term?: string) => {\n try {\n const params = new URLSearchParams({ pageSize: '100' })\n if (term && term.trim().length) params.set('search', term.trim())\n const payload = await readApiResultOrThrow<{ items?: Array<{ label?: string }> }>(\n `/api/catalog/tags?${params.toString()}`,\n undefined,\n { errorMessage: t('catalog.products.filters.tagsLoadError', 'Failed to load tags') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const options = items\n .map((entry) => {\n const rawLabel = typeof entry.label === 'string' ? entry.label.trim() : ''\n if (!rawLabel) return null\n return { value: rawLabel, label: rawLabel }\n })\n .filter(\n (option: { value: string; label: string } | null): option is { value: string; label: string } => !!option,\n )\n registerPickerOptions(setTagOptionsMap, options)\n return options\n } catch {\n return []\n }\n },\n [registerPickerOptions, t],\n )\n\n return (\n <div className=\"space-y-6\">\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.categoriesLabel', 'Categories')}</Label>\n <TagsInput\n value={Array.isArray(values.categoryIds) ? values.categoryIds : []}\n onChange={(next) => setValue('categoryIds', next)}\n suggestions={categorySuggestions}\n loadSuggestions={loadCategorySuggestions}\n allowCustomValues={false}\n resolveLabel={resolveCategoryLabel}\n resolveDescription={resolveCategoryDescription}\n placeholder={t('catalog.products.create.organize.categoriesPlaceholder', 'Search categories')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.categoriesHelp', 'Assign products to one or more taxonomy nodes.')}\n </p>\n {errors.categoryIds ? <p className=\"text-xs text-red-600\">{errors.categoryIds}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.channelsLabel', 'Sales channels')}</Label>\n <TagsInput\n value={Array.isArray(values.channelIds) ? values.channelIds : []}\n onChange={(next) => setValue('channelIds', next)}\n suggestions={channelSuggestions}\n loadSuggestions={loadChannelSuggestions}\n allowCustomValues={false}\n resolveLabel={resolveChannelLabel}\n resolveDescription={resolveChannelDescription}\n placeholder={t('catalog.products.create.organize.channelsPlaceholder', 'Pick channels')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.channelsHelp', 'Selected channels will receive default offers for this product.')}\n </p>\n {errors.channelIds ? <p className=\"text-xs text-red-600\">{errors.channelIds}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <Label>{t('catalog.products.create.organize.tagsLabel', 'Tags')}</Label>\n <TagsInput\n value={Array.isArray(values.tags) ? values.tags : []}\n onChange={(next) => setValue('tags', next)}\n suggestions={tagSuggestions}\n loadSuggestions={loadTagSuggestions}\n resolveLabel={resolveTagLabel}\n placeholder={t('catalog.products.create.organize.tagsPlaceholder', 'Add tag and press Enter')}\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('catalog.products.create.organize.tagsHelp', 'Describe products with shared labels to build quick filters.')}\n </p>\n {errors.tags ? <p className=\"text-xs text-red-600\">{errors.tags}</p> : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -126,7 +126,10 @@ function createSyncEngine(deps) {
126
126
  return {
127
127
  async runImport(runId, batchSize, scope) {
128
128
  const run = await syncRunService.getRun(runId, scope);
129
- if (!run) throw new Error(`Sync run ${runId} not found`);
129
+ if (!run) {
130
+ console.warn(`[data-sync] Skipping stale import job for missing run ${runId}`);
131
+ return;
132
+ }
130
133
  const providerKey = resolveProviderKey(run.integrationId);
131
134
  const adapter = getDataSyncAdapter(providerKey);
132
135
  if (!adapter?.streamImport) {
@@ -214,7 +217,10 @@ function createSyncEngine(deps) {
214
217
  },
215
218
  async runExport(runId, batchSize, scope) {
216
219
  const run = await syncRunService.getRun(runId, scope);
217
- if (!run) throw new Error(`Sync run ${runId} not found`);
220
+ if (!run) {
221
+ console.warn(`[data-sync] Skipping stale export job for missing run ${runId}`);
222
+ return;
223
+ }
218
224
  const providerKey = resolveProviderKey(run.integrationId);
219
225
  const adapter = getDataSyncAdapter(providerKey);
220
226
  if (!adapter?.streamExport) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/lib/sync-engine.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport type { IntegrationLogService } from '../../integrations/lib/log-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport { emitDataSyncEvent } from '../events'\nimport type { DataSyncAdapter, DataMapping, ExportBatch, ImportBatch } from './adapter'\nimport { getDataSyncAdapter } from './adapter-registry'\nimport type { SyncRunService } from './sync-run-service'\n\ntype SyncScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\ntype EngineDeps = {\n em: EntityManager\n syncRunService: SyncRunService\n integrationCredentialsService: CredentialsService\n integrationLogService: IntegrationLogService\n progressService: ProgressService\n}\n\nfunction resolveProviderKey(integrationId: string): string {\n return getIntegration(integrationId)?.providerKey ?? integrationId\n}\n\nfunction applyImportCounters(batch: ImportBatch): Pick<Required<SyncCounterDelta>, 'createdCount' | 'updatedCount' | 'skippedCount' | 'failedCount'> {\n let createdCount = 0\n let updatedCount = 0\n let skippedCount = 0\n let failedCount = 0\n\n for (const item of batch.items) {\n if (item.action === 'create') createdCount += 1\n else if (item.action === 'update') updatedCount += 1\n else skippedCount += 1\n }\n\n return { createdCount, updatedCount, skippedCount, failedCount }\n}\n\ntype SyncCounterDelta = {\n createdCount?: number\n updatedCount?: number\n skippedCount?: number\n failedCount?: number\n processedCount: number\n}\n\nfunction applyExportCounters(batch: ExportBatch): SyncCounterDelta {\n let failedCount = 0\n let skippedCount = 0\n let updatedCount = 0\n\n for (const result of batch.results) {\n if (result.status === 'error') failedCount += 1\n else if (result.status === 'skipped') skippedCount += 1\n else updatedCount += 1\n }\n\n return {\n failedCount,\n skippedCount,\n updatedCount,\n processedCount: batch.results.length,\n }\n}\n\nexport function createSyncEngine(deps: EngineDeps) {\n const { syncRunService, integrationCredentialsService, integrationLogService, progressService } = deps\n\n async function resolveMapping(adapter: DataSyncAdapter, entityType: string, scope: SyncScope): Promise<DataMapping> {\n return adapter.getMapping({\n entityType,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })\n }\n\n async function updateProgress(progressJobId: string | null | undefined, processedCount: number, totalCount: number | null, scope: SyncScope): Promise<void> {\n if (!progressJobId) return\n\n await progressService.updateProgress(\n progressJobId,\n {\n processedCount,\n totalCount: totalCount ?? undefined,\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n }\n\n async function finalizeRun(runId: string, status: 'completed' | 'failed' | 'cancelled', scope: SyncScope, error?: string): Promise<void> {\n const run = await syncRunService.markStatus(runId, status, scope, error)\n if (!run) return\n\n if (run.progressJobId) {\n if (status === 'completed') {\n await progressService.completeJob(\n run.progressJobId,\n {\n resultSummary: {\n createdCount: run.createdCount,\n updatedCount: run.updatedCount,\n skippedCount: run.skippedCount,\n failedCount: run.failedCount,\n batchesCompleted: run.batchesCompleted,\n },\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n } else if (status === 'failed') {\n await progressService.failJob(\n run.progressJobId,\n {\n errorMessage: error ?? 'Sync run failed',\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n }\n }\n\n if (status === 'completed') {\n await emitDataSyncEvent('data_sync.run.completed', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return\n }\n\n if (status === 'cancelled') {\n await emitDataSyncEvent('data_sync.run.cancelled', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return\n }\n\n await emitDataSyncEvent('data_sync.run.failed', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n error: error ?? null,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n }\n\n return {\n async runImport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {\n const run = await syncRunService.getRun(runId, scope)\n if (!run) throw new Error(`Sync run ${runId} not found`)\n\n const providerKey = resolveProviderKey(run.integrationId)\n const adapter = getDataSyncAdapter(providerKey)\n if (!adapter?.streamImport) {\n throw new Error(`No import adapter registered for provider ${providerKey}`)\n }\n\n const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)\n if (!credentials) {\n throw new Error(`Integration ${run.integrationId} is missing credentials`)\n }\n\n await syncRunService.markStatus(run.id, 'running', scope)\n await emitDataSyncEvent('data_sync.run.started', {\n runId: run.id,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (run.progressJobId) {\n await progressService.startJob(run.progressJobId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n })\n }\n\n const mapping = await resolveMapping(adapter, run.entityType, scope)\n let processedCount = 0\n let totalCount: number | null = null\n\n try {\n for await (const batch of adapter.streamImport({\n entityType: run.entityType,\n cursor: run.cursor ?? undefined,\n batchSize,\n credentials,\n mapping,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })) {\n if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId)) {\n await finalizeRun(run.id, 'cancelled', scope)\n return\n }\n\n const delta = applyImportCounters(batch)\n processedCount += batch.items.length\n totalCount = batch.totalEstimate ?? totalCount\n\n await syncRunService.updateCounts(\n run.id,\n {\n ...delta,\n batchesCompleted: 1,\n },\n scope,\n )\n await syncRunService.updateCursor(run.id, batch.cursor, scope)\n\n await updateProgress(run.progressJobId, processedCount, totalCount, scope)\n\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'info',\n message: `Processed import batch ${batch.batchIndex}`,\n payload: {\n processedCount,\n batchSize: batch.items.length,\n cursor: batch.cursor,\n },\n },\n scope,\n )\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Sync import failed'\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'error',\n message,\n },\n scope,\n )\n await finalizeRun(run.id, 'failed', scope, message)\n return\n }\n\n await finalizeRun(run.id, 'completed', scope)\n },\n\n async runExport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {\n const run = await syncRunService.getRun(runId, scope)\n if (!run) throw new Error(`Sync run ${runId} not found`)\n\n const providerKey = resolveProviderKey(run.integrationId)\n const adapter = getDataSyncAdapter(providerKey)\n if (!adapter?.streamExport) {\n throw new Error(`No export adapter registered for provider ${providerKey}`)\n }\n\n const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)\n if (!credentials) {\n throw new Error(`Integration ${run.integrationId} is missing credentials`)\n }\n\n await syncRunService.markStatus(run.id, 'running', scope)\n await emitDataSyncEvent('data_sync.run.started', {\n runId: run.id,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (run.progressJobId) {\n await progressService.startJob(run.progressJobId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n })\n }\n\n const mapping = await resolveMapping(adapter, run.entityType, scope)\n let processedCount = 0\n\n try {\n for await (const batch of adapter.streamExport({\n entityType: run.entityType,\n cursor: run.cursor ?? undefined,\n batchSize,\n credentials,\n mapping,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })) {\n if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId)) {\n await finalizeRun(run.id, 'cancelled', scope)\n return\n }\n\n const delta = applyExportCounters(batch)\n processedCount += delta.processedCount\n\n await syncRunService.updateCounts(\n run.id,\n {\n createdCount: 0,\n updatedCount: delta.updatedCount,\n skippedCount: delta.skippedCount,\n failedCount: delta.failedCount,\n batchesCompleted: 1,\n },\n scope,\n )\n\n await syncRunService.updateCursor(run.id, batch.cursor, scope)\n await updateProgress(run.progressJobId, processedCount, null, scope)\n\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'info',\n message: `Processed export batch ${batch.batchIndex}`,\n payload: {\n processedCount,\n batchSize: batch.results.length,\n cursor: batch.cursor,\n },\n },\n scope,\n )\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Sync export failed'\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'error',\n message,\n },\n scope,\n )\n await finalizeRun(run.id, 'failed', scope, message)\n return\n }\n\n await finalizeRun(run.id, 'completed', scope)\n },\n }\n}\n\nexport type SyncEngine = ReturnType<typeof createSyncEngine>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport type { IntegrationLogService } from '../../integrations/lib/log-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport { emitDataSyncEvent } from '../events'\nimport type { DataSyncAdapter, DataMapping, ExportBatch, ImportBatch } from './adapter'\nimport { getDataSyncAdapter } from './adapter-registry'\nimport type { SyncRunService } from './sync-run-service'\n\ntype SyncScope = {\n organizationId: string\n tenantId: string\n userId?: string | null\n}\n\ntype EngineDeps = {\n em: EntityManager\n syncRunService: SyncRunService\n integrationCredentialsService: CredentialsService\n integrationLogService: IntegrationLogService\n progressService: ProgressService\n}\n\nfunction resolveProviderKey(integrationId: string): string {\n return getIntegration(integrationId)?.providerKey ?? integrationId\n}\n\nfunction applyImportCounters(batch: ImportBatch): Pick<Required<SyncCounterDelta>, 'createdCount' | 'updatedCount' | 'skippedCount' | 'failedCount'> {\n let createdCount = 0\n let updatedCount = 0\n let skippedCount = 0\n let failedCount = 0\n\n for (const item of batch.items) {\n if (item.action === 'create') createdCount += 1\n else if (item.action === 'update') updatedCount += 1\n else skippedCount += 1\n }\n\n return { createdCount, updatedCount, skippedCount, failedCount }\n}\n\ntype SyncCounterDelta = {\n createdCount?: number\n updatedCount?: number\n skippedCount?: number\n failedCount?: number\n processedCount: number\n}\n\nfunction applyExportCounters(batch: ExportBatch): SyncCounterDelta {\n let failedCount = 0\n let skippedCount = 0\n let updatedCount = 0\n\n for (const result of batch.results) {\n if (result.status === 'error') failedCount += 1\n else if (result.status === 'skipped') skippedCount += 1\n else updatedCount += 1\n }\n\n return {\n failedCount,\n skippedCount,\n updatedCount,\n processedCount: batch.results.length,\n }\n}\n\nexport function createSyncEngine(deps: EngineDeps) {\n const { syncRunService, integrationCredentialsService, integrationLogService, progressService } = deps\n\n async function resolveMapping(adapter: DataSyncAdapter, entityType: string, scope: SyncScope): Promise<DataMapping> {\n return adapter.getMapping({\n entityType,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })\n }\n\n async function updateProgress(progressJobId: string | null | undefined, processedCount: number, totalCount: number | null, scope: SyncScope): Promise<void> {\n if (!progressJobId) return\n\n await progressService.updateProgress(\n progressJobId,\n {\n processedCount,\n totalCount: totalCount ?? undefined,\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n }\n\n async function finalizeRun(runId: string, status: 'completed' | 'failed' | 'cancelled', scope: SyncScope, error?: string): Promise<void> {\n const run = await syncRunService.markStatus(runId, status, scope, error)\n if (!run) return\n\n if (run.progressJobId) {\n if (status === 'completed') {\n await progressService.completeJob(\n run.progressJobId,\n {\n resultSummary: {\n createdCount: run.createdCount,\n updatedCount: run.updatedCount,\n skippedCount: run.skippedCount,\n failedCount: run.failedCount,\n batchesCompleted: run.batchesCompleted,\n },\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n } else if (status === 'failed') {\n await progressService.failJob(\n run.progressJobId,\n {\n errorMessage: error ?? 'Sync run failed',\n },\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n },\n )\n }\n }\n\n if (status === 'completed') {\n await emitDataSyncEvent('data_sync.run.completed', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return\n }\n\n if (status === 'cancelled') {\n await emitDataSyncEvent('data_sync.run.cancelled', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return\n }\n\n await emitDataSyncEvent('data_sync.run.failed', {\n runId,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n error: error ?? null,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n }\n\n return {\n async runImport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {\n const run = await syncRunService.getRun(runId, scope)\n if (!run) {\n console.warn(`[data-sync] Skipping stale import job for missing run ${runId}`)\n return\n }\n\n const providerKey = resolveProviderKey(run.integrationId)\n const adapter = getDataSyncAdapter(providerKey)\n if (!adapter?.streamImport) {\n throw new Error(`No import adapter registered for provider ${providerKey}`)\n }\n\n const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)\n if (!credentials) {\n throw new Error(`Integration ${run.integrationId} is missing credentials`)\n }\n\n await syncRunService.markStatus(run.id, 'running', scope)\n await emitDataSyncEvent('data_sync.run.started', {\n runId: run.id,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (run.progressJobId) {\n await progressService.startJob(run.progressJobId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n })\n }\n\n const mapping = await resolveMapping(adapter, run.entityType, scope)\n let processedCount = 0\n let totalCount: number | null = null\n\n try {\n for await (const batch of adapter.streamImport({\n entityType: run.entityType,\n cursor: run.cursor ?? undefined,\n batchSize,\n credentials,\n mapping,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })) {\n if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId)) {\n await finalizeRun(run.id, 'cancelled', scope)\n return\n }\n\n const delta = applyImportCounters(batch)\n processedCount += batch.items.length\n totalCount = batch.totalEstimate ?? totalCount\n\n await syncRunService.updateCounts(\n run.id,\n {\n ...delta,\n batchesCompleted: 1,\n },\n scope,\n )\n await syncRunService.updateCursor(run.id, batch.cursor, scope)\n\n await updateProgress(run.progressJobId, processedCount, totalCount, scope)\n\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'info',\n message: `Processed import batch ${batch.batchIndex}`,\n payload: {\n processedCount,\n batchSize: batch.items.length,\n cursor: batch.cursor,\n },\n },\n scope,\n )\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Sync import failed'\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'error',\n message,\n },\n scope,\n )\n await finalizeRun(run.id, 'failed', scope, message)\n return\n }\n\n await finalizeRun(run.id, 'completed', scope)\n },\n\n async runExport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {\n const run = await syncRunService.getRun(runId, scope)\n if (!run) {\n console.warn(`[data-sync] Skipping stale export job for missing run ${runId}`)\n return\n }\n\n const providerKey = resolveProviderKey(run.integrationId)\n const adapter = getDataSyncAdapter(providerKey)\n if (!adapter?.streamExport) {\n throw new Error(`No export adapter registered for provider ${providerKey}`)\n }\n\n const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)\n if (!credentials) {\n throw new Error(`Integration ${run.integrationId} is missing credentials`)\n }\n\n await syncRunService.markStatus(run.id, 'running', scope)\n await emitDataSyncEvent('data_sync.run.started', {\n runId: run.id,\n integrationId: run.integrationId,\n entityType: run.entityType,\n direction: run.direction,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (run.progressJobId) {\n await progressService.startJob(run.progressJobId, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: scope.userId,\n })\n }\n\n const mapping = await resolveMapping(adapter, run.entityType, scope)\n let processedCount = 0\n\n try {\n for await (const batch of adapter.streamExport({\n entityType: run.entityType,\n cursor: run.cursor ?? undefined,\n batchSize,\n credentials,\n mapping,\n scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },\n })) {\n if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId)) {\n await finalizeRun(run.id, 'cancelled', scope)\n return\n }\n\n const delta = applyExportCounters(batch)\n processedCount += delta.processedCount\n\n await syncRunService.updateCounts(\n run.id,\n {\n createdCount: 0,\n updatedCount: delta.updatedCount,\n skippedCount: delta.skippedCount,\n failedCount: delta.failedCount,\n batchesCompleted: 1,\n },\n scope,\n )\n\n await syncRunService.updateCursor(run.id, batch.cursor, scope)\n await updateProgress(run.progressJobId, processedCount, null, scope)\n\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'info',\n message: `Processed export batch ${batch.batchIndex}`,\n payload: {\n processedCount,\n batchSize: batch.results.length,\n cursor: batch.cursor,\n },\n },\n scope,\n )\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Sync export failed'\n await integrationLogService.write(\n {\n integrationId: run.integrationId,\n runId: run.id,\n level: 'error',\n message,\n },\n scope,\n )\n await finalizeRun(run.id, 'failed', scope, message)\n return\n }\n\n await finalizeRun(run.id, 'completed', scope)\n },\n }\n}\n\nexport type SyncEngine = ReturnType<typeof createSyncEngine>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -170,7 +170,7 @@ function DictionaryEntrySelect({
170
170
  "select",
171
171
  {
172
172
  className: [
173
- "h-9 w-full rounded border px-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-70",
173
+ "h-9 w-full rounded border pl-3 pr-8 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-70",
174
174
  selectClassName
175
175
  ].filter(Boolean).join(" "),
176
176
  value: value ?? "",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dictionaries/components/DictionaryEntrySelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Plus, Settings, Save } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { DictionaryValue, renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\nimport { AppearanceSelector, type AppearanceSelectorLabels, useAppearanceState } from './AppearanceSelector'\n\nconst DEFAULT_APPEARANCE_LABELS: AppearanceSelectorLabels = {\n colorLabel: 'Color',\n colorHelp: 'Pick a highlight color for this entry.',\n colorClearLabel: 'Remove color',\n iconLabel: 'Icon or emoji',\n iconPlaceholder: 'Type an emoji or icon token.',\n iconPickerTriggerLabel: 'Browse icons and emoji',\n iconSearchPlaceholder: 'Search icons or emojis\u2026',\n iconSearchEmptyLabel: 'No icons match your search.',\n iconSuggestionsLabel: 'Suggestions',\n iconClearLabel: 'Remove icon',\n previewEmptyLabel: 'No appearance selected',\n}\n\nexport type DictionaryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\nexport type DictionarySelectLabels = {\n placeholder: string\n addLabel: string\n addPrompt?: string\n dialogTitle: string\n valueLabel: string\n valuePlaceholder: string\n labelLabel: string\n labelPlaceholder: string\n emptyError: string\n cancelLabel: string\n saveLabel: string\n saveShortcutHint?: string\n successCreateLabel?: string\n errorLoad: string\n errorSave: string\n loadingLabel: string\n manageTitle: string\n}\n\nexport type DictionaryEntrySelectProps = {\n value?: string\n onChange: (value: string | undefined) => void\n fetchOptions: () => Promise<DictionaryOption[]>\n createOption?: (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => Promise<DictionaryOption | null>\n labels: DictionarySelectLabels\n manageHref?: string\n selectClassName?: string\n allowInlineCreate?: boolean\n allowAppearance?: boolean\n appearanceLabels?: AppearanceSelectorLabels\n disabled?: boolean\n showLabelInput?: boolean\n showManage?: boolean\n}\n\nexport function DictionaryEntrySelect({\n value,\n onChange,\n fetchOptions,\n createOption,\n labels,\n manageHref,\n selectClassName,\n allowInlineCreate = true,\n allowAppearance = false,\n appearanceLabels,\n disabled: disabledProp = false,\n showLabelInput = true,\n showManage = true,\n}: DictionaryEntrySelectProps) {\n const [options, setOptions] = React.useState<DictionaryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [newValue, setNewValue] = React.useState('')\n const [newLabel, setNewLabel] = React.useState('')\n const [formError, setFormError] = React.useState<string | null>(null)\n const appearance = useAppearanceState(null, null)\n\n const loadOptions = React.useCallback(async () => {\n setLoading(true)\n try {\n const items = await fetchOptions()\n setOptions(items.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })))\n } catch (err) {\n console.error('DictionaryEntrySelect.fetchOptions failed', err)\n flash(labels.errorLoad, 'error')\n setOptions([])\n } finally {\n setLoading(false)\n }\n }, [fetchOptions, labels.errorLoad])\n\n React.useEffect(() => {\n loadOptions().catch(() => {})\n }, [loadOptions])\n\n const resetDialogState = React.useCallback(() => {\n setNewValue('')\n setNewLabel('')\n setFormError(null)\n appearance.setColor(null)\n appearance.setIcon(null)\n setSaving(false)\n }, [appearance])\n\n React.useEffect(() => {\n if (!dialogOpen) resetDialogState()\n }, [dialogOpen, resetDialogState])\n\n const activeOption = React.useMemo(\n () => options.find((option) => option.value === value) ?? null,\n [options, value],\n )\n\n const handleCreate = React.useCallback(async () => {\n if (!createOption) return\n const trimmedValue = newValue.trim()\n if (!trimmedValue.length) {\n setFormError(labels.emptyError)\n return\n }\n setSaving(true)\n try {\n const payload = await createOption({\n value: trimmedValue,\n label: showLabelInput ? newLabel.trim() || undefined : undefined,\n color: allowAppearance && appearance.color ? appearance.color : undefined,\n icon: allowAppearance && appearance.icon ? appearance.icon : undefined,\n })\n if (!payload) throw new Error('createOption did not return an entry')\n setOptions((previous) => {\n const map = new Map(previous.map((option) => [option.value, option]))\n map.set(payload.value, {\n value: payload.value,\n label: payload.label,\n color: payload.color ?? null,\n icon: payload.icon ?? null,\n })\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))\n })\n await loadOptions()\n onChange(payload.value)\n setDialogOpen(false)\n if (labels.successCreateLabel) {\n flash(labels.successCreateLabel, 'success')\n }\n } catch (err) {\n console.error('DictionaryEntrySelect.createOption failed', err)\n flash(labels.errorSave, 'error')\n } finally {\n setSaving(false)\n }\n }, [\n allowAppearance,\n appearance.color,\n appearance.icon,\n createOption,\n labels.emptyError,\n labels.errorSave,\n labels.successCreateLabel,\n loadOptions,\n newLabel,\n newValue,\n onChange,\n ])\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n if (!saving) {\n setDialogOpen(false)\n }\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!saving && newValue.trim().length) {\n handleCreate().catch(() => {})\n } else if (!saving && !newValue.trim().length) {\n setFormError(labels.emptyError)\n }\n }\n },\n [handleCreate, labels.emptyError, newValue, saving],\n )\n\n const shortcutHint = React.useMemo(() => {\n const provided = typeof labels.saveShortcutHint === 'string' ? labels.saveShortcutHint.trim() : ''\n if (provided.length) return provided\n return '\u2318/Ctrl + Enter'\n }, [labels.saveShortcutHint])\n\n const disabled = disabledProp || loading || saving\n const manageLink = manageHref ?? '/backend/config/dictionaries'\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <select\n className={[\n 'h-9 w-full rounded border px-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-70',\n selectClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n value={value ?? ''}\n onChange={(event) => onChange(event.target.value ? event.target.value : undefined)}\n disabled={disabled}\n >\n <option value=\"\">{labels.placeholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <div className=\"flex items-center gap-1\">\n {allowInlineCreate && createOption ? (\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n disabled={disabled}\n title={labels.addLabel}\n aria-label={labels.addLabel}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{labels.dialogTitle}</DialogTitle>\n {labels.addPrompt ? <DialogDescription>{labels.addPrompt}</DialogDescription> : null}\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.valueLabel}</label>\n <input\n type=\"text\"\n className=\"w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={newValue}\n onChange={(event) => {\n setNewValue(event.target.value)\n if (formError) setFormError(null)\n }}\n placeholder={labels.valuePlaceholder}\n autoFocus\n disabled={saving}\n />\n </div>\n {showLabelInput ? (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.labelLabel}</label>\n <input\n type=\"text\"\n className=\"w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={newLabel}\n onChange={(event) => setNewLabel(event.target.value)}\n placeholder={labels.labelPlaceholder}\n disabled={saving}\n />\n </div>\n ) : null}\n {allowAppearance ? (\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={appearance.setIcon}\n onColorChange={appearance.setColor}\n labels={appearanceLabels ?? DEFAULT_APPEARANCE_LABELS}\n />\n ) : null}\n {formError ? <p className=\"text-sm text-red-600\">{formError}</p> : null}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={saving}>\n {labels.cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleCreate} disabled={saving || !newValue.trim()}>\n {saving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n <span className=\"flex items-center gap-2\">\n <span>{labels.saveLabel}</span>\n {!saving ? (\n <span className=\"text-xs text-muted-foreground\">{`(${shortcutHint})`}</span>\n ) : null}\n </span>\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n {showManage ? (\n <Button asChild variant=\"ghost\" size=\"icon\" title={labels.manageTitle} aria-label={labels.manageTitle}>\n <Link href={manageLink}>\n <Settings className=\"h-4 w-4\" />\n <span className=\"sr-only\">{labels.manageTitle}</span>\n </Link>\n </Button>\n ) : null}\n </div>\n </div>\n {activeOption && (activeOption.icon || activeOption.color) ? (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2 rounded border border-dashed px-2 py-1\">\n {activeOption.icon ? renderDictionaryIcon(activeOption.icon, 'h-4 w-4') : null}\n {activeOption.color ? renderDictionaryColor(activeOption.color, 'h-4 w-4 rounded-sm') : null}\n </span>\n {activeOption.color ? <span>{activeOption.color}</span> : null}\n </div>\n ) : null}\n {loading ? <div className=\"text-xs text-muted-foreground\">{labels.loadingLabel}</div> : null}\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Plus, Settings, Save } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { DictionaryValue, renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\nimport { AppearanceSelector, type AppearanceSelectorLabels, useAppearanceState } from './AppearanceSelector'\n\nconst DEFAULT_APPEARANCE_LABELS: AppearanceSelectorLabels = {\n colorLabel: 'Color',\n colorHelp: 'Pick a highlight color for this entry.',\n colorClearLabel: 'Remove color',\n iconLabel: 'Icon or emoji',\n iconPlaceholder: 'Type an emoji or icon token.',\n iconPickerTriggerLabel: 'Browse icons and emoji',\n iconSearchPlaceholder: 'Search icons or emojis\u2026',\n iconSearchEmptyLabel: 'No icons match your search.',\n iconSuggestionsLabel: 'Suggestions',\n iconClearLabel: 'Remove icon',\n previewEmptyLabel: 'No appearance selected',\n}\n\nexport type DictionaryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\nexport type DictionarySelectLabels = {\n placeholder: string\n addLabel: string\n addPrompt?: string\n dialogTitle: string\n valueLabel: string\n valuePlaceholder: string\n labelLabel: string\n labelPlaceholder: string\n emptyError: string\n cancelLabel: string\n saveLabel: string\n saveShortcutHint?: string\n successCreateLabel?: string\n errorLoad: string\n errorSave: string\n loadingLabel: string\n manageTitle: string\n}\n\nexport type DictionaryEntrySelectProps = {\n value?: string\n onChange: (value: string | undefined) => void\n fetchOptions: () => Promise<DictionaryOption[]>\n createOption?: (input: { value: string; label?: string; color?: string | null; icon?: string | null }) => Promise<DictionaryOption | null>\n labels: DictionarySelectLabels\n manageHref?: string\n selectClassName?: string\n allowInlineCreate?: boolean\n allowAppearance?: boolean\n appearanceLabels?: AppearanceSelectorLabels\n disabled?: boolean\n showLabelInput?: boolean\n showManage?: boolean\n}\n\nexport function DictionaryEntrySelect({\n value,\n onChange,\n fetchOptions,\n createOption,\n labels,\n manageHref,\n selectClassName,\n allowInlineCreate = true,\n allowAppearance = false,\n appearanceLabels,\n disabled: disabledProp = false,\n showLabelInput = true,\n showManage = true,\n}: DictionaryEntrySelectProps) {\n const [options, setOptions] = React.useState<DictionaryOption[]>([])\n const [loading, setLoading] = React.useState(true)\n const [saving, setSaving] = React.useState(false)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [newValue, setNewValue] = React.useState('')\n const [newLabel, setNewLabel] = React.useState('')\n const [formError, setFormError] = React.useState<string | null>(null)\n const appearance = useAppearanceState(null, null)\n\n const loadOptions = React.useCallback(async () => {\n setLoading(true)\n try {\n const items = await fetchOptions()\n setOptions(items.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })))\n } catch (err) {\n console.error('DictionaryEntrySelect.fetchOptions failed', err)\n flash(labels.errorLoad, 'error')\n setOptions([])\n } finally {\n setLoading(false)\n }\n }, [fetchOptions, labels.errorLoad])\n\n React.useEffect(() => {\n loadOptions().catch(() => {})\n }, [loadOptions])\n\n const resetDialogState = React.useCallback(() => {\n setNewValue('')\n setNewLabel('')\n setFormError(null)\n appearance.setColor(null)\n appearance.setIcon(null)\n setSaving(false)\n }, [appearance])\n\n React.useEffect(() => {\n if (!dialogOpen) resetDialogState()\n }, [dialogOpen, resetDialogState])\n\n const activeOption = React.useMemo(\n () => options.find((option) => option.value === value) ?? null,\n [options, value],\n )\n\n const handleCreate = React.useCallback(async () => {\n if (!createOption) return\n const trimmedValue = newValue.trim()\n if (!trimmedValue.length) {\n setFormError(labels.emptyError)\n return\n }\n setSaving(true)\n try {\n const payload = await createOption({\n value: trimmedValue,\n label: showLabelInput ? newLabel.trim() || undefined : undefined,\n color: allowAppearance && appearance.color ? appearance.color : undefined,\n icon: allowAppearance && appearance.icon ? appearance.icon : undefined,\n })\n if (!payload) throw new Error('createOption did not return an entry')\n setOptions((previous) => {\n const map = new Map(previous.map((option) => [option.value, option]))\n map.set(payload.value, {\n value: payload.value,\n label: payload.label,\n color: payload.color ?? null,\n icon: payload.icon ?? null,\n })\n return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))\n })\n await loadOptions()\n onChange(payload.value)\n setDialogOpen(false)\n if (labels.successCreateLabel) {\n flash(labels.successCreateLabel, 'success')\n }\n } catch (err) {\n console.error('DictionaryEntrySelect.createOption failed', err)\n flash(labels.errorSave, 'error')\n } finally {\n setSaving(false)\n }\n }, [\n allowAppearance,\n appearance.color,\n appearance.icon,\n createOption,\n labels.emptyError,\n labels.errorSave,\n labels.successCreateLabel,\n loadOptions,\n newLabel,\n newValue,\n onChange,\n ])\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n if (!saving) {\n setDialogOpen(false)\n }\n return\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n if (!saving && newValue.trim().length) {\n handleCreate().catch(() => {})\n } else if (!saving && !newValue.trim().length) {\n setFormError(labels.emptyError)\n }\n }\n },\n [handleCreate, labels.emptyError, newValue, saving],\n )\n\n const shortcutHint = React.useMemo(() => {\n const provided = typeof labels.saveShortcutHint === 'string' ? labels.saveShortcutHint.trim() : ''\n if (provided.length) return provided\n return '\u2318/Ctrl + Enter'\n }, [labels.saveShortcutHint])\n\n const disabled = disabledProp || loading || saving\n const manageLink = manageHref ?? '/backend/config/dictionaries'\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <select\n className={[\n 'h-9 w-full rounded border pl-3 pr-8 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-70',\n selectClassName,\n ]\n .filter(Boolean)\n .join(' ')}\n value={value ?? ''}\n onChange={(event) => onChange(event.target.value ? event.target.value : undefined)}\n disabled={disabled}\n >\n <option value=\"\">{labels.placeholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <div className=\"flex items-center gap-1\">\n {allowInlineCreate && createOption ? (\n <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n disabled={disabled}\n title={labels.addLabel}\n aria-label={labels.addLabel}\n >\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{labels.dialogTitle}</DialogTitle>\n {labels.addPrompt ? <DialogDescription>{labels.addPrompt}</DialogDescription> : null}\n </DialogHeader>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.valueLabel}</label>\n <input\n type=\"text\"\n className=\"w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={newValue}\n onChange={(event) => {\n setNewValue(event.target.value)\n if (formError) setFormError(null)\n }}\n placeholder={labels.valuePlaceholder}\n autoFocus\n disabled={saving}\n />\n </div>\n {showLabelInput ? (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{labels.labelLabel}</label>\n <input\n type=\"text\"\n className=\"w-full rounded border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n value={newLabel}\n onChange={(event) => setNewLabel(event.target.value)}\n placeholder={labels.labelPlaceholder}\n disabled={saving}\n />\n </div>\n ) : null}\n {allowAppearance ? (\n <AppearanceSelector\n icon={appearance.icon}\n color={appearance.color}\n onIconChange={appearance.setIcon}\n onColorChange={appearance.setColor}\n labels={appearanceLabels ?? DEFAULT_APPEARANCE_LABELS}\n />\n ) : null}\n {formError ? <p className=\"text-sm text-red-600\">{formError}</p> : null}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialogOpen(false)} disabled={saving}>\n {labels.cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleCreate} disabled={saving || !newValue.trim()}>\n {saving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n <span className=\"flex items-center gap-2\">\n <span>{labels.saveLabel}</span>\n {!saving ? (\n <span className=\"text-xs text-muted-foreground\">{`(${shortcutHint})`}</span>\n ) : null}\n </span>\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n ) : null}\n {showManage ? (\n <Button asChild variant=\"ghost\" size=\"icon\" title={labels.manageTitle} aria-label={labels.manageTitle}>\n <Link href={manageLink}>\n <Settings className=\"h-4 w-4\" />\n <span className=\"sr-only\">{labels.manageTitle}</span>\n </Link>\n </Button>\n ) : null}\n </div>\n </div>\n {activeOption && (activeOption.icon || activeOption.color) ? (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"inline-flex items-center gap-2 rounded border border-dashed px-2 py-1\">\n {activeOption.icon ? renderDictionaryIcon(activeOption.icon, 'h-4 w-4') : null}\n {activeOption.color ? renderDictionaryColor(activeOption.color, 'h-4 w-4 rounded-sm') : null}\n </span>\n {activeOption.color ? <span>{activeOption.color}</span> : null}\n </div>\n ) : null}\n {loading ? <div className=\"text-xs text-muted-foreground\">{labels.loadingLabel}</div> : null}\n </div>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import { JsonBuilder } from "@open-mercato/ui/backend/JsonBuilder";
3
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/components/formConfig.tsx"],
4
- "sourcesContent": ["import { CrudFormGroup, CrudCustomFieldRenderProps, CrudField } from \"@open-mercato/ui/backend/CrudForm\";\nimport { JsonBuilder } from \"@open-mercato/ui/backend/JsonBuilder\";\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n\nexport function renderDefaultValueCreateComponent(props: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedType = props.values?.type as string;\n\n switch (selectedType) {\n case 'boolean':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.boolean.label', 'Default Value (Boolean)')}</label>\n <select\n value={props.value as string || 'false'}\n onChange={(e) => props.setValue(e.target.value === 'true')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n >\n <option value=\"true\">{t('feature_toggles.values.true', 'True')}</option>\n <option value=\"false\">{t('feature_toggles.values.false', 'False')}</option>\n </select>\n </div>\n );\n\n case 'string':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.string.label', 'Default Value (String)')}</label>\n <input\n type=\"text\"\n value={props.value as string || ''}\n onChange={(e) => props.setValue(e.target.value)}\n placeholder={t('feature_toggles.form.fields.defaultValue.string.placeholder', 'Enter default string value')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'number':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.number.label', 'Default Value (Number)')}</label>\n <input\n type=\"number\"\n value={props.value as number || 0}\n onChange={(e) => props.setValue(Number(e.target.value) || 0)}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'json':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.json.label', 'Default Value (JSON)')}</label>\n <JsonBuilder\n value={props.value}\n onChange={props.setValue}\n disabled={props.disabled}\n />\n </div>\n );\n\n default:\n return (\n <div className=\"text-sm text-muted-foreground p-4 text-center bg-muted/20 rounded border border-dashed\">\n {t('feature_toggles.form.fields.defaultValue.selectType', 'Please select a type above to configure the default value')}\n </div>\n );\n }\n}\n\nexport function createFieldDefinitions(\n t: (key: string) => string,\n): CrudField[] {\n return [\n {\n id: 'identifier',\n label: t('feature_toggles.form.fields.identifier.label'),\n type: 'text',\n required: true,\n },\n {\n id: 'name',\n label: t('feature_toggles.form.fields.name.label'),\n type: 'text',\n required: true,\n },\n {\n id: 'description',\n label: t('feature_toggles.form.fields.description.label'),\n type: 'textarea',\n required: false,\n },\n {\n id: 'category',\n label: t('feature_toggles.form.fields.category.label'),\n type: 'text',\n required: false,\n },\n {\n id: 'type',\n label: t('feature_toggles.form.fields.type.label'),\n type: 'select',\n required: true,\n options: [\n { label: t('feature_toggles.types.boolean'), value: 'boolean' },\n { label: t('feature_toggles.types.string'), value: 'string' },\n { label: t('feature_toggles.types.number'), value: 'number' },\n { label: t('feature_toggles.types.json'), value: 'json' },\n ],\n },\n {\n id: 'defaultValue',\n label: '',\n type: 'custom',\n component: renderDefaultValueCreateComponent,\n description: t('feature_toggles.form.fields.defaultValue.description'),\n },\n ]\n}\n\nexport function createFormGroups(\n t: (key: string) => string,\n): CrudFormGroup[] {\n return [\n {\n id: 'basic',\n title: t('feature_toggles.form.groups.basic'),\n column: 1,\n fields: [\n 'identifier',\n 'name',\n 'description',\n 'category',\n ],\n },\n {\n id: 'type',\n title: t('feature_toggles.form.groups.type'),\n column: 1,\n fields: ['type'],\n },\n {\n id: 'defaultValue',\n title: t('feature_toggles.form.groups.defaultValue'),\n column: 1,\n fields: ['defaultValue'],\n },\n ]\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { CrudFormGroup, CrudCustomFieldRenderProps, CrudField } from \"@open-mercato/ui/backend/CrudForm\";\nimport { JsonBuilder } from \"@open-mercato/ui/backend/JsonBuilder\";\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n\nexport function renderDefaultValueCreateComponent(props: CrudCustomFieldRenderProps) {\n const t = useT()\n const selectedType = props.values?.type as string;\n\n switch (selectedType) {\n case 'boolean':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.boolean.label', 'Default Value (Boolean)')}</label>\n <select\n value={props.value as string || 'false'}\n onChange={(e) => props.setValue(e.target.value === 'true')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n >\n <option value=\"true\">{t('feature_toggles.values.true', 'True')}</option>\n <option value=\"false\">{t('feature_toggles.values.false', 'False')}</option>\n </select>\n </div>\n );\n\n case 'string':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.string.label', 'Default Value (String)')}</label>\n <input\n type=\"text\"\n value={props.value as string || ''}\n onChange={(e) => props.setValue(e.target.value)}\n placeholder={t('feature_toggles.form.fields.defaultValue.string.placeholder', 'Enter default string value')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'number':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.number.label', 'Default Value (Number)')}</label>\n <input\n type=\"number\"\n value={props.value as number || 0}\n onChange={(e) => props.setValue(Number(e.target.value) || 0)}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'json':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.form.fields.defaultValue.json.label', 'Default Value (JSON)')}</label>\n <JsonBuilder\n value={props.value}\n onChange={props.setValue}\n disabled={props.disabled}\n />\n </div>\n );\n\n default:\n return (\n <div className=\"text-sm text-muted-foreground p-4 text-center bg-muted/20 rounded border border-dashed\">\n {t('feature_toggles.form.fields.defaultValue.selectType', 'Please select a type above to configure the default value')}\n </div>\n );\n }\n}\n\nexport function createFieldDefinitions(\n t: (key: string) => string,\n): CrudField[] {\n return [\n {\n id: 'identifier',\n label: t('feature_toggles.form.fields.identifier.label'),\n type: 'text',\n required: true,\n },\n {\n id: 'name',\n label: t('feature_toggles.form.fields.name.label'),\n type: 'text',\n required: true,\n },\n {\n id: 'description',\n label: t('feature_toggles.form.fields.description.label'),\n type: 'textarea',\n required: false,\n },\n {\n id: 'category',\n label: t('feature_toggles.form.fields.category.label'),\n type: 'text',\n required: false,\n },\n {\n id: 'type',\n label: t('feature_toggles.form.fields.type.label'),\n type: 'select',\n required: true,\n options: [\n { label: t('feature_toggles.types.boolean'), value: 'boolean' },\n { label: t('feature_toggles.types.string'), value: 'string' },\n { label: t('feature_toggles.types.number'), value: 'number' },\n { label: t('feature_toggles.types.json'), value: 'json' },\n ],\n },\n {\n id: 'defaultValue',\n label: '',\n type: 'custom',\n component: renderDefaultValueCreateComponent,\n description: t('feature_toggles.form.fields.defaultValue.description'),\n },\n ]\n}\n\nexport function createFormGroups(\n t: (key: string) => string,\n): CrudFormGroup[] {\n return [\n {\n id: 'basic',\n title: t('feature_toggles.form.groups.basic'),\n column: 1,\n fields: [\n 'identifier',\n 'name',\n 'description',\n 'category',\n ],\n },\n {\n id: 'type',\n title: t('feature_toggles.form.groups.type'),\n column: 1,\n fields: ['type'],\n },\n {\n id: 'defaultValue',\n title: t('feature_toggles.form.groups.defaultValue'),\n column: 1,\n fields: ['defaultValue'],\n },\n ]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import { JsonBuilder } from "@open-mercato/ui/backend/JsonBuilder";
3
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";