@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.
- package/dist/chat/chat-surface.d.ts +2 -0
- package/dist/chat/chat-surface.d.ts.map +1 -1
- package/dist/chat/chat-surface.js +2 -1
- package/dist/chat/chat-surface.js.map +1 -1
- package/dist/chat/messenger-screen/index.d.ts +1 -1
- package/dist/chat/messenger-screen/index.d.ts.map +1 -1
- package/dist/chat/messenger-screen/message-adapters.d.ts +7 -1
- package/dist/chat/messenger-screen/message-adapters.d.ts.map +1 -1
- package/dist/chat/messenger-screen/message-adapters.js +12 -3
- package/dist/chat/messenger-screen/message-adapters.js.map +1 -1
- package/dist/chat/messenger-screen/messenger-screen.d.ts.map +1 -1
- package/dist/chat/messenger-screen/messenger-screen.js +2 -2
- package/dist/chat/messenger-screen/messenger-screen.js.map +1 -1
- package/dist/chat/messenger-screen/room-panel.d.ts +3 -1
- package/dist/chat/messenger-screen/room-panel.d.ts.map +1 -1
- package/dist/chat/messenger-screen/room-panel.js +46 -9
- package/dist/chat/messenger-screen/room-panel.js.map +1 -1
- package/dist/chat/messenger-screen/types.d.ts +16 -1
- package/dist/chat/messenger-screen/types.d.ts.map +1 -1
- package/dist/components/certification-upload-card.d.ts +25 -3
- package/dist/components/certification-upload-card.d.ts.map +1 -1
- package/dist/components/certification-upload-card.js +34 -4
- package/dist/components/certification-upload-card.js.map +1 -1
- package/dist/components/journey-card.d.ts +58 -3
- package/dist/components/journey-card.d.ts.map +1 -1
- package/dist/components/journey-card.js +35 -11
- package/dist/components/journey-card.js.map +1 -1
- package/dist/components/party-logo-image.d.ts +16 -0
- package/dist/components/party-logo-image.d.ts.map +1 -0
- package/dist/components/party-logo-image.js +16 -0
- package/dist/components/party-logo-image.js.map +1 -0
- package/dist/components/sortable-card-context.d.ts +7 -0
- package/dist/components/sortable-card-context.d.ts.map +1 -1
- package/dist/components/sortable-card-context.js.map +1 -1
- package/dist/components/sortable-card-shell.d.ts +13 -8
- package/dist/components/sortable-card-shell.d.ts.map +1 -1
- package/dist/components/sortable-card-shell.js +41 -18
- package/dist/components/sortable-card-shell.js.map +1 -1
- package/dist/hooks/use-kanban-dnd.d.ts +1 -0
- package/dist/hooks/use-kanban-dnd.d.ts.map +1 -1
- package/dist/hooks/use-kanban-dnd.js +2 -2
- package/dist/hooks/use-kanban-dnd.js.map +1 -1
- package/dist/offer/index.d.ts +2 -0
- package/dist/offer/index.d.ts.map +1 -1
- package/dist/offer/index.js +1 -0
- package/dist/offer/index.js.map +1 -1
- package/dist/offer/negotiation-flow-timeline.d.ts +7 -1
- package/dist/offer/negotiation-flow-timeline.d.ts.map +1 -1
- package/dist/offer/negotiation-flow-timeline.js +23 -6
- package/dist/offer/negotiation-flow-timeline.js.map +1 -1
- package/dist/offer/negotiation-labels.d.ts +3 -0
- package/dist/offer/negotiation-labels.d.ts.map +1 -1
- package/dist/offer/negotiation-labels.js +2 -0
- package/dist/offer/negotiation-labels.js.map +1 -1
- package/dist/offer/negotiation-section.d.ts +4 -1
- package/dist/offer/negotiation-section.d.ts.map +1 -1
- package/dist/offer/negotiation-section.js +2 -2
- package/dist/offer/negotiation-section.js.map +1 -1
- package/dist/offer/offer-page.d.ts +7 -1
- package/dist/offer/offer-page.d.ts.map +1 -1
- package/dist/offer/offer-page.js +2 -2
- package/dist/offer/offer-page.js.map +1 -1
- package/dist/offer/participants.d.ts +53 -0
- package/dist/offer/participants.d.ts.map +1 -0
- package/dist/offer/participants.js +36 -0
- package/dist/offer/participants.js.map +1 -0
- package/dist/offer/types.d.ts +5 -0
- package/dist/offer/types.d.ts.map +1 -1
- package/dist/product-editor/index.d.ts +1 -1
- package/dist/product-editor/index.d.ts.map +1 -1
- package/dist/product-editor/index.js.map +1 -1
- package/dist/product-editor/product-editor-resource-types.d.ts +40 -2
- package/dist/product-editor/product-editor-resource-types.d.ts.map +1 -1
- package/dist/product-editor/tabs/resources/certifications-panel.d.ts +10 -3
- package/dist/product-editor/tabs/resources/certifications-panel.d.ts.map +1 -1
- package/dist/product-editor/tabs/resources/certifications-panel.js +239 -24
- package/dist/product-editor/tabs/resources/certifications-panel.js.map +1 -1
- package/dist/product-editor/types.d.ts +1 -1
- package/dist/product-editor/types.d.ts.map +1 -1
- 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;
|
|
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
|
-
|
|
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":"AAAA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,2BAA4B,SAAQ,SAAS;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAC9B,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,aAAa,GACb,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
233
|
-
const kinds = allowedKinds(
|
|
302
|
+
const openDraft = (next) => {
|
|
303
|
+
const kinds = allowedKinds(next.type);
|
|
234
304
|
setConfirmWithdraw(null);
|
|
235
305
|
setConfirmRemove(null);
|
|
236
|
-
setProofKind(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
282
|
-
: tr("productEditor.resources.
|
|
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
|
-
|
|
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
|
-
|
|
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" ||
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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: [
|
|
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"
|
|
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);
|