@open-mercato/core 0.6.4-develop.4236.1.9fa6806b34 → 0.6.4-develop.4254.1.7a123d970c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/authFixtures.js +70 -1
- package/dist/helpers/integration/authFixtures.js.map +2 -2
- package/dist/helpers/integration/dbFixtures.js +98 -0
- package/dist/helpers/integration/dbFixtures.js.map +7 -0
- package/dist/modules/business_rules/api/execute/route.js +2 -1
- package/dist/modules/business_rules/api/execute/route.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +10 -0
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/business_rules/backend/logs/[id]/page.js +24 -5
- package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
- package/dist/modules/business_rules/cli.js +6 -0
- package/dist/modules/business_rules/cli.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-engine.js +116 -9
- package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
- package/dist/modules/business_rules/subscribers/crud-rule-trigger.js +3 -2
- package/dist/modules/business_rules/subscribers/crud-rule-trigger.js.map +2 -2
- package/dist/modules/catalog/api/offers/route.js +15 -5
- package/dist/modules/catalog/api/offers/route.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +21 -4
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/lib/pricing.js +6 -0
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/services/catalogPricingService.js +5 -1
- package/dist/modules/catalog/services/catalogPricingService.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +15 -2
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/comments/route.js +15 -3
- package/dist/modules/customers/api/comments/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/people/route.js +2 -4
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +2 -4
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js +2 -4
- package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +2 -4
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +2 -9
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/stats/route.js +2 -9
- package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
- package/dist/modules/customers/api/entity-roles-factory.js +2 -8
- package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/context.js +2 -4
- package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +2 -4
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +2 -4
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
- package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
- package/dist/modules/directory/utils/organizationScopeGuard.js +22 -0
- package/dist/modules/directory/utils/organizationScopeGuard.js.map +7 -0
- package/dist/modules/progress/acl.js +8 -4
- package/dist/modules/progress/acl.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
- package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/cli.js +8 -0
- package/dist/modules/workflows/cli.js.map +2 -2
- package/dist/modules/workflows/lib/seeds.js +8 -4
- package/dist/modules/workflows/lib/seeds.js.map +2 -2
- package/dist/modules/workflows/setup.js +3 -1
- package/dist/modules/workflows/setup.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/authFixtures.ts +98 -0
- package/src/helpers/integration/dbFixtures.ts +144 -0
- package/src/modules/business_rules/api/execute/route.ts +2 -1
- package/src/modules/business_rules/api/rules/route.ts +10 -0
- package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
- package/src/modules/business_rules/cli.ts +6 -0
- package/src/modules/business_rules/lib/rule-engine.ts +163 -9
- package/src/modules/business_rules/subscribers/crud-rule-trigger.ts +3 -2
- package/src/modules/catalog/api/offers/route.ts +20 -5
- package/src/modules/catalog/api/products/route.ts +23 -4
- package/src/modules/catalog/lib/pricing.ts +9 -0
- package/src/modules/catalog/services/catalogPricingService.ts +6 -0
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
- package/src/modules/currencies/i18n/de.json +1 -0
- package/src/modules/currencies/i18n/en.json +1 -0
- package/src/modules/currencies/i18n/es.json +1 -0
- package/src/modules/currencies/i18n/pl.json +1 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
- package/src/modules/customers/api/activities/route.ts +16 -5
- package/src/modules/customers/api/comments/route.ts +15 -5
- package/src/modules/customers/api/companies/[id]/people/route.ts +2 -4
- package/src/modules/customers/api/companies/[id]/route.ts +2 -5
- package/src/modules/customers/api/deals/[id]/companies/route.ts +2 -4
- package/src/modules/customers/api/deals/[id]/people/route.ts +2 -4
- package/src/modules/customers/api/deals/[id]/route.ts +2 -9
- package/src/modules/customers/api/deals/[id]/stats/route.ts +2 -9
- package/src/modules/customers/api/entity-roles-factory.ts +2 -12
- package/src/modules/customers/api/people/[id]/companies/context.ts +2 -5
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +2 -5
- package/src/modules/customers/api/people/[id]/route.ts +2 -5
- package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
- package/src/modules/directory/utils/organizationScopeGuard.ts +39 -0
- package/src/modules/progress/acl.ts +4 -0
- package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
- package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
- package/src/modules/workflows/cli.ts +8 -0
- package/src/modules/workflows/i18n/de.json +1 -0
- package/src/modules/workflows/i18n/en.json +1 -0
- package/src/modules/workflows/i18n/es.json +1 -0
- package/src/modules/workflows/i18n/pl.json +1 -0
- package/src/modules/workflows/lib/seeds.ts +13 -3
- package/src/modules/workflows/setup.ts +3 -1
|
@@ -11,6 +11,7 @@ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
|
11
11
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
12
12
|
import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages";
|
|
13
13
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
14
|
+
import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
14
15
|
function EditCurrencyPage({ params }) {
|
|
15
16
|
const t = useT();
|
|
16
17
|
const router = useRouter();
|
|
@@ -18,14 +19,17 @@ function EditCurrencyPage({ params }) {
|
|
|
18
19
|
const [currency, setCurrency] = React.useState(null);
|
|
19
20
|
const [loading, setLoading] = React.useState(true);
|
|
20
21
|
const [error, setError] = React.useState(null);
|
|
22
|
+
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
21
23
|
React.useEffect(() => {
|
|
22
24
|
async function loadCurrency() {
|
|
23
25
|
try {
|
|
24
26
|
const response = await apiCall(`/api/currencies/currencies?id=${params?.id}`);
|
|
25
27
|
if (response.ok && response.result && response.result.items.length > 0) {
|
|
26
28
|
setCurrency(response.result.items[0]);
|
|
29
|
+
} else if (!response.ok) {
|
|
30
|
+
setError(t("currencies.form.errors.load"));
|
|
27
31
|
} else {
|
|
28
|
-
|
|
32
|
+
setIsNotFound(true);
|
|
29
33
|
}
|
|
30
34
|
} catch (err) {
|
|
31
35
|
setError(t("currencies.form.errors.load"));
|
|
@@ -132,9 +136,22 @@ function EditCurrencyPage({ params }) {
|
|
|
132
136
|
ConfirmDialogElement
|
|
133
137
|
] });
|
|
134
138
|
}
|
|
139
|
+
if (isNotFound) {
|
|
140
|
+
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
141
|
+
/* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
142
|
+
RecordNotFoundState,
|
|
143
|
+
{
|
|
144
|
+
label: t("currencies.form.errors.notFound", "Currency not found."),
|
|
145
|
+
backHref: "/backend/currencies",
|
|
146
|
+
backLabel: t("currencies.form.actions.backToList", "Back to currencies")
|
|
147
|
+
}
|
|
148
|
+
) }),
|
|
149
|
+
ConfirmDialogElement
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
135
152
|
if (error || !currency) {
|
|
136
153
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
137
|
-
/* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
154
|
+
/* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("currencies.form.errors.notFound", "Currency not found.") }) }),
|
|
138
155
|
ConfirmDialogElement
|
|
139
156
|
] });
|
|
140
157
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/currencies/backend/currencies/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else {\n setError(t('currencies.form.errors.
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useParams } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport { DataLoader } from '@open-mercato/ui/primitives/DataLoader'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype CurrencyData = {\n id: string\n code: string\n name: string\n symbol: string | null\n decimalPlaces: number\n thousandsSeparator: string | null\n decimalSeparator: string | null\n isBase: boolean\n isActive: boolean\n organizationId: string\n tenantId: string\n}\n\nexport default function EditCurrencyPage({ params }: { params?: { id?: string } }) {\n const t = useT()\n const router = useRouter()\n const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()\n\n const [currency, setCurrency] = React.useState<CurrencyData | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n React.useEffect(() => {\n async function loadCurrency() {\n try {\n const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)\n if (response.ok && response.result && response.result.items.length > 0) {\n setCurrency(response.result.items[0])\n } else if (!response.ok) {\n setError(t('currencies.form.errors.load'))\n } else {\n setIsNotFound(true)\n }\n } catch (err) {\n setError(t('currencies.form.errors.load'))\n } finally {\n setLoading(false)\n }\n }\n loadCurrency()\n }, [params, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n column: 1,\n title: t('currencies.form.group.details'),\n fields: [\n {\n id: 'code',\n type: 'text',\n label: t('currencies.form.field.code'),\n placeholder: t('currencies.form.field.codePlaceholder'),\n required: true,\n maxLength: 3,\n helpText: t('currencies.form.field.codeHelp'),\n },\n {\n id: 'name',\n type: 'text',\n label: t('currencies.form.field.name'),\n placeholder: t('currencies.form.field.namePlaceholder'),\n required: true,\n },\n {\n id: 'symbol',\n type: 'text',\n label: t('currencies.form.field.symbol'),\n placeholder: t('currencies.form.field.symbolPlaceholder'),\n },\n ],\n },\n {\n id: 'formatting',\n column: 2,\n title: t('currencies.form.group.formatting'),\n fields: [\n {\n id: 'decimalPlaces',\n type: 'number',\n label: t('currencies.form.field.decimalPlaces'),\n min: 0,\n max: 8,\n },\n {\n id: 'thousandsSeparator',\n type: 'text',\n label: t('currencies.form.field.thousandsSeparator'),\n placeholder: ',',\n maxLength: 5,\n },\n {\n id: 'decimalSeparator',\n type: 'text',\n label: t('currencies.form.field.decimalSeparator'),\n placeholder: '.',\n maxLength: 5,\n },\n {\n id: 'isBase',\n type: 'checkbox',\n label: t('currencies.form.field.isBase'),\n },\n {\n id: 'isActive',\n type: 'checkbox',\n label: t('currencies.form.field.isActive'),\n },\n ],\n },\n ],\n [t]\n )\n\n const handleDelete = React.useCallback(async () => {\n if (!currency) return\n\n const confirmed = await confirmDialog({\n title: t('currencies.list.confirmDelete', { code: currency.code }),\n variant: 'destructive',\n })\n if (!confirmed) return\n\n try {\n await apiCall('/api/currencies/currencies', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: currency.id, organizationId: currency.organizationId, tenantId: currency.tenantId }),\n })\n\n flash(t('currencies.flash.deleted'), 'success')\n router.push('/backend/currencies')\n } catch (error) {\n flash(t('currencies.flash.deleteError'), 'error')\n }\n }, [currency, t, router, confirmDialog])\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center p-8\">\n <div className=\"text-muted-foreground\">{t('currencies.form.loading')}</div>\n </div>\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('currencies.form.errors.notFound', 'Currency not found.')}\n backHref=\"/backend/currencies\"\n backLabel={t('currencies.form.actions.backToList', 'Back to currencies')}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n if (error || !currency) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('currencies.form.errors.notFound', 'Currency not found.')} />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={t('currencies.edit.title')}\n backHref=\"/backend/currencies\"\n versionHistory={{ resourceKind: 'currencies.currency', resourceId: currency.id }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'currencies',\n entityType: 'currency',\n entityId: currency.id,\n previewData: {\n title: currency.name,\n subtitle: currency.code,\n metadata: {\n [t('currencies.form.field.code')]: currency.code,\n [t('currencies.form.field.name')]: currency.name,\n [t('currencies.form.field.symbol')]: currency.symbol || '-',\n },\n },\n }}\n viewHref={`/backend/currencies/${currency.id}`}\n />\n )}\n fields={[]}\n groups={groups}\n initialValues={{\n code: currency.code,\n name: currency.name,\n symbol: currency.symbol || '',\n decimalPlaces: currency.decimalPlaces,\n thousandsSeparator: currency.thousandsSeparator || '',\n decimalSeparator: currency.decimalSeparator || '',\n isBase: currency.isBase,\n isActive: currency.isActive,\n }}\n submitLabel={t('currencies.form.action.save')}\n cancelHref=\"/backend/currencies\"\n onSubmit={async (values) => {\n // Validate currency code\n const code = String(values.code || '').trim().toUpperCase()\n if (!/^[A-Z]{3}$/.test(code)) {\n throw createCrudFormError(t('currencies.form.errors.codeFormat'), {\n code: t('currencies.form.errors.codeFormat'),\n })\n }\n\n const payload = {\n id: currency.id,\n code,\n name: String(values.name || '').trim(),\n symbol: values.symbol ? String(values.symbol).trim() : null,\n decimalPlaces: values.decimalPlaces ? parseInt(String(values.decimalPlaces)) : 2,\n thousandsSeparator: values.thousandsSeparator ? String(values.thousandsSeparator) : null,\n decimalSeparator: values.decimalSeparator ? String(values.decimalSeparator) : null,\n isBase: !!values.isBase,\n isActive: values.isActive !== false,\n }\n\n await updateCrud('currencies/currencies', payload)\n\n flash(t('currencies.flash.updated'), 'success')\n router.push('/backend/currencies')\n }}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA8JM,SAGM,KAHN;AA5JN,YAAY,WAAW;AACvB,SAAS,iBAA4B;AACrC,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAoC;AAC7C,SAAS,kBAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,+BAA+B;AAExC,SAAS,wBAAwB;AACjC,SAAS,qBAAqB,oBAAoB;AAgBnC,SAAR,iBAAkC,EAAE,OAAO,GAAiC;AACjF,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,SAAS,eAAe,qBAAqB,IAAI,iBAAiB;AAE1E,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA8B,IAAI;AACxE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,UAAU,MAAM;AACpB,mBAAe,eAAe;AAC5B,UAAI;AACF,cAAM,WAAW,MAAM,QAAmC,iCAAiC,QAAQ,EAAE,EAAE;AACvG,YAAI,SAAS,MAAM,SAAS,UAAU,SAAS,OAAO,MAAM,SAAS,GAAG;AACtE,sBAAY,SAAS,OAAO,MAAM,CAAC,CAAC;AAAA,QACtC,WAAW,CAAC,SAAS,IAAI;AACvB,mBAAS,EAAE,6BAA6B,CAAC;AAAA,QAC3C,OAAO;AACL,wBAAc,IAAI;AAAA,QACpB;AAAA,MACF,SAAS,KAAK;AACZ,iBAAS,EAAE,6BAA6B,CAAC;AAAA,MAC3C,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AACA,iBAAa;AAAA,EACf,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEd,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO,EAAE,+BAA+B;AAAA,QACxC,QAAQ;AAAA,UACN;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,4BAA4B;AAAA,YACrC,aAAa,EAAE,uCAAuC;AAAA,YACtD,UAAU;AAAA,YACV,WAAW;AAAA,YACX,UAAU,EAAE,gCAAgC;AAAA,UAC9C;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,4BAA4B;AAAA,YACrC,aAAa,EAAE,uCAAuC;AAAA,YACtD,UAAU;AAAA,UACZ;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,8BAA8B;AAAA,YACvC,aAAa,EAAE,yCAAyC;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO,EAAE,kCAAkC;AAAA,QAC3C,QAAQ;AAAA,UACN;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,qCAAqC;AAAA,YAC9C,KAAK;AAAA,YACL,KAAK;AAAA,UACP;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,0CAA0C;AAAA,YACnD,aAAa;AAAA,YACb,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,wCAAwC;AAAA,YACjD,aAAa;AAAA,YACb,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,8BAA8B;AAAA,UACzC;AAAA,UACA;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,OAAO,EAAE,gCAAgC;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,eAAe,MAAM,YAAY,YAAY;AACjD,QAAI,CAAC,SAAU;AAEf,UAAM,YAAY,MAAM,cAAc;AAAA,MACpC,OAAO,EAAE,iCAAiC,EAAE,MAAM,SAAS,KAAK,CAAC;AAAA,MACjE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAEhB,QAAI;AACF,YAAM,QAAQ,8BAA8B;AAAA,QAC1C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,SAAS,IAAI,gBAAgB,SAAS,gBAAgB,UAAU,SAAS,SAAS,CAAC;AAAA,MAChH,CAAC;AAED,YAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,aAAO,KAAK,qBAAqB;AAAA,IACnC,SAASA,QAAO;AACd,YAAM,EAAE,8BAA8B,GAAG,OAAO;AAAA,IAClD;AAAA,EACF,GAAG,CAAC,UAAU,GAAG,QAAQ,aAAa,CAAC;AAEvC,MAAI,SAAS;AACX,WACE,qBAAC,QACC;AAAA,0BAAC,YACC,8BAAC,SAAI,WAAU,wCACb,8BAAC,SAAI,WAAU,yBAAyB,YAAE,yBAAyB,GAAE,GACvE,GACF;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,MAAI,YAAY;AACd,WACE,qBAAC,QACC;AAAA,0BAAC,YACC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,EAAE,mCAAmC,qBAAqB;AAAA,UACjE,UAAS;AAAA,UACT,WAAW,EAAE,sCAAsC,oBAAoB;AAAA;AAAA,MACzE,GACF;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,MAAI,SAAS,CAAC,UAAU;AACtB,WACE,qBAAC,QACC;AAAA,0BAAC,YACC,8BAAC,gBAAa,OAAO,SAAS,EAAE,mCAAmC,qBAAqB,GAAG,GAC7F;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,SACE,qBAAC,QACC;AAAA,wBAAC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,uBAAuB;AAAA,QAChC,UAAS;AAAA,QACT,gBAAgB,EAAE,cAAc,uBAAuB,YAAY,SAAS,GAAG;AAAA,QAC/E,cACE;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ;AAAA,cACN,cAAc;AAAA,cACd,YAAY;AAAA,cACZ,UAAU,SAAS;AAAA,cACnB,aAAa;AAAA,gBACX,OAAO,SAAS;AAAA,gBAChB,UAAU,SAAS;AAAA,gBACnB,UAAU;AAAA,kBACR,CAAC,EAAE,4BAA4B,CAAC,GAAG,SAAS;AAAA,kBAC5C,CAAC,EAAE,4BAA4B,CAAC,GAAG,SAAS;AAAA,kBAC5C,CAAC,EAAE,8BAA8B,CAAC,GAAG,SAAS,UAAU;AAAA,gBAC1D;AAAA,cACF;AAAA,YACF;AAAA,YACA,UAAU,uBAAuB,SAAS,EAAE;AAAA;AAAA,QAC9C;AAAA,QAEF,QAAQ,CAAC;AAAA,QACT;AAAA,QACA,eAAe;AAAA,UACb,MAAM,SAAS;AAAA,UACf,MAAM,SAAS;AAAA,UACf,QAAQ,SAAS,UAAU;AAAA,UAC3B,eAAe,SAAS;AAAA,UACxB,oBAAoB,SAAS,sBAAsB;AAAA,UACnD,kBAAkB,SAAS,oBAAoB;AAAA,UAC/C,QAAQ,SAAS;AAAA,UACjB,UAAU,SAAS;AAAA,QACrB;AAAA,QACA,aAAa,EAAE,6BAA6B;AAAA,QAC5C,YAAW;AAAA,QACX,UAAU,OAAO,WAAW;AAE1B,gBAAM,OAAO,OAAO,OAAO,QAAQ,EAAE,EAAE,KAAK,EAAE,YAAY;AAC1D,cAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,kBAAM,oBAAoB,EAAE,mCAAmC,GAAG;AAAA,cAChE,MAAM,EAAE,mCAAmC;AAAA,YAC7C,CAAC;AAAA,UACH;AAEA,gBAAM,UAAU;AAAA,YACd,IAAI,SAAS;AAAA,YACb;AAAA,YACA,MAAM,OAAO,OAAO,QAAQ,EAAE,EAAE,KAAK;AAAA,YACrC,QAAQ,OAAO,SAAS,OAAO,OAAO,MAAM,EAAE,KAAK,IAAI;AAAA,YACvD,eAAe,OAAO,gBAAgB,SAAS,OAAO,OAAO,aAAa,CAAC,IAAI;AAAA,YAC/E,oBAAoB,OAAO,qBAAqB,OAAO,OAAO,kBAAkB,IAAI;AAAA,YACpF,kBAAkB,OAAO,mBAAmB,OAAO,OAAO,gBAAgB,IAAI;AAAA,YAC9E,QAAQ,CAAC,CAAC,OAAO;AAAA,YACjB,UAAU,OAAO,aAAa;AAAA,UAChC;AAEA,gBAAM,WAAW,yBAAyB,OAAO;AAEjD,gBAAM,EAAE,0BAA0B,GAAG,SAAS;AAC9C,iBAAO,KAAK,qBAAqB;AAAA,QACnC;AAAA;AAAA,IACF,GACF;AAAA,IACC;AAAA,KACH;AAEJ;",
|
|
6
6
|
"names": ["error"]
|
|
7
7
|
}
|
|
@@ -10,6 +10,7 @@ import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
|
10
10
|
import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
11
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
12
12
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
13
|
+
import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
13
14
|
const PORTAL_FEATURES = [
|
|
14
15
|
{ id: "portal.profile.view", labelKey: "customer_accounts.admin.portalFeatures.profile.view", fallback: "View profile", descriptionKey: "customer_accounts.admin.portalFeatures.profile.view.description", descriptionFallback: "Allows viewing own profile information and account details" },
|
|
15
16
|
{ id: "portal.profile.edit", labelKey: "customer_accounts.admin.portalFeatures.profile.edit", fallback: "Edit profile", descriptionKey: "customer_accounts.admin.portalFeatures.profile.edit.description", descriptionFallback: "Allows editing display name and other profile settings" },
|
|
@@ -121,9 +122,10 @@ function CustomerRoleDetailPage({ params }) {
|
|
|
121
122
|
const [data, setData] = React.useState(null);
|
|
122
123
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
123
124
|
const [error, setError] = React.useState(null);
|
|
125
|
+
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
124
126
|
React.useEffect(() => {
|
|
125
127
|
if (!id) {
|
|
126
|
-
|
|
128
|
+
setIsNotFound(true);
|
|
127
129
|
setIsLoading(false);
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
@@ -131,6 +133,7 @@ function CustomerRoleDetailPage({ params }) {
|
|
|
131
133
|
async function load() {
|
|
132
134
|
setIsLoading(true);
|
|
133
135
|
setError(null);
|
|
136
|
+
setIsNotFound(false);
|
|
134
137
|
try {
|
|
135
138
|
const payload = await readApiResultOrThrow(
|
|
136
139
|
`/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,
|
|
@@ -141,8 +144,12 @@ function CustomerRoleDetailPage({ params }) {
|
|
|
141
144
|
setData(payload);
|
|
142
145
|
} catch (err) {
|
|
143
146
|
if (cancelled) return;
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
if (err.status === 404) {
|
|
148
|
+
setIsNotFound(true);
|
|
149
|
+
} else {
|
|
150
|
+
const message = err instanceof Error ? err.message : t("customer_accounts.admin.roleDetail.error.load", "Failed to load role");
|
|
151
|
+
setError(message);
|
|
152
|
+
}
|
|
146
153
|
} finally {
|
|
147
154
|
if (!cancelled) setIsLoading(false);
|
|
148
155
|
}
|
|
@@ -263,11 +270,24 @@ function CustomerRoleDetailPage({ params }) {
|
|
|
263
270
|
/* @__PURE__ */ jsx("span", { children: t("customer_accounts.admin.roleDetail.loading", "Loading role...") })
|
|
264
271
|
] }) }) });
|
|
265
272
|
}
|
|
273
|
+
if (isNotFound) {
|
|
274
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
275
|
+
RecordNotFoundState,
|
|
276
|
+
{
|
|
277
|
+
label: t("customer_accounts.admin.roleDetail.error.notFound", "Role not found"),
|
|
278
|
+
backHref: "/backend/customer_accounts/roles",
|
|
279
|
+
backLabel: t("customer_accounts.admin.roleDetail.actions.backToList", "Back to roles")
|
|
280
|
+
}
|
|
281
|
+
) }) });
|
|
282
|
+
}
|
|
266
283
|
if (error || !data) {
|
|
267
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
285
|
+
ErrorMessage,
|
|
286
|
+
{
|
|
287
|
+
label: error ?? t("customer_accounts.admin.roleDetail.error.notFound", "Role not found"),
|
|
288
|
+
action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/roles", children: t("customer_accounts.admin.roleDetail.actions.backToList", "Back to roles") }) })
|
|
289
|
+
}
|
|
290
|
+
) }) });
|
|
271
291
|
}
|
|
272
292
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
273
293
|
CrudForm,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customer_accounts/backend/customer_accounts/roles/%5Bid%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ntype RoleDetail = {\n id: string\n name: string\n slug: string\n description: string | null\n isDefault: boolean\n isSystem: boolean\n customerAssignable: boolean\n features: string[]\n}\n\nconst PORTAL_FEATURES = [\n { id: 'portal.profile.view', labelKey: 'customer_accounts.admin.portalFeatures.profile.view', fallback: 'View profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.view.description', descriptionFallback: 'Allows viewing own profile information and account details' },\n { id: 'portal.profile.edit', labelKey: 'customer_accounts.admin.portalFeatures.profile.edit', fallback: 'Edit profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.edit.description', descriptionFallback: 'Allows editing display name and other profile settings' },\n { id: 'portal.orders.view', labelKey: 'customer_accounts.admin.portalFeatures.orders.view', fallback: 'View orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.view.description', descriptionFallback: 'Allows viewing order history and order details' },\n { id: 'portal.orders.create', labelKey: 'customer_accounts.admin.portalFeatures.orders.create', fallback: 'Create orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.create.description', descriptionFallback: 'Allows placing new orders through the portal' },\n { id: 'portal.invoices.view', labelKey: 'customer_accounts.admin.portalFeatures.invoices.view', fallback: 'View invoices', descriptionKey: 'customer_accounts.admin.portalFeatures.invoices.view.description', descriptionFallback: 'Allows viewing invoices and payment history' },\n { id: 'portal.quotes.view', labelKey: 'customer_accounts.admin.portalFeatures.quotes.view', fallback: 'View quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.view.description', descriptionFallback: 'Allows viewing received quotes and their details' },\n { id: 'portal.quotes.request', labelKey: 'customer_accounts.admin.portalFeatures.quotes.request', fallback: 'Request quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.request.description', descriptionFallback: 'Allows requesting new quotes from the company' },\n { id: 'portal.addresses.view', labelKey: 'customer_accounts.admin.portalFeatures.addresses.view', fallback: 'View addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.view.description', descriptionFallback: 'Allows viewing saved shipping and billing addresses' },\n { id: 'portal.addresses.manage', labelKey: 'customer_accounts.admin.portalFeatures.addresses.manage', fallback: 'Manage addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.manage.description', descriptionFallback: 'Allows adding, editing, and removing addresses' },\n { id: 'portal.users.view', labelKey: 'customer_accounts.admin.portalFeatures.users.view', fallback: 'View team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.view.description', descriptionFallback: 'Allows viewing other team members in the organization' },\n { id: 'portal.users.invite', labelKey: 'customer_accounts.admin.portalFeatures.users.invite', fallback: 'Invite team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.invite.description', descriptionFallback: 'Allows sending portal invitations to new team members' },\n { id: 'portal.users.manage', labelKey: 'customer_accounts.admin.portalFeatures.users.manage', fallback: 'Manage team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.manage.description', descriptionFallback: 'Allows editing roles and removing team members' },\n]\n\nconst FEATURE_GROUPS: Array<{ id: string; labelKey: string; fallback: string; features: string[] }> = (() => {\n const groups = new Map<string, string[]>()\n for (const feature of PORTAL_FEATURES) {\n const parts = feature.id.split('.')\n const groupKey = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : parts[0]\n const existing = groups.get(groupKey)\n if (existing) {\n existing.push(feature.id)\n } else {\n groups.set(groupKey, [feature.id])\n }\n }\n return Array.from(groups.entries()).map(([groupId, features]) => {\n const scope = groupId.split('.').slice(1).join('')\n const fallback = scope.replace(/^\\w/, (ch) => ch.toUpperCase())\n return {\n id: groupId,\n labelKey: `customer_accounts.admin.portalFeatures.groups.${scope}`,\n fallback,\n features,\n }\n })\n})()\n\nfunction PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentProps) {\n const t = useT()\n const features = React.useMemo(\n () => Array.isArray(values.features) ? values.features as string[] : [],\n [values.features],\n )\n\n const handleFeatureToggle = React.useCallback((featureId: string) => {\n const next = features.includes(featureId)\n ? features.filter((existingId) => existingId !== featureId)\n : [...features, featureId]\n setValue('features', next)\n }, [features, setValue])\n\n const handleGroupToggle = React.useCallback((featureIds: string[]) => {\n const allSelected = featureIds.every((featureId) => features.includes(featureId))\n let next: string[]\n if (allSelected) {\n next = features.filter((featureId) => !featureIds.includes(featureId))\n } else {\n next = [...features]\n for (const featureId of featureIds) {\n if (!next.includes(featureId)) next.push(featureId)\n }\n }\n setValue('features', next)\n }, [features, setValue])\n\n return (\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {FEATURE_GROUPS.map((group) => {\n const groupFeatures = group.features\n const allSelected = groupFeatures.every((featureId) => features.includes(featureId))\n const someSelected = groupFeatures.some((featureId) => features.includes(featureId))\n return (\n <div key={group.id} className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <span className=\"text-sm font-semibold\">{t(group.labelKey, group.fallback)}</span>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}\n onChange={() => handleGroupToggle(groupFeatures)}\n className=\"rounded border-border\"\n />\n {t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}\n </label>\n </div>\n <div className=\"divide-y\">\n {groupFeatures.map((featureId) => {\n const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)\n return (\n <label key={featureId} className=\"flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors\">\n <input\n type=\"checkbox\"\n checked={features.includes(featureId)}\n onChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5 rounded border-border\"\n />\n <div className=\"space-y-0.5\">\n <div className=\"text-sm font-medium\">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>\n {feature && (\n <div className=\"text-xs text-muted-foreground\">{t(feature.descriptionKey, feature.descriptionFallback)}</div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default function CustomerRoleDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [data, setData] = React.useState<RoleDetail | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!id) {\n setError(t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found'))\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n try {\n const payload = await readApiResultOrThrow<RoleDetail>(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role') },\n )\n if (cancelled) return\n setData(payload)\n } catch (err) {\n if (cancelled) return\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role')\n setError(message)\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n if (!data) return []\n return [\n {\n id: 'name',\n type: 'text' as const,\n label: t('customer_accounts.admin.roleDetail.fields.name', 'Name'),\n required: true,\n disabled: data.isSystem,\n },\n {\n id: 'description',\n type: 'textarea' as const,\n label: t('customer_accounts.admin.roleDetail.fields.description', 'Description'),\n },\n {\n id: 'isDefault',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.isDefault', 'Default role (auto-assigned to new users)'),\n },\n {\n id: 'customerAssignable',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.customerAssignable', 'Customers can self-assign'),\n },\n ]\n }, [data, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('customer_accounts.admin.roleDetail.sections.details', 'Role Details'),\n column: 1,\n fields: ['name', 'description'],\n },\n {\n id: 'options',\n title: t('customer_accounts.admin.roleDetail.sections.options', 'Options'),\n column: 1,\n fields: ['isDefault', 'customerAssignable'],\n },\n {\n id: 'permissions',\n title: t('customer_accounts.admin.roleDetail.sections.permissions', 'Portal Permissions'),\n column: 1,\n component: PortalPermissionsEditor,\n },\n ], [t])\n\n const initialValues = React.useMemo(() => {\n if (!data) return {}\n return {\n name: data.name,\n description: data.description || '',\n isDefault: data.isDefault,\n customerAssignable: data.customerAssignable,\n features: data.features,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const roleCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: (values.name as string)?.trim(),\n description: (values.description as string)?.trim() || null,\n isDefault: values.isDefault,\n customerAssignable: values.customerAssignable,\n }),\n },\n )\n if (!roleCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.save', 'Failed to save role'), 'error')\n return\n }\n const features = Array.isArray(values.features) ? values.features : []\n const aclCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}/acl`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features }),\n },\n )\n if (!aclCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.saveAcl', 'Failed to save permissions'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roleDetail.flash.saved', 'Role updated'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const call = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.roles.error.delete', 'Failed to delete role'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roles.flash.deleted', 'Role deleted'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('customer_accounts.admin.roleDetail.loading', 'Loading role...')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <p>{error || t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}</p>\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/customer_accounts/roles\">\n {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n </Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={data.name}\n backHref=\"/backend/customer_accounts/roles\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n entityId=\"customer_accounts:customer_role\"\n onSubmit={handleSubmit}\n onDelete={!data.isSystem ? handleDelete : undefined}\n cancelHref=\"/backend/customer_accounts/roles\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\n\ntype RoleDetail = {\n id: string\n name: string\n slug: string\n description: string | null\n isDefault: boolean\n isSystem: boolean\n customerAssignable: boolean\n features: string[]\n}\n\nconst PORTAL_FEATURES = [\n { id: 'portal.profile.view', labelKey: 'customer_accounts.admin.portalFeatures.profile.view', fallback: 'View profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.view.description', descriptionFallback: 'Allows viewing own profile information and account details' },\n { id: 'portal.profile.edit', labelKey: 'customer_accounts.admin.portalFeatures.profile.edit', fallback: 'Edit profile', descriptionKey: 'customer_accounts.admin.portalFeatures.profile.edit.description', descriptionFallback: 'Allows editing display name and other profile settings' },\n { id: 'portal.orders.view', labelKey: 'customer_accounts.admin.portalFeatures.orders.view', fallback: 'View orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.view.description', descriptionFallback: 'Allows viewing order history and order details' },\n { id: 'portal.orders.create', labelKey: 'customer_accounts.admin.portalFeatures.orders.create', fallback: 'Create orders', descriptionKey: 'customer_accounts.admin.portalFeatures.orders.create.description', descriptionFallback: 'Allows placing new orders through the portal' },\n { id: 'portal.invoices.view', labelKey: 'customer_accounts.admin.portalFeatures.invoices.view', fallback: 'View invoices', descriptionKey: 'customer_accounts.admin.portalFeatures.invoices.view.description', descriptionFallback: 'Allows viewing invoices and payment history' },\n { id: 'portal.quotes.view', labelKey: 'customer_accounts.admin.portalFeatures.quotes.view', fallback: 'View quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.view.description', descriptionFallback: 'Allows viewing received quotes and their details' },\n { id: 'portal.quotes.request', labelKey: 'customer_accounts.admin.portalFeatures.quotes.request', fallback: 'Request quotes', descriptionKey: 'customer_accounts.admin.portalFeatures.quotes.request.description', descriptionFallback: 'Allows requesting new quotes from the company' },\n { id: 'portal.addresses.view', labelKey: 'customer_accounts.admin.portalFeatures.addresses.view', fallback: 'View addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.view.description', descriptionFallback: 'Allows viewing saved shipping and billing addresses' },\n { id: 'portal.addresses.manage', labelKey: 'customer_accounts.admin.portalFeatures.addresses.manage', fallback: 'Manage addresses', descriptionKey: 'customer_accounts.admin.portalFeatures.addresses.manage.description', descriptionFallback: 'Allows adding, editing, and removing addresses' },\n { id: 'portal.users.view', labelKey: 'customer_accounts.admin.portalFeatures.users.view', fallback: 'View team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.view.description', descriptionFallback: 'Allows viewing other team members in the organization' },\n { id: 'portal.users.invite', labelKey: 'customer_accounts.admin.portalFeatures.users.invite', fallback: 'Invite team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.invite.description', descriptionFallback: 'Allows sending portal invitations to new team members' },\n { id: 'portal.users.manage', labelKey: 'customer_accounts.admin.portalFeatures.users.manage', fallback: 'Manage team members', descriptionKey: 'customer_accounts.admin.portalFeatures.users.manage.description', descriptionFallback: 'Allows editing roles and removing team members' },\n]\n\nconst FEATURE_GROUPS: Array<{ id: string; labelKey: string; fallback: string; features: string[] }> = (() => {\n const groups = new Map<string, string[]>()\n for (const feature of PORTAL_FEATURES) {\n const parts = feature.id.split('.')\n const groupKey = parts.length >= 2 ? `${parts[0]}.${parts[1]}` : parts[0]\n const existing = groups.get(groupKey)\n if (existing) {\n existing.push(feature.id)\n } else {\n groups.set(groupKey, [feature.id])\n }\n }\n return Array.from(groups.entries()).map(([groupId, features]) => {\n const scope = groupId.split('.').slice(1).join('')\n const fallback = scope.replace(/^\\w/, (ch) => ch.toUpperCase())\n return {\n id: groupId,\n labelKey: `customer_accounts.admin.portalFeatures.groups.${scope}`,\n fallback,\n features,\n }\n })\n})()\n\nfunction PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentProps) {\n const t = useT()\n const features = React.useMemo(\n () => Array.isArray(values.features) ? values.features as string[] : [],\n [values.features],\n )\n\n const handleFeatureToggle = React.useCallback((featureId: string) => {\n const next = features.includes(featureId)\n ? features.filter((existingId) => existingId !== featureId)\n : [...features, featureId]\n setValue('features', next)\n }, [features, setValue])\n\n const handleGroupToggle = React.useCallback((featureIds: string[]) => {\n const allSelected = featureIds.every((featureId) => features.includes(featureId))\n let next: string[]\n if (allSelected) {\n next = features.filter((featureId) => !featureIds.includes(featureId))\n } else {\n next = [...features]\n for (const featureId of featureIds) {\n if (!next.includes(featureId)) next.push(featureId)\n }\n }\n setValue('features', next)\n }, [features, setValue])\n\n return (\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {FEATURE_GROUPS.map((group) => {\n const groupFeatures = group.features\n const allSelected = groupFeatures.every((featureId) => features.includes(featureId))\n const someSelected = groupFeatures.some((featureId) => features.includes(featureId))\n return (\n <div key={group.id} className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <span className=\"text-sm font-semibold\">{t(group.labelKey, group.fallback)}</span>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input\n type=\"checkbox\"\n checked={allSelected}\n ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}\n onChange={() => handleGroupToggle(groupFeatures)}\n className=\"rounded border-border\"\n />\n {t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}\n </label>\n </div>\n <div className=\"divide-y\">\n {groupFeatures.map((featureId) => {\n const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)\n return (\n <label key={featureId} className=\"flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors\">\n <input\n type=\"checkbox\"\n checked={features.includes(featureId)}\n onChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5 rounded border-border\"\n />\n <div className=\"space-y-0.5\">\n <div className=\"text-sm font-medium\">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>\n {feature && (\n <div className=\"text-xs text-muted-foreground\">{t(feature.descriptionKey, feature.descriptionFallback)}</div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n\nexport default function CustomerRoleDetailPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const [data, setData] = React.useState<RoleDetail | 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\n React.useEffect(() => {\n if (!id) {\n setIsNotFound(true)\n setIsLoading(false)\n return\n }\n let cancelled = false\n async function load() {\n setIsLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const payload = await readApiResultOrThrow<RoleDetail>(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id!)}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role') },\n )\n if (cancelled) return\n setData(payload)\n } catch (err) {\n if (cancelled) return\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role')\n setError(message)\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id, t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n if (!data) return []\n return [\n {\n id: 'name',\n type: 'text' as const,\n label: t('customer_accounts.admin.roleDetail.fields.name', 'Name'),\n required: true,\n disabled: data.isSystem,\n },\n {\n id: 'description',\n type: 'textarea' as const,\n label: t('customer_accounts.admin.roleDetail.fields.description', 'Description'),\n },\n {\n id: 'isDefault',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.isDefault', 'Default role (auto-assigned to new users)'),\n },\n {\n id: 'customerAssignable',\n type: 'checkbox' as const,\n label: t('customer_accounts.admin.roleDetail.fields.customerAssignable', 'Customers can self-assign'),\n },\n ]\n }, [data, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'details',\n title: t('customer_accounts.admin.roleDetail.sections.details', 'Role Details'),\n column: 1,\n fields: ['name', 'description'],\n },\n {\n id: 'options',\n title: t('customer_accounts.admin.roleDetail.sections.options', 'Options'),\n column: 1,\n fields: ['isDefault', 'customerAssignable'],\n },\n {\n id: 'permissions',\n title: t('customer_accounts.admin.roleDetail.sections.permissions', 'Portal Permissions'),\n column: 1,\n component: PortalPermissionsEditor,\n },\n ], [t])\n\n const initialValues = React.useMemo(() => {\n if (!data) return {}\n return {\n name: data.name,\n description: data.description || '',\n isDefault: data.isDefault,\n customerAssignable: data.customerAssignable,\n features: data.features,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const roleCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: (values.name as string)?.trim(),\n description: (values.description as string)?.trim() || null,\n isDefault: values.isDefault,\n customerAssignable: values.customerAssignable,\n }),\n },\n )\n if (!roleCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.save', 'Failed to save role'), 'error')\n return\n }\n const features = Array.isArray(values.features) ? values.features : []\n const aclCall = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}/acl`,\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features }),\n },\n )\n if (!aclCall.ok) {\n flash(t('customer_accounts.admin.roleDetail.error.saveAcl', 'Failed to save permissions'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roleDetail.flash.saved', 'Role updated'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const call = await apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n if (!call.ok) {\n flash(t('customer_accounts.admin.roles.error.delete', 'Failed to delete role'), 'error')\n return\n }\n flash(t('customer_accounts.admin.roles.flash.deleted', 'Role deleted'), 'success')\n router.push('/backend/customer_accounts/roles')\n }, [id, router, t])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground\">\n <Spinner className=\"h-6 w-6\" />\n <span>{t('customer_accounts.admin.roleDetail.loading', 'Loading role...')}</span>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}\n backHref=\"/backend/customer_accounts/roles\"\n backLabel={t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}\n action={\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href=\"/backend/customer_accounts/roles\">\n {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}\n </Link>\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={data.name}\n backHref=\"/backend/customer_accounts/roles\"\n fields={fields}\n groups={groups}\n initialValues={initialValues}\n entityId=\"customer_accounts:customer_role\"\n onSubmit={handleSubmit}\n onDelete={!data.isSystem ? handleDelete : undefined}\n cancelHref=\"/backend/customer_accounts/roles\"\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
16
16
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
17
17
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
18
18
|
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
19
|
+
import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
19
20
|
function formatDate(value, fallback) {
|
|
20
21
|
if (!value) return fallback;
|
|
21
22
|
const date = new Date(value);
|
|
@@ -111,6 +112,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
111
112
|
const [data, setData] = React.useState(null);
|
|
112
113
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
113
114
|
const [error, setError] = React.useState(null);
|
|
115
|
+
const [isNotFound, setIsNotFound] = React.useState(false);
|
|
114
116
|
const [isSaving, setIsSaving] = React.useState(false);
|
|
115
117
|
const [editActive, setEditActive] = React.useState(null);
|
|
116
118
|
const [editDisplayName, setEditDisplayName] = React.useState("");
|
|
@@ -144,7 +146,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
144
146
|
);
|
|
145
147
|
React.useEffect(() => {
|
|
146
148
|
if (!id) {
|
|
147
|
-
|
|
149
|
+
setIsNotFound(true);
|
|
148
150
|
setIsLoading(false);
|
|
149
151
|
return;
|
|
150
152
|
}
|
|
@@ -152,6 +154,7 @@ function CustomerUserDetailPage({ params }) {
|
|
|
152
154
|
async function load() {
|
|
153
155
|
setIsLoading(true);
|
|
154
156
|
setError(null);
|
|
157
|
+
setIsNotFound(false);
|
|
155
158
|
try {
|
|
156
159
|
const payload = await readApiResultOrThrow(
|
|
157
160
|
`/api/customer_accounts/admin/users/${encodeURIComponent(id)}`,
|
|
@@ -167,8 +170,12 @@ function CustomerUserDetailPage({ params }) {
|
|
|
167
170
|
setEditCustomerEntityId(payload.customerEntityId);
|
|
168
171
|
} catch (err) {
|
|
169
172
|
if (cancelled) return;
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
if (err.status === 404) {
|
|
174
|
+
setIsNotFound(true);
|
|
175
|
+
} else {
|
|
176
|
+
const message = err instanceof Error ? err.message : t("customer_accounts.admin.detail.error.load", "Failed to load user");
|
|
177
|
+
setError(message);
|
|
178
|
+
}
|
|
172
179
|
} finally {
|
|
173
180
|
if (!cancelled) setIsLoading(false);
|
|
174
181
|
}
|
|
@@ -416,11 +423,24 @@ function CustomerUserDetailPage({ params }) {
|
|
|
416
423
|
/* @__PURE__ */ jsx("span", { children: t("customer_accounts.admin.detail.loading", "Loading user...") })
|
|
417
424
|
] }) }) });
|
|
418
425
|
}
|
|
426
|
+
if (isNotFound) {
|
|
427
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
428
|
+
RecordNotFoundState,
|
|
429
|
+
{
|
|
430
|
+
label: t("customer_accounts.admin.detail.error.notFound", "User not found"),
|
|
431
|
+
backHref: "/backend/customer_accounts/users",
|
|
432
|
+
backLabel: t("customer_accounts.admin.detail.actions.backToList", "Back to list")
|
|
433
|
+
}
|
|
434
|
+
) }) });
|
|
435
|
+
}
|
|
419
436
|
if (error || !data) {
|
|
420
|
-
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
438
|
+
ErrorMessage,
|
|
439
|
+
{
|
|
440
|
+
label: error ?? t("customer_accounts.admin.detail.error.notFound", "User not found"),
|
|
441
|
+
action: /* @__PURE__ */ jsx(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/customer_accounts/users", children: t("customer_accounts.admin.detail.actions.backToList", "Back to list") }) })
|
|
442
|
+
}
|
|
443
|
+
) }) });
|
|
424
444
|
}
|
|
425
445
|
return /* @__PURE__ */ jsxs(Page, { children: [
|
|
426
446
|
/* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
|