@open-mercato/core 0.6.6-develop.6198.1.0822f97cc6 → 0.6.6-develop.6201.1.8ceb502c4b

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 (25) hide show
  1. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +81 -37
  2. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  3. package/dist/modules/integrations/backend/integrations/page.js +45 -24
  4. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  5. package/dist/modules/notifications/api/[id]/action/route.js +18 -3
  6. package/dist/modules/notifications/api/[id]/action/route.js.map +2 -2
  7. package/dist/modules/notifications/api/[id]/restore/route.js +19 -3
  8. package/dist/modules/notifications/api/[id]/restore/route.js.map +2 -2
  9. package/dist/modules/notifications/api/mark-all-read/route.js +18 -4
  10. package/dist/modules/notifications/api/mark-all-read/route.js.map +2 -2
  11. package/dist/modules/notifications/api/route.js +17 -4
  12. package/dist/modules/notifications/api/route.js.map +2 -2
  13. package/dist/modules/notifications/api/settings/route.js +26 -5
  14. package/dist/modules/notifications/api/settings/route.js.map +2 -2
  15. package/dist/modules/notifications/lib/routeHelpers.js +64 -5
  16. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  17. package/package.json +7 -7
  18. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +78 -35
  19. package/src/modules/integrations/backend/integrations/page.tsx +42 -21
  20. package/src/modules/notifications/api/[id]/action/route.ts +17 -2
  21. package/src/modules/notifications/api/[id]/restore/route.ts +19 -3
  22. package/src/modules/notifications/api/mark-all-read/route.ts +18 -4
  23. package/src/modules/notifications/api/route.ts +16 -3
  24. package/src/modules/notifications/api/settings/route.ts +26 -5
  25. package/src/modules/notifications/lib/routeHelpers.ts +101 -4
