@open-mercato/core 0.10.1-develop.7879.1.80d3740285 → 0.10.1-develop.7888.1.96c8c2bd5f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/lib/transferClient.js +72 -0
  3. package/dist/modules/attachments/lib/transferClient.js.map +7 -0
  4. package/dist/modules/attachments/lib/transferLimits.js +5 -0
  5. package/dist/modules/attachments/lib/transferLimits.js.map +7 -0
  6. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +23 -26
  7. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  8. package/dist/modules/catalog/backend/catalog/products/create/page.js +31 -23
  9. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  10. package/dist/modules/catalog/components/products/mediaTransfer.js +27 -0
  11. package/dist/modules/catalog/components/products/mediaTransfer.js.map +7 -0
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +5 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +2 -1
  15. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  16. package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js +5 -2
  17. package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js.map +2 -2
  18. package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js +1 -1
  19. package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js.map +2 -2
  20. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +44 -15
  21. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  22. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +31 -5
  23. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +3 -3
  24. package/dist/modules/warranty_claims/lib/claimLines.js +20 -0
  25. package/dist/modules/warranty_claims/lib/claimLines.js.map +7 -0
  26. package/package.json +7 -7
  27. package/src/modules/attachments/lib/transferClient.ts +87 -0
  28. package/src/modules/attachments/lib/transferLimits.ts +1 -0
  29. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +23 -32
  30. package/src/modules/catalog/backend/catalog/products/create/page.tsx +31 -28
  31. package/src/modules/catalog/components/products/mediaTransfer.ts +40 -0
  32. package/src/modules/catalog/i18n/de.json +2 -0
  33. package/src/modules/catalog/i18n/en.json +2 -0
  34. package/src/modules/catalog/i18n/es.json +2 -0
  35. package/src/modules/catalog/i18n/ko.json +2 -0
  36. package/src/modules/catalog/i18n/pl.json +2 -0
  37. package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +6 -2
  38. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +3 -2
  39. package/src/modules/warranty_claims/backend/components/ReceivingPanel.tsx +6 -3
  40. package/src/modules/warranty_claims/backend/components/ReturnLabelPanel.tsx +2 -2
  41. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +42 -16
  42. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +33 -5
  43. package/src/modules/warranty_claims/lib/claimLines.ts +22 -0
@@ -5,6 +5,7 @@ import Link from "next/link";
5
5
  import { ArrowLeft, Check, CircleAlert, Info, Paperclip, RefreshCw, ShieldCheck, Trash2, TriangleAlert } from "lucide-react";
