@cellarnode/ui 0.180.0 → 0.182.0

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 (80) hide show
  1. package/dist/chat/chat-surface.d.ts +2 -0
  2. package/dist/chat/chat-surface.d.ts.map +1 -1
  3. package/dist/chat/chat-surface.js +2 -1
  4. package/dist/chat/chat-surface.js.map +1 -1
  5. package/dist/chat/messenger-screen/index.d.ts +1 -1
  6. package/dist/chat/messenger-screen/index.d.ts.map +1 -1
  7. package/dist/chat/messenger-screen/message-adapters.d.ts +7 -1
  8. package/dist/chat/messenger-screen/message-adapters.d.ts.map +1 -1
  9. package/dist/chat/messenger-screen/message-adapters.js +12 -3
  10. package/dist/chat/messenger-screen/message-adapters.js.map +1 -1
  11. package/dist/chat/messenger-screen/messenger-screen.d.ts.map +1 -1
  12. package/dist/chat/messenger-screen/messenger-screen.js +2 -2
  13. package/dist/chat/messenger-screen/messenger-screen.js.map +1 -1
  14. package/dist/chat/messenger-screen/room-panel.d.ts +3 -1
  15. package/dist/chat/messenger-screen/room-panel.d.ts.map +1 -1
  16. package/dist/chat/messenger-screen/room-panel.js +46 -9
  17. package/dist/chat/messenger-screen/room-panel.js.map +1 -1
  18. package/dist/chat/messenger-screen/types.d.ts +16 -1
  19. package/dist/chat/messenger-screen/types.d.ts.map +1 -1
  20. package/dist/components/certification-upload-card.d.ts +25 -3
  21. package/dist/components/certification-upload-card.d.ts.map +1 -1
  22. package/dist/components/certification-upload-card.js +34 -4
  23. package/dist/components/certification-upload-card.js.map +1 -1
  24. package/dist/components/journey-card.d.ts +58 -3
  25. package/dist/components/journey-card.d.ts.map +1 -1
  26. package/dist/components/journey-card.js +35 -11
  27. package/dist/components/journey-card.js.map +1 -1
  28. package/dist/components/party-logo-image.d.ts +16 -0
  29. package/dist/components/party-logo-image.d.ts.map +1 -0
  30. package/dist/components/party-logo-image.js +16 -0
  31. package/dist/components/party-logo-image.js.map +1 -0
  32. package/dist/components/sortable-card-context.d.ts +7 -0
  33. package/dist/components/sortable-card-context.d.ts.map +1 -1
  34. package/dist/components/sortable-card-context.js.map +1 -1
  35. package/dist/components/sortable-card-shell.d.ts +13 -8
  36. package/dist/components/sortable-card-shell.d.ts.map +1 -1
  37. package/dist/components/sortable-card-shell.js +41 -18
  38. package/dist/components/sortable-card-shell.js.map +1 -1
  39. package/dist/hooks/use-kanban-dnd.d.ts +1 -0
  40. package/dist/hooks/use-kanban-dnd.d.ts.map +1 -1
  41. package/dist/hooks/use-kanban-dnd.js +2 -2
  42. package/dist/hooks/use-kanban-dnd.js.map +1 -1
  43. package/dist/offer/index.d.ts +2 -0
  44. package/dist/offer/index.d.ts.map +1 -1
  45. package/dist/offer/index.js +1 -0
  46. package/dist/offer/index.js.map +1 -1
  47. package/dist/offer/negotiation-flow-timeline.d.ts +7 -1
  48. package/dist/offer/negotiation-flow-timeline.d.ts.map +1 -1
  49. package/dist/offer/negotiation-flow-timeline.js +23 -6
  50. package/dist/offer/negotiation-flow-timeline.js.map +1 -1
  51. package/dist/offer/negotiation-labels.d.ts +3 -0
  52. package/dist/offer/negotiation-labels.d.ts.map +1 -1
  53. package/dist/offer/negotiation-labels.js +2 -0
  54. package/dist/offer/negotiation-labels.js.map +1 -1
  55. package/dist/offer/negotiation-section.d.ts +4 -1
  56. package/dist/offer/negotiation-section.d.ts.map +1 -1
  57. package/dist/offer/negotiation-section.js +2 -2
  58. package/dist/offer/negotiation-section.js.map +1 -1
  59. package/dist/offer/offer-page.d.ts +7 -1
  60. package/dist/offer/offer-page.d.ts.map +1 -1
  61. package/dist/offer/offer-page.js +2 -2
  62. package/dist/offer/offer-page.js.map +1 -1
  63. package/dist/offer/participants.d.ts +53 -0
  64. package/dist/offer/participants.d.ts.map +1 -0
  65. package/dist/offer/participants.js +36 -0
  66. package/dist/offer/participants.js.map +1 -0
  67. package/dist/offer/types.d.ts +5 -0
  68. package/dist/offer/types.d.ts.map +1 -1
  69. package/dist/product-editor/index.d.ts +1 -1
  70. package/dist/product-editor/index.d.ts.map +1 -1
  71. package/dist/product-editor/index.js.map +1 -1
  72. package/dist/product-editor/product-editor-resource-types.d.ts +40 -2
  73. package/dist/product-editor/product-editor-resource-types.d.ts.map +1 -1
  74. package/dist/product-editor/tabs/resources/certifications-panel.d.ts +10 -3
  75. package/dist/product-editor/tabs/resources/certifications-panel.d.ts.map +1 -1
  76. package/dist/product-editor/tabs/resources/certifications-panel.js +239 -24
  77. package/dist/product-editor/tabs/resources/certifications-panel.js.map +1 -1
  78. package/dist/product-editor/types.d.ts +1 -1
  79. package/dist/product-editor/types.d.ts.map +1 -1
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/product-editor/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAKxE,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,qBAAqB,EACrB,6BAA6B,EAC7B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGvE,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,2BAA2B,EAC3B,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,4BAA4B,EAC5B,wBAAwB,EACxB,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,4BAA4B,EAC5B,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,YAAY,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAC;AAG/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,cAAc,4BAA4B,CAAC;AAC3C,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/product-editor/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAKxE,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,qBAAqB,EACrB,6BAA6B,EAC7B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAGvE,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,2BAA2B,EAC3B,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,4BAA4B,EAC5B,+BAA+B,EAC/B,wBAAwB,EACxB,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,4BAA4B,EAC5B,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,YAAY,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAC;AAG/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,YAAY,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,cAAc,4BAA4B,CAAC;AAC3C,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/product-editor/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGtE,8EAA8E;AAC9E,6EAA6E;AAC7E,wDAAwD;AACxD,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,qBAAqB,EACrB,6BAA6B,EAC7B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAgDhC,eAAe;AACf,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAGrF,kEAAkE;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAG7D,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/product-editor/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGtE,8EAA8E;AAC9E,6EAA6E;AAC7E,wDAAwD;AACxD,OAAO,EACL,wBAAwB,EACxB,gCAAgC,EAChC,qBAAqB,EACrB,6BAA6B,EAC7B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAiDhC,eAAe;AACf,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAGrF,kEAAkE;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAG7D,cAAc,4BAA4B,CAAC"}
@@ -57,8 +57,12 @@ export type CertificationProofKind = "certificate" | "scheme_licence" | "lab_ana
57
57
  *