@@ -19,6 +19,7 @@ import {
19
19
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
20
20
  import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
21
21
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
22
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
22
23
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
23
24
  import { useT } from "@open-mercato/shared/lib/i18n/context";
24
25
  import { LoadingMessage, ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
@@ -47,6 +48,13 @@ function BundleConfigPage({ params }) {
47
48
  const [credValues, setCredValues] = React.useState({});
48
49
  const [isSavingCreds, setIsSavingCreds] = React.useState(false);
49
50
  const [togglingIds, setTogglingIds] = React.useState(/* @__PURE__ */ new Set());
51
+ const mutationContextId = React.useMemo(
52
+ () => `integrations.bundle:${bundleId ?? "unknown"}`,
53
+ [bundleId]
54
+ );
55
+ const { runMutation, retryLastMutation } = useGuardedMutation({
56
+ contextId: mutationContextId
57
+ });
50
58
  const resolveCurrentBundleId = React.useCallback(() => {
51
59
  return bundleId ?? (typeof window !== "undefined" ? resolvePathnameId(window.location.pathname) : void 0);
52
60
  }, [bundleId]);
@@ -100,50 +108,86 @@ function BundleConfigPage({ params }) {
100
108
  const currentBundleId = resolveCurrentBundleId();
101
109
  if (!currentBundleId) return;
102
110
  setIsSavingCreds(true);
103
- const call = await withScopedApiRequestHeaders(
104
- buildOptimisticLockHeader(void 0),
105
- () => apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {
106
- method: "PUT",
107
- headers: { "Content-Type": "application/json" },
108
- body: JSON.stringify({ credentials: credValues })
109
- }, { fallback: null })
110
- );
111
- if (call.ok) {
112
- flash(t("integrations.detail.credentials.saved"), "success");
113
- } else {
111
+ try {
112
+ const call = await runMutation({
113
+ mutationPayload: { bundleId: currentBundleId, credentials: credValues },
114
+ context: {
115
+ formId: mutationContextId,
116
+ operation: "update",
117
+ actionId: "save-credentials",
118
+ resourceKind: "integrations.bundle",
119
+ resourceId: currentBundleId,
120
+ bundleId: currentBundleId,
121
+ retryLastMutation
122
+ },
123
+ // TODO(#2373-B): thread updatedAt — integration detail/state response does not expose a record version yet
124
+ operation: () => withScopedApiRequestHeaders(
125
+ buildOptimisticLockHeader(void 0),
126
+ () => apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {
127
+ method: "PUT",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: JSON.stringify({ credentials: credValues })
130
+ }, { fallback: null })
131
+ )
132
+ });
133
+ if (call.ok) {
134
+ flash(t("integrations.detail.credentials.saved"), "success");
135
+ } else {
136
+ flash(t("integrations.detail.credentials.saveError"), "error");
137
+ }
138
+ } catch {
114
139
  flash(t("integrations.detail.credentials.saveError"), "error");
140
+ } finally {
141
+ setIsSavingCreds(false);
115
142
  }
116
- setIsSavingCreds(false);
117
- }, [resolveCurrentBundleId, credValues, t]);
143
+ }, [resolveCurrentBundleId, runMutation, mutationContextId, retryLastMutation, credValues, t]);
118
144
  const handleToggle = React.useCallback(async (integrationId, enabled) => {
119
145
  setTogglingIds((prev) => new Set(prev).add(integrationId));
120
- const call = await withScopedApiRequestHeaders(
121
- buildOptimisticLockHeader(void 0),
122
- () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
123
- method: "PUT",
124
- headers: { "Content-Type": "application/json" },
125
- body: JSON.stringify({ isEnabled: enabled })
126
- }, { fallback: null })
127
- );
128
- if (call.ok) {
129
- setDetail((prev) => {
130
- if (!prev) return prev;
131
- return {
132
- ...prev,
133
- bundleIntegrations: prev.bundleIntegrations.map(
134
- (item) => item.id === integrationId ? { ...item, isEnabled: enabled } : item
135
- )
136
- };
146
+ try {
147
+ const call = await runMutation({
148
+ mutationPayload: { integrationId, isEnabled: enabled },
149
+ context: {
150
+ formId: mutationContextId,
151
+ operation: "update",
152
+ actionId: "toggle-state",
153
+ resourceKind: "integrations.integration",
154
+ resourceId: integrationId,
155
+ integrationId,
156
+ retryLastMutation
157
+ },
158
+ // TODO(#2373-B): thread updatedAt — integration detail/state response does not expose a record version yet
159
+ operation: () => withScopedApiRequestHeaders(
160
+ buildOptimisticLockHeader(void 0),
161
+ () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
162
+ method: "PUT",
163
+ headers: { "Content-Type": "application/json" },
164
+ body: JSON.stringify({ isEnabled: enabled })
165
+ }, { fallback: null })
166
+ )
137
167
  });
138
- } else {
168
+ if (call.ok) {
169
+ setDetail((prev) => {
170
+ if (!prev) return prev;
171
+ return {
172
+ ...prev,
173
+ bundleIntegrations: prev.bundleIntegrations.map(
174
+ (item) => item.id === integrationId ? { ...item, isEnabled: enabled } : item
175
+ )
176
+ };
177
+ });
178
+ } else {
179
+ flash(t("integrations.detail.stateError"), "error");
180
+ }
181
+ } catch {
139
182
  flash(t("integrations.detail.stateError"), "error");
183
+ } finally {
184
+ setTogglingIds((prev) => {
185
+ const next = new Set(prev);
186
+ next.delete(integrationId);
187
+ return next;
188
+ });
140
189
  }
141
- setTogglingIds((prev) => {
142
- const next = new Set(prev);
143
- next.delete(integrationId);
144
- return next;
145
- });
146
- }, [t]);
190
+ }, [runMutation, mutationContextId, retryLastMutation, t]);
147
191
  const handleBulkToggle = React.useCallback(async (enabled) => {
148
192
  if (!detail) return;
149
193
  const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/integrations/backend/integrations/bundle/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\ntype BundleConfigPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const bundleId = parts.at(-1)\n if (!bundleId || bundleId === 'bundle' || bundleId === 'integrations') return undefined\n return decodeURIComponent(bundleId)\n}\n\nexport default function BundleConfigPage({ params }: BundleConfigPageProps) {\n const pathname = usePathname()\n const bundleId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const resolveCurrentBundleId = React.useCallback(() => {\n return bundleId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [bundleId])\n\n const load = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n if (call.status === 404) {\n setIsNotFound(true)\n } else {\n setError(t('integrations.detail.loadError'))\n }\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n const next = { ...credCall.result.credentials }\n if (currentBundleId === 'storage_s3') {\n const authMode = next.authMode\n if (authMode !== 'access_keys' && authMode !== 'ambient') {\n const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)\n next.authMode = hasKeys ? 'access_keys' : 'ambient'\n }\n }\n setCredValues(next)\n }\n setIsLoading(false)\n }, [resolveCurrentBundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) return\n setIsSavingCreds(true)\n // TODO(#2373-B): thread updatedAt \u2014 integration detail/state response does not expose a record version yet\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(undefined),\n () => apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null }),\n )\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n setIsSavingCreds(false)\n }, [resolveCurrentBundleId, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n // TODO(#2373-B): thread updatedAt \u2014 integration detail/state response does not expose a record version yet\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(undefined),\n () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null }),\n )\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('integrations.detail.notFound', 'Integration not found.')}\n backHref=\"/backend/integrations\"\n backLabel={t('integrations.detail.backToList', 'Back to integrations')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)\n\n function isFieldVisible(field: CredentialField): boolean {\n if (!field.visibleWhen) return true\n return credValues[field.visibleWhen.field] === field.visibleWhen.equals\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.filter(isFieldVisible).map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <Select\n value={(credValues[field.key] as string) || undefined}\n onValueChange={(value) => setCredValues((prev) => ({ ...prev, [field.key]: value ?? '' }))}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"\u2014\" />\n </SelectTrigger>\n <SelectContent>\n {field.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\n\ntype CredentialField = IntegrationCredentialField\n\nconst UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])\n\nfunction isEditableCredentialField(field: CredentialField): boolean {\n return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)\n}\n\ntype BundleIntegration = {\n id: string\n title: string\n description?: string\n category?: string\n isEnabled: boolean\n}\n\ntype BundleDetail = {\n integration: {\n id: string\n title: string\n description?: string\n bundleId?: string\n }\n bundle?: {\n id: string\n title: string\n description?: string\n credentials?: { fields: CredentialField[] }\n }\n bundleIntegrations: BundleIntegration[]\n state: { isEnabled: boolean }\n hasCredentials: boolean\n}\n\ntype BundleConfigPageProps = {\n params?: {\n id?: string | string[]\n }\n}\n\nfunction resolveRouteId(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) return value[0]\n return value\n}\n\nfunction resolvePathnameId(pathname: string): string | undefined {\n const parts = pathname.split('/').filter(Boolean)\n const bundleId = parts.at(-1)\n if (!bundleId || bundleId === 'bundle' || bundleId === 'integrations') return undefined\n return decodeURIComponent(bundleId)\n}\n\nexport default function BundleConfigPage({ params }: BundleConfigPageProps) {\n const pathname = usePathname()\n const bundleId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)\n const t = useT()\n\n const [detail, setDetail] = React.useState<BundleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [credValues, setCredValues] = React.useState<Record<string, unknown>>({})\n const [isSavingCreds, setIsSavingCreds] = React.useState(false)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n\n const mutationContextId = React.useMemo(\n () => `integrations.bundle:${bundleId ?? 'unknown'}`,\n [bundleId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: mutationContextId,\n })\n\n const resolveCurrentBundleId = React.useCallback(() => {\n return bundleId ?? (\n typeof window !== 'undefined'\n ? resolvePathnameId(window.location.pathname)\n : undefined\n )\n }, [bundleId])\n\n const load = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) {\n setError(t('integrations.detail.loadError'))\n setIsLoading(false)\n return\n }\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n const call = await apiCall<BundleDetail>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}`,\n undefined,\n { fallback: null },\n )\n if (!call.ok || !call.result) {\n if (call.status === 404) {\n setIsNotFound(true)\n } else {\n setError(t('integrations.detail.loadError'))\n }\n setIsLoading(false)\n return\n }\n setDetail(call.result)\n\n const credCall = await apiCall<{ credentials: Record<string, unknown> }>(\n `/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`,\n undefined,\n { fallback: null },\n )\n if (credCall.ok && credCall.result?.credentials) {\n const next = { ...credCall.result.credentials }\n if (currentBundleId === 'storage_s3') {\n const authMode = next.authMode\n if (authMode !== 'access_keys' && authMode !== 'ambient') {\n const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)\n next.authMode = hasKeys ? 'access_keys' : 'ambient'\n }\n }\n setCredValues(next)\n }\n setIsLoading(false)\n }, [resolveCurrentBundleId, t])\n\n React.useEffect(() => { void load() }, [load])\n\n const handleSaveCredentials = React.useCallback(async () => {\n const currentBundleId = resolveCurrentBundleId()\n if (!currentBundleId) return\n setIsSavingCreds(true)\n try {\n const call = await runMutation({\n mutationPayload: { bundleId: currentBundleId, credentials: credValues },\n context: {\n formId: mutationContextId,\n operation: 'update',\n actionId: 'save-credentials',\n resourceKind: 'integrations.bundle',\n resourceId: currentBundleId,\n bundleId: currentBundleId,\n retryLastMutation,\n },\n // TODO(#2373-B): thread updatedAt \u2014 integration detail/state response does not expose a record version yet\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(undefined),\n () => apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ credentials: credValues }),\n }, { fallback: null }),\n ),\n })\n if (call.ok) {\n flash(t('integrations.detail.credentials.saved'), 'success')\n } else {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n }\n } catch {\n flash(t('integrations.detail.credentials.saveError'), 'error')\n } finally {\n setIsSavingCreds(false)\n }\n }, [resolveCurrentBundleId, runMutation, mutationContextId, retryLastMutation, credValues, t])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n try {\n const call = await runMutation({\n mutationPayload: { integrationId, isEnabled: enabled },\n context: {\n formId: mutationContextId,\n operation: 'update',\n actionId: 'toggle-state',\n resourceKind: 'integrations.integration',\n resourceId: integrationId,\n integrationId,\n retryLastMutation,\n },\n // TODO(#2373-B): thread updatedAt \u2014 integration detail/state response does not expose a record version yet\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(undefined),\n () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null }),\n ),\n })\n if (call.ok) {\n setDetail((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n bundleIntegrations: prev.bundleIntegrations.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n } else {\n flash(t('integrations.detail.stateError'), 'error')\n }\n } catch {\n flash(t('integrations.detail.stateError'), 'error')\n } finally {\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }\n }, [runMutation, mutationContextId, retryLastMutation, t])\n\n const handleBulkToggle = React.useCallback(async (enabled: boolean) => {\n if (!detail) return\n const targets = detail.bundleIntegrations.filter((item) => item.isEnabled !== enabled)\n await Promise.all(targets.map((item) => handleToggle(item.id, enabled)))\n }, [detail, handleToggle])\n\n if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('integrations.detail.notFound', 'Integration not found.')}\n backHref=\"/backend/integrations\"\n backLabel={t('integrations.detail.backToList', 'Back to integrations')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>\n\n const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)\n\n function isFieldVisible(field: CredentialField): boolean {\n if (!field.visibleWhen) return true\n return credValues[field.visibleWhen.field] === field.visibleWhen.equals\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <div>\n <Link href=\"/backend/integrations\" className=\"text-sm text-muted-foreground hover:underline\">\n {t('integrations.detail.back')}\n </Link>\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold\">{detail.bundle.title}</h1>\n {detail.bundle.description && (\n <p className=\"text-muted-foreground mt-1\">{detail.bundle.description}</p>\n )}\n </div>\n\n {credFields.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {credFields.filter(isFieldVisible).map((field) => (\n <div key={field.key} className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">\n {field.label}{field.required && <span className=\"text-red-500 ml-0.5\">*</span>}\n </label>\n {field.type === 'select' && field.options ? (\n <Select\n value={(credValues[field.key] as string) || undefined}\n onValueChange={(value) => setCredValues((prev) => ({ ...prev, [field.key]: value ?? '' }))}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"\u2014\" />\n </SelectTrigger>\n <SelectContent>\n {field.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : field.type === 'boolean' ? (\n <Switch\n checked={Boolean(credValues[field.key])}\n onCheckedChange={(checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))}\n />\n ) : (\n <Input\n type={field.type === 'secret' ? 'password' : 'text'}\n placeholder={field.placeholder}\n value={(credValues[field.key] as string) ?? ''}\n onChange={(e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))}\n />\n )}\n </div>\n ))}\n <Button type=\"button\" onClick={() => void handleSaveCredentials()} disabled={isSavingCreds}>\n {isSavingCreds ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('integrations.detail.credentials.save')}\n </Button>\n </CardContent>\n </Card>\n )}\n\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('integrations.bundle.integrationToggles')}</CardTitle>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(true)}>\n {t('integrations.marketplace.enableAll')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void handleBulkToggle(false)}>\n {t('integrations.marketplace.disableAll')}\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {detail.bundleIntegrations.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between rounded-lg border p-3\">\n <div>\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant=\"secondary\" className=\"ml-2 text-xs\">{item.category}</Badge>\n )}\n {item.description && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{item.description}</p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <Button asChild variant=\"ghost\" size=\"sm\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.bundle.configureIntegration')}\n </Link>\n </Button>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,6 +10,7 @@ import { Input } from "@open-mercato/ui/primitives/input";