6
6
  import { replaceWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
7
7
  import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
8
9
  import { localizeDictionaryLabel } from "@open-mercato/core/modules/warranty_claims/lib/dictionaryLabels";
9
10
  import { formatQuantity } from "@open-mercato/core/modules/warranty_claims/lib/quantity";
10
11
  import {
@@ -30,6 +31,7 @@ import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/u
30
31
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
31
32
  import { usePortalContext } from "@open-mercato/ui/portal/PortalContext";
32
33
  import { PortalEmptyState } from "@open-mercato/ui/portal/components/PortalEmptyState";
34
+ import { usePortalAppEvent } from "@open-mercato/ui/portal/hooks/usePortalAppEvent";
33
35
  const TRACKER_STEP_KEYS = ["received", "review", "decision", "return", "resolved"];
34
36
  const STATUS_STEP_INDEX = {
35
37
  draft: 0,
@@ -236,25 +238,32 @@ function WarrantyClaimPortalDetailPage({ params }) {
236
238
  const [actionSubmitting, setActionSubmitting] = React.useState(false);
237
239
  const replacementInputRef = React.useRef(null);
238
240
  const replacementTargetRef = React.useRef(null);
241
+ const claimRequestSequenceRef = React.useRef(0);
242
+ const eventsRequestSequenceRef = React.useRef(0);
239
243
  React.useEffect(() => {
240
244
  if (!loading && !user) {
241
245
  replaceWithPageReload(`/${params.orgSlug}/portal/login`);
242
246
  }
243
247
  }, [loading, user, params.orgSlug]);
244
248
  const refreshEvents = React.useCallback(async (claimId) => {
249
+ const requestSequence = ++eventsRequestSequenceRef.current;
245
250
  const result = await apiCall(`/api/warranty_claims/portal/events?claimId=${encodeURIComponent(claimId)}`);
246
- if (result.ok && result.result) setEvents(result.result.items);
251
+ if (requestSequence === eventsRequestSequenceRef.current && result.ok && result.result) {
252
+ setEvents(result.result.items);
253
+ }
247
254
  }, []);
248
255
  const refreshAttachments = React.useCallback(async (claimId) => {
249
256
  const result = await apiCall(`/api/warranty_claims/portal/attachments?claimId=${encodeURIComponent(claimId)}`);
250
257
  if (result.ok && result.result) setAttachments(result.result.items);
251
258
  }, []);
252
- const loadClaim = React.useCallback(async () => {
253
- setIsLoading(true);
259
+ const loadClaim = React.useCallback(async (showLoading = true) => {
260
+ const requestSequence = ++claimRequestSequenceRef.current;
261
+ if (showLoading) setIsLoading(true);
254
262
  setError(null);
255
263
  setNotFound(false);
256
264
  try {
257
265
  const detail = await apiCall(`/api/warranty_claims/portal/claims/${encodeURIComponent(params.id)}`);
266
+ if (requestSequence !== claimRequestSequenceRef.current) return;
258
267
  if (detail.status === 404) {
259
268
  setClaim(null);
260
269
  setNotFound(true);
@@ -270,14 +279,31 @@ function WarrantyClaimPortalDetailPage({ params }) {
270
279
  refreshAttachments(detail.result.item.id)
271
280
  ]);
272
281
  } catch {
273
- setError(t("warranty_claims.portal.detail.loadError"));
282
+ if (requestSequence === claimRequestSequenceRef.current) {
283
+ setError(t("warranty_claims.portal.detail.loadError"));
284
+ }
274
285
  } finally {
275
- setIsLoading(false);
286
+ if (requestSequence === claimRequestSequenceRef.current) setIsLoading(false);
276
287
  }
277
288
  }, [params.id, refreshAttachments, refreshEvents, t]);
278
289
  React.useEffect(() => {
279
290
  if (user) void loadClaim();
280
291
  }, [loadClaim, user]);
292
+ usePortalAppEvent("warranty_claims.claim.portal_status_changed", (event) => {
293
+ if (!user || event.payload.claimId !== params.id) return;
294
+ void loadClaim(false);
295
+ });
296
+ usePortalAppEvent("warranty_claims.claim.comment_added", (event) => {
297
+ if (!user || event.payload.claimId !== params.id) return;
298
+ void refreshEvents(params.id).catch((error2) => {
299
+ getTelemetryRuntime()?.reportError(error2, {
300
+ module: "warranty_claims",
301
+ code: "warranty_claims.portal_timeline_refresh_failed",
302
+ attributes: { claimId: params.id }
303
+ });
304
+ setError(t("warranty_claims.portal.detail.loadError"));
305
+ });
306
+ });
281
307
  React.useEffect(() => {
282
308
  if (!user) return;
283
309
  let cancelled = false;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/warranty_claims/frontend/%5BorgSlug%5D/portal/claims/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ArrowLeft, Check, CircleAlert, Info, Paperclip, RefreshCw, ShieldCheck, Trash2, TriangleAlert } from 'lucide-react'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useLocale, useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { localizeDictionaryLabel } from '@open-mercato/core/modules/warranty_claims/lib/dictionaryLabels'\nimport { formatQuantity } from '@open-mercato/core/modules/warranty_claims/lib/quantity'\nimport {\n ATTACHMENT_ACCEPT_TYPES,\n validateAttachmentFile,\n} from '@open-mercato/core/modules/warranty_claims/lib/portalAttachmentValidation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\n\ntype Props = { params: { orgSlug: string; id: string } }\n\ntype PortalClaimLine = {\n id: string\n lineNo: number\n sku: string | null\n productName: string | null\n serialNumber: string | null\n faultCode: string | null\n faultDescription: string | null\n qtyClaimed: string\n qtyApproved: string | null\n lineStatus: string\n disposition: string | null\n creditAmount: string | null\n}\n\ntype PortalClaim = {\n id: string\n claimNumber: string\n claimType: string\n status: string\n priority: string\n orderId: string | null\n orderNumber: string | null\n reasonCode: string | null\n rejectionReasonCode: string | null\n resolutionSummary: string | null\n submittedAt: string | null\n resolvedAt: string | null\n closedAt: string | null\n createdAt: string | null\n updatedAt: string | null\n lines: PortalClaimLine[]\n}\n\ntype PortalClaimEvent = {\n id: string\n kind: string\n body: string | null\n payload: Record<string, unknown> | null\n actorCustomerId: string | null\n createdAt: string | null\n}\n\ntype PortalAttachment = {\n id: string\n url: string\n downloadUrl: string\n fileName: string\n fileSize: number\n mimeType: string | null\n thumbnailUrl: string\n createdAt: string | null\n}\n\ntype ClaimResponse = { item: PortalClaim }\ntype EventResponse = { items: PortalClaimEvent[] }\ntype AttachmentResponse = { items: PortalAttachment[] }\ntype MutationOkResponse = { ok: boolean; error?: string }\ntype UploadResponse = MutationOkResponse & { item?: PortalAttachment }\ntype ClaimActionResponse = MutationOkResponse & { claimId?: string; status?: string }\ntype PortalOption = { value: string; label: string }\ntype PortalOptionsResponse = { ok: boolean; result?: { reasons: PortalOption[] } }\n\ntype PortalClaimAction = 'submit' | 'withdraw'\n\ntype TrackerStepState = 'complete' | 'current' | 'pending'\n\ntype TrackerStep = {\n id: string\n title: string\n description: string\n state: TrackerStepState\n dateLabel: string | null\n}\n\ntype BannerTone = 'info' | 'success' | 'warning' | 'error'\n\ntype ActivityEntry = {\n id: string\n title: string\n description: string\n createdAt: string | null\n href?: string\n attachment?: PortalAttachment\n}\n\nconst TRACKER_STEP_KEYS = ['received', 'review', 'decision', 'return', 'resolved'] as const\n\nconst STATUS_STEP_INDEX: Record<string, number> = {\n draft: 0,\n submitted: 1,\n in_review: 1,\n info_requested: 1,\n rejected: 2,\n cancelled: 2,\n approved: 3,\n awaiting_return: 3,\n received: 4,\n inspecting: 4,\n resolved: 5,\n closed: 5,\n}\n\nconst LINE_STATUS_DOT_CLASSES: Record<string, string> = {\n pending: 'bg-status-neutral-icon',\n approved: 'bg-status-success-icon',\n rejected: 'bg-status-error-icon',\n received: 'bg-status-info-icon',\n inspected: 'bg-status-info-icon',\n resolved: 'bg-status-success-icon',\n}\n\nconst BANNER_TONE_CLASSES: Record<BannerTone, { wrapper: string; text: string; icon: string }> = {\n info: {\n wrapper: 'bg-status-info-bg',\n text: 'text-status-info-text',\n icon: 'text-status-info-icon',\n },\n success: {\n wrapper: 'bg-status-success-bg',\n text: 'text-status-success-text',\n icon: 'text-status-success-icon',\n },\n warning: {\n wrapper: 'bg-status-warning-bg',\n text: 'text-status-warning-text',\n icon: 'text-status-warning-icon',\n },\n error: {\n wrapper: 'bg-status-error-bg',\n text: 'text-status-error-text',\n icon: 'text-status-error-icon',\n },\n}\n\nfunction bannerTone(status: string): BannerTone {\n if (status === 'rejected' || status === 'cancelled') return 'error'\n if (status === 'info_requested') return 'warning'\n if (status === 'approved' || status === 'resolved' || status === 'closed') return 'success'\n return 'info'\n}\n\nfunction bannerMessage(claim: PortalClaim, t: TranslateFn): string {\n switch (claim.status) {\n case 'draft':\n return t('warranty_claims.portal.tracker.banner.draft')\n case 'info_requested':\n return t('warranty_claims.portal.tracker.banner.infoRequested')\n case 'approved':\n return t('warranty_claims.portal.tracker.banner.approved')\n case 'awaiting_return':\n return t('warranty_claims.portal.tracker.banner.awaitingReturn')\n case 'received':\n case 'inspecting':\n return t('warranty_claims.portal.tracker.banner.goodsFlow')\n case 'resolved':\n case 'closed':\n return claim.resolutionSummary ?? t('warranty_claims.portal.tracker.banner.resolved')\n case 'rejected': {\n const reason = claim.rejectionReasonCode\n ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonCode)\n : null\n const base = t('warranty_claims.portal.detail.terminal.rejected')\n const parts = [base, reason, claim.resolutionSummary].filter((part): part is string => Boolean(part))\n return parts.join(' ')\n }\n case 'cancelled':\n return t('warranty_claims.portal.detail.terminal.cancelled')\n default:\n return t('warranty_claims.portal.tracker.banner.review')\n }\n}\n\nfunction formatShortDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction formatLongDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction buildTrackerSteps(claim: PortalClaim, t: TranslateFn, locale: string): TrackerStep[] {\n const currentIndex = STATUS_STEP_INDEX[claim.status] ?? 1\n const terminalComplete = claim.status === 'resolved' || claim.status === 'closed'\n return TRACKER_STEP_KEYS.map((key, index) => {\n const state: TrackerStepState = index < currentIndex || (terminalComplete && index === currentIndex)\n ? 'complete'\n : index === currentIndex\n ? 'current'\n : 'pending'\n let dateLabel: string | null = null\n if (index === 0 && state === 'complete') dateLabel = formatShortDate(claim.submittedAt, locale)\n if (index === TRACKER_STEP_KEYS.length - 1 && state === 'complete') {\n dateLabel = formatShortDate(\n claim.status === 'closed'\n ? claim.closedAt ?? claim.resolvedAt\n : claim.resolvedAt ?? claim.closedAt,\n locale,\n )\n }\n if (state === 'current') dateLabel = t('warranty_claims.portal.tracker.step.now')\n return {\n id: key,\n title: t(`warranty_claims.portal.tracker.step.${key}.title`),\n description: t(`warranty_claims.portal.tracker.step.${key}.description`),\n state,\n dateLabel,\n }\n })\n}\n\nfunction buildClaimTitle(claim: PortalClaim, reasonOptions: PortalOption[], t: TranslateFn): string {\n const typeLabel = t(`warranty_claims.claimType.${claim.claimType}`)\n if (claim.reasonCode) {\n const reasonCode = claim.reasonCode\n const dictionaryLabel = reasonOptions.find((option) => option.value === reasonCode)?.label ?? reasonCode\n const reasonLabel = localizeDictionaryLabel(t, 'reason', reasonCode, dictionaryLabel)\n return t('warranty_claims.portal.tracker.titleWithReason', { reason: reasonLabel, type: typeLabel })\n }\n return t('warranty_claims.portal.tracker.titleFallback', { type: typeLabel })\n}\n\nfunction buildClaimSubtitle(claim: PortalClaim, t: TranslateFn, locale: string): string {\n if (claim.status === 'draft') {\n const created = formatLongDate(claim.createdAt, locale)\n return created\n ? t('warranty_claims.portal.tracker.draftCreated', { date: created })\n : t('warranty_claims.portal.detail.notSubmitted')\n }\n const submitted = formatLongDate(claim.submittedAt, locale) ?? formatLongDate(claim.createdAt, locale)\n if (!submitted) return t('warranty_claims.portal.value.notAvailable')\n if (claim.orderNumber) {\n return t('warranty_claims.portal.tracker.submittedFromOrder', { date: submitted, order: claim.orderNumber })\n }\n return t('warranty_claims.portal.tracker.submittedOn', { date: submitted })\n}\n\nfunction formatEventBody(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.body) return event.body\n const payload = event.payload\n const from = typeof payload?.from === 'string' ? payload.from : null\n const to = typeof payload?.to === 'string' ? payload.to : null\n if (from && to) {\n return t('warranty_claims.portal.event.transition', {\n from: t(`warranty_claims.status.${from}`),\n to: t(`warranty_claims.status.${to}`),\n })\n }\n return t('warranty_claims.portal.event.noDetails')\n}\n\nfunction eventTitle(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.kind === 'comment') {\n return event.actorCustomerId\n ? t('warranty_claims.portal.tracker.event.youReplied')\n : t('warranty_claims.portal.tracker.event.teamReplied')\n }\n return t(`warranty_claims.portal.event.${event.kind}`)\n}\n\nfunction buildActivityEntries(\n events: PortalClaimEvent[],\n attachments: PortalAttachment[],\n t: TranslateFn,\n): ActivityEntry[] {\n const eventEntries: ActivityEntry[] = events.map((event) => ({\n id: `event-${event.id}`,\n title: eventTitle(event, t),\n description: formatEventBody(event, t),\n createdAt: event.createdAt,\n }))\n const attachmentEntries: ActivityEntry[] = attachments.map((attachment) => ({\n id: `attachment-${attachment.id}`,\n title: t('warranty_claims.portal.tracker.event.attachmentAdded'),\n description: attachment.fileName,\n createdAt: attachment.createdAt,\n href: attachment.downloadUrl,\n attachment,\n }))\n return [...eventEntries, ...attachmentEntries].sort((left, right) => {\n const leftTime = left.createdAt ? new Date(left.createdAt).getTime() : 0\n const rightTime = right.createdAt ? new Date(right.createdAt).getTime() : 0\n return rightTime - leftTime\n })\n}\n\nconst DETAIL_MUTATION_CONTEXT_ID = 'warranty_claims.portal.claim.detail'\n\nconst SECTION_CLASS = 'flex w-full flex-col gap-3 border-t border-border px-6 pb-6 pt-5'\nconst SECTION_HEADING_CLASS = 'text-sm font-semibold text-foreground'\nconst DARK_BUTTON_CLASS = 'rounded-md px-3 py-2 text-sm font-medium'\n\nexport default function WarrantyClaimPortalDetailPage({ params }: Props) {\n const t = useT()\n const locale = useLocale()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n const guardedMutation = useGuardedMutation<Record<string, unknown>>({\n contextId: DETAIL_MUTATION_CONTEXT_ID,\n blockedMessage: t('warranty_claims.portal.detail.blocked'),\n })\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [claim, setClaim] = React.useState<PortalClaim | null>(null)\n const [events, setEvents] = React.useState<PortalClaimEvent[]>([])\n const [attachments, setAttachments] = React.useState<PortalAttachment[]>([])\n const [reasonOptions, setReasonOptions] = React.useState<PortalOption[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n const [comment, setComment] = React.useState('')\n const [commentSubmitting, setCommentSubmitting] = React.useState(false)\n const [fileInputKey, setFileInputKey] = React.useState(0)\n const [uploading, setUploading] = React.useState(false)\n const [attachmentMutationId, setAttachmentMutationId] = React.useState<string | null>(null)\n const [claimAction, setClaimAction] = React.useState<PortalClaimAction | null>(null)\n const [actionSubmitting, setActionSubmitting] = React.useState(false)\n const replacementInputRef = React.useRef<HTMLInputElement | null>(null)\n const replacementTargetRef = React.useRef<PortalAttachment | null>(null)\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n React.useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const refreshEvents = React.useCallback(async (claimId: string) => {\n const result = await apiCall<EventResponse>(`/api/warranty_claims/portal/events?claimId=${encodeURIComponent(claimId)}`)\n if (result.ok && result.result) setEvents(result.result.items)\n }, [])\n\n const refreshAttachments = React.useCallback(async (claimId: string) => {\n const result = await apiCall<AttachmentResponse>(`/api/warranty_claims/portal/attachments?claimId=${encodeURIComponent(claimId)}`)\n if (result.ok && result.result) setAttachments(result.result.items)\n }, [])\n\n const loadClaim = React.useCallback(async () => {\n setIsLoading(true)\n setError(null)\n setNotFound(false)\n try {\n const detail = await apiCall<ClaimResponse>(`/api/warranty_claims/portal/claims/${encodeURIComponent(params.id)}`)\n if (detail.status === 404) {\n setClaim(null)\n setNotFound(true)\n return\n }\n if (!detail.ok || !detail.result) {\n setError(t('warranty_claims.portal.detail.loadError'))\n return\n }\n setClaim(detail.result.item)\n await Promise.all([\n refreshEvents(detail.result.item.id),\n refreshAttachments(detail.result.item.id),\n ])\n } catch {\n setError(t('warranty_claims.portal.detail.loadError'))\n } finally {\n setIsLoading(false)\n }\n }, [params.id, refreshAttachments, refreshEvents, t])\n\n React.useEffect(() => {\n if (user) void loadClaim()\n }, [loadClaim, user])\n\n React.useEffect(() => {\n if (!user) return\n let cancelled = false\n apiCall<PortalOptionsResponse>('/api/warranty_claims/portal/options')\n .then((result) => {\n if (cancelled) return\n setReasonOptions(result.result?.ok ? result.result.result?.reasons ?? [] : [])\n })\n .catch(() => {})\n return () => {\n cancelled = true\n }\n }, [user])\n\n const submitComment = React.useCallback(async () => {\n if (!claim || commentSubmitting) return\n const body = comment.trim()\n if (!body) return\n setCommentSubmitting(true)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>('/api/warranty_claims/portal/events', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ claimId: claim.id, body }),\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim_event',\n operation: 'portal_comment',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim_event',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, body },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.commentError'))\n return\n }\n setComment('')\n flash(t('warranty_claims.portal.detail.commentSuccess'), 'success')\n await refreshEvents(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.commentError'))\n } finally {\n setCommentSubmitting(false)\n }\n }, [claim, comment, commentSubmitting, guardedMutation, refreshEvents, t])\n\n const uploadAttachment = React.useCallback(async (file: File, replacementTarget?: PortalAttachment) => {\n if (!claim || uploading) return\n const validationError = await validateAttachmentFile(file, t)\n if (validationError) {\n setError(validationError)\n flash(validationError, 'error')\n return\n }\n setUploading(true)\n setAttachmentMutationId(replacementTarget?.id ?? 'new')\n setError(null)\n try {\n const form = new FormData()\n form.set('claimId', claim.id)\n form.set('file', file)\n if (replacementTarget) form.set('attachmentId', replacementTarget.id)\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<UploadResponse>('/api/warranty_claims/portal/attachments', {\n method: replacementTarget ? 'PUT' : 'POST',\n credentials: 'include',\n body: form,\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: replacementTarget ? 'portal_attachment_replace' : 'portal_attachment_upload',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: replacementTarget?.id ?? claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: {\n claimId: claim.id,\n attachmentId: replacementTarget?.id,\n fileName: file.name,\n fileSize: file.size,\n },\n })\n if (!result.ok || !result.result?.ok) {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n return\n }\n flash(\n replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceSuccess', 'Attachment replaced.')\n : t('warranty_claims.portal.detail.attachmentSuccess'),\n 'success',\n )\n await refreshAttachments(claim.id)\n } catch {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n setFileInputKey((current) => current + 1)\n }\n }, [claim, guardedMutation, refreshAttachments, t, uploading])\n\n const deleteAttachment = React.useCallback(async (attachment: PortalAttachment) => {\n if (!claim || uploading) return\n const confirmed = await confirm({\n title: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n text: t(\n 'warranty_claims.portal.detail.attachmentDeleteConfirm',\n 'Delete \"{name}\"? This action cannot be undone.',\n { name: attachment.fileName },\n ),\n confirmText: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n cancelText: t('warranty_claims.portal.detail.dialogCancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n setUploading(true)\n setAttachmentMutationId(attachment.id)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>(\n `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n { method: 'DELETE', credentials: 'include' },\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: 'portal_attachment_delete',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: attachment.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, attachmentId: attachment.id },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n return\n }\n flash(t('warranty_claims.portal.detail.attachmentDeleteSuccess', 'Attachment deleted.'), 'success')\n await refreshAttachments(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n }\n }, [claim, confirm, guardedMutation, refreshAttachments, t, uploading])\n\n const requestAttachmentReplacement = React.useCallback((attachment: PortalAttachment) => {\n if (uploading) return\n replacementTargetRef.current = attachment\n replacementInputRef.current?.click()\n }, [uploading])\n\n const runClaimAction = React.useCallback(async (action: PortalClaimAction) => {\n if (!claim || actionSubmitting) return\n setActionSubmitting(true)\n setError(null)\n try {\n const endpoint = `/api/warranty_claims/portal/claims/${encodeURIComponent(claim.id)}/${action}`\n const result = await guardedMutation.runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(claim.updatedAt),\n () => apiCall<ClaimActionResponse>(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n }),\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim',\n operation: action === 'submit' ? 'portal_submit' : 'portal_withdraw',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, action },\n })\n if (!result.ok || !result.result?.ok) {\n const message = result.status === 409\n ? t('warranty_claims.portal.detail.actionConflict', 'This claim changed in the meantime. It has been reloaded \u2014 please try again.')\n : action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n if (result.status === 409) {\n setClaimAction(null)\n await loadClaim()\n }\n return\n }\n setClaimAction(null)\n flash(\n action === 'submit'\n ? t('warranty_claims.portal.detail.submitSuccess', 'Your claim has been submitted.')\n : t('warranty_claims.portal.detail.withdrawSuccess', 'Your claim has been withdrawn.'),\n 'success',\n )\n await loadClaim()\n } catch {\n const message = action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n } finally {\n setActionSubmitting(false)\n }\n }, [actionSubmitting, claim, guardedMutation, loadClaim, t])\n\n const handleSelectedFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null\n if (!file) return\n void uploadAttachment(file)\n }, [uploadAttachment])\n\n const handleReplacementFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const replacementTarget = replacementTargetRef.current\n replacementTargetRef.current = null\n const file = event.target.files?.[0] ?? null\n event.target.value = ''\n if (!replacementTarget || !file) return\n void uploadAttachment(file, replacementTarget)\n }, [uploadAttachment])\n\n const handleCommentSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n void submitComment()\n }, [submitComment])\n\n const handleCommentKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitComment()\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setComment('')\n }\n }, [submitComment])\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n if (isLoading) {\n return <LoadingMessage label={t('warranty_claims.portal.detail.loading')} />\n }\n\n if (notFound) {\n return (\n <PortalEmptyState\n icon={<ShieldCheck className=\"size-5\" />}\n title={t('warranty_claims.portal.detail.notFoundTitle')}\n description={t('warranty_claims.portal.detail.notFoundDescription')}\n action={\n <Button asChild>\n <Link href={`/${params.orgSlug}/portal/claims`}>\n <ArrowLeft className=\"size-4\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.back')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n if (!claim) {\n return <ErrorMessage label={error ?? t('warranty_claims.portal.detail.loadError')} />\n }\n\n const trackerSteps = buildTrackerSteps(claim, t, locale)\n const isDraft = claim.status === 'draft'\n const canWithdraw = claim.status === 'draft' || claim.status === 'submitted'\n const tone = bannerTone(claim.status)\n const toneClasses = BANNER_TONE_CLASSES[tone]\n const BannerIcon = tone === 'error' ? CircleAlert : tone === 'warning' ? TriangleAlert : Info\n const activityEntries = buildActivityEntries(events, attachments, t)\n\n return (\n <div className=\"mx-auto flex w-full max-w-3xl flex-col gap-4\">\n <Link\n href={`/${params.orgSlug}/portal/claims`}\n className=\"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n {'\u2190'} {t('warranty_claims.portal.listTitle')}\n </Link>\n\n {error ? <ErrorMessage label={error} /> : null}\n\n <div className=\"w-full overflow-hidden rounded-lg border border-border bg-card\">\n <div className=\"flex w-full flex-col gap-3 px-6 pb-5 pt-6\">\n <div className=\"flex w-full items-center gap-2\">\n <div className=\"flex items-center gap-2 rounded-sm border border-border bg-background px-3 py-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t(`warranty_claims.claimType.${claim.claimType}`)}\n </span>\n <span className=\"text-sm font-semibold text-foreground\">\n {claim.claimNumber}\n </span>\n </div>\n <div className=\"min-w-px flex-1\" />\n {isDraft ? (\n <Button\n type=\"button\"\n size=\"sm\"\n className={DARK_BUTTON_CLASS}\n onClick={() => setClaimAction('submit')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.submit')}\n </Button>\n ) : null}\n {canWithdraw ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"rounded-md px-3 py-2 text-sm font-medium\"\n onClick={() => setClaimAction('withdraw')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.withdraw', 'Withdraw claim')}\n </Button>\n ) : null}\n </div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {buildClaimTitle(claim, reasonOptions, t)}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {buildClaimSubtitle(claim, t, locale)}\n </p>\n </div>\n\n <div className=\"w-full px-6 pb-6\">\n <div className={`flex w-full items-start gap-3 rounded-lg px-4 py-3 ${toneClasses.wrapper}`}>\n <BannerIcon className={`mt-px size-4 shrink-0 ${toneClasses.icon}`} aria-hidden=\"true\" />\n <p className={`min-w-px flex-1 text-sm ${toneClasses.text}`}>\n {bannerMessage(claim, t)}\n </p>\n </div>\n </div>\n\n <div className=\"flex w-full flex-col px-6 pb-6\">\n {trackerSteps.map((step, index) => (\n <div key={step.id} className=\"flex w-full items-stretch gap-4\">\n <div className=\"flex flex-col items-center\">\n {step.state === 'complete' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-status-success-icon\">\n <Check className=\"size-3 text-status-success-bg\" strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : step.state === 'current' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground\">\n {index + 1}\n </span>\n ) : (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold text-muted-foreground\">\n {index + 1}\n </span>\n )}\n {index < trackerSteps.length - 1 ? (\n <span\n className={`min-h-2 w-px flex-1 ${step.state === 'complete' ? 'bg-status-success-icon' : 'bg-border'}`}\n />\n ) : null}\n </div>\n <div className=\"flex min-w-px flex-1 flex-col gap-1 pb-4\">\n <div className=\"flex w-full items-center gap-2\">\n <p\n className={\n step.state === 'pending'\n ? 'text-sm font-medium text-muted-foreground'\n : 'text-sm font-semibold text-foreground'\n }\n >\n {step.title}\n </p>\n <div className=\"min-w-px flex-1\" />\n {step.dateLabel ? (\n <p className=\"text-xs text-muted-foreground\">{step.dateLabel}</p>\n ) : null}\n </div>\n <p className=\"w-full text-xs text-muted-foreground\">{step.description}</p>\n </div>\n </div>\n ))}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.items')}</p>\n {claim.lines.length > 0 ? (\n claim.lines.map((line) => (\n <div\n key={line.id}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3\"\n >\n <div className=\"flex min-w-px flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium text-foreground\">\n {line.productName ?? line.sku ?? t('warranty_claims.portal.value.unnamedLine')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {line.sku\n ? t('warranty_claims.portal.tracker.itemMeta', {\n sku: line.sku,\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })\n : t('warranty_claims.portal.tracker.itemQty', {\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2 rounded-sm border border-border bg-background px-2 py-1\">\n <span\n className={`size-2 shrink-0 rounded-full ${LINE_STATUS_DOT_CLASSES[line.lineStatus] ?? 'bg-status-neutral-icon'}`}\n aria-hidden=\"true\"\n />\n <span className=\"text-xs font-medium text-foreground\">\n {t(`warranty_claims.lineStatus.${line.lineStatus}`)}\n </span>\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.linesEmpty.description')}\n </p>\n )}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.activity')}</p>\n {activityEntries.length > 0 ? (\n activityEntries.map((entry) => (\n <div key={entry.id} className=\"flex w-full items-start gap-3\">\n <span className=\"mt-1 size-2 shrink-0 rounded-full bg-status-neutral-icon\" aria-hidden=\"true\" />\n <div className=\"flex min-w-px flex-1 flex-col gap-px\">\n <div className=\"flex w-full items-start gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{entry.title}</p>\n <div className=\"min-w-px flex-1\" />\n {entry.createdAt ? (\n <p className=\"text-xs text-muted-foreground\">{formatShortDate(entry.createdAt, locale)}</p>\n ) : null}\n </div>\n {entry.href ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={entry.href}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"min-w-0 whitespace-pre-wrap text-xs text-muted-foreground underline-offset-2 hover:underline\"\n >\n {entry.description}\n </a>\n {entry.attachment ? (\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentReplaceNamed',\n 'Replace {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => requestAttachmentReplacement(entry.attachment!)}\n disabled={uploading}\n >\n {attachmentMutationId === entry.attachment.id ? (\n <Spinner className=\"size-3\" />\n ) : (\n <RefreshCw className=\"size-3\" aria-hidden=\"true\" />\n )}\n {t('warranty_claims.portal.detail.attachmentReplace', 'Replace')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs text-status-error-text\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentDeleteNamed',\n 'Delete {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => { void deleteAttachment(entry.attachment!) }}\n disabled={uploading}\n >\n <Trash2 className=\"size-3\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <p className=\"whitespace-pre-wrap text-xs text-muted-foreground\">{entry.description}</p>\n )}\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.timelineEmpty.description')}\n </p>\n )}\n <input\n ref={replacementInputRef}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleReplacementFileChange}\n disabled={uploading}\n className=\"sr-only\"\n aria-label={t('warranty_claims.portal.detail.attachmentChooseReplacement', 'Choose replacement file')}\n />\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.message')}</p>\n <form id=\"warranty-claim-comment-box\" className=\"flex w-full flex-col gap-3\" onSubmit={handleCommentSubmit}>\n <Textarea\n value={comment}\n onChange={(event) => setComment(event.target.value)}\n onKeyDown={handleCommentKeyDown}\n placeholder={t('warranty_claims.portal.tracker.messagePlaceholder')}\n disabled={commentSubmitting}\n maxLength={8000}\n className=\"h-40 resize-none rounded-lg border-input bg-background px-3 py-2 text-sm shadow-xs placeholder:text-muted-foreground\"\n />\n <div className=\"flex w-full items-center gap-2\">\n <label className={`flex items-center gap-2 ${uploading ? 'cursor-default opacity-70' : 'cursor-pointer'}`}>\n {uploading ? (\n <Spinner className=\"size-4\" />\n ) : (\n <Paperclip className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n )}\n <span className=\"text-xs font-medium text-muted-foreground transition-colors hover:text-foreground\">\n {uploading\n ? t('warranty_claims.portal.detail.uploading')\n : t('warranty_claims.portal.tracker.attachFiles')}\n </span>\n <input\n key={fileInputKey}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleSelectedFileChange}\n disabled={uploading}\n className=\"sr-only\"\n />\n </label>\n <div className=\"min-w-px flex-1\" />\n <Button type=\"submit\" size=\"sm\" className={DARK_BUTTON_CLASS} disabled={commentSubmitting || !comment.trim()}>\n {commentSubmitting\n ? t('warranty_claims.portal.tracker.sending')\n : t('warranty_claims.portal.tracker.send')}\n </Button>\n </div>\n </form>\n </div>\n </div>\n\n <Dialog\n open={claimAction !== null}\n onOpenChange={(open) => {\n if (!open && !actionSubmitting) setClaimAction(null)\n }}\n >\n <DialogContent\n className=\"max-w-lg\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (claimAction && !actionSubmitting) void runClaimAction(claimAction)\n }\n if (event.key === 'Escape' && !actionSubmitting) {\n setClaimAction(null)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.title', 'Withdraw this claim?')\n : t('warranty_claims.portal.detail.submitDialog.title', 'Submit this claim?')}\n </DialogTitle>\n <DialogDescription>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.description', 'The claim will be cancelled and cannot be reopened. This cannot be undone.')\n : t('warranty_claims.portal.detail.submitDialog.description', 'Your claim will be sent to our support team for review. You can still add comments and photos afterwards.')}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setClaimAction(null)}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.dialogCancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant={claimAction === 'withdraw' ? 'destructive' : 'default'}\n onClick={() => {\n if (claimAction) void runClaimAction(claimAction)\n }}\n disabled={actionSubmitting}\n >\n {claimAction === 'withdraw'\n ? (actionSubmitting\n ? t('warranty_claims.portal.detail.withdrawing', 'Withdrawing...')\n : t('warranty_claims.portal.detail.withdrawConfirm', 'Withdraw claim'))\n : (actionSubmitting\n ? t('warranty_claims.portal.detail.submitting', 'Submitting...')\n : t('warranty_claims.portal.detail.submitConfirm', 'Submit claim'))}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ArrowLeft, Check, CircleAlert, Info, Paperclip, RefreshCw, ShieldCheck, Trash2, TriangleAlert } from 'lucide-react'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useLocale, useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { localizeDictionaryLabel } from '@open-mercato/core/modules/warranty_claims/lib/dictionaryLabels'\nimport { formatQuantity } from '@open-mercato/core/modules/warranty_claims/lib/quantity'\nimport {\n ATTACHMENT_ACCEPT_TYPES,\n validateAttachmentFile,\n} from '@open-mercato/core/modules/warranty_claims/lib/portalAttachmentValidation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalEmptyState } from '@open-mercato/ui/portal/components/PortalEmptyState'\nimport { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'\n\ntype Props = { params: { orgSlug: string; id: string } }\n\ntype PortalClaimLine = {\n id: string\n lineNo: number\n sku: string | null\n productName: string | null\n serialNumber: string | null\n faultCode: string | null\n faultDescription: string | null\n qtyClaimed: string\n qtyApproved: string | null\n lineStatus: string\n disposition: string | null\n creditAmount: string | null\n}\n\ntype PortalClaim = {\n id: string\n claimNumber: string\n claimType: string\n status: string\n priority: string\n orderId: string | null\n orderNumber: string | null\n reasonCode: string | null\n rejectionReasonCode: string | null\n resolutionSummary: string | null\n submittedAt: string | null\n resolvedAt: string | null\n closedAt: string | null\n createdAt: string | null\n updatedAt: string | null\n lines: PortalClaimLine[]\n}\n\ntype PortalClaimEvent = {\n id: string\n kind: string\n body: string | null\n payload: Record<string, unknown> | null\n actorCustomerId: string | null\n createdAt: string | null\n}\n\ntype PortalAttachment = {\n id: string\n url: string\n downloadUrl: string\n fileName: string\n fileSize: number\n mimeType: string | null\n thumbnailUrl: string\n createdAt: string | null\n}\n\ntype ClaimResponse = { item: PortalClaim }\ntype EventResponse = { items: PortalClaimEvent[] }\ntype AttachmentResponse = { items: PortalAttachment[] }\ntype MutationOkResponse = { ok: boolean; error?: string }\ntype UploadResponse = MutationOkResponse & { item?: PortalAttachment }\ntype ClaimActionResponse = MutationOkResponse & { claimId?: string; status?: string }\ntype PortalOption = { value: string; label: string }\ntype PortalOptionsResponse = { ok: boolean; result?: { reasons: PortalOption[] } }\n\ntype PortalClaimAction = 'submit' | 'withdraw'\n\ntype TrackerStepState = 'complete' | 'current' | 'pending'\n\ntype TrackerStep = {\n id: string\n title: string\n description: string\n state: TrackerStepState\n dateLabel: string | null\n}\n\ntype BannerTone = 'info' | 'success' | 'warning' | 'error'\n\ntype ActivityEntry = {\n id: string\n title: string\n description: string\n createdAt: string | null\n href?: string\n attachment?: PortalAttachment\n}\n\nconst TRACKER_STEP_KEYS = ['received', 'review', 'decision', 'return', 'resolved'] as const\n\nconst STATUS_STEP_INDEX: Record<string, number> = {\n draft: 0,\n submitted: 1,\n in_review: 1,\n info_requested: 1,\n rejected: 2,\n cancelled: 2,\n approved: 3,\n awaiting_return: 3,\n received: 4,\n inspecting: 4,\n resolved: 5,\n closed: 5,\n}\n\nconst LINE_STATUS_DOT_CLASSES: Record<string, string> = {\n pending: 'bg-status-neutral-icon',\n approved: 'bg-status-success-icon',\n rejected: 'bg-status-error-icon',\n received: 'bg-status-info-icon',\n inspected: 'bg-status-info-icon',\n resolved: 'bg-status-success-icon',\n}\n\nconst BANNER_TONE_CLASSES: Record<BannerTone, { wrapper: string; text: string; icon: string }> = {\n info: {\n wrapper: 'bg-status-info-bg',\n text: 'text-status-info-text',\n icon: 'text-status-info-icon',\n },\n success: {\n wrapper: 'bg-status-success-bg',\n text: 'text-status-success-text',\n icon: 'text-status-success-icon',\n },\n warning: {\n wrapper: 'bg-status-warning-bg',\n text: 'text-status-warning-text',\n icon: 'text-status-warning-icon',\n },\n error: {\n wrapper: 'bg-status-error-bg',\n text: 'text-status-error-text',\n icon: 'text-status-error-icon',\n },\n}\n\nfunction bannerTone(status: string): BannerTone {\n if (status === 'rejected' || status === 'cancelled') return 'error'\n if (status === 'info_requested') return 'warning'\n if (status === 'approved' || status === 'resolved' || status === 'closed') return 'success'\n return 'info'\n}\n\nfunction bannerMessage(claim: PortalClaim, t: TranslateFn): string {\n switch (claim.status) {\n case 'draft':\n return t('warranty_claims.portal.tracker.banner.draft')\n case 'info_requested':\n return t('warranty_claims.portal.tracker.banner.infoRequested')\n case 'approved':\n return t('warranty_claims.portal.tracker.banner.approved')\n case 'awaiting_return':\n return t('warranty_claims.portal.tracker.banner.awaitingReturn')\n case 'received':\n case 'inspecting':\n return t('warranty_claims.portal.tracker.banner.goodsFlow')\n case 'resolved':\n case 'closed':\n return claim.resolutionSummary ?? t('warranty_claims.portal.tracker.banner.resolved')\n case 'rejected': {\n const reason = claim.rejectionReasonCode\n ? localizeDictionaryLabel(t, 'rejection', claim.rejectionReasonCode, claim.rejectionReasonCode)\n : null\n const base = t('warranty_claims.portal.detail.terminal.rejected')\n const parts = [base, reason, claim.resolutionSummary].filter((part): part is string => Boolean(part))\n return parts.join(' ')\n }\n case 'cancelled':\n return t('warranty_claims.portal.detail.terminal.cancelled')\n default:\n return t('warranty_claims.portal.tracker.banner.review')\n }\n}\n\nfunction formatShortDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { month: 'short', day: 'numeric' })\n}\n\nfunction formatLongDate(value: string | null, locale: string): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return date.toLocaleDateString(locale || undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n}\n\nfunction buildTrackerSteps(claim: PortalClaim, t: TranslateFn, locale: string): TrackerStep[] {\n const currentIndex = STATUS_STEP_INDEX[claim.status] ?? 1\n const terminalComplete = claim.status === 'resolved' || claim.status === 'closed'\n return TRACKER_STEP_KEYS.map((key, index) => {\n const state: TrackerStepState = index < currentIndex || (terminalComplete && index === currentIndex)\n ? 'complete'\n : index === currentIndex\n ? 'current'\n : 'pending'\n let dateLabel: string | null = null\n if (index === 0 && state === 'complete') dateLabel = formatShortDate(claim.submittedAt, locale)\n if (index === TRACKER_STEP_KEYS.length - 1 && state === 'complete') {\n dateLabel = formatShortDate(\n claim.status === 'closed'\n ? claim.closedAt ?? claim.resolvedAt\n : claim.resolvedAt ?? claim.closedAt,\n locale,\n )\n }\n if (state === 'current') dateLabel = t('warranty_claims.portal.tracker.step.now')\n return {\n id: key,\n title: t(`warranty_claims.portal.tracker.step.${key}.title`),\n description: t(`warranty_claims.portal.tracker.step.${key}.description`),\n state,\n dateLabel,\n }\n })\n}\n\nfunction buildClaimTitle(claim: PortalClaim, reasonOptions: PortalOption[], t: TranslateFn): string {\n const typeLabel = t(`warranty_claims.claimType.${claim.claimType}`)\n if (claim.reasonCode) {\n const reasonCode = claim.reasonCode\n const dictionaryLabel = reasonOptions.find((option) => option.value === reasonCode)?.label ?? reasonCode\n const reasonLabel = localizeDictionaryLabel(t, 'reason', reasonCode, dictionaryLabel)\n return t('warranty_claims.portal.tracker.titleWithReason', { reason: reasonLabel, type: typeLabel })\n }\n return t('warranty_claims.portal.tracker.titleFallback', { type: typeLabel })\n}\n\nfunction buildClaimSubtitle(claim: PortalClaim, t: TranslateFn, locale: string): string {\n if (claim.status === 'draft') {\n const created = formatLongDate(claim.createdAt, locale)\n return created\n ? t('warranty_claims.portal.tracker.draftCreated', { date: created })\n : t('warranty_claims.portal.detail.notSubmitted')\n }\n const submitted = formatLongDate(claim.submittedAt, locale) ?? formatLongDate(claim.createdAt, locale)\n if (!submitted) return t('warranty_claims.portal.value.notAvailable')\n if (claim.orderNumber) {\n return t('warranty_claims.portal.tracker.submittedFromOrder', { date: submitted, order: claim.orderNumber })\n }\n return t('warranty_claims.portal.tracker.submittedOn', { date: submitted })\n}\n\nfunction formatEventBody(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.body) return event.body\n const payload = event.payload\n const from = typeof payload?.from === 'string' ? payload.from : null\n const to = typeof payload?.to === 'string' ? payload.to : null\n if (from && to) {\n return t('warranty_claims.portal.event.transition', {\n from: t(`warranty_claims.status.${from}`),\n to: t(`warranty_claims.status.${to}`),\n })\n }\n return t('warranty_claims.portal.event.noDetails')\n}\n\nfunction eventTitle(event: PortalClaimEvent, t: TranslateFn): string {\n if (event.kind === 'comment') {\n return event.actorCustomerId\n ? t('warranty_claims.portal.tracker.event.youReplied')\n : t('warranty_claims.portal.tracker.event.teamReplied')\n }\n return t(`warranty_claims.portal.event.${event.kind}`)\n}\n\nfunction buildActivityEntries(\n events: PortalClaimEvent[],\n attachments: PortalAttachment[],\n t: TranslateFn,\n): ActivityEntry[] {\n const eventEntries: ActivityEntry[] = events.map((event) => ({\n id: `event-${event.id}`,\n title: eventTitle(event, t),\n description: formatEventBody(event, t),\n createdAt: event.createdAt,\n }))\n const attachmentEntries: ActivityEntry[] = attachments.map((attachment) => ({\n id: `attachment-${attachment.id}`,\n title: t('warranty_claims.portal.tracker.event.attachmentAdded'),\n description: attachment.fileName,\n createdAt: attachment.createdAt,\n href: attachment.downloadUrl,\n attachment,\n }))\n return [...eventEntries, ...attachmentEntries].sort((left, right) => {\n const leftTime = left.createdAt ? new Date(left.createdAt).getTime() : 0\n const rightTime = right.createdAt ? new Date(right.createdAt).getTime() : 0\n return rightTime - leftTime\n })\n}\n\nconst DETAIL_MUTATION_CONTEXT_ID = 'warranty_claims.portal.claim.detail'\n\nconst SECTION_CLASS = 'flex w-full flex-col gap-3 border-t border-border px-6 pb-6 pt-5'\nconst SECTION_HEADING_CLASS = 'text-sm font-semibold text-foreground'\nconst DARK_BUTTON_CLASS = 'rounded-md px-3 py-2 text-sm font-medium'\n\nexport default function WarrantyClaimPortalDetailPage({ params }: Props) {\n const t = useT()\n const locale = useLocale()\n const { auth } = usePortalContext()\n const { user, loading } = auth\n const guardedMutation = useGuardedMutation<Record<string, unknown>>({\n contextId: DETAIL_MUTATION_CONTEXT_ID,\n blockedMessage: t('warranty_claims.portal.detail.blocked'),\n })\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [claim, setClaim] = React.useState<PortalClaim | null>(null)\n const [events, setEvents] = React.useState<PortalClaimEvent[]>([])\n const [attachments, setAttachments] = React.useState<PortalAttachment[]>([])\n const [reasonOptions, setReasonOptions] = React.useState<PortalOption[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [notFound, setNotFound] = React.useState(false)\n const [comment, setComment] = React.useState('')\n const [commentSubmitting, setCommentSubmitting] = React.useState(false)\n const [fileInputKey, setFileInputKey] = React.useState(0)\n const [uploading, setUploading] = React.useState(false)\n const [attachmentMutationId, setAttachmentMutationId] = React.useState<string | null>(null)\n const [claimAction, setClaimAction] = React.useState<PortalClaimAction | null>(null)\n const [actionSubmitting, setActionSubmitting] = React.useState(false)\n const replacementInputRef = React.useRef<HTMLInputElement | null>(null)\n const replacementTargetRef = React.useRef<PortalAttachment | null>(null)\n const claimRequestSequenceRef = React.useRef(0)\n const eventsRequestSequenceRef = React.useRef(0)\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n React.useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const refreshEvents = React.useCallback(async (claimId: string) => {\n const requestSequence = ++eventsRequestSequenceRef.current\n const result = await apiCall<EventResponse>(`/api/warranty_claims/portal/events?claimId=${encodeURIComponent(claimId)}`)\n if (requestSequence === eventsRequestSequenceRef.current && result.ok && result.result) {\n setEvents(result.result.items)\n }\n }, [])\n\n const refreshAttachments = React.useCallback(async (claimId: string) => {\n const result = await apiCall<AttachmentResponse>(`/api/warranty_claims/portal/attachments?claimId=${encodeURIComponent(claimId)}`)\n if (result.ok && result.result) setAttachments(result.result.items)\n }, [])\n\n const loadClaim = React.useCallback(async (showLoading = true) => {\n const requestSequence = ++claimRequestSequenceRef.current\n if (showLoading) setIsLoading(true)\n setError(null)\n setNotFound(false)\n try {\n const detail = await apiCall<ClaimResponse>(`/api/warranty_claims/portal/claims/${encodeURIComponent(params.id)}`)\n if (requestSequence !== claimRequestSequenceRef.current) return\n if (detail.status === 404) {\n setClaim(null)\n setNotFound(true)\n return\n }\n if (!detail.ok || !detail.result) {\n setError(t('warranty_claims.portal.detail.loadError'))\n return\n }\n setClaim(detail.result.item)\n await Promise.all([\n refreshEvents(detail.result.item.id),\n refreshAttachments(detail.result.item.id),\n ])\n } catch {\n if (requestSequence === claimRequestSequenceRef.current) {\n setError(t('warranty_claims.portal.detail.loadError'))\n }\n } finally {\n if (requestSequence === claimRequestSequenceRef.current) setIsLoading(false)\n }\n }, [params.id, refreshAttachments, refreshEvents, t])\n\n React.useEffect(() => {\n if (user) void loadClaim()\n }, [loadClaim, user])\n\n usePortalAppEvent('warranty_claims.claim.portal_status_changed', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void loadClaim(false)\n })\n\n usePortalAppEvent('warranty_claims.claim.comment_added', (event) => {\n if (!user || event.payload.claimId !== params.id) return\n void refreshEvents(params.id).catch((error) => {\n getTelemetryRuntime()?.reportError(error, {\n module: 'warranty_claims',\n code: 'warranty_claims.portal_timeline_refresh_failed',\n attributes: { claimId: params.id },\n })\n setError(t('warranty_claims.portal.detail.loadError'))\n })\n })\n\n React.useEffect(() => {\n if (!user) return\n let cancelled = false\n apiCall<PortalOptionsResponse>('/api/warranty_claims/portal/options')\n .then((result) => {\n if (cancelled) return\n setReasonOptions(result.result?.ok ? result.result.result?.reasons ?? [] : [])\n })\n .catch(() => {})\n return () => {\n cancelled = true\n }\n }, [user])\n\n const submitComment = React.useCallback(async () => {\n if (!claim || commentSubmitting) return\n const body = comment.trim()\n if (!body) return\n setCommentSubmitting(true)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>('/api/warranty_claims/portal/events', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ claimId: claim.id, body }),\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim_event',\n operation: 'portal_comment',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim_event',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, body },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.commentError'))\n return\n }\n setComment('')\n flash(t('warranty_claims.portal.detail.commentSuccess'), 'success')\n await refreshEvents(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.commentError'))\n } finally {\n setCommentSubmitting(false)\n }\n }, [claim, comment, commentSubmitting, guardedMutation, refreshEvents, t])\n\n const uploadAttachment = React.useCallback(async (file: File, replacementTarget?: PortalAttachment) => {\n if (!claim || uploading) return\n const validationError = await validateAttachmentFile(file, t)\n if (validationError) {\n setError(validationError)\n flash(validationError, 'error')\n return\n }\n setUploading(true)\n setAttachmentMutationId(replacementTarget?.id ?? 'new')\n setError(null)\n try {\n const form = new FormData()\n form.set('claimId', claim.id)\n form.set('file', file)\n if (replacementTarget) form.set('attachmentId', replacementTarget.id)\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<UploadResponse>('/api/warranty_claims/portal/attachments', {\n method: replacementTarget ? 'PUT' : 'POST',\n credentials: 'include',\n body: form,\n }),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: replacementTarget ? 'portal_attachment_replace' : 'portal_attachment_upload',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: replacementTarget?.id ?? claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: {\n claimId: claim.id,\n attachmentId: replacementTarget?.id,\n fileName: file.name,\n fileSize: file.size,\n },\n })\n if (!result.ok || !result.result?.ok) {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n return\n }\n flash(\n replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceSuccess', 'Attachment replaced.')\n : t('warranty_claims.portal.detail.attachmentSuccess'),\n 'success',\n )\n await refreshAttachments(claim.id)\n } catch {\n setError(replacementTarget\n ? t('warranty_claims.portal.detail.attachmentReplaceError', 'Attachment replacement failed.')\n : t('warranty_claims.portal.detail.attachmentError'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n setFileInputKey((current) => current + 1)\n }\n }, [claim, guardedMutation, refreshAttachments, t, uploading])\n\n const deleteAttachment = React.useCallback(async (attachment: PortalAttachment) => {\n if (!claim || uploading) return\n const confirmed = await confirm({\n title: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n text: t(\n 'warranty_claims.portal.detail.attachmentDeleteConfirm',\n 'Delete \"{name}\"? This action cannot be undone.',\n { name: attachment.fileName },\n ),\n confirmText: t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment'),\n cancelText: t('warranty_claims.portal.detail.dialogCancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!confirmed) return\n setUploading(true)\n setAttachmentMutationId(attachment.id)\n setError(null)\n try {\n const result = await guardedMutation.runMutation({\n operation: () => apiCall<MutationOkResponse>(\n `/api/warranty_claims/portal/attachments?attachmentId=${encodeURIComponent(attachment.id)}`,\n { method: 'DELETE', credentials: 'include' },\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'attachments.attachment',\n operation: 'portal_attachment_delete',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'attachments.attachment',\n resourceId: attachment.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, attachmentId: attachment.id },\n })\n if (!result.ok || !result.result?.ok) {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n return\n }\n flash(t('warranty_claims.portal.detail.attachmentDeleteSuccess', 'Attachment deleted.'), 'success')\n await refreshAttachments(claim.id)\n } catch {\n setError(t('warranty_claims.portal.detail.attachmentDeleteError', 'Attachment deletion failed.'))\n } finally {\n setUploading(false)\n setAttachmentMutationId(null)\n }\n }, [claim, confirm, guardedMutation, refreshAttachments, t, uploading])\n\n const requestAttachmentReplacement = React.useCallback((attachment: PortalAttachment) => {\n if (uploading) return\n replacementTargetRef.current = attachment\n replacementInputRef.current?.click()\n }, [uploading])\n\n const runClaimAction = React.useCallback(async (action: PortalClaimAction) => {\n if (!claim || actionSubmitting) return\n setActionSubmitting(true)\n setError(null)\n try {\n const endpoint = `/api/warranty_claims/portal/claims/${encodeURIComponent(claim.id)}/${action}`\n const result = await guardedMutation.runMutation({\n operation: () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(claim.updatedAt),\n () => apiCall<ClaimActionResponse>(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({}),\n }),\n ),\n context: {\n moduleId: 'warranty_claims',\n entityId: 'warranty_claims.claim',\n operation: action === 'submit' ? 'portal_submit' : 'portal_withdraw',\n claimId: claim.id,\n formId: DETAIL_MUTATION_CONTEXT_ID,\n resourceKind: 'warranty_claims.claim',\n resourceId: claim.id,\n retryLastMutation: guardedMutation.retryLastMutation,\n },\n mutationPayload: { claimId: claim.id, action },\n })\n if (!result.ok || !result.result?.ok) {\n const message = result.status === 409\n ? t('warranty_claims.portal.detail.actionConflict', 'This claim changed in the meantime. It has been reloaded \u2014 please try again.')\n : action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n if (result.status === 409) {\n setClaimAction(null)\n await loadClaim()\n }\n return\n }\n setClaimAction(null)\n flash(\n action === 'submit'\n ? t('warranty_claims.portal.detail.submitSuccess', 'Your claim has been submitted.')\n : t('warranty_claims.portal.detail.withdrawSuccess', 'Your claim has been withdrawn.'),\n 'success',\n )\n await loadClaim()\n } catch {\n const message = action === 'submit'\n ? t('warranty_claims.portal.detail.submitError', 'The claim could not be submitted.')\n : t('warranty_claims.portal.detail.withdrawError', 'The claim could not be withdrawn.')\n setError(message)\n flash(message, 'error')\n } finally {\n setActionSubmitting(false)\n }\n }, [actionSubmitting, claim, guardedMutation, loadClaim, t])\n\n const handleSelectedFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0] ?? null\n if (!file) return\n void uploadAttachment(file)\n }, [uploadAttachment])\n\n const handleReplacementFileChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const replacementTarget = replacementTargetRef.current\n replacementTargetRef.current = null\n const file = event.target.files?.[0] ?? null\n event.target.value = ''\n if (!replacementTarget || !file) return\n void uploadAttachment(file, replacementTarget)\n }, [uploadAttachment])\n\n const handleCommentSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n void submitComment()\n }, [submitComment])\n\n const handleCommentKeyDown = React.useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitComment()\n return\n }\n if (event.key === 'Escape') {\n event.preventDefault()\n setComment('')\n }\n }, [submitComment])\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n if (isLoading) {\n return <LoadingMessage label={t('warranty_claims.portal.detail.loading')} />\n }\n\n if (notFound) {\n return (\n <PortalEmptyState\n icon={<ShieldCheck className=\"size-5\" />}\n title={t('warranty_claims.portal.detail.notFoundTitle')}\n description={t('warranty_claims.portal.detail.notFoundDescription')}\n action={\n <Button asChild>\n <Link href={`/${params.orgSlug}/portal/claims`}>\n <ArrowLeft className=\"size-4\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.back')}\n </Link>\n </Button>\n }\n />\n )\n }\n\n if (!claim) {\n return <ErrorMessage label={error ?? t('warranty_claims.portal.detail.loadError')} />\n }\n\n const trackerSteps = buildTrackerSteps(claim, t, locale)\n const isDraft = claim.status === 'draft'\n const canWithdraw = claim.status === 'draft' || claim.status === 'submitted'\n const tone = bannerTone(claim.status)\n const toneClasses = BANNER_TONE_CLASSES[tone]\n const BannerIcon = tone === 'error' ? CircleAlert : tone === 'warning' ? TriangleAlert : Info\n const activityEntries = buildActivityEntries(events, attachments, t)\n\n return (\n <div className=\"mx-auto flex w-full max-w-3xl flex-col gap-4\">\n <Link\n href={`/${params.orgSlug}/portal/claims`}\n className=\"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n >\n {'\u2190'} {t('warranty_claims.portal.listTitle')}\n </Link>\n\n {error ? <ErrorMessage label={error} /> : null}\n\n <div className=\"w-full overflow-hidden rounded-lg border border-border bg-card\">\n <div className=\"flex w-full flex-col gap-3 px-6 pb-5 pt-6\">\n <div className=\"flex w-full items-center gap-2\">\n <div className=\"flex items-center gap-2 rounded-sm border border-border bg-background px-3 py-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t(`warranty_claims.claimType.${claim.claimType}`)}\n </span>\n <span className=\"text-sm font-semibold text-foreground\">\n {claim.claimNumber}\n </span>\n </div>\n <div className=\"min-w-px flex-1\" />\n {isDraft ? (\n <Button\n type=\"button\"\n size=\"sm\"\n className={DARK_BUTTON_CLASS}\n onClick={() => setClaimAction('submit')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.submit')}\n </Button>\n ) : null}\n {canWithdraw ? (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"destructive\"\n className=\"rounded-md px-3 py-2 text-sm font-medium\"\n onClick={() => setClaimAction('withdraw')}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.withdraw', 'Withdraw claim')}\n </Button>\n ) : null}\n </div>\n <h1 className=\"text-xl font-bold text-foreground\">\n {buildClaimTitle(claim, reasonOptions, t)}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {buildClaimSubtitle(claim, t, locale)}\n </p>\n </div>\n\n <div className=\"w-full px-6 pb-6\">\n <div className={`flex w-full items-start gap-3 rounded-lg px-4 py-3 ${toneClasses.wrapper}`}>\n <BannerIcon className={`mt-px size-4 shrink-0 ${toneClasses.icon}`} aria-hidden=\"true\" />\n <p className={`min-w-px flex-1 text-sm ${toneClasses.text}`}>\n {bannerMessage(claim, t)}\n </p>\n </div>\n </div>\n\n <div className=\"flex w-full flex-col px-6 pb-6\">\n {trackerSteps.map((step, index) => (\n <div key={step.id} className=\"flex w-full items-stretch gap-4\">\n <div className=\"flex flex-col items-center\">\n {step.state === 'complete' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-status-success-icon\">\n <Check className=\"size-3 text-status-success-bg\" strokeWidth={2.5} aria-hidden=\"true\" />\n </span>\n ) : step.state === 'current' ? (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground\">\n {index + 1}\n </span>\n ) : (\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-full border border-border text-xs font-semibold text-muted-foreground\">\n {index + 1}\n </span>\n )}\n {index < trackerSteps.length - 1 ? (\n <span\n className={`min-h-2 w-px flex-1 ${step.state === 'complete' ? 'bg-status-success-icon' : 'bg-border'}`}\n />\n ) : null}\n </div>\n <div className=\"flex min-w-px flex-1 flex-col gap-1 pb-4\">\n <div className=\"flex w-full items-center gap-2\">\n <p\n className={\n step.state === 'pending'\n ? 'text-sm font-medium text-muted-foreground'\n : 'text-sm font-semibold text-foreground'\n }\n >\n {step.title}\n </p>\n <div className=\"min-w-px flex-1\" />\n {step.dateLabel ? (\n <p className=\"text-xs text-muted-foreground\">{step.dateLabel}</p>\n ) : null}\n </div>\n <p className=\"w-full text-xs text-muted-foreground\">{step.description}</p>\n </div>\n </div>\n ))}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.items')}</p>\n {claim.lines.length > 0 ? (\n claim.lines.map((line) => (\n <div\n key={line.id}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3\"\n >\n <div className=\"flex min-w-px flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium text-foreground\">\n {line.productName ?? line.sku ?? t('warranty_claims.portal.value.unnamedLine')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {line.sku\n ? t('warranty_claims.portal.tracker.itemMeta', {\n sku: line.sku,\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })\n : t('warranty_claims.portal.tracker.itemQty', {\n qty: formatQuantity(line.qtyClaimed, t('warranty_claims.portal.value.notAvailable')),\n })}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2 rounded-sm border border-border bg-background px-2 py-1\">\n <span\n className={`size-2 shrink-0 rounded-full ${LINE_STATUS_DOT_CLASSES[line.lineStatus] ?? 'bg-status-neutral-icon'}`}\n aria-hidden=\"true\"\n />\n <span className=\"text-xs font-medium text-foreground\">\n {t(`warranty_claims.lineStatus.${line.lineStatus}`)}\n </span>\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.linesEmpty.description')}\n </p>\n )}\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.activity')}</p>\n {activityEntries.length > 0 ? (\n activityEntries.map((entry) => (\n <div key={entry.id} className=\"flex w-full items-start gap-3\">\n <span className=\"mt-1 size-2 shrink-0 rounded-full bg-status-neutral-icon\" aria-hidden=\"true\" />\n <div className=\"flex min-w-px flex-1 flex-col gap-px\">\n <div className=\"flex w-full items-start gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{entry.title}</p>\n <div className=\"min-w-px flex-1\" />\n {entry.createdAt ? (\n <p className=\"text-xs text-muted-foreground\">{formatShortDate(entry.createdAt, locale)}</p>\n ) : null}\n </div>\n {entry.href ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={entry.href}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"min-w-0 whitespace-pre-wrap text-xs text-muted-foreground underline-offset-2 hover:underline\"\n >\n {entry.description}\n </a>\n {entry.attachment ? (\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentReplaceNamed',\n 'Replace {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => requestAttachmentReplacement(entry.attachment!)}\n disabled={uploading}\n >\n {attachmentMutationId === entry.attachment.id ? (\n <Spinner className=\"size-3\" />\n ) : (\n <RefreshCw className=\"size-3\" aria-hidden=\"true\" />\n )}\n {t('warranty_claims.portal.detail.attachmentReplace', 'Replace')}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-xs text-status-error-text\"\n aria-label={t(\n 'warranty_claims.portal.detail.attachmentDeleteNamed',\n 'Delete {name}',\n { name: entry.attachment.fileName },\n )}\n onClick={() => { void deleteAttachment(entry.attachment!) }}\n disabled={uploading}\n >\n <Trash2 className=\"size-3\" aria-hidden=\"true\" />\n {t('warranty_claims.portal.detail.attachmentDelete', 'Delete attachment')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <p className=\"whitespace-pre-wrap text-xs text-muted-foreground\">{entry.description}</p>\n )}\n </div>\n </div>\n ))\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t('warranty_claims.portal.detail.timelineEmpty.description')}\n </p>\n )}\n <input\n ref={replacementInputRef}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleReplacementFileChange}\n disabled={uploading}\n className=\"sr-only\"\n aria-label={t('warranty_claims.portal.detail.attachmentChooseReplacement', 'Choose replacement file')}\n />\n </div>\n\n <div className={SECTION_CLASS}>\n <p className={SECTION_HEADING_CLASS}>{t('warranty_claims.portal.tracker.message')}</p>\n <form id=\"warranty-claim-comment-box\" className=\"flex w-full flex-col gap-3\" onSubmit={handleCommentSubmit}>\n <Textarea\n value={comment}\n onChange={(event) => setComment(event.target.value)}\n onKeyDown={handleCommentKeyDown}\n placeholder={t('warranty_claims.portal.tracker.messagePlaceholder')}\n disabled={commentSubmitting}\n maxLength={8000}\n className=\"h-40 resize-none rounded-lg border-input bg-background px-3 py-2 text-sm shadow-xs placeholder:text-muted-foreground\"\n />\n <div className=\"flex w-full items-center gap-2\">\n <label className={`flex items-center gap-2 ${uploading ? 'cursor-default opacity-70' : 'cursor-pointer'}`}>\n {uploading ? (\n <Spinner className=\"size-4\" />\n ) : (\n <Paperclip className=\"size-4 text-muted-foreground\" aria-hidden=\"true\" />\n )}\n <span className=\"text-xs font-medium text-muted-foreground transition-colors hover:text-foreground\">\n {uploading\n ? t('warranty_claims.portal.detail.uploading')\n : t('warranty_claims.portal.tracker.attachFiles')}\n </span>\n <input\n key={fileInputKey}\n type=\"file\"\n accept={ATTACHMENT_ACCEPT_TYPES}\n onChange={handleSelectedFileChange}\n disabled={uploading}\n className=\"sr-only\"\n />\n </label>\n <div className=\"min-w-px flex-1\" />\n <Button type=\"submit\" size=\"sm\" className={DARK_BUTTON_CLASS} disabled={commentSubmitting || !comment.trim()}>\n {commentSubmitting\n ? t('warranty_claims.portal.tracker.sending')\n : t('warranty_claims.portal.tracker.send')}\n </Button>\n </div>\n </form>\n </div>\n </div>\n\n <Dialog\n open={claimAction !== null}\n onOpenChange={(open) => {\n if (!open && !actionSubmitting) setClaimAction(null)\n }}\n >\n <DialogContent\n className=\"max-w-lg\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (claimAction && !actionSubmitting) void runClaimAction(claimAction)\n }\n if (event.key === 'Escape' && !actionSubmitting) {\n setClaimAction(null)\n }\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.title', 'Withdraw this claim?')\n : t('warranty_claims.portal.detail.submitDialog.title', 'Submit this claim?')}\n </DialogTitle>\n <DialogDescription>\n {claimAction === 'withdraw'\n ? t('warranty_claims.portal.detail.withdrawDialog.description', 'The claim will be cancelled and cannot be reopened. This cannot be undone.')\n : t('warranty_claims.portal.detail.submitDialog.description', 'Your claim will be sent to our support team for review. You can still add comments and photos afterwards.')}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setClaimAction(null)}\n disabled={actionSubmitting}\n >\n {t('warranty_claims.portal.detail.dialogCancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant={claimAction === 'withdraw' ? 'destructive' : 'default'}\n onClick={() => {\n if (claimAction) void runClaimAction(claimAction)\n }}\n disabled={actionSubmitting}\n >\n {claimAction === 'withdraw'\n ? (actionSubmitting\n ? t('warranty_claims.portal.detail.withdrawing', 'Withdrawing...')\n : t('warranty_claims.portal.detail.withdrawConfirm', 'Withdraw claim'))\n : (actionSubmitting\n ? t('warranty_claims.portal.detail.submitting', 'Submitting...')\n : t('warranty_claims.portal.detail.submitConfirm', 'Submit claim'))}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["error"]
7
7
  }
@@ -0,0 +1,20 @@
1
+ import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
2
+ async function loadClaimLines(claimId, errorMessage) {
3
+ const readPage = (page) => readApiResultOrThrow(
4
+ `/api/warranty_claims/lines?claimId=${encodeURIComponent(claimId)}&page=${page}&pageSize=100&sortField=lineNo&sortDir=asc`,
5
+ void 0,
6
+ { fallback: { items: [] }, errorMessage }
7
+ );
8
+ const firstPage = await readPage(1);
9
+ const items = [...firstPage.items ?? []];
10
+ const totalPages = firstPage.totalPages ?? 1;
11
+ for (let page = 2; page <= totalPages; page += 1) {
12
+ const payload = await readPage(page);
13
+ items.push(...payload.items ?? []);
14
+ }
15
+ return { items };
16
+ }
17
+ export {
18
+ loadClaimLines
19
+ };
20
+ //# sourceMappingURL=claimLines.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/warranty_claims/lib/claimLines.ts"],
4
+ "sourcesContent": ["import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\ntype ClaimLinesPage = {\n items?: unknown[]\n totalPages?: number\n}\n\nexport async function loadClaimLines(claimId: string, errorMessage: string): Promise<{ items: unknown[] }> {\n const readPage = (page: number) => readApiResultOrThrow<ClaimLinesPage>(\n `/api/warranty_claims/lines?claimId=${encodeURIComponent(claimId)}&page=${page}&pageSize=100&sortField=lineNo&sortDir=asc`,\n undefined,\n { fallback: { items: [] }, errorMessage },\n )\n const firstPage = await readPage(1)\n const items = [...(firstPage.items ?? [])]\n const totalPages = firstPage.totalPages ?? 1\n for (let page = 2; page <= totalPages; page += 1) {\n const payload = await readPage(page)\n items.push(...(payload.items ?? []))\n }\n return { items }\n}\n"],
5
+ "mappings": "AAAA,SAAS,4BAA4B;AAOrC,eAAsB,eAAe,SAAiB,cAAqD;AACzG,QAAM,WAAW,CAAC,SAAiB;AAAA,IACjC,sCAAsC,mBAAmB,OAAO,CAAC,SAAS,IAAI;AAAA,IAC9E;AAAA,IACA,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,GAAG,aAAa;AAAA,EAC1C;AACA,QAAM,YAAY,MAAM,SAAS,CAAC;AAClC,QAAM,QAAQ,CAAC,GAAI,UAAU,SAAS,CAAC,CAAE;AACzC,QAAM,aAAa,UAAU,cAAc;AAC3C,WAAS,OAAO,GAAG,QAAQ,YAAY,QAAQ,GAAG;AAChD,UAAM,UAAU,MAAM,SAAS,IAAI;AACnC,UAAM,KAAK,GAAI,QAAQ,SAAS,CAAC,CAAE;AAAA,EACrC;AACA,SAAO,EAAE,MAAM;AACjB;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7879.1.80d3740285",
3
+ "version": "0.10.1-develop.7888.1.96c8c2bd5f",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.6.6"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7879.1.80d3740285",
270
- "@open-mercato/shared": "0.10.1-develop.7879.1.80d3740285",
271
- "@open-mercato/ui": "0.10.1-develop.7879.1.80d3740285",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7888.1.96c8c2bd5f",
270
+ "@open-mercato/shared": "0.10.1-develop.7888.1.96c8c2bd5f",
271
+ "@open-mercato/ui": "0.10.1-develop.7888.1.96c8c2bd5f",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7879.1.80d3740285",
277
- "@open-mercato/shared": "0.10.1-develop.7879.1.80d3740285",
278
- "@open-mercato/ui": "0.10.1-develop.7879.1.80d3740285",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7888.1.96c8c2bd5f",
277
+ "@open-mercato/shared": "0.10.1-develop.7888.1.96c8c2bd5f",
278
+ "@open-mercato/ui": "0.10.1-develop.7888.1.96c8c2bd5f",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -0,0 +1,87 @@
1
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
2
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
3
+ import { ATTACHMENT_TRANSFER_MAX_IDS } from './transferLimits'
4
+
5
+ export type TransferAttachmentsInput = {
6
+ entityId: string
7
+ attachmentIds: string[]
8
+ fromRecordId?: string | null
9
+ toRecordId: string
10
+ }
11
+
12
+ export type TransferAttachmentsResult = {
13
+ transferredCount: number
14
+ failedIds: string[]
15
+ unconfirmedCount: number
16
+ errors: string[]
17
+ }
18
+
19
+ export function chunkAttachmentIds(attachmentIds: string[], size = ATTACHMENT_TRANSFER_MAX_IDS): string[][] {
20
+ const chunks: string[][] = []
21
+ for (let index = 0; index < attachmentIds.length; index += size) {
22
+ chunks.push(attachmentIds.slice(index, index + size))
23
+ }
24
+ return chunks
25
+ }
26
+
27
+ function stopsRemainingChunks(error: unknown): boolean {
28
+ const name = typeof error === 'object' && error !== null ? (error as { name?: unknown }).name : undefined
29
+ return name === 'AbortError' || name === 'UnauthorizedError'
30
+ }
31
+
32
+ function describeError(error: unknown): string {
33
+ return error instanceof Error && error.message ? error.message : String(error)
34
+ }
35
+
36
+ export async function transferAttachmentsInChunks({
37
+ entityId,
38
+ attachmentIds,
39
+ fromRecordId,
40
+ toRecordId,
41
+ }: TransferAttachmentsInput): Promise<TransferAttachmentsResult> {
42
+ const result: TransferAttachmentsResult = { transferredCount: 0, failedIds: [], unconfirmedCount: 0, errors: [] }
43
+ const chunks = chunkAttachmentIds(attachmentIds)
44
+ for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex += 1) {
45
+ const chunk = chunks[chunkIndex]
46
+ try {
47
+ const call = await apiCall<{ ok?: boolean; updated?: number; error?: string }>(
48
+ '/api/attachments/transfer',
49
+ {
50
+ method: 'POST',
51
+ headers: { 'content-type': 'application/json' },
52
+ body: JSON.stringify({
53
+ entityId,
54
+ attachmentIds: chunk,
55
+ ...(fromRecordId ? { fromRecordId } : {}),
56
+ toRecordId,
57
+ }),
58
+ },
59
+ { fallback: null },
60
+ )
61
+ if (!call.ok) {
62
+ result.failedIds.push(...chunk)
63
+ if (call.result?.error) result.errors.push(call.result.error)
64
+ continue
65
+ }
66
+ const updated = call.result?.updated
67
+ const confirmed = typeof updated === 'number' && Number.isFinite(updated)
68
+ ? Math.min(Math.max(Math.trunc(updated), 0), chunk.length)
69
+ : 0
70
+ result.transferredCount += confirmed
71
+ result.unconfirmedCount += chunk.length - confirmed
72
+ } catch (error) {
73
+ getTelemetryRuntime()?.reportError(error, {
74
+ module: 'attachments',
75
+ code: 'attachments.transfer_chunk_failed',
76
+ attributes: { chunkSize: chunk.length },
77
+ })
78
+ result.errors.push(describeError(error))
79
+ if (stopsRemainingChunks(error)) {
80
+ result.failedIds.push(...chunks.slice(chunkIndex).flat())
81
+ break
82
+ }
83
+ result.failedIds.push(...chunk)
84
+ }
85
+ }
86
+ return result
87
+ }
@@ -0,0 +1 @@
1
+ export const ATTACHMENT_TRANSFER_MAX_IDS = 100