58
58
  * `pending` is the state a record is created in. `rejected` and `revoked` are
59
59
  * terminal and remain queryable — an absence is not a rejection.
60
+ *
61
+ * `unsubmitted` (CEL-2172) is a file that was uploaded but never sent for
62
+ * review, so no reviewer has seen it. It is never live and must not be shown as
63
+ * accepted or as in review.
60
64
  */
61
- export type CertificationReviewState = "pending" | "approved" | "rejected" | "revoked" | (string & {});
65
+ export type CertificationReviewState = "unsubmitted" | "pending" | "approved" | "rejected" | "revoked" | (string & {});
62
66
  /**
63
67
  * One submitted evidence record for one certification claim.
64
68
  *
@@ -119,6 +123,19 @@ export interface CertificationProofSubmission {
119
123
  /** ISO date (`YYYY-MM-DD`) transcribed from the document. */
120
124
  validUntil?: string;
121
125
  }
126
+ /**
127
+ * Submits an already-uploaded, `unsubmitted` file for review (CEL-2172). The
128
+ * panel collects the same description it collects for a new file and hands it
129
+ * back with the record, so the consumer can create the claim evidence for it.
130
+ */
131
+ export interface CertificationExistingSubmission {
132
+ /** The `unsubmitted` record the producer chose to send. */
133
+ evidence: CertificationEvidenceItem;
134
+ proofKind: CertificationProofKind;
135
+ controlBodyCode?: string;
136
+ /** ISO date (`YYYY-MM-DD`). Required unless `proofKind` is `attestation`. */
137
+ validUntil?: string;
138
+ }
122
139
  export interface CertificatesController {
123
140
  items: CertificationEvidenceItem[];
124
141
  isLoading: boolean;
@@ -137,7 +154,28 @@ export interface CertificatesController {
137
154
  * `onDelete`, which the producer reaches deliberately (CEL-1312).
138
155
  */
139
156
  onToggle(type: string, claimed: boolean): void;
140
- onUploadProof(input: CertificationProofSubmission): void;
157
+ /**
158
+ * Sends a new proof file for review.
159
+ *
160
+ * May return a promise. The panel then keeps the describe form open until it
161
+ * settles: a rejection keeps the form and shows the error (see the panel's
162
+ * `formatError`), a resolve closes it and moves focus to the card's state
163
+ * line. A synchronous return is treated as success.
164
+ */
165
+ onUploadProof(input: CertificationProofSubmission): void | Promise<void>;
166
+ /**
167
+ * Optional (CEL-2172, additive). Enables "Submit for review" on an
168
+ * `unsubmitted` record. Same promise semantics as `onUploadProof`. Omit it
169
+ * and an `unsubmitted` card offers Remove only.
170
+ */
171
+ onSubmitExisting?(input: CertificationExistingSubmission): void | Promise<void>;
172
+ /**
173
+ * Optional (CEL-2172, additive). Claim keys with a submission in flight that
174
+ * the consumer owns (for example a mutation started elsewhere). While a key
175
+ * is present its card hides Add and Replace and announces "Sending for
176
+ * review...". The panel also tracks its own in-flight promises.
177
+ */
178
+ submittingTypes?: ReadonlySet<string>;
141
179
  /**
142
180
  * Removes one evidence record.
143
181
  *
@@ -1 +1 @@
1
- {"version":3,"file":"product-editor-resource-types.d.ts","sourceRoot":"","sources":["../../src/product-editor/product-editor-resource-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"product-editor-resource-types.d.ts","sourceRoot":"","sources":["../../src/product-editor/product-editor-resource-types.ts"],"names":[],"mappings":"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"}
@@ -9,9 +9,9 @@ import type { CertificatesController, CertificationEvidenceItem } from "../../ty
9
9
  * the old two-way split showed a finished-looking card for a claim the public
10
10
  * label was silently dropping.
11
11
  */
12
- export type ClaimState = "unclaimed" | "claimed" | "pending" | "evidenced";
12
+ export type ClaimState = "unclaimed" | "claimed" | "unsubmitted" | "pending" | "evidenced";
13
13
  /** Why a claim that has an evidence record still is not live. */
14
- type NotLiveReason = "expired" | "not-yet-in-force" | "rejected" | "revoked" | "unreviewed" | null;
14
+ type NotLiveReason = "expired" | "not-yet-in-force" | "rejected" | "revoked" | "unreviewed" | "unsubmitted" | null;
15
15
  export interface ResolvedClaim {
16
16
  state: ClaimState;
17
17
  /** The record the state was derived from, if the producer has submitted one. */
@@ -42,7 +42,14 @@ export interface CertificationsPanelProps {
42
42
  locale?: string;
43
43
  /** Injectable clock. Present so expiry and renewal copy is testable. */
44
44
  now?: Date;
45
+ /**
46
+ * Turns a rejected `onUploadProof` / `onSubmitExisting` promise into the text
47
+ * shown in the describe form's inline alert. A throwing or empty result falls
48
+ * back to a generic sentence, so raw error text never reaches the producer
49
+ * unasked.
50
+ */
51
+ formatError?: (error: unknown) => string;
45
52
  }
46
- export declare function CertificationsPanel({ form, variantId, certificates, t, locale, now, }: CertificationsPanelProps): import("react/jsx-runtime").JSX.Element;
53
+ export declare function CertificationsPanel({ form, variantId, certificates, t, locale, now, formatError, }: CertificationsPanelProps): import("react/jsx-runtime").JSX.Element;
47
54
  export {};
48
55
  //# sourceMappingURL=certifications-panel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"certifications-panel.d.ts","sourceRoot":"","sources":["../../../../src/product-editor/tabs/resources/certifications-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAG1B,MAAM,aAAa,CAAC;AAqDrB;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3E,iEAAiE;AACjE,KAAK,aAAa,GAAG,SAAS,GAAG,kBAAkB,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC;AAEnG,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,gFAAgF;IAChF,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,aAAa,EAAE,aAAa,CAAC;IAC7B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AA+CD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,yBAAyB,EAAE,GAAG,SAAS,EAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,GACZ,aAAa,CAkDf;AAuCD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,CAAC,EACD,MAAgB,EAChB,GAAG,GACJ,EAAE,wBAAwB,2CA0b1B"}
1
+ {"version":3,"file":"certifications-panel.d.ts","sourceRoot":"","sources":["../../../../src/product-editor/tabs/resources/certifications-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAG1B,MAAM,aAAa,CAAC;AAqDrB;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAE3F,iEAAiE;AACjE,KAAK,aAAa,GACd,SAAS,GACT,kBAAkB,GAClB,UAAU,GACV,SAAS,GACT,YAAY,GACZ,aAAa,GACb,IAAI,CAAC;AAET,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,gFAAgF;IAChF,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,aAAa,EAAE,aAAa,CAAC;IAC7B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAiDD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,yBAAyB,EAAE,GAAG,SAAS,EAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,GACZ,aAAa,CAqDf;AAgDD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,GAAG,CAAC,EAAE,IAAI,CAAC;IACX;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAC1C;AAkBD,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,CAAC,EACD,MAAgB,EAChB,GAAG,EACH,WAAW,GACZ,EAAE,wBAAwB,2CAgtB1B"}
@@ -65,7 +65,10 @@ function evidenceRank(item, nowMs) {
65
65
  }
66
66
  if (item.reviewState === "pending")
67
67
  return 1;
68
- return 3;
68
+ // An unsent file never outranks a record the reviewer actually decided on.
69
+ if (item.reviewState === "unsubmitted")
70
+ return 3;
71
+ return 4;
69
72
  }
70
73
  function pickEvidence(items, certType, nowMs) {
71
74
  let best = null;
@@ -122,6 +125,10 @@ export function resolveClaim(items, certType, claimed, nowMs) {
122
125
  else if (evidence.reviewState === "pending") {
123
126
  state = "pending";
124
127
  }
128
+ else if (evidence.reviewState === "unsubmitted") {
129
+ state = "unsubmitted";
130
+ notLiveReason = "unsubmitted";
131
+ }
125
132
  else if (evidence.reviewState === "rejected") {
126
133
  state = "claimed";
127
134
  notLiveReason = "rejected";
@@ -145,9 +152,17 @@ export function resolveClaim(items, certType, claimed, nowMs) {
145
152
  const CARD_STATUS = {
146
153
  unclaimed: "optional",
147
154
  claimed: "required",
155
+ unsubmitted: "unsubmitted",
148
156
  pending: "pending",
149
157
  evidenced: "filled",
150
158
  };
159
+ /** The consumer's local calendar day as `YYYY-MM-DD`. */
160
+ function localDay(ms, addDays = 0) {
161
+ const base = new Date(ms);
162
+ const d = new Date(base.getFullYear(), base.getMonth(), base.getDate() + addDays);
163
+ const pad = (n) => String(n).padStart(2, "0");
164
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
165
+ }
151
166
  /** Substitutes `{name}` placeholders so a translated string keeps its values. */
152
167
  function fill(template, values) {
153
168
  return template.replace(/\{(\w+)\}/g, (match, key) => Object.hasOwn(values, key) ? values[key] : match);
@@ -172,19 +187,74 @@ function formatDay(value, locale) {
172
187
  year: "numeric",
173
188
  }).format(new Date(ms));
174
189
  }
175
- export function CertificationsPanel({ form, variantId, certificates, t, locale = "en-GB", now, }) {
190
+ function isThenable(value) {
191
+ return (typeof value === "object" &&
192
+ value !== null &&
193
+ typeof value.then === "function");
194
+ }
195
+ export function CertificationsPanel({ form, variantId, certificates, t, locale = "en-GB", now, formatError, }) {
176
196
  const tr = React.useMemo(() => t ?? ((_k, fb) => fb ?? _k), [t]);
177
197
  const fieldId = React.useId();
178
198
  // One clock for the whole panel, read once at mount so all three cards agree
179
199
  // on "today" and a card does not flip from live to expired mid-edit.
180
200
  const [mountedMs] = React.useState(() => Date.now());
181
201
  const nowMs = now ? now.getTime() : mountedMs;
202
+ // The form's date rules read the clock when they run, not the mount-time
203
+ // snapshot, so a panel left open past midnight does not accept yesterday.
204
+ const currentMs = () => (now ? now.getTime() : Date.now());
182
205
  // A file the producer picked but has not described yet. Nothing is sent to
183
206
  // the API until they say what kind of proof it is.
184
207
  const [draft, setDraft] = React.useState(null);
185
208
  const [proofKind, setProofKind] = React.useState("");
186
209
  const [controlBodyCode, setControlBodyCode] = React.useState("");
187
210
  const [validUntil, setValidUntil] = React.useState("");
211
+ // Failed submissions by claim, with a sequence so a repeated identical error
212
+ // still re-announces and re-takes focus.
213
+ const [submitErrors, setSubmitErrors] = React.useState({});
214
+ const errorSeq = React.useRef(0);
215
+ const setSubmitError = (certType, message) => setSubmitErrors((prev) => {
216
+ const next = { ...prev };
217
+ if (message === null)
218
+ delete next[certType];
219
+ else
220
+ next[certType] = { message, seq: ++errorSeq.current };
221
+ return next;
222
+ });
223
+ const alertRef = React.useRef(null);
224
+ // Claims this panel has an in-flight submission for, and claims it has just
225
+ // sent (so the state line exists to take focus before the consumer's list
226
+ // refetches).
227
+ const [inFlight, setInFlight] = React.useState(() => new Set());
228
+ const [justSent, setJustSent] = React.useState(() => new Set());
229
+ // Per claim, so one card's pending submission never blocks or closes another's.
230
+ const inFlightRef = React.useRef(new Set());
231
+ const mountedRef = React.useRef(true);
232
+ React.useEffect(() => {
233
+ mountedRef.current = true;
234
+ return () => {
235
+ mountedRef.current = false;
236
+ };
237
+ }, []);
238
+ // After a failure, focus moves to the alert so it is read and the form stays
239
+ // reachable from there.
240
+ const errorSeqForDraft = draft ? submitErrors[draft.type]?.seq : undefined;
241
+ React.useEffect(() => {
242
+ if (errorSeqForDraft !== undefined)
243
+ alertRef.current?.focus();
244
+ }, [errorSeqForDraft]);
245
+ // The list has caught up (or moved on), so the optimistic line is stale.
246
+ const items = certificates?.items;
247
+ React.useEffect(() => {
248
+ setJustSent((prev) => (prev.size === 0 ? prev : new Set()));
249
+ }, [items]);
250
+ const statusRefs = React.useRef({});
251
+ const [focusType, setFocusType] = React.useState(null);
252
+ React.useEffect(() => {
253
+ if (!focusType)
254
+ return;
255
+ statusRefs.current[focusType]?.focus();
256
+ setFocusType(null);
257
+ }, [focusType]);
188
258
  // Two-step confirmations. Neither withdrawing a claim nor removing a document
189
259
  // happens on a single click.
190
260
  const [confirmWithdraw, setConfirmWithdraw] = React.useState(null);
@@ -229,28 +299,104 @@ export function CertificationsPanel({ form, variantId, certificates, t, locale =
229
299
  form.setValue(certType, false, { shouldDirty: true });
230
300
  certificates?.onToggle(certType, false);
231
301
  };
232
- const openDraft = (certType, file) => {
233
- const kinds = allowedKinds(certType);
302
+ const openDraft = (next) => {
303
+ const kinds = allowedKinds(next.type);
234
304
  setConfirmWithdraw(null);
235
305
  setConfirmRemove(null);
236
- setProofKind(kinds.length === 1 ? String(kinds[0]) : "");
237
- setControlBodyCode("");
238
- setValidUntil("");
239
- setDraft({ type: certType, file });
306
+ setProofKind(next.existing?.proofKind && kinds.includes(next.existing.proofKind)
307
+ ? String(next.existing.proofKind)
308
+ : kinds.length === 1
309
+ ? String(kinds[0])
310
+ : "");
311
+ setControlBodyCode(next.existing?.controlBodyCode ?? "");
312
+ setValidUntil(next.existing?.validUntil ? next.existing.validUntil.slice(0, 10) : "");
313
+ setSubmitError(next.type, null);
314
+ setDraft(next);
315
+ };
316
+ const messageFor = (error) => {
317
+ try {
318
+ const formatted = formatError?.(error);
319
+ if (typeof formatted === "string" && formatted.trim() !== "")
320
+ return formatted;
321
+ }
322
+ catch {
323
+ // A throwing formatter must not turn a failed submission into a crash.
324
+ }
325
+ return tr("productEditor.resources.certSubmitFailed", "Could not send this proof for review. Try again.");
326
+ };
327
+ /**
328
+ * Called only for a RESOLVED promise. A `void` return proves nothing (the
329
+ * consumers on main return `void` even when they refuse), so it never reaches
330
+ * here and the real state comes from `items`.
331
+ */
332
+ const settleSent = (certType) => {
333
+ // Close only this claim's form: another card's draft may be open by now.
334
+ setDraft((current) => (current?.type === certType ? null : current));
335
+ setSubmitError(certType, null);
336
+ setJustSent((prev) => new Set(prev).add(certType));
337
+ setFocusType(certType);
240
338
  };
241
339
  const submitDraft = () => {
242
- if (!draft)
340
+ if (!draft || inFlightRef.current.has(draft.type))
341
+ return;
342
+ if (!canSubmitDraft)
243
343
  return;
244
- if (!proofKind)
344
+ const certType = draft.type;
345
+ // Re-check the date against the clock now, not the last render's.
346
+ if (validUntil ? validUntil <= localDay(currentMs()) : needsExpiry)
245
347
  return;
246
- certificates?.onUploadProof({
247
- type: draft.type,
248
- file: draft.file,
348
+ const details = {
249
349
  proofKind,
250
350
  controlBodyCode: controlBodyCode.trim() || undefined,
251
351
  validUntil: validUntil || undefined,
352
+ };
353
+ let result;
354
+ try {
355
+ result = draft.existing
356
+ ? certificates?.onSubmitExisting?.({
357
+ evidence: draft.existing,
358
+ ...details,
359
+ })
360
+ : draft.file
361
+ ? certificates?.onUploadProof({
362
+ type: certType,
363
+ file: draft.file,
364
+ ...details,
365
+ })
366
+ : undefined;
367
+ }
368
+ catch (error) {
369
+ setSubmitError(certType, messageFor(error));
370
+ return;
371
+ }
372
+ if (!isThenable(result)) {
373
+ // Old behaviour: close now, claim nothing. `items` carries the outcome.
374
+ setDraft((current) => (current?.type === certType ? null : current));
375
+ setSubmitError(certType, null);
376
+ setFocusType(certType);
377
+ return;
378
+ }
379
+ inFlightRef.current.add(certType);
380
+ setSubmitError(certType, null);
381
+ setInFlight((prev) => new Set(prev).add(certType));
382
+ const finish = () => {
383
+ inFlightRef.current.delete(certType);
384
+ if (!mountedRef.current)
385
+ return false;
386
+ setInFlight((prev) => {
387
+ const next = new Set(prev);
388
+ next.delete(certType);
389
+ return next;
390
+ });
391
+ return true;
392
+ };
393
+ result.then(() => {
394
+ if (finish())
395
+ settleSent(certType);
396
+ }, (error) => {
397
+ if (finish())
398
+ setSubmitError(certType, messageFor(error));
252
399
  });
253
- setDraft(null);
254
400
  };
255
401
  const commitRemove = (certType, evidence) => {
256
402
  closeAll();
@@ -275,11 +421,18 @@ export function CertificationsPanel({ form, variantId, certificates, t, locale =
275
421
  ? tr("productEditor.resources.certUnclaimedWithProof", "Proof is on file. Claim this to show it on your public label.")
276
422
  : null;
277
423
  }
424
+ // A submission just went through and the consumer's list has not caught up.
425
+ if (justSent.has(certType) && (state === "claimed" || state === "unsubmitted")) {
426
+ return tr("productEditor.resources.certInReviewSent", "In review. Sent for review. Not on your public label yet.");
427
+ }
428
+ if (state === "unsubmitted") {
429
+ return tr("productEditor.resources.certUnsubmitted", "Uploaded, not submitted for review. It will not show on your label until you submit it.");
430
+ }
278
431
  if (state === "pending") {
279
432
  const on = formatDay(evidence?.submittedAt, locale);
280
433
  return on
281
- ? fill(tr("productEditor.resources.certPendingOn", "Sent for review on {date}. Not on your public label yet."), { date: on })
282
- : tr("productEditor.resources.certPending", "Sent for review. Not on your public label yet.");
434
+ ? fill(tr("productEditor.resources.certInReviewOn", "In review. Sent for review on {date}. Not on your public label yet."), { date: on })
435
+ : tr("productEditor.resources.certInReviewSent", "In review. Sent for review. Not on your public label yet.");
283
436
  }
284
437
  if (state === "evidenced") {
285
438
  const until = formatDay(evidence?.validUntil, locale);
@@ -321,7 +474,10 @@ export function CertificationsPanel({ form, variantId, certificates, t, locale =
321
474
  : tr("productEditor.resources.certRejected", "Proof was not accepted. This will not appear on your public label until you add proof.");
322
475
  }
323
476
  if (notLiveReason === "revoked") {
324
- return tr("productEditor.resources.certRevoked", "Proof was withdrawn. This will not appear on your public label until you add proof.");
477
+ const note = evidence?.reviewNote?.trim();
478
+ return note
479
+ ? fill(tr("productEditor.resources.certRevokedWithNote", "Proof was withdrawn: {note} This will not appear on your public label until you add proof."), { note })
480
+ : tr("productEditor.resources.certRevoked", "Proof was withdrawn. This will not appear on your public label until you add proof.");
325
481
  }
326
482
  return tr("productEditor.resources.certUnevidenced", "This will not appear on your public label until you add proof.");
327
483
  };
@@ -331,6 +487,8 @@ export function CertificationsPanel({ form, variantId, certificates, t, locale =
331
487
  return tr("productEditor.resources.certAriaUnclaimed", "not claimed");
332
488
  case "claimed":
333
489
  return tr("productEditor.resources.certAriaClaimed", "claimed, no proof accepted");
490
+ case "unsubmitted":
491
+ return tr("productEditor.resources.certAriaUnsubmitted", "file uploaded, not submitted for review");
334
492
  case "pending":
335
493
  return tr("productEditor.resources.certAriaPending", "proof sent for review");
336
494
  default:
@@ -345,25 +503,82 @@ export function CertificationsPanel({ form, variantId, certificates, t, locale =
345
503
  const draftPolicy = draft ? policyFor(draft.type) : undefined;
346
504
  const draftLabel = CERT_TYPES.find((c) => c.type === draft?.type)?.label ?? "";
347
505
  const needsCode = draftPolicy?.requiresControlBodyCode === true;
348
- const canSubmitDraft = Boolean(proofKind) && (!needsCode || controlBodyCode.trim().length > 0);
506
+ // CEL-2172: an expiry is required for every kind but an attestation, and a
507
+ // date that is given must be after today in the producer's own calendar.
508
+ const today = localDay(currentMs());
509
+ const tomorrow = localDay(currentMs(), 1);
510
+ const needsExpiry = proofKind !== "attestation";
511
+ const expiryOk = validUntil ? validUntil > today : !needsExpiry;
512
+ const draftBusy = draft ? inFlight.has(draft.type) : false;
513
+ // Validity only. A pending submission is `aria-disabled` on the button, not
514
+ // natively disabled, so keyboard focus stays on Send instead of dropping to
515
+ // <body> the moment it is pressed.
516
+ const canSubmitDraft = Boolean(proofKind) && (!needsCode || controlBodyCode.trim().length > 0) && expiryOk;
517
+ const draftError = draft ? (submitErrors[draft.type] ?? null) : null;
518
+ const otherErrors = CERT_TYPES.filter((c) => c.type !== draft?.type && submitErrors[c.type]).map((c) => ({ label: c.label, message: submitErrors[c.type].message }));
349
519
  return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsx("div", { className: "grid grid-cols-3 gap-3", children: CERT_TYPES.map((cert) => {
350
520
  const claimed = Boolean(form.watch(cert.type));
351
521
  const resolved = resolveClaim(certificates?.items, cert.type, claimed, nowMs);
352
522
  const status = CARD_STATUS[resolved.state];
353
523
  const evidence = resolved.evidence;
354
- const file = (resolved.state === "pending" || resolved.state === "evidenced") && evidence?.fileName
524
+ const file = (resolved.state === "pending" ||
525
+ resolved.state === "evidenced" ||
526
+ resolved.state === "unsubmitted") &&
527
+ evidence?.fileName
355
528
  ? { name: evidence.fileName }
356
529
  : null;
357
530
  const kinds = allowedKinds(cert.type);
531
+ const submitting = inFlight.has(cert.type) || Boolean(certificates?.submittingTypes?.has(cert.type));
358
532
  // Proof is offered once the claim is made, and only where the API
359
533
  // accepts some kind of it. An empty policy is an answer, not a gap.
360
- const canSubmit = resolved.state !== "unclaimed" && kinds.length > 0;
361
- return (_jsx(CertificationUploadCard, { type: cert.type, label: cert.label, icon: cert.icon, status: status, file: file, detail: detailFor(cert.type, resolved), canSubmit: canSubmit, submitLabel: tr("productEditor.resources.certAddProof", "Add proof"), claimed: claimed, statusLabel: ariaStateFor(resolved.state), onToggleClaim: () => setClaim(cert.type, !claimed, evidence), onUpload: (f) => openDraft(cert.type, f), onRemove: evidence ? () => setConfirmRemove(cert.type) : undefined }, cert.type));
362
- }) }), draft && (_jsxs("div", { role: "group", "aria-label": tr("productEditor.resources.certProofFormLabel", "Describe this proof"), className: "rounded-lg border border-[var(--tone-info-border)] bg-[var(--tone-info-soft)]/40 p-3", children: [_jsx("p", { className: "text-sm font-semibold", children: tr("productEditor.resources.certProofFormTitle", `Describe the proof for ${draftLabel}`) }), _jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: draft.file.name }), _jsxs("div", { className: "mt-3 grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { htmlFor: `${fieldId}-kind`, className: "text-xs font-medium", children: tr("productEditor.resources.certProofKind", "Kind of proof") }), _jsxs(Select, { id: `${fieldId}-kind`, value: proofKind, onChange: (e) => setProofKind(e.target.value), children: [_jsx("option", { value: "", children: tr("productEditor.resources.certProofKindEmpty", "Select a kind") }), kindOptions.map((kind) => (_jsx("option", { value: kind, children: PROOF_KIND_LABELS[kind] ?? kind }, kind)))] })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { htmlFor: `${fieldId}-code`, className: "text-xs font-medium", children: needsCode
534
+ // Never while a submission is in flight, while a record is in review
535
+ // (a second pending record would be a duplicate), or for an
536
+ // unsubmitted file (which is sent, not replaced).
537
+ const canSubmit = resolved.state !== "unclaimed" &&
538
+ resolved.state !== "pending" &&
539
+ resolved.state !== "unsubmitted" &&
540
+ kinds.length > 0 &&
541
+ !submitting;
542
+ const submitExisting = resolved.state === "unsubmitted" &&
543
+ !justSent.has(cert.type) &&
544
+ evidence &&
545
+ certificates?.onSubmitExisting &&
546
+ kinds.length > 0 &&
547
+ !submitting
548
+ ? () => openDraft({
549
+ type: cert.type,
550
+ fileName: evidence.fileName ?? "",
551
+ file: null,
552
+ existing: evidence,
553
+ })
554
+ : undefined;
555
+ return (_jsx(CertificationUploadCard, { type: cert.type, label: cert.label, icon: cert.icon, status: status, file: file, detail: detailFor(cert.type, resolved), canSubmit: canSubmit, submitLabel: tr("productEditor.resources.certAddProof", "Add proof"), claimed: claimed, statusLabel: ariaStateFor(resolved.state), statusMessage: submitting
556
+ ? tr("productEditor.resources.certSending", "Sending for review\u2026")
557
+ : null, detailRef: (el) => {
558
+ statusRefs.current[cert.type] = el;
559
+ }, onSubmitExisting: submitExisting, submitExistingLabel: tr("productEditor.resources.certSubmitExisting", "Submit for review"), onToggleClaim: () => setClaim(cert.type, !claimed, evidence), onUpload: (f) => openDraft({
560
+ type: cert.type,
561
+ fileName: f.name,
562
+ file: f,
563
+ existing: null,
564
+ }), onRemove: evidence ? () => setConfirmRemove(cert.type) : undefined }, cert.type));
565
+ }) }), draft && (_jsxs("div", { role: "group", "aria-label": tr("productEditor.resources.certProofFormLabel", "Describe this proof"), className: "rounded-lg border border-[var(--tone-info-border)] bg-[var(--tone-info-soft)]/40 p-3", children: [_jsx("p", { className: "text-sm font-semibold", children: tr("productEditor.resources.certProofFormTitle", `Describe the proof for ${draftLabel}`) }), _jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: draft.fileName }), _jsxs("div", { className: "mt-3 grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { htmlFor: `${fieldId}-kind`, className: "text-xs font-medium", children: tr("productEditor.resources.certProofKind", "Kind of proof") }), _jsxs(Select, { id: `${fieldId}-kind`, value: proofKind, onChange: (e) => setProofKind(e.target.value), children: [_jsx("option", { value: "", children: tr("productEditor.resources.certProofKindEmpty", "Select a kind") }), kindOptions.map((kind) => (_jsx("option", { value: kind, children: PROOF_KIND_LABELS[kind] ?? kind }, kind)))] })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { htmlFor: `${fieldId}-code`, className: "text-xs font-medium", children: needsCode
363
566
  ? tr("productEditor.resources.certControlBodyRequired", "Control body code")
364
- : tr("productEditor.resources.certControlBodyOptional", "Control body code (optional)") }), _jsx("input", { id: `${fieldId}-code`, value: controlBodyCode, onChange: (e) => setControlBodyCode(e.target.value), className: "h-9 rounded-md border border-border bg-background px-2 text-sm" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { htmlFor: `${fieldId}-until`, className: "text-xs font-medium", children: tr("productEditor.resources.certValidUntilField", "Valid until") }), _jsx("input", { id: `${fieldId}-until`, type: "date", value: validUntil, onChange: (e) => setValidUntil(e.target.value), className: "h-9 rounded-md border border-border bg-background px-2 text-sm" })] })] }), needsCode && controlBodyCode.trim().length === 0 && (_jsx("p", { className: "mt-2 text-xs text-[var(--tone-warning-fg)]", children: tr("productEditor.resources.certControlBodyHint", "This claim needs a control body code before it can be reviewed.") })), _jsxs("div", { className: "mt-3 flex items-center gap-2", children: [_jsx("button", { type: "button", disabled: !canSubmitDraft, onClick: submitDraft, className: cn("rounded-md px-3 py-1.5 text-xs font-semibold", canSubmitDraft
567
+ : tr("productEditor.resources.certControlBodyOptional", "Control body code (optional)") }), _jsx("input", { id: `${fieldId}-code`, value: controlBodyCode, onChange: (e) => setControlBodyCode(e.target.value), className: "h-9 rounded-md border border-border bg-background px-2 text-sm" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("label", { htmlFor: `${fieldId}-until`, className: "text-xs font-medium", children: [tr("productEditor.resources.certValidUntilField", "Valid until"), needsExpiry && (_jsx("span", { "aria-hidden": "true", className: "ms-0.5 text-[var(--tone-danger-fg)]", children: "*" }))] }), _jsx("input", { id: `${fieldId}-until`, type: "date", min: tomorrow, value: validUntil, required: needsExpiry, "aria-required": needsExpiry, "aria-invalid": Boolean(validUntil) && !expiryOk ? true : undefined, "aria-describedby": `${fieldId}-until-help`, onChange: (e) => setValidUntil(e.target.value), className: "h-9 rounded-md border border-border bg-background px-2 text-sm" }), _jsx("p", { id: `${fieldId}-until-help`, className: "text-xs text-muted-foreground", children: needsExpiry
568
+ ? tr("productEditor.resources.certValidUntilHelp", "Required. Choose the date the certificate expires.")
569
+ : tr("productEditor.resources.certValidUntilHelpOptional", "Optional for an attestation.") }), Boolean(validUntil) && !expiryOk && (_jsx("p", { className: "text-xs text-[var(--tone-danger-fg)]", children: tr("productEditor.resources.certValidUntilFuture", "Choose a date after today.") }))] })] }), needsCode && controlBodyCode.trim().length === 0 && (_jsx("p", { className: "mt-2 text-xs text-[var(--tone-warning-fg)]", children: tr("productEditor.resources.certControlBodyHint", "This claim needs a control body code before it can be reviewed.") })), draftError && (_jsx("p", { ref: alertRef, role: "alert", tabIndex: -1, className: "mt-2 text-xs text-[var(--tone-danger-fg)] outline-hidden", children: draftError.message })), _jsxs("div", { className: "mt-3 flex items-center gap-2", children: [_jsx("button", { type: "button", disabled: !canSubmitDraft, "aria-disabled": draftBusy || undefined, "aria-busy": draftBusy || undefined, onClick: () => {
570
+ if (!draftBusy)
571
+ submitDraft();
572
+ }, className: cn("rounded-md px-3 py-1.5 text-xs font-semibold", canSubmitDraft
365
573
  ? "bg-[var(--tone-info-solid)] text-[var(--tone-info-solid-fg)]"
366
- : "cursor-not-allowed bg-muted text-muted-foreground"), children: tr("productEditor.resources.certSendForReview", "Send for review") }), _jsx("button", { type: "button", onClick: () => setDraft(null), className: "rounded-md px-3 py-1.5 text-xs font-medium text-muted-foreground hover:underline", children: tr("productEditor.resources.certCancel", "Cancel") })] })] })), confirmWithdraw && (_jsx(ConfirmRow, { confirmRef: confirmRef, label: tr("productEditor.resources.certWithdrawLabel", "Withdraw this claim"), message: tr("productEditor.resources.certWithdrawMessage", "This claim stops showing on your public label. Your proof stays on file."), confirmLabel: tr("productEditor.resources.certWithdrawConfirm", "Withdraw claim"), cancelLabel: tr("productEditor.resources.certCancel", "Cancel"), onConfirm: () => commitWithdraw(confirmWithdraw), onCancel: () => setConfirmWithdraw(null) })), confirmRemove && (_jsx(ConfirmRow, { confirmRef: confirmRef, label: tr("productEditor.resources.certRemoveLabel", "Remove this proof"), message: tr("productEditor.resources.certRemoveMessage", "The document and its review outcome are deleted. You would need to submit it again."), confirmLabel: tr("productEditor.resources.certRemoveConfirm", "Remove proof"), cancelLabel: tr("productEditor.resources.certCancel", "Cancel"), destructive: true, onConfirm: () => {
574
+ : "cursor-not-allowed bg-muted text-muted-foreground", draftBusy && "cursor-not-allowed opacity-60"), children: draftBusy
575
+ ? tr("productEditor.resources.certSending", "Sending for review\u2026")
576
+ : tr("productEditor.resources.certSendForReview", "Send for review") }), _jsx("button", { type: "button", "aria-disabled": draftBusy || undefined, onClick: () => {
577
+ if (draftBusy || !draft)
578
+ return;
579
+ setSubmitError(draft.type, null);
580
+ setDraft(null);
581
+ }, className: "rounded-md px-3 py-1.5 text-xs font-medium text-muted-foreground hover:underline disabled:cursor-not-allowed disabled:opacity-60", children: tr("productEditor.resources.certCancel", "Cancel") })] })] })), otherErrors.map((e) => (_jsx("p", { role: "alert", className: "text-xs text-[var(--tone-danger-fg)]", children: `${e.label}: ${e.message}` }, e.label))), confirmWithdraw && (_jsx(ConfirmRow, { confirmRef: confirmRef, label: tr("productEditor.resources.certWithdrawLabel", "Withdraw this claim"), message: tr("productEditor.resources.certWithdrawMessage", "This claim stops showing on your public label. Your proof stays on file."), confirmLabel: tr("productEditor.resources.certWithdrawConfirm", "Withdraw claim"), cancelLabel: tr("productEditor.resources.certCancel", "Cancel"), onConfirm: () => commitWithdraw(confirmWithdraw), onCancel: () => setConfirmWithdraw(null) })), confirmRemove && (_jsx(ConfirmRow, { confirmRef: confirmRef, label: tr("productEditor.resources.certRemoveLabel", "Remove this proof"), message: tr("productEditor.resources.certRemoveMessage", "The document and its review outcome are deleted. You would need to submit it again."), confirmLabel: tr("productEditor.resources.certRemoveConfirm", "Remove proof"), cancelLabel: tr("productEditor.resources.certCancel", "Cancel"), destructive: true, onConfirm: () => {
367
582
  const evidence = resolveClaim(certificates?.items, confirmRemove, Boolean(form.watch(confirmRemove)), nowMs).evidence;
368
583
  if (evidence)
369
584
  commitRemove(confirmRemove, evidence);