10
10
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
11
  import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
12
12
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
13
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
13
14
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
14
15
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
15
16
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -87,6 +88,9 @@ function IntegrationsMarketplacePage() {
87
88
  const [togglingIds, setTogglingIds] = React.useState(/* @__PURE__ */ new Set());
88
89
  const scopeVersion = useOrganizationScopeVersion();
89
90
  const t = useT();
91
+ const { runMutation, retryLastMutation } = useGuardedMutation({
92
+ contextId: "integrations.marketplace"
93
+ });
90
94
  React.useEffect(() => {
91
95
  const timer = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300);
92
96
  return () => clearTimeout(timer);
@@ -135,33 +139,50 @@ function IntegrationsMarketplacePage() {
135
139
  }, [debouncedSearch, filterValues, selectedCategory, sortField, sortOrder]);
136
140
  const handleToggle = React.useCallback(async (integrationId, enabled, updatedAt) => {
137
141
  setTogglingIds((prev) => new Set(prev).add(integrationId));
138
- const call = await withScopedApiRequestHeaders(
139
- buildOptimisticLockHeader(updatedAt),
140
- () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
141
- method: "PUT",
142
- headers: { "Content-Type": "application/json" },
143
- body: JSON.stringify({ isEnabled: enabled })
144
- }, { fallback: null })
145
- );
146
- if (!call.ok) {
142
+ try {
143
+ const call = await runMutation({
144
+ mutationPayload: { integrationId, isEnabled: enabled },
145
+ context: {
146
+ formId: "integrations.marketplace",
147
+ operation: "update",
148
+ actionId: "toggle-state",
149
+ resourceKind: "integrations.integration",
150
+ resourceId: integrationId,
151
+ integrationId,
152
+ retryLastMutation
153
+ },
154
+ operation: () => withScopedApiRequestHeaders(
155
+ buildOptimisticLockHeader(updatedAt),
156
+ () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
157
+ method: "PUT",
158
+ headers: { "Content-Type": "application/json" },
159
+ body: JSON.stringify({ isEnabled: enabled })
160
+ }, { fallback: null })
161
+ )
162
+ });
163
+ if (!call.ok) {
164
+ flash(t("integrations.detail.stateError"), "error");
165
+ } else {
166
+ setData((prev) => {
167
+ if (!prev) return prev;
168
+ return {
169
+ ...prev,
170
+ items: prev.items.map(
171
+ (item) => item.id === integrationId ? { ...item, isEnabled: enabled } : item
172
+ )
173
+ };
174
+ });
175
+ }
176
+ } catch {
147
177
  flash(t("integrations.detail.stateError"), "error");
148
- } else {
149
- setData((prev) => {
150
- if (!prev) return prev;
151
- return {
152
- ...prev,
153
- items: prev.items.map(
154
- (item) => item.id === integrationId ? { ...item, isEnabled: enabled } : item
155
- )
156
- };
178
+ } finally {
179
+ setTogglingIds((prev) => {
180
+ const next = new Set(prev);
181
+ next.delete(integrationId);
182
+ return next;
157
183
  });
158
184
  }
159
- setTogglingIds((prev) => {
160
- const next = new Set(prev);
161
- next.delete(integrationId);
162
- return next;
163
- });
164
- }, [t]);
185
+ }, [retryLastMutation, runMutation, t]);
165
186
  const grouped = React.useMemo(() => {
166
187
  if (!data) return { bundles: [], standalone: [] };
167
188
  const bundled = /* @__PURE__ */ new Map();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/integrations/backend/integrations/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FilterBar, type FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Bell, Cog, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Search, Truck, Webhook } from 'lucide-react'\nimport {\n buildIntegrationMarketplaceFilterDefs,\n getIntegrationMarketplaceCategory,\n getListQueryFromFilterValues,\n INTEGRATION_MARKETPLACE_CATEGORIES,\n normalizeIntegrationMarketplaceFilterValues,\n} from './filters'\n\ntype IntegrationAnalytics = {\n lastActivityAt: string | null\n totalCount: number\n errorCount: number\n errorRate: number\n dailyCounts: number[]\n}\n\ntype IntegrationItem = {\n id: string\n title: string\n description?: string\n category?: string\n tags?: string[]\n bundleId?: string\n author?: string\n company?: string\n version?: string\n isEnabled: boolean\n hasCredentials: boolean\n healthStatus: 'healthy' | 'degraded' | 'unhealthy' | 'unconfigured'\n analytics: IntegrationAnalytics\n updatedAt?: string | null\n}\n\ntype BundleItem = {\n id: string\n title: string\n description?: string\n icon?: string\n integrationCount: number\n enabledCount: number\n}\n\ntype ListResponse = {\n items: IntegrationItem[]\n bundles: BundleItem[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nconst CATEGORY_ICONS: Record<string, React.ElementType> = {\n all: LayoutGrid,\n payment: CreditCard,\n shipping: Truck,\n data_sync: RefreshCw,\n communication: MessageSquare,\n notification: Bell,\n storage: HardDrive,\n webhook: Webhook,\n}\n\nconst HEALTH_BADGE_CLASS: Record<string, string> = {\n healthy: 'bg-emerald-500/15 text-emerald-800 dark:text-emerald-300',\n degraded: 'bg-amber-500/15 text-amber-900 dark:text-amber-300',\n unhealthy: 'bg-destructive/15 text-destructive',\n unconfigured: 'bg-muted text-muted-foreground',\n}\n\nfunction buildListQueryString(input: {\n q?: string\n category?: string\n bundleId?: string\n isEnabled?: boolean\n healthStatus?: string\n sort?: string\n order?: string\n page?: number\n pageSize?: number\n}): string {\n const params = new URLSearchParams()\n if (input.q) params.set('q', input.q)\n if (input.category) params.set('category', input.category)\n if (input.bundleId) params.set('bundleId', input.bundleId)\n if (input.isEnabled !== undefined) params.set('isEnabled', String(input.isEnabled))\n if (input.healthStatus) params.set('healthStatus', input.healthStatus)\n if (input.sort) params.set('sort', input.sort)\n if (input.order) params.set('order', input.order)\n if (input.page != null && input.page > 1) params.set('page', String(input.page))\n if (input.pageSize != null && input.pageSize !== 100) params.set('pageSize', String(input.pageSize))\n const qs = params.toString()\n return qs ? `?${qs}` : ''\n}\n\nfunction LogSparkline({ counts, className }: { counts: number[]; className?: string }) {\n const max = Math.max(1, ...counts)\n const w = 72\n const h = 22\n const step = counts.length > 1 ? w / (counts.length - 1) : w\n const points = counts.map((count, index) => {\n const x = index * step\n const y = h - (count / max) * (h - 3) - 1.5\n return `${x},${y}`\n }).join(' ')\n return (\n <svg width={w} height={h} className={className} aria-hidden>\n <polyline\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.2\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n points={points}\n className=\"text-muted-foreground/80\"\n />\n </svg>\n )\n}\n\nexport default function IntegrationsMarketplacePage() {\n const [data, setData] = React.useState<ListResponse | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [searchInput, setSearchInput] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [sortField, setSortField] = React.useState<'title' | 'category' | 'enabledAt' | 'healthStatus'>('title')\n const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('asc')\n const [page, setPage] = React.useState(1)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n\n React.useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300)\n return () => clearTimeout(timer)\n }, [searchInput])\n\n const selectedCategory = React.useMemo(() => getIntegrationMarketplaceCategory(filterValues), [filterValues])\n\n const listQuery = React.useMemo(() => {\n const fromFilters = getListQueryFromFilterValues(filterValues)\n const category = selectedCategory !== 'all' ? selectedCategory : fromFilters.category\n return buildListQueryString({\n q: debouncedSearch || undefined,\n ...(category ? { category } : {}),\n ...(fromFilters.bundleId ? { bundleId: fromFilters.bundleId } : {}),\n ...(fromFilters.isEnabled !== undefined ? { isEnabled: fromFilters.isEnabled } : {}),\n ...(fromFilters.healthStatus ? { healthStatus: fromFilters.healthStatus } : {}),\n sort: sortField,\n order: sortOrder,\n page,\n pageSize: 100,\n })\n }, [debouncedSearch, filterValues, page, selectedCategory, sortField, sortOrder])\n\n const bundleFilterOptions = React.useMemo(\n () => (data?.bundles ?? []).map((bundle) => ({ id: bundle.id, title: bundle.title })),\n [data?.bundles],\n )\n\n const categoryFilters = React.useMemo(\n () => buildIntegrationMarketplaceFilterDefs(t, bundleFilterOptions),\n [bundleFilterOptions, t],\n )\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n const fallback: ListResponse = { items: [], bundles: [], total: 0, page: 1, pageSize: 100, totalPages: 1 }\n const call = await apiCall<ListResponse>(`/api/integrations${listQuery}`, undefined, { fallback })\n if (!call.ok) {\n flash(t('integrations.marketplace.loadError'), 'error')\n setIsLoading(false)\n return\n }\n setData(call.result ?? fallback)\n setIsLoading(false)\n }, [listQuery, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n React.useEffect(() => {\n setPage(1)\n }, [debouncedSearch, filterValues, selectedCategory, sortField, sortOrder])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean, updatedAt?: string | null) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null }),\n )\n\n if (!call.ok) {\n flash(t('integrations.detail.stateError'), 'error')\n } else {\n setData((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n items: prev.items.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const grouped = React.useMemo(() => {\n if (!data) return { bundles: [] as Array<BundleItem & { integrations: IntegrationItem[] }>, standalone: [] as IntegrationItem[] }\n\n const bundled = new Map<string, IntegrationItem[]>()\n const standalone: IntegrationItem[] = []\n for (const item of data.items) {\n if (item.bundleId) {\n const list = bundled.get(item.bundleId) ?? []\n list.push(item)\n bundled.set(item.bundleId, list)\n } else {\n standalone.push(item)\n }\n }\n\n const bundles = (data.bundles ?? [])\n .filter((b) => bundled.has(b.id))\n .map((b) => ({ ...b, integrations: bundled.get(b.id) ?? [] }))\n\n return { bundles, standalone }\n }, [data])\n\n const renderCategoryIcon = React.useCallback((category: string | undefined, className: string) => {\n if (!category) return null\n const Icon = CATEGORY_ICONS[category]\n if (!Icon) return null\n return <Icon className={className} />\n }, [])\n\n const renderHealthBadge = React.useCallback((status: IntegrationItem['healthStatus']) => {\n return (\n <span\n className={`inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-semibold uppercase tracking-wide ${HEALTH_BADGE_CLASS[status] ?? HEALTH_BADGE_CLASS.unconfigured}`}\n >\n {t(`integrations.marketplace.health.${status}`, status)}\n </span>\n )\n }, [t])\n\n if (isLoading && !data) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center py-16\">\n <Spinner />\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <section className=\"space-y-6 rounded-lg border bg-background p-6\">\n <header className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"space-y-0.5\">\n <h2 className=\"text-lg font-semibold\">{t('integrations.marketplace.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('integrations.marketplace.description')}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative w-full min-w-[200px] max-w-xs lg:w-64\">\n <Search className=\"absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground pointer-events-none\" />\n <Input\n placeholder={t('integrations.marketplace.search')}\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"pl-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"sr-only\">{t('integrations.marketplace.sort.label', 'Sort by')}</span>\n <select\n className=\"h-9 rounded-md border border-input bg-background px-2 text-sm\"\n value={sortField}\n onChange={(e) => setSortField(e.target.value as typeof sortField)}\n >\n <option value=\"title\">{t('integrations.marketplace.sort.title', 'Title')}</option>\n <option value=\"category\">{t('integrations.marketplace.sort.category', 'Category')}</option>\n <option value=\"enabledAt\">{t('integrations.marketplace.sort.enabledAt', 'Enabled date')}</option>\n <option value=\"healthStatus\">{t('integrations.marketplace.sort.health', 'Health')}</option>\n </select>\n </label>\n <select\n className=\"h-9 rounded-md border border-input bg-background px-2 text-sm\"\n value={sortOrder}\n onChange={(e) => setSortOrder(e.target.value as typeof sortOrder)}\n aria-label={t('integrations.marketplace.sort.order', 'Sort order')}\n >\n <option value=\"asc\">{t('integrations.marketplace.sort.asc', 'Ascending')}</option>\n <option value=\"desc\">{t('integrations.marketplace.sort.desc', 'Descending')}</option>\n </select>\n </div>\n </header>\n\n <div className=\"lg:hidden\">\n <FilterBar\n searchValue={searchInput}\n onSearchChange={setSearchInput}\n searchPlaceholder={t('integrations.marketplace.search')}\n searchAlign=\"left\"\n filters={categoryFilters}\n values={filterValues}\n onApply={(values) => setFilterValues(normalizeIntegrationMarketplaceFilterValues(values))}\n onClear={() => setFilterValues({})}\n />\n </div>\n\n <div className=\"hidden lg:flex flex-wrap gap-1.5\">\n {INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {\n const Icon = CATEGORY_ICONS[category]\n return (\n <Button\n key={category}\n type=\"button\"\n variant={selectedCategory === category ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category }))}\n >\n {Icon ? <Icon className=\"mr-1.5 h-3.5 w-3.5\" /> : null}\n {t(`integrations.marketplace.categories.${category}`)}\n </Button>\n )\n })}\n </div>\n\n {isLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : null}\n\n {data && data.totalPages > 1 ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm text-muted-foreground\">\n <span>\n {data.total === 0\n ? t('integrations.marketplace.pagination.empty', 'No results')\n : t('integrations.marketplace.pagination.summary', {\n from: (data.page - 1) * data.pageSize + 1,\n to: Math.min(data.page * data.pageSize, data.total),\n total: data.total,\n })}\n </span>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={data.page <= 1}\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n >\n {t('integrations.marketplace.pagination.prev', 'Previous')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={data.page >= data.totalPages}\n onClick={() => setPage((p) => p + 1)}\n >\n {t('integrations.marketplace.pagination.next', 'Next')}\n </Button>\n </div>\n </div>\n ) : null}\n\n {grouped.bundles.map((bundle) => (\n <Card key={bundle.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{bundle.title}</CardTitle>\n {bundle.description && (\n <p className=\"text-muted-foreground text-sm mt-1\">{bundle.description}</p>\n )}\n <p className=\"text-muted-foreground text-xs mt-1\">\n {t('integrations.marketplace.integrations', { count: bundle.integrations.length })}\n </p>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/integrations/bundle/${encodeURIComponent(bundle.id)}`}>\n <Cog className=\"mr-1.5 h-4 w-4\" />\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {bundle.integrations.map((item) => (\n <div\n key={item.id}\n className=\"flex flex-col gap-2 rounded-lg border p-3\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground shrink-0')}\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"truncate text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {renderHealthBadge(item.healthStatus)}\n </div>\n {(item.company || item.author || item.version) ? (\n <p className=\"text-xs text-muted-foreground\">\n {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' \u00B7 ')}\n </p>\n ) : null}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked, item.updatedAt)}\n className=\"shrink-0\"\n />\n </div>\n <div className=\"flex items-end justify-between gap-2 border-t pt-2\">\n <div className=\"text-xs text-muted-foreground space-y-0.5\">\n <div>\n {t('integrations.marketplace.analytics.events', { count: item.analytics.totalCount })}\n </div>\n <div>\n {t('integrations.marketplace.analytics.errorRate', {\n rate: `${Math.round(item.analytics.errorRate * 1000) / 10}%`,\n })}\n </div>\n </div>\n <LogSparkline counts={item.analytics.dailyCounts} />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n\n {grouped.standalone.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {grouped.standalone.map((item) => (\n <Card key={item.id} className=\"flex flex-col\">\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground shrink-0')}\n <CardTitle className=\"text-base\">{item.title}</CardTitle>\n {renderHealthBadge(item.healthStatus)}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n className=\"shrink-0\"\n />\n </div>\n </CardHeader>\n <CardContent className=\"flex-1 space-y-2\">\n {item.description && (\n <p className=\"text-muted-foreground text-sm\">{item.description}</p>\n )}\n {(item.company || item.author || item.version) && (\n <p className=\"text-muted-foreground text-xs\">\n {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' \u00B7 ')}\n </p>\n )}\n <div className=\"flex items-end justify-between gap-2 border-t pt-3\">\n <div className=\"text-xs text-muted-foreground space-y-0.5\">\n <div>\n {t('integrations.marketplace.analytics.events', { count: item.analytics.totalCount })}\n </div>\n <div>\n {t('integrations.marketplace.analytics.errorRate', {\n rate: `${Math.round(item.analytics.errorRate * 1000) / 10}%`,\n })}\n </div>\n </div>\n <LogSparkline counts={item.analytics.dailyCounts} />\n </div>\n </CardContent>\n <div className=\"px-6 pb-4\">\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n <Cog className=\"mr-1.5 h-4 w-4\" />\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n {grouped.bundles.length === 0 && grouped.standalone.length === 0 && !isLoading && (\n <div className=\"text-center py-12 text-muted-foreground\">\n {t('integrations.marketplace.noResults')}\n </div>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FilterBar, type FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Bell, Cog, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Search, Truck, Webhook } from 'lucide-react'\nimport {\n buildIntegrationMarketplaceFilterDefs,\n getIntegrationMarketplaceCategory,\n getListQueryFromFilterValues,\n INTEGRATION_MARKETPLACE_CATEGORIES,\n normalizeIntegrationMarketplaceFilterValues,\n} from './filters'\n\ntype IntegrationAnalytics = {\n lastActivityAt: string | null\n totalCount: number\n errorCount: number\n errorRate: number\n dailyCounts: number[]\n}\n\ntype IntegrationItem = {\n id: string\n title: string\n description?: string\n category?: string\n tags?: string[]\n bundleId?: string\n author?: string\n company?: string\n version?: string\n isEnabled: boolean\n hasCredentials: boolean\n healthStatus: 'healthy' | 'degraded' | 'unhealthy' | 'unconfigured'\n analytics: IntegrationAnalytics\n updatedAt?: string | null\n}\n\ntype BundleItem = {\n id: string\n title: string\n description?: string\n icon?: string\n integrationCount: number\n enabledCount: number\n}\n\ntype ListResponse = {\n items: IntegrationItem[]\n bundles: BundleItem[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nconst CATEGORY_ICONS: Record<string, React.ElementType> = {\n all: LayoutGrid,\n payment: CreditCard,\n shipping: Truck,\n data_sync: RefreshCw,\n communication: MessageSquare,\n notification: Bell,\n storage: HardDrive,\n webhook: Webhook,\n}\n\nconst HEALTH_BADGE_CLASS: Record<string, string> = {\n healthy: 'bg-emerald-500/15 text-emerald-800 dark:text-emerald-300',\n degraded: 'bg-amber-500/15 text-amber-900 dark:text-amber-300',\n unhealthy: 'bg-destructive/15 text-destructive',\n unconfigured: 'bg-muted text-muted-foreground',\n}\n\nfunction buildListQueryString(input: {\n q?: string\n category?: string\n bundleId?: string\n isEnabled?: boolean\n healthStatus?: string\n sort?: string\n order?: string\n page?: number\n pageSize?: number\n}): string {\n const params = new URLSearchParams()\n if (input.q) params.set('q', input.q)\n if (input.category) params.set('category', input.category)\n if (input.bundleId) params.set('bundleId', input.bundleId)\n if (input.isEnabled !== undefined) params.set('isEnabled', String(input.isEnabled))\n if (input.healthStatus) params.set('healthStatus', input.healthStatus)\n if (input.sort) params.set('sort', input.sort)\n if (input.order) params.set('order', input.order)\n if (input.page != null && input.page > 1) params.set('page', String(input.page))\n if (input.pageSize != null && input.pageSize !== 100) params.set('pageSize', String(input.pageSize))\n const qs = params.toString()\n return qs ? `?${qs}` : ''\n}\n\nfunction LogSparkline({ counts, className }: { counts: number[]; className?: string }) {\n const max = Math.max(1, ...counts)\n const w = 72\n const h = 22\n const step = counts.length > 1 ? w / (counts.length - 1) : w\n const points = counts.map((count, index) => {\n const x = index * step\n const y = h - (count / max) * (h - 3) - 1.5\n return `${x},${y}`\n }).join(' ')\n return (\n <svg width={w} height={h} className={className} aria-hidden>\n <polyline\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.2\"\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n points={points}\n className=\"text-muted-foreground/80\"\n />\n </svg>\n )\n}\n\nexport default function IntegrationsMarketplacePage() {\n const [data, setData] = React.useState<ListResponse | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [searchInput, setSearchInput] = React.useState('')\n const [debouncedSearch, setDebouncedSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [sortField, setSortField] = React.useState<'title' | 'category' | 'enabledAt' | 'healthStatus'>('title')\n const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>('asc')\n const [page, setPage] = React.useState(1)\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const { runMutation, retryLastMutation } = useGuardedMutation<Record<string, unknown>>({\n contextId: 'integrations.marketplace',\n })\n\n React.useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(searchInput.trim()), 300)\n return () => clearTimeout(timer)\n }, [searchInput])\n\n const selectedCategory = React.useMemo(() => getIntegrationMarketplaceCategory(filterValues), [filterValues])\n\n const listQuery = React.useMemo(() => {\n const fromFilters = getListQueryFromFilterValues(filterValues)\n const category = selectedCategory !== 'all' ? selectedCategory : fromFilters.category\n return buildListQueryString({\n q: debouncedSearch || undefined,\n ...(category ? { category } : {}),\n ...(fromFilters.bundleId ? { bundleId: fromFilters.bundleId } : {}),\n ...(fromFilters.isEnabled !== undefined ? { isEnabled: fromFilters.isEnabled } : {}),\n ...(fromFilters.healthStatus ? { healthStatus: fromFilters.healthStatus } : {}),\n sort: sortField,\n order: sortOrder,\n page,\n pageSize: 100,\n })\n }, [debouncedSearch, filterValues, page, selectedCategory, sortField, sortOrder])\n\n const bundleFilterOptions = React.useMemo(\n () => (data?.bundles ?? []).map((bundle) => ({ id: bundle.id, title: bundle.title })),\n [data?.bundles],\n )\n\n const categoryFilters = React.useMemo(\n () => buildIntegrationMarketplaceFilterDefs(t, bundleFilterOptions),\n [bundleFilterOptions, t],\n )\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n const fallback: ListResponse = { items: [], bundles: [], total: 0, page: 1, pageSize: 100, totalPages: 1 }\n const call = await apiCall<ListResponse>(`/api/integrations${listQuery}`, undefined, { fallback })\n if (!call.ok) {\n flash(t('integrations.marketplace.loadError'), 'error')\n setIsLoading(false)\n return\n }\n setData(call.result ?? fallback)\n setIsLoading(false)\n }, [listQuery, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n React.useEffect(() => {\n setPage(1)\n }, [debouncedSearch, filterValues, selectedCategory, sortField, sortOrder])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean, updatedAt?: string | null) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n try {\n const call = await runMutation({\n mutationPayload: { integrationId, isEnabled: enabled },\n context: {\n formId: 'integrations.marketplace',\n operation: 'update',\n actionId: 'toggle-state',\n resourceKind: 'integrations.integration',\n resourceId: integrationId,\n integrationId,\n retryLastMutation,\n },\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(updatedAt),\n () => apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null }),\n ),\n })\n\n if (!call.ok) {\n flash(t('integrations.detail.stateError'), 'error')\n } else {\n setData((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n items: prev.items.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n }\n } catch {\n flash(t('integrations.detail.stateError'), 'error')\n } finally {\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }\n }, [retryLastMutation, runMutation, t])\n\n const grouped = React.useMemo(() => {\n if (!data) return { bundles: [] as Array<BundleItem & { integrations: IntegrationItem[] }>, standalone: [] as IntegrationItem[] }\n\n const bundled = new Map<string, IntegrationItem[]>()\n const standalone: IntegrationItem[] = []\n for (const item of data.items) {\n if (item.bundleId) {\n const list = bundled.get(item.bundleId) ?? []\n list.push(item)\n bundled.set(item.bundleId, list)\n } else {\n standalone.push(item)\n }\n }\n\n const bundles = (data.bundles ?? [])\n .filter((b) => bundled.has(b.id))\n .map((b) => ({ ...b, integrations: bundled.get(b.id) ?? [] }))\n\n return { bundles, standalone }\n }, [data])\n\n const renderCategoryIcon = React.useCallback((category: string | undefined, className: string) => {\n if (!category) return null\n const Icon = CATEGORY_ICONS[category]\n if (!Icon) return null\n return <Icon className={className} />\n }, [])\n\n const renderHealthBadge = React.useCallback((status: IntegrationItem['healthStatus']) => {\n return (\n <span\n className={`inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-semibold uppercase tracking-wide ${HEALTH_BADGE_CLASS[status] ?? HEALTH_BADGE_CLASS.unconfigured}`}\n >\n {t(`integrations.marketplace.health.${status}`, status)}\n </span>\n )\n }, [t])\n\n if (isLoading && !data) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center py-16\">\n <Spinner />\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <section className=\"space-y-6 rounded-lg border bg-background p-6\">\n <header className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"space-y-0.5\">\n <h2 className=\"text-lg font-semibold\">{t('integrations.marketplace.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('integrations.marketplace.description')}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"relative w-full min-w-[200px] max-w-xs lg:w-64\">\n <Search className=\"absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground pointer-events-none\" />\n <Input\n placeholder={t('integrations.marketplace.search')}\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"pl-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"sr-only\">{t('integrations.marketplace.sort.label', 'Sort by')}</span>\n <select\n className=\"h-9 rounded-md border border-input bg-background px-2 text-sm\"\n value={sortField}\n onChange={(e) => setSortField(e.target.value as typeof sortField)}\n >\n <option value=\"title\">{t('integrations.marketplace.sort.title', 'Title')}</option>\n <option value=\"category\">{t('integrations.marketplace.sort.category', 'Category')}</option>\n <option value=\"enabledAt\">{t('integrations.marketplace.sort.enabledAt', 'Enabled date')}</option>\n <option value=\"healthStatus\">{t('integrations.marketplace.sort.health', 'Health')}</option>\n </select>\n </label>\n <select\n className=\"h-9 rounded-md border border-input bg-background px-2 text-sm\"\n value={sortOrder}\n onChange={(e) => setSortOrder(e.target.value as typeof sortOrder)}\n aria-label={t('integrations.marketplace.sort.order', 'Sort order')}\n >\n <option value=\"asc\">{t('integrations.marketplace.sort.asc', 'Ascending')}</option>\n <option value=\"desc\">{t('integrations.marketplace.sort.desc', 'Descending')}</option>\n </select>\n </div>\n </header>\n\n <div className=\"lg:hidden\">\n <FilterBar\n searchValue={searchInput}\n onSearchChange={setSearchInput}\n searchPlaceholder={t('integrations.marketplace.search')}\n searchAlign=\"left\"\n filters={categoryFilters}\n values={filterValues}\n onApply={(values) => setFilterValues(normalizeIntegrationMarketplaceFilterValues(values))}\n onClear={() => setFilterValues({})}\n />\n </div>\n\n <div className=\"hidden lg:flex flex-wrap gap-1.5\">\n {INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {\n const Icon = CATEGORY_ICONS[category]\n return (\n <Button\n key={category}\n type=\"button\"\n variant={selectedCategory === category ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category }))}\n >\n {Icon ? <Icon className=\"mr-1.5 h-3.5 w-3.5\" /> : null}\n {t(`integrations.marketplace.categories.${category}`)}\n </Button>\n )\n })}\n </div>\n\n {isLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : null}\n\n {data && data.totalPages > 1 ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm text-muted-foreground\">\n <span>\n {data.total === 0\n ? t('integrations.marketplace.pagination.empty', 'No results')\n : t('integrations.marketplace.pagination.summary', {\n from: (data.page - 1) * data.pageSize + 1,\n to: Math.min(data.page * data.pageSize, data.total),\n total: data.total,\n })}\n </span>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={data.page <= 1}\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n >\n {t('integrations.marketplace.pagination.prev', 'Previous')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={data.page >= data.totalPages}\n onClick={() => setPage((p) => p + 1)}\n >\n {t('integrations.marketplace.pagination.next', 'Next')}\n </Button>\n </div>\n </div>\n ) : null}\n\n {grouped.bundles.map((bundle) => (\n <Card key={bundle.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{bundle.title}</CardTitle>\n {bundle.description && (\n <p className=\"text-muted-foreground text-sm mt-1\">{bundle.description}</p>\n )}\n <p className=\"text-muted-foreground text-xs mt-1\">\n {t('integrations.marketplace.integrations', { count: bundle.integrations.length })}\n </p>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/integrations/bundle/${encodeURIComponent(bundle.id)}`}>\n <Cog className=\"mr-1.5 h-4 w-4\" />\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {bundle.integrations.map((item) => (\n <div\n key={item.id}\n className=\"flex flex-col gap-2 rounded-lg border p-3\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground shrink-0')}\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"truncate text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {renderHealthBadge(item.healthStatus)}\n </div>\n {(item.company || item.author || item.version) ? (\n <p className=\"text-xs text-muted-foreground\">\n {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' \u00B7 ')}\n </p>\n ) : null}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked, item.updatedAt)}\n className=\"shrink-0\"\n />\n </div>\n <div className=\"flex items-end justify-between gap-2 border-t pt-2\">\n <div className=\"text-xs text-muted-foreground space-y-0.5\">\n <div>\n {t('integrations.marketplace.analytics.events', { count: item.analytics.totalCount })}\n </div>\n <div>\n {t('integrations.marketplace.analytics.errorRate', {\n rate: `${Math.round(item.analytics.errorRate * 1000) / 10}%`,\n })}\n </div>\n </div>\n <LogSparkline counts={item.analytics.dailyCounts} />\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n\n {grouped.standalone.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {grouped.standalone.map((item) => (\n <Card key={item.id} className=\"flex flex-col\">\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground shrink-0')}\n <CardTitle className=\"text-base\">{item.title}</CardTitle>\n {renderHealthBadge(item.healthStatus)}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n className=\"shrink-0\"\n />\n </div>\n </CardHeader>\n <CardContent className=\"flex-1 space-y-2\">\n {item.description && (\n <p className=\"text-muted-foreground text-sm\">{item.description}</p>\n )}\n {(item.company || item.author || item.version) && (\n <p className=\"text-muted-foreground text-xs\">\n {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' \u00B7 ')}\n </p>\n )}\n <div className=\"flex items-end justify-between gap-2 border-t pt-3\">\n <div className=\"text-xs text-muted-foreground space-y-0.5\">\n <div>\n {t('integrations.marketplace.analytics.events', { count: item.analytics.totalCount })}\n </div>\n <div>\n {t('integrations.marketplace.analytics.errorRate', {\n rate: `${Math.round(item.analytics.errorRate * 1000) / 10}%`,\n })}\n </div>\n </div>\n <LogSparkline counts={item.analytics.dailyCounts} />\n </div>\n </CardContent>\n <div className=\"px-6 pb-4\">\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n <Cog className=\"mr-1.5 h-4 w-4\" />\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n {grouped.bundles.length === 0 && grouped.standalone.length === 0 && !isLoading && (\n <div className=\"text-center py-12 text-muted-foreground\">\n {t('integrations.marketplace.noResults')}\n </div>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,11 @@
1
1
  import { executeActionSchema } from "../../../data/validators.js";
2
2
  import { actionResultResponseSchema, errorResponseSchema } from "../../openapi.js";
3
3
  import {
4
+ NOTIFICATION_RESOURCE_KIND,
4
5
  notificationCrudErrorResponse,
5
6
  notificationValidationErrorResponse,
6
- resolveNotificationContext
7
+ resolveNotificationContext,
8
+ runGuardedNotificationWrite
7
9
  } from "../../../lib/routeHelpers.js";
8
10
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
9
11
  const metadata = {
@@ -11,7 +13,7 @@ const metadata = {
11
13
  };
12
14
  async function POST(req, { params }) {
13
15
  const { id } = await params;
14
- const { service, scope } = await resolveNotificationContext(req);
16
+ const { service, scope, ctx } = await resolveNotificationContext(req);
15
17
  const body = await req.json().catch(() => ({}));
16
18
  const parsed = executeActionSchema.safeParse(body);
17
19
  if (!parsed.success) {
@@ -19,7 +21,20 @@ async function POST(req, { params }) {
19
21
  }
20
22
  const input = parsed.data;
21
23
  try {
22
- const { notification, result } = await service.executeAction(id, input, scope);
24
+ const guarded = await runGuardedNotificationWrite(
25
+ ctx.container,
26
+ scope,
27
+ req,
28
+ {
29
+ resourceKind: NOTIFICATION_RESOURCE_KIND,
30
+ resourceId: id,
31
+ operation: "custom",
32
+ payload: input
33
+ },
34
+ () => service.executeAction(id, input, scope)
35
+ );
36
+ if (!guarded.ok) return guarded.response;
37
+ const { notification, result } = guarded.result;
23
38
  const action = notification.actionData?.actions?.find((a) => a.id === input.actionId);
24
39
  const href = action?.href?.replace("{sourceEntityId}", notification.sourceEntityId ?? "");
25
40
  return Response.json({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/notifications/api/%5Bid%5D/action/route.ts"],
4
- "sourcesContent": ["import { executeActionSchema } from '../../../data/validators'\nimport { actionResultResponseSchema, errorResponseSchema } from '../../openapi'\nimport {\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n} from '../../../lib/routeHelpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = executeActionSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n const input = parsed.data\n\n try {\n const { notification, result } = await service.executeAction(id, input, scope)\n\n const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)\n const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')\n\n return Response.json({\n ok: true,\n result,\n href,\n })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n\n const { t } = await resolveTranslations()\n const fallback = t('notifications.error.action', 'Failed to execute action')\n const message = error instanceof Error && error.message ? error.message : fallback\n return Response.json({ error: message }, { status: 400 })\n }\n}\n\nexport const openApi = {\n POST: {\n summary: 'Execute notification action',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: executeActionSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Action executed successfully',\n content: {\n 'application/json': {\n schema: actionResultResponseSchema,\n },\n },\n },\n 400: {\n description: 'Action not found or failed',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,2BAA2B;AAChE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,KAAK;AAC5B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAwC;AACxF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,MAAM,IAAI,MAAM,2BAA2B,GAAG;AAE/D,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,oBAAoB,UAAU,IAAI;AACjD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,oCAAoC,OAAO,KAAK;AAAA,EACzD;AACA,QAAM,QAAQ,OAAO;AAErB,MAAI;AACF,UAAM,EAAE,cAAc,OAAO,IAAI,MAAM,QAAQ,cAAc,IAAI,OAAO,KAAK;AAE7E,UAAM,SAAS,aAAa,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,QAAQ;AACpF,UAAM,OAAO,QAAQ,MAAM,QAAQ,oBAAoB,aAAa,kBAAkB,EAAE;AAExF,WAAO,SAAS,KAAK;AAAA,MACnB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,gBAAgB,8BAA8B,KAAK;AACzD,QAAI,cAAe,QAAO;AAE1B,UAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,UAAM,WAAW,EAAE,8BAA8B,0BAA0B;AAC3E,UAAM,UAAU,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;AAC1E,WAAO,SAAS,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { executeActionSchema } from '../../../data/validators'\nimport { actionResultResponseSchema, errorResponseSchema } from '../../openapi'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../../../lib/routeHelpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = executeActionSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n const input = parsed.data\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n resourceId: id,\n operation: 'custom',\n payload: input as Record<string, unknown>,\n },\n () => service.executeAction(id, input, scope),\n )\n if (!guarded.ok) return guarded.response\n const { notification, result } = guarded.result\n\n const action = notification.actionData?.actions?.find((a) => a.id === input.actionId)\n const href = action?.href?.replace('{sourceEntityId}', notification.sourceEntityId ?? '')\n\n return Response.json({\n ok: true,\n result,\n href,\n })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n\n const { t } = await resolveTranslations()\n const fallback = t('notifications.error.action', 'Failed to execute action')\n const message = error instanceof Error && error.message ? error.message : fallback\n return Response.json({ error: message }, { status: 400 })\n }\n}\n\nexport const openApi = {\n POST: {\n summary: 'Execute notification action',\n tags: ['Notifications'],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string', format: 'uuid' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: executeActionSchema,\n },\n },\n },\n responses: {\n 200: {\n description: 'Action executed successfully',\n content: {\n 'application/json': {\n schema: actionResultResponseSchema,\n },\n },\n },\n 400: {\n description: 'Action not found or failed',\n content: {\n 'application/json': {\n schema: errorResponseSchema,\n },\n },\n },\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,2BAA2B;AAChE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,KAAK;AAC5B;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAwC;AACxF,QAAM,EAAE,GAAG,IAAI,MAAM;AACrB,QAAM,EAAE,SAAS,OAAO,IAAI,IAAI,MAAM,2BAA2B,GAAG;AAEpE,QAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,QAAM,SAAS,oBAAoB,UAAU,IAAI;AACjD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,oCAAoC,OAAO,KAAK;AAAA,EACzD;AACA,QAAM,QAAQ,OAAO;AAErB,MAAI;AACF,UAAM,UAAU,MAAM;AAAA,MACpB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,QACE,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,SAAS;AAAA,MACX;AAAA,MACA,MAAM,QAAQ,cAAc,IAAI,OAAO,KAAK;AAAA,IAC9C;AACA,QAAI,CAAC,QAAQ,GAAI,QAAO,QAAQ;AAChC,UAAM,EAAE,cAAc,OAAO,IAAI,QAAQ;AAEzC,UAAM,SAAS,aAAa,YAAY,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,QAAQ;AACpF,UAAM,OAAO,QAAQ,MAAM,QAAQ,oBAAoB,aAAa,kBAAkB,EAAE;AAExF,WAAO,SAAS,KAAK;AAAA,MACnB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,gBAAgB,8BAA8B,KAAK;AACzD,QAAI,cAAe,QAAO;AAE1B,UAAM,EAAE,EAAE,IAAI,MAAM,oBAAoB;AACxC,UAAM,WAAW,EAAE,8BAA8B,0BAA0B;AAC3E,UAAM,UAAU,iBAAiB,SAAS,MAAM,UAAU,MAAM,UAAU;AAC1E,WAAO,SAAS,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,MAAM,CAAC,eAAe;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,MAC3C;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX,UAAU;AAAA,MACV,SAAS;AAAA,QACP,oBAAoB;AAAA,UAClB,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA,WAAW;AAAA,MACT,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,MACA,KAAK;AAAA,QACH,aAAa;AAAA,QACb,SAAS;AAAA,UACP,oBAAoB;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }