@open-mercato/core 0.6.6-develop.6412.1.e99f3c32d9 → 0.6.6-develop.6413.1.ae875fb682

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.
@@ -130,8 +130,8 @@ async function DELETE(req, { params }) {
130
130
  if (!role) {
131
131
  return NextResponse.json({ ok: false, error: "Role not found" }, { status: 404 });
132
132
  }
133
- if (role.isSystem) {
134
- return NextResponse.json({ ok: false, error: "Cannot delete a system role" }, { status: 400 });
133
+ if (role.isDefault) {
134
+ return NextResponse.json({ ok: false, error: "Cannot delete the default role. Set another role as default first." }, { status: 400 });
135
135
  }
136
136
  try {
137
137
  await enforceCommandOptimisticLockWithGuards(container, {
@@ -210,11 +210,11 @@ const putMethodDoc = {
210
210
  };
211
211
  const deleteMethodDoc = {
212
212
  summary: "Delete customer role (admin)",
213
- description: "Soft deletes a customer role and its ACL. System roles and roles with assigned users cannot be deleted.",
213
+ description: "Soft deletes a customer role and its ACL. The default role (auto-assigned to new portal users) and roles with assigned users cannot be deleted.",
214
214
  tags: ["Customer Accounts Admin"],
215
215
  responses: [{ status: 200, description: "Role deleted", schema: successSchema }],
216
216
  errors: [
217
- { status: 400, description: "System role or has assigned users", schema: errorSchema },
217
+ { status: 400, description: "Default role or has assigned users", schema: errorSchema },
218
218
  { status: 401, description: "Not authenticated", schema: errorSchema },
219
219
  { status: 403, description: "Insufficient permissions", schema: errorSchema },
220
220
  { status: 404, description: "Role not found", schema: errorSchema }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customer_accounts/api/admin/roles/%5Bid%5D.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerRole, CustomerRoleAcl, CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { updateRoleSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nexport const metadata = {}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n if (!UUID_RE.test(params.id)) {\n return NextResponse.json({ ok: false, error: 'Invalid role ID' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.view'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n const acl = await em.findOne(CustomerRoleAcl, {\n role: role.id as any,\n tenantId: auth.tenantId,\n })\n\n const features = acl && Array.isArray(acl.featuresJson) ? acl.featuresJson : []\n\n return NextResponse.json({\n ok: true,\n id: role.id,\n name: role.name,\n slug: role.slug,\n description: role.description || null,\n isDefault: role.isDefault,\n isSystem: role.isSystem,\n customerAssignable: role.customerAssignable,\n createdAt: role.createdAt,\n updatedAt: role.updatedAt || null,\n features,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.roles.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = updateRoleSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n if (role.isSystem && parsed.data.name !== undefined && parsed.data.name !== role.name) {\n return NextResponse.json({ ok: false, error: 'Cannot change name of a system role' }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale overwrite so two admins editing the same role\n // in parallel cannot silently clobber each other (#2055). Strictly additive.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.role',\n resourceId: role.id,\n current: role.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Always bump updated_at so the lock version advances on every save \u2014 `nativeUpdate`\n // bypasses MikroORM's `onUpdate` hook, so set it explicitly.\n const nextUpdatedAt = new Date()\n const updates: Record<string, unknown> = { updatedAt: nextUpdatedAt }\n if (parsed.data.name !== undefined) updates.name = parsed.data.name\n if (parsed.data.description !== undefined) updates.description = parsed.data.description\n if (parsed.data.isDefault !== undefined) updates.isDefault = parsed.data.isDefault\n if (parsed.data.customerAssignable !== undefined) updates.customerAssignable = parsed.data.customerAssignable\n\n await em.nativeUpdate(CustomerRole, { id: role.id }, updates)\n\n void emitCustomerAccountsEvent('customer_accounts.role.updated', {\n id: role.id,\n name: updates.name as string || role.name,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true, updatedAt: nextUpdatedAt.toISOString() })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.roles.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n if (role.isSystem) {\n return NextResponse.json({ ok: false, error: 'Cannot delete a system role' }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale delete. Strictly additive.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.role',\n resourceId: role.id,\n current: role.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const assignedUsersCount = await em.count(CustomerUserRole, {\n role: role.id as any,\n deletedAt: null,\n })\n if (assignedUsersCount > 0) {\n return NextResponse.json({ ok: false, error: `Cannot delete role with ${assignedUsersCount} assigned user(s). Reassign users first.` }, { status: 400 })\n }\n\n await em.nativeUpdate(CustomerRole, { id: role.id }, { deletedAt: new Date() })\n await em.nativeUpdate(CustomerRoleAcl, { role: role.id as any }, { deletedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.role.deleted', {\n id: role.id,\n name: role.name,\n slug: role.slug,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst aclSchema = z.object({\n features: z.array(z.string()),\n isPortalAdmin: z.boolean(),\n})\nconst roleDetailSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n slug: z.string(),\n description: z.string().nullable(),\n isDefault: z.boolean(),\n isSystem: z.boolean(),\n customerAssignable: z.boolean(),\n createdAt: z.string().datetime(),\n updatedAt: z.string().datetime().nullable(),\n acl: aclSchema,\n})\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst getMethodDoc: OpenApiMethodDoc = {\n summary: 'Get customer role detail (admin)',\n description: 'Returns full customer role details including ACL features.',\n tags: ['Customer Accounts Admin'],\n responses: [{\n status: 200,\n description: 'Role detail with ACL',\n schema: z.object({ ok: z.literal(true), role: roleDetailSchema }),\n }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nconst putMethodDoc: OpenApiMethodDoc = {\n summary: 'Update customer role (admin)',\n description: 'Updates a customer role. System roles are protected from name changes.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: updateRoleSchema },\n responses: [{ status: 200, description: 'Role updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed or system role restriction', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nconst deleteMethodDoc: OpenApiMethodDoc = {\n summary: 'Delete customer role (admin)',\n description: 'Soft deletes a customer role and its ACL. System roles and roles with assigned users cannot be deleted.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'Role deleted', schema: successSchema }],\n errors: [\n { status: 400, description: 'System role or has assigned users', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer role detail management (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: getMethodDoc,\n PUT: putMethodDoc,\n DELETE: deleteMethodDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CustomerRole, CustomerRoleAcl, CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { updateRoleSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\nexport const metadata = {}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n if (!UUID_RE.test(params.id)) {\n return NextResponse.json({ ok: false, error: 'Invalid role ID' }, { status: 400 })\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.view'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n const acl = await em.findOne(CustomerRoleAcl, {\n role: role.id as any,\n tenantId: auth.tenantId,\n })\n\n const features = acl && Array.isArray(acl.featuresJson) ? acl.featuresJson : []\n\n return NextResponse.json({\n ok: true,\n id: role.id,\n name: role.name,\n slug: role.slug,\n description: role.description || null,\n isDefault: role.isDefault,\n isSystem: role.isSystem,\n customerAssignable: role.customerAssignable,\n createdAt: role.createdAt,\n updatedAt: role.updatedAt || null,\n features,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.roles.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = updateRoleSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n if (role.isSystem && parsed.data.name !== undefined && parsed.data.name !== role.name) {\n return NextResponse.json({ ok: false, error: 'Cannot change name of a system role' }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale overwrite so two admins editing the same role\n // in parallel cannot silently clobber each other (#2055). Strictly additive.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.role',\n resourceId: role.id,\n current: role.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Always bump updated_at so the lock version advances on every save \u2014 `nativeUpdate`\n // bypasses MikroORM's `onUpdate` hook, so set it explicitly.\n const nextUpdatedAt = new Date()\n const updates: Record<string, unknown> = { updatedAt: nextUpdatedAt }\n if (parsed.data.name !== undefined) updates.name = parsed.data.name\n if (parsed.data.description !== undefined) updates.description = parsed.data.description\n if (parsed.data.isDefault !== undefined) updates.isDefault = parsed.data.isDefault\n if (parsed.data.customerAssignable !== undefined) updates.customerAssignable = parsed.data.customerAssignable\n\n await em.nativeUpdate(CustomerRole, { id: role.id }, updates)\n\n void emitCustomerAccountsEvent('customer_accounts.role.updated', {\n id: role.id,\n name: updates.name as string || role.name,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true, updatedAt: nextUpdatedAt.toISOString() })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const hasAccess = await rbacService.userHasAllFeatures(auth.sub, ['customer_accounts.roles.manage'], { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!hasAccess) {\n return NextResponse.json({ ok: false, error: 'Insufficient permissions' }, { status: 403 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n const role = await em.findOne(CustomerRole, {\n id: params.id,\n tenantId: auth.tenantId,\n deletedAt: null,\n })\n if (!role) {\n return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })\n }\n\n // System roles are seed-provided defaults but may still be removed once they are no\n // longer needed (#3556). Only the default role is hard-protected here because signup\n // auto-assigns it to new portal users \u2014 reassign the default first, then delete.\n if (role.isDefault) {\n return NextResponse.json({ ok: false, error: 'Cannot delete the default role. Set another role as default first.' }, { status: 400 })\n }\n\n // Optimistic lock: refuse a stale delete. Strictly additive.\n try {\n await enforceCommandOptimisticLockWithGuards(container, {\n resourceKind: 'customer_accounts.role',\n resourceId: role.id,\n current: role.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const assignedUsersCount = await em.count(CustomerUserRole, {\n role: role.id as any,\n deletedAt: null,\n })\n if (assignedUsersCount > 0) {\n return NextResponse.json({ ok: false, error: `Cannot delete role with ${assignedUsersCount} assigned user(s). Reassign users first.` }, { status: 400 })\n }\n\n await em.nativeUpdate(CustomerRole, { id: role.id }, { deletedAt: new Date() })\n await em.nativeUpdate(CustomerRoleAcl, { role: role.id as any }, { deletedAt: new Date() })\n\n void emitCustomerAccountsEvent('customer_accounts.role.deleted', {\n id: role.id,\n name: role.name,\n slug: role.slug,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({ ok: true })\n}\n\nconst aclSchema = z.object({\n features: z.array(z.string()),\n isPortalAdmin: z.boolean(),\n})\nconst roleDetailSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n slug: z.string(),\n description: z.string().nullable(),\n isDefault: z.boolean(),\n isSystem: z.boolean(),\n customerAssignable: z.boolean(),\n createdAt: z.string().datetime(),\n updatedAt: z.string().datetime().nullable(),\n acl: aclSchema,\n})\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst getMethodDoc: OpenApiMethodDoc = {\n summary: 'Get customer role detail (admin)',\n description: 'Returns full customer role details including ACL features.',\n tags: ['Customer Accounts Admin'],\n responses: [{\n status: 200,\n description: 'Role detail with ACL',\n schema: z.object({ ok: z.literal(true), role: roleDetailSchema }),\n }],\n errors: [\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nconst putMethodDoc: OpenApiMethodDoc = {\n summary: 'Update customer role (admin)',\n description: 'Updates a customer role. System roles are protected from name changes.',\n tags: ['Customer Accounts Admin'],\n requestBody: { schema: updateRoleSchema },\n responses: [{ status: 200, description: 'Role updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed or system role restriction', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nconst deleteMethodDoc: OpenApiMethodDoc = {\n summary: 'Delete customer role (admin)',\n description: 'Soft deletes a customer role and its ACL. The default role (auto-assigned to new portal users) and roles with assigned users cannot be deleted.',\n tags: ['Customer Accounts Admin'],\n responses: [{ status: 200, description: 'Role deleted', schema: successSchema }],\n errors: [\n { status: 400, description: 'Default role or has assigned users', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'Role not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Customer role detail management (admin)',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: {\n GET: getMethodDoc,\n PUT: putMethodDoc,\n DELETE: deleteMethodDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -305,7 +305,7 @@ function CustomerRoleDetailPage({ params }) {
305
305
  optimisticLockUpdatedAt: data.updatedAt ?? data.updated_at ?? null,
306
306
  entityId: "customer_accounts:customer_role",
307
307
  onSubmit: handleSubmit,
308
- onDelete: !data.isSystem ? handleDelete : void 0,
308
+ onDelete: !data.isDefault ? handleDelete : void 0,
309
309
  cancelHref: "/backend/customer_accounts/roles"
310
310
  }
311
311
  ) }) });
@@ -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 { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\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 updatedAt?: string | null\n updated_at?: string | null\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 <Checkbox\n checked={allSelected ? true : (someSelected ? 'indeterminate' : false)}\n onCheckedChange={() => handleGroupToggle(groupFeatures)}\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 <Checkbox\n checked={features.includes(featureId)}\n onCheckedChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5\"\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 updatedAt: data.updatedAt ?? data.updated_at ?? null,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const headers = buildOptimisticLockHeader(data?.updatedAt ?? data?.updated_at ?? null)\n const roleCall = await withScopedApiRequestHeaders(headers, () => (\n 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 ))\n if (!roleCall.ok) {\n if (surfaceRecordConflict({ status: roleCall.status, body: roleCall.result }, t)) return\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 // The role PUT just bumped the aggregate's `updatedAt`; the ACL write guards\n // the SAME role aggregate, so send its NEW version (not the stale pre-edit one)\n // to avoid a false 409 while still blocking a concurrent overwrite (#3194).\n const roleResult = (roleCall.result ?? null) as { updatedAt?: string | null } | null\n const aclHeaders = buildOptimisticLockHeader(roleResult?.updatedAt ?? data?.updatedAt ?? data?.updated_at ?? null)\n const aclCall = await withScopedApiRequestHeaders(aclHeaders, () => (\n 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 ))\n if (!aclCall.ok) {\n if (surfaceRecordConflict({ status: aclCall.status, body: aclCall.result }, t)) return\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 }, [data?.updatedAt, data?.updated_at, id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const headers = buildOptimisticLockHeader(data?.updatedAt ?? data?.updated_at ?? null)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n ))\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\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 }, [data?.updatedAt, data?.updated_at, 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 optimisticLockUpdatedAt={data.updatedAt ?? data.updated_at ?? null}\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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",
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 { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\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 updatedAt?: string | null\n updated_at?: string | null\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 <Checkbox\n checked={allSelected ? true : (someSelected ? 'indeterminate' : false)}\n onCheckedChange={() => handleGroupToggle(groupFeatures)}\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 <Checkbox\n checked={features.includes(featureId)}\n onCheckedChange={() => handleFeatureToggle(featureId)}\n className=\"mt-0.5\"\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 updatedAt: data.updatedAt ?? data.updated_at ?? null,\n }\n }, [data])\n\n const handleSubmit = React.useCallback(async (values: Record<string, unknown>) => {\n if (!id) return\n const headers = buildOptimisticLockHeader(data?.updatedAt ?? data?.updated_at ?? null)\n const roleCall = await withScopedApiRequestHeaders(headers, () => (\n 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 ))\n if (!roleCall.ok) {\n if (surfaceRecordConflict({ status: roleCall.status, body: roleCall.result }, t)) return\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 // The role PUT just bumped the aggregate's `updatedAt`; the ACL write guards\n // the SAME role aggregate, so send its NEW version (not the stale pre-edit one)\n // to avoid a false 409 while still blocking a concurrent overwrite (#3194).\n const roleResult = (roleCall.result ?? null) as { updatedAt?: string | null } | null\n const aclHeaders = buildOptimisticLockHeader(roleResult?.updatedAt ?? data?.updatedAt ?? data?.updated_at ?? null)\n const aclCall = await withScopedApiRequestHeaders(aclHeaders, () => (\n 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 ))\n if (!aclCall.ok) {\n if (surfaceRecordConflict({ status: aclCall.status, body: aclCall.result }, t)) return\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 }, [data?.updatedAt, data?.updated_at, id, router, t])\n\n const handleDelete = React.useCallback(async () => {\n if (!id) return\n const headers = buildOptimisticLockHeader(data?.updatedAt ?? data?.updated_at ?? null)\n const call = await withScopedApiRequestHeaders(headers, () => (\n apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(id)}`,\n { method: 'DELETE' },\n )\n ))\n if (!call.ok) {\n if (surfaceRecordConflict({ status: call.status, body: call.result }, t)) return\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 }, [data?.updatedAt, data?.updated_at, 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 optimisticLockUpdatedAt={data.updatedAt ?? data.updated_at ?? null}\n entityId=\"customer_accounts:customer_role\"\n onSubmit={handleSubmit}\n onDelete={!data.isDefault ? 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
  }
@@ -78,8 +78,8 @@ function CustomerRolesPage() {
78
78
  };
79
79
  }, [queryParams, reloadToken, t]);
80
80
  const handleDelete = React.useCallback(async (role) => {
81
- if (role.isSystem) {
82
- flash(t("customer_accounts.admin.roles.error.deleteSystem", "System roles cannot be deleted"), "error");
81
+ if (role.isDefault) {
82
+ flash(t("customer_accounts.admin.roles.error.deleteDefault", "The default role cannot be deleted. Set another role as default first."), "error");
83
83
  return;
84
84
  }
85
85
  const confirmed = await confirm({
@@ -182,7 +182,7 @@ function CustomerRolesPage() {
182
182
  router.push(`/backend/customer_accounts/roles/${row.id}`);
183
183
  }
184
184
  },
185
- ...!row.isSystem ? [{
185
+ ...!row.isDefault ? [{
186
186
  id: "delete",
187
187
  label: t("customer_accounts.admin.roles.actions.delete", "Delete"),
188
188
  destructive: true,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customer_accounts/backend/customer_accounts/roles/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 { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall, readApiResultOrThrow, 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 { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\n\ntype RoleRow = {\n id: string\n name: string\n slug: string\n description: string | null\n isSystem: boolean\n isDefault: boolean\n customerAssignable: boolean\n createdAt: string\n updatedAt?: string | null\n}\n\ntype RolesResponse = {\n items?: RoleRow[]\n total?: number\n totalPages?: number\n}\n\nexport default function CustomerRolesPage() {\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const t = useT()\n const router = useRouter()\n const [rows, setRows] = React.useState<RoleRow[]>([])\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(50)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n\n const { runMutation } = useGuardedMutation<{ entityType: string }>({\n contextId: 'customer_accounts:roles-list',\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { entityType: 'customer_accounts:role' },\n })\n },\n [runMutation],\n )\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (search.trim()) params.set('search', search.trim())\n return params.toString()\n }, [page, pageSize, search])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const fallback: RolesResponse = { items: [], total: 0, totalPages: 1 }\n const payload = await readApiResultOrThrow<RolesResponse>(\n `/api/customer_accounts/admin/roles?${queryParams}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roles.error.load', 'Failed to load roles'), fallback },\n )\n if (cancelled) return\n const items = Array.isArray(payload?.items) ? payload.items : []\n setRows(items)\n setTotal(typeof payload?.total === 'number' ? payload.total : items.length)\n setTotalPages(typeof payload?.totalPages === 'number' ? payload.totalPages : 1)\n } catch (err) {\n if (!cancelled) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roles.error.load', 'Failed to load roles')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [queryParams, reloadToken, t])\n\n const handleDelete = React.useCallback(async (role: RoleRow) => {\n if (role.isSystem) {\n flash(t('customer_accounts.admin.roles.error.deleteSystem', 'System roles cannot be deleted'), 'error')\n return\n }\n const confirmed = await confirm({\n title: t('customer_accounts.admin.roles.confirm.delete', 'Delete role \"{{name}}\"?', { name: role.name }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(role.updatedAt),\n () => apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(role.id)}`,\n { method: 'DELETE' },\n ),\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 setReloadToken((token) => token + 1)\n }, { id: role.id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roles.error.delete', 'Failed to delete role')\n flash(message, 'error')\n }\n }, [confirm, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<RoleRow>[]>(() => [\n {\n accessorKey: 'name',\n header: t('customer_accounts.admin.roles.columns.name', 'Name'),\n cell: ({ row }) => (\n <Link\n href={`/backend/customer_accounts/roles/${row.original.id}`}\n className=\"font-medium hover:underline\"\n >\n {row.original.name}\n </Link>\n ),\n },\n {\n accessorKey: 'slug',\n header: t('customer_accounts.admin.roles.columns.slug', 'Slug'),\n cell: ({ row }) => (\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs\">{row.original.slug}</code>\n ),\n },\n {\n accessorKey: 'description',\n header: t('customer_accounts.admin.roles.columns.description', 'Description'),\n cell: ({ row }) => row.original.description || <span className=\"text-muted-foreground\">-</span>,\n },\n {\n accessorKey: 'isSystem',\n header: t('customer_accounts.admin.roles.columns.isSystem', 'System'),\n cell: ({ row }) => (\n row.original.isSystem ? (\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {t('customer_accounts.admin.roles.system', 'System')}\n </span>\n ) : null\n ),\n },\n {\n accessorKey: 'isDefault',\n header: t('customer_accounts.admin.roles.columns.isDefault', 'Default'),\n cell: ({ row }) => (\n row.original.isDefault ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\">\n {t('customer_accounts.admin.roles.default', 'Default')}\n </span>\n ) : null\n ),\n },\n {\n accessorKey: 'customerAssignable',\n header: t('customer_accounts.admin.roles.columns.customerAssignable', 'Self-assignable'),\n cell: ({ row }) => (\n row.original.customerAssignable ? (\n <Badge variant=\"brand\">\n {t('customer_accounts.admin.roles.assignable', 'Yes')}\n </Badge>\n ) : (\n <span className=\"text-muted-foreground text-sm\">\n {t('customer_accounts.admin.roles.notAssignable', 'No')}\n </span>\n )\n ),\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<RoleRow>\n title={t('customer_accounts.admin.roles.title', 'Customer Roles')}\n actions={(\n <Button asChild>\n <Link href=\"/backend/customer_accounts/roles/create\">\n {t('customer_accounts.admin.roles.actions.create', 'Create Role')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('customer_accounts.admin.roles.searchPlaceholder', 'Search roles...')}\n perspective={{ tableId: 'customer_accounts.admin.roles' }}\n emptyState={(\n <ListEmptyState\n entityName={t('customer_accounts.admin.roles.title', 'Customer Roles')}\n createHref=\"/backend/customer_accounts/roles/create\"\n createLabel={t('customer_accounts.admin.roles.actions.create', 'Create Role')}\n />\n )}\n onRowClick={(row) => router.push(`/backend/customer_accounts/roles/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('customer_accounts.admin.roles.actions.edit', 'Edit'),\n onSelect: () => { router.push(`/backend/customer_accounts/roles/${row.id}`) },\n },\n ...(!row.isSystem ? [{\n id: 'delete',\n label: t('customer_accounts.admin.roles.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(row) },\n }] : []),\n ]}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
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 { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall, readApiResultOrThrow, 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 { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\n\ntype RoleRow = {\n id: string\n name: string\n slug: string\n description: string | null\n isSystem: boolean\n isDefault: boolean\n customerAssignable: boolean\n createdAt: string\n updatedAt?: string | null\n}\n\ntype RolesResponse = {\n items?: RoleRow[]\n total?: number\n totalPages?: number\n}\n\nexport default function CustomerRolesPage() {\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const t = useT()\n const router = useRouter()\n const [rows, setRows] = React.useState<RoleRow[]>([])\n const [page, setPage] = React.useState(1)\n const [pageSize] = React.useState(50)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n\n const { runMutation } = useGuardedMutation<{ entityType: string }>({\n contextId: 'customer_accounts:roles-list',\n })\n\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: { entityType: 'customer_accounts:role' },\n })\n },\n [runMutation],\n )\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (search.trim()) params.set('search', search.trim())\n return params.toString()\n }, [page, pageSize, search])\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const fallback: RolesResponse = { items: [], total: 0, totalPages: 1 }\n const payload = await readApiResultOrThrow<RolesResponse>(\n `/api/customer_accounts/admin/roles?${queryParams}`,\n undefined,\n { errorMessage: t('customer_accounts.admin.roles.error.load', 'Failed to load roles'), fallback },\n )\n if (cancelled) return\n const items = Array.isArray(payload?.items) ? payload.items : []\n setRows(items)\n setTotal(typeof payload?.total === 'number' ? payload.total : items.length)\n setTotalPages(typeof payload?.totalPages === 'number' ? payload.totalPages : 1)\n } catch (err) {\n if (!cancelled) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roles.error.load', 'Failed to load roles')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [queryParams, reloadToken, t])\n\n const handleDelete = React.useCallback(async (role: RoleRow) => {\n if (role.isDefault) {\n flash(t('customer_accounts.admin.roles.error.deleteDefault', 'The default role cannot be deleted. Set another role as default first.'), 'error')\n return\n }\n const confirmed = await confirm({\n title: t('customer_accounts.admin.roles.confirm.delete', 'Delete role \"{{name}}\"?', { name: role.name }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutationWithContext(async () => {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(role.updatedAt),\n () => apiCall(\n `/api/customer_accounts/admin/roles/${encodeURIComponent(role.id)}`,\n { method: 'DELETE' },\n ),\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 setReloadToken((token) => token + 1)\n }, { id: role.id })\n } catch (err) {\n const message = err instanceof Error ? err.message : t('customer_accounts.admin.roles.error.delete', 'Failed to delete role')\n flash(message, 'error')\n }\n }, [confirm, runMutationWithContext, t])\n\n const columns = React.useMemo<ColumnDef<RoleRow>[]>(() => [\n {\n accessorKey: 'name',\n header: t('customer_accounts.admin.roles.columns.name', 'Name'),\n cell: ({ row }) => (\n <Link\n href={`/backend/customer_accounts/roles/${row.original.id}`}\n className=\"font-medium hover:underline\"\n >\n {row.original.name}\n </Link>\n ),\n },\n {\n accessorKey: 'slug',\n header: t('customer_accounts.admin.roles.columns.slug', 'Slug'),\n cell: ({ row }) => (\n <code className=\"rounded bg-muted px-1.5 py-0.5 text-xs\">{row.original.slug}</code>\n ),\n },\n {\n accessorKey: 'description',\n header: t('customer_accounts.admin.roles.columns.description', 'Description'),\n cell: ({ row }) => row.original.description || <span className=\"text-muted-foreground\">-</span>,\n },\n {\n accessorKey: 'isSystem',\n header: t('customer_accounts.admin.roles.columns.isSystem', 'System'),\n cell: ({ row }) => (\n row.original.isSystem ? (\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {t('customer_accounts.admin.roles.system', 'System')}\n </span>\n ) : null\n ),\n },\n {\n accessorKey: 'isDefault',\n header: t('customer_accounts.admin.roles.columns.isDefault', 'Default'),\n cell: ({ row }) => (\n row.original.isDefault ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg px-2 py-0.5 text-xs font-medium text-status-success-text\">\n {t('customer_accounts.admin.roles.default', 'Default')}\n </span>\n ) : null\n ),\n },\n {\n accessorKey: 'customerAssignable',\n header: t('customer_accounts.admin.roles.columns.customerAssignable', 'Self-assignable'),\n cell: ({ row }) => (\n row.original.customerAssignable ? (\n <Badge variant=\"brand\">\n {t('customer_accounts.admin.roles.assignable', 'Yes')}\n </Badge>\n ) : (\n <span className=\"text-muted-foreground text-sm\">\n {t('customer_accounts.admin.roles.notAssignable', 'No')}\n </span>\n )\n ),\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable<RoleRow>\n title={t('customer_accounts.admin.roles.title', 'Customer Roles')}\n actions={(\n <Button asChild>\n <Link href=\"/backend/customer_accounts/roles/create\">\n {t('customer_accounts.admin.roles.actions.create', 'Create Role')}\n </Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n searchPlaceholder={t('customer_accounts.admin.roles.searchPlaceholder', 'Search roles...')}\n perspective={{ tableId: 'customer_accounts.admin.roles' }}\n emptyState={(\n <ListEmptyState\n entityName={t('customer_accounts.admin.roles.title', 'Customer Roles')}\n createHref=\"/backend/customer_accounts/roles/create\"\n createLabel={t('customer_accounts.admin.roles.actions.create', 'Create Role')}\n />\n )}\n onRowClick={(row) => router.push(`/backend/customer_accounts/roles/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('customer_accounts.admin.roles.actions.edit', 'Edit'),\n onSelect: () => { router.push(`/backend/customer_accounts/roles/${row.id}`) },\n },\n ...(!row.isDefault ? [{\n id: 'delete',\n label: t('customer_accounts.admin.roles.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(row) },\n }] : []),\n ]}\n />\n )}\n pagination={{ page, pageSize, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6412.1.e99f3c32d9",
3
+ "version": "0.6.6-develop.6413.1.ae875fb682",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6412.1.e99f3c32d9",
257
- "@open-mercato/shared": "0.6.6-develop.6412.1.e99f3c32d9",
258
- "@open-mercato/ui": "0.6.6-develop.6412.1.e99f3c32d9",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6413.1.ae875fb682",
257
+ "@open-mercato/shared": "0.6.6-develop.6413.1.ae875fb682",
258
+ "@open-mercato/ui": "0.6.6-develop.6413.1.ae875fb682",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6412.1.e99f3c32d9",
264
- "@open-mercato/shared": "0.6.6-develop.6412.1.e99f3c32d9",
265
- "@open-mercato/ui": "0.6.6-develop.6412.1.e99f3c32d9",
263
+ "@open-mercato/ai-assistant": "0.6.6-develop.6413.1.ae875fb682",
264
+ "@open-mercato/shared": "0.6.6-develop.6413.1.ae875fb682",
265
+ "@open-mercato/ui": "0.6.6-develop.6413.1.ae875fb682",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -163,8 +163,11 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
163
163
  return NextResponse.json({ ok: false, error: 'Role not found' }, { status: 404 })
164
164
  }
165
165
 
166
- if (role.isSystem) {
167
- return NextResponse.json({ ok: false, error: 'Cannot delete a system role' }, { status: 400 })
166
+ // System roles are seed-provided defaults but may still be removed once they are no
167
+ // longer needed (#3556). Only the default role is hard-protected here because signup
168
+ // auto-assigns it to new portal users — reassign the default first, then delete.
169
+ if (role.isDefault) {
170
+ return NextResponse.json({ ok: false, error: 'Cannot delete the default role. Set another role as default first.' }, { status: 400 })
168
171
  }
169
172
 
170
173
  // Optimistic lock: refuse a stale delete. Strictly additive.
@@ -254,11 +257,11 @@ const putMethodDoc: OpenApiMethodDoc = {
254
257
 
255
258
  const deleteMethodDoc: OpenApiMethodDoc = {
256
259
  summary: 'Delete customer role (admin)',
257
- description: 'Soft deletes a customer role and its ACL. System roles and roles with assigned users cannot be deleted.',
260
+ description: 'Soft deletes a customer role and its ACL. The default role (auto-assigned to new portal users) and roles with assigned users cannot be deleted.',
258
261
  tags: ['Customer Accounts Admin'],
259
262
  responses: [{ status: 200, description: 'Role deleted', schema: successSchema }],
260
263
  errors: [
261
- { status: 400, description: 'System role or has assigned users', schema: errorSchema },
264
+ { status: 400, description: 'Default role or has assigned users', schema: errorSchema },
262
265
  { status: 401, description: 'Not authenticated', schema: errorSchema },
263
266
  { status: 403, description: 'Insufficient permissions', schema: errorSchema },
264
267
  { status: 404, description: 'Role not found', schema: errorSchema },
@@ -370,7 +370,7 @@ export default function CustomerRoleDetailPage({ params }: { params?: { id?: str
370
370
  optimisticLockUpdatedAt={data.updatedAt ?? data.updated_at ?? null}
371
371
  entityId="customer_accounts:customer_role"
372
372
  onSubmit={handleSubmit}
373
- onDelete={!data.isSystem ? handleDelete : undefined}
373
+ onDelete={!data.isDefault ? handleDelete : undefined}
374
374
  cancelHref="/backend/customer_accounts/roles"
375
375
  />
376
376
  </PageBody>
@@ -101,8 +101,8 @@ export default function CustomerRolesPage() {
101
101
  }, [queryParams, reloadToken, t])
102
102
 
103
103
  const handleDelete = React.useCallback(async (role: RoleRow) => {
104
- if (role.isSystem) {
105
- flash(t('customer_accounts.admin.roles.error.deleteSystem', 'System roles cannot be deleted'), 'error')
104
+ if (role.isDefault) {
105
+ flash(t('customer_accounts.admin.roles.error.deleteDefault', 'The default role cannot be deleted. Set another role as default first.'), 'error')
106
106
  return
107
107
  }
108
108
  const confirmed = await confirm({
@@ -230,7 +230,7 @@ export default function CustomerRolesPage() {
230
230
  label: t('customer_accounts.admin.roles.actions.edit', 'Edit'),
231
231
  onSelect: () => { router.push(`/backend/customer_accounts/roles/${row.id}`) },
232
232
  },
233
- ...(!row.isSystem ? [{
233
+ ...(!row.isDefault ? [{
234
234
  id: 'delete',
235
235
  label: t('customer_accounts.admin.roles.actions.delete', 'Delete'),
236
236
  destructive: true,
@@ -180,7 +180,7 @@
180
180
  "customer_accounts.admin.roles.confirm.delete": "Rolle \"{{name}}\" löschen?",
181
181
  "customer_accounts.admin.roles.default": "Standard",
182
182
  "customer_accounts.admin.roles.error.delete": "Rolle konnte nicht gelöscht werden",
183
- "customer_accounts.admin.roles.error.deleteSystem": "Systemrollen können nicht gelöscht werden",
183
+ "customer_accounts.admin.roles.error.deleteDefault": "Die Standardrolle kann nicht gelöscht werden. Legen Sie zuerst eine andere Rolle als Standard fest.",
184
184
  "customer_accounts.admin.roles.error.load": "Rollen konnten nicht geladen werden",
185
185
  "customer_accounts.admin.roles.flash.deleted": "Rolle gelöscht",
186
186
  "customer_accounts.admin.roles.notAssignable": "Nein",
@@ -180,7 +180,7 @@
180
180
  "customer_accounts.admin.roles.confirm.delete": "Delete role \"{{name}}\"?",
181
181
  "customer_accounts.admin.roles.default": "Default",
182
182
  "customer_accounts.admin.roles.error.delete": "Failed to delete role",
183
- "customer_accounts.admin.roles.error.deleteSystem": "System roles cannot be deleted",
183
+ "customer_accounts.admin.roles.error.deleteDefault": "The default role cannot be deleted. Set another role as default first.",
184
184
  "customer_accounts.admin.roles.error.load": "Failed to load roles",
185
185
  "customer_accounts.admin.roles.flash.deleted": "Role deleted",
186
186
  "customer_accounts.admin.roles.notAssignable": "No",
@@ -180,7 +180,7 @@
180
180
  "customer_accounts.admin.roles.confirm.delete": "Delete role \"{{name}}\"?",
181
181
  "customer_accounts.admin.roles.default": "Default",
182
182
  "customer_accounts.admin.roles.error.delete": "Failed to delete role",
183
- "customer_accounts.admin.roles.error.deleteSystem": "System roles cannot be deleted",
183
+ "customer_accounts.admin.roles.error.deleteDefault": "No se puede eliminar el rol predeterminado. Establezca primero otro rol como predeterminado.",
184
184
  "customer_accounts.admin.roles.error.load": "Failed to load roles",
185
185
  "customer_accounts.admin.roles.flash.deleted": "Role deleted",
186
186
  "customer_accounts.admin.roles.notAssignable": "No",
@@ -180,7 +180,7 @@
180
180
  "customer_accounts.admin.roles.confirm.delete": "Usunąć rolę \"{{name}}\"?",
181
181
  "customer_accounts.admin.roles.default": "Domyślna",
182
182
  "customer_accounts.admin.roles.error.delete": "Nie udało się usunąć roli",
183
- "customer_accounts.admin.roles.error.deleteSystem": "Ról systemowych nie można usunąć",
183
+ "customer_accounts.admin.roles.error.deleteDefault": "Roli domyślnej nie można usunąć. Najpierw ustaw inną rolę jako domyślną.",
184
184
  "customer_accounts.admin.roles.error.load": "Nie udało się załadować ról",
185
185
  "customer_accounts.admin.roles.flash.deleted": "Rola usunięta",
186
186
  "customer_accounts.admin.roles.notAssignable": "Nie",