@cellarnode/ui 0.187.0 → 0.189.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/brand/brand-assets-api.d.ts +65 -0
- package/dist/brand/brand-assets-api.d.ts.map +1 -0
- package/dist/brand/brand-assets-api.js +172 -0
- package/dist/brand/brand-assets-api.js.map +1 -0
- package/dist/brand/brand-assets-section-copy.d.ts +63 -0
- package/dist/brand/brand-assets-section-copy.d.ts.map +1 -0
- package/dist/brand/brand-assets-section-copy.js +56 -0
- package/dist/brand/brand-assets-section-copy.js.map +1 -0
- package/dist/brand/brand-assets-section.d.ts +62 -0
- package/dist/brand/brand-assets-section.d.ts.map +1 -0
- package/dist/brand/brand-assets-section.js +172 -0
- package/dist/brand/brand-assets-section.js.map +1 -0
- package/dist/brand/brand-font-family.d.ts +37 -0
- package/dist/brand/brand-font-family.d.ts.map +1 -0
- package/dist/brand/brand-font-family.js +83 -0
- package/dist/brand/brand-font-family.js.map +1 -0
- package/dist/brand/brand-font-upload.d.ts +17 -0
- package/dist/brand/brand-font-upload.d.ts.map +1 -0
- package/dist/brand/brand-font-upload.js +42 -0
- package/dist/brand/brand-font-upload.js.map +1 -0
- package/dist/brand/brand-generation-client.d.ts +8 -0
- package/dist/brand/brand-generation-client.d.ts.map +1 -1
- package/dist/brand/brand-generation-client.js +1 -1
- package/dist/brand/brand-generation-client.js.map +1 -1
- package/dist/brand/brand-generation-start.d.ts +49 -0
- package/dist/brand/brand-generation-start.d.ts.map +1 -0
- package/dist/brand/brand-generation-start.js +48 -0
- package/dist/brand/brand-generation-start.js.map +1 -0
- package/dist/brand/brand-image-chooser.d.ts +3 -1
- package/dist/brand/brand-image-chooser.d.ts.map +1 -1
- package/dist/brand/brand-image-chooser.js +7 -1
- package/dist/brand/brand-image-chooser.js.map +1 -1
- package/dist/brand/brand-image-choosers.d.ts +37 -0
- package/dist/brand/brand-image-choosers.d.ts.map +1 -0
- package/dist/brand/brand-image-choosers.js +76 -0
- package/dist/brand/brand-image-choosers.js.map +1 -0
- package/dist/brand/brand-workspace-change-notice.d.ts +20 -0
- package/dist/brand/brand-workspace-change-notice.d.ts.map +1 -0
- package/dist/brand/brand-workspace-change-notice.js +19 -0
- package/dist/brand/brand-workspace-change-notice.js.map +1 -0
- package/dist/brand/index.d.ts +23 -0
- package/dist/brand/index.d.ts.map +1 -1
- package/dist/brand/index.js +21 -0
- package/dist/brand/index.js.map +1 -1
- package/dist/brand/scan-page-banner.d.ts +21 -0
- package/dist/brand/scan-page-banner.d.ts.map +1 -0
- package/dist/brand/scan-page-banner.js +48 -0
- package/dist/brand/scan-page-banner.js.map +1 -0
- package/dist/brand/use-brand-image-operation-result.d.ts +43 -0
- package/dist/brand/use-brand-image-operation-result.d.ts.map +1 -0
- package/dist/brand/use-brand-image-operation-result.js +162 -0
- package/dist/brand/use-brand-image-operation-result.js.map +1 -0
- package/dist/brand/use-brand-profile-acceptance.d.ts +38 -0
- package/dist/brand/use-brand-profile-acceptance.d.ts.map +1 -0
- package/dist/brand/use-brand-profile-acceptance.js +84 -0
- package/dist/brand/use-brand-profile-acceptance.js.map +1 -0
- package/dist/brand/use-brand-public-visibility.d.ts +59 -0
- package/dist/brand/use-brand-public-visibility.d.ts.map +1 -0
- package/dist/brand/use-brand-public-visibility.js +69 -0
- package/dist/brand/use-brand-public-visibility.js.map +1 -0
- package/dist/brand/use-brand-workspace-concurrency.d.ts +131 -0
- package/dist/brand/use-brand-workspace-concurrency.d.ts.map +1 -0
- package/dist/brand/use-brand-workspace-concurrency.js +251 -0
- package/dist/brand/use-brand-workspace-concurrency.js.map +1 -0
- package/dist/brand/use-brand-workspace-image-candidates.d.ts +19 -0
- package/dist/brand/use-brand-workspace-image-candidates.d.ts.map +1 -0
- package/dist/brand/use-brand-workspace-image-candidates.js +39 -0
- package/dist/brand/use-brand-workspace-image-candidates.js.map +1 -0
- package/dist/moderation/focus-upload-control.d.ts +15 -0
- package/dist/moderation/focus-upload-control.d.ts.map +1 -0
- package/dist/moderation/focus-upload-control.js +21 -0
- package/dist/moderation/focus-upload-control.js.map +1 -0
- package/dist/moderation/index.d.ts +1 -0
- package/dist/moderation/index.d.ts.map +1 -1
- package/dist/moderation/index.js +1 -0
- package/dist/moderation/index.js.map +1 -1
- package/dist/moderation/moderation-event.d.ts +2 -0
- package/dist/moderation/moderation-event.d.ts.map +1 -1
- package/dist/moderation/moderation-event.js +7 -0
- package/dist/moderation/moderation-event.js.map +1 -1
- package/dist/moderation/moderation-scope.d.ts +31 -0
- package/dist/moderation/moderation-scope.d.ts.map +1 -0
- package/dist/moderation/moderation-scope.js +109 -0
- package/dist/moderation/moderation-scope.js.map +1 -0
- package/dist/moderation/moderation-store.d.ts.map +1 -1
- package/dist/moderation/moderation-store.js +4 -2
- package/dist/moderation/moderation-store.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ModerationPersistedRead } from "../moderation/index.js";
|
|
2
|
+
import type { BrandAsset, BrandProfileAssetSlot } from "./types.js";
|
|
3
|
+
/** The moderation-relevant fields the backend returns with every asset. */
|
|
4
|
+
export interface BrandAssetModeration {
|
|
5
|
+
readonly outcome: "pending" | "pass" | "reject" | "unsure" | null;
|
|
6
|
+
readonly publishedAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export type BrandAssetRecord = BrandAsset & BrandAssetModeration;
|
|
9
|
+
export interface UploadBrandAssetArgs {
|
|
10
|
+
readonly profileId: string;
|
|
11
|
+
readonly slot: BrandProfileAssetSlot;
|
|
12
|
+
readonly file: File;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* CEL-2127 — the transport-agnostic seam of the brand image/asset flow
|
|
16
|
+
* (`/brand-profiles/{id}/assets*`, present in both the producer and importer
|
|
17
|
+
* OpenAPIs). A consumer supplies one implementation: producer may wrap its
|
|
18
|
+
* generated client, importer uses {@link createBrandAssetsClient}. Cache
|
|
19
|
+
* invalidation stays with the consumer (wrap `upload`).
|
|
20
|
+
*/
|
|
21
|
+
export interface BrandAssetsApi {
|
|
22
|
+
/** `GET /brand-profiles/{id}/assets`. */
|
|
23
|
+
list(profileId: string): Promise<BrandAssetRecord[]>;
|
|
24
|
+
/** `POST .../assets/upload-url`, `PUT` the file to storage, `POST .../assets/confirm`. */
|
|
25
|
+
upload(args: UploadBrandAssetArgs): Promise<BrandAssetRecord>;
|
|
26
|
+
/** `DELETE /brand-profiles/{id}/assets/{assetId}`. */
|
|
27
|
+
remove(profileId: string, assetId: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* The durable, asset-id-keyed moderation read the shared moderation operation
|
|
30
|
+
* falls back to when no live verdict event arrives. `null` when the exact
|
|
31
|
+
* asset no longer exists; rejects on transport failure so the bounded poll
|
|
32
|
+
* retries instead of reporting a false outcome.
|
|
33
|
+
*/
|
|
34
|
+
fetchModeration(profileId: string, assetId: string): Promise<ModerationPersistedRead | null>;
|
|
35
|
+
}
|
|
36
|
+
/** A non-2xx answer, or a failed storage upload, from the assets endpoints. */
|
|
37
|
+
export declare class BrandAssetsRequestError extends Error {
|
|
38
|
+
readonly status: number;
|
|
39
|
+
readonly code: string;
|
|
40
|
+
constructor(status: number, code: string, message: string);
|
|
41
|
+
}
|
|
42
|
+
export interface BrandAssetsClientOptions {
|
|
43
|
+
/** API origin (e.g. `http://localhost:4000`). */
|
|
44
|
+
readonly baseUrl: string;
|
|
45
|
+
/** Bearer token source. Omit for cookie auth (`credentials: "include"`). */
|
|
46
|
+
readonly getAccessToken?: () => Promise<string | null>;
|
|
47
|
+
/** Defaults to the global `fetch`. Used for the API calls only, never for the storage PUT. */
|
|
48
|
+
readonly fetch?: typeof fetch;
|
|
49
|
+
/** The injected `fetch` refreshes an expired session itself: never retry a 401 here. */
|
|
50
|
+
readonly fetchRefreshesSession?: boolean;
|
|
51
|
+
/** Abort the storage PUT after this long. Default 30000. */
|
|
52
|
+
readonly uploadTimeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Browsers report fonts as octet-stream or empty; the backend whitelists real font MIME types. */
|
|
55
|
+
export declare function resolveBrandAssetMimeType(file: File): string;
|
|
56
|
+
/** The shared HTTP implementation of {@link BrandAssetsApi}. */
|
|
57
|
+
export declare function createBrandAssetsClient(options: BrandAssetsClientOptions): BrandAssetsApi;
|
|
58
|
+
/**
|
|
59
|
+
* Safe read of the moderation fields off a plain {@link BrandAsset}. Assets that
|
|
60
|
+
* came through {@link BrandAssetsApi} are {@link BrandAssetRecord}s, but
|
|
61
|
+
* components type them as the narrower `BrandAsset`. Returns `null` (not "no
|
|
62
|
+
* verdict yet") when the fields are genuinely absent, e.g. a hand-built fixture.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readBrandAssetModeration(asset: BrandAsset): BrandAssetModeration | null;
|
|
65
|
+
//# sourceMappingURL=brand-assets-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-assets-api.d.ts","sourceRoot":"","sources":["../../src/brand/brand-assets-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjE,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAC;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,oBAAoB,CAAC;AAEjE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACrD,0FAA0F;IAC1F,MAAM,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9D,sDAAsD;IACtD,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;CAC9F;AAED,+EAA+E;AAC/E,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvD,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AASD,mGAAmG;AACnG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAM5D;AAyCD,gEAAgE;AAChE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,cAAc,CA2HzF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,oBAAoB,GAAG,IAAI,CAIvF"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** A non-2xx answer, or a failed storage upload, from the assets endpoints. */
|
|
2
|
+
export class BrandAssetsRequestError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "BrandAssetsRequestError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const FONT_MIME_BY_EXTENSION = {
|
|
13
|
+
woff2: "font/woff2",
|
|
14
|
+
woff: "font/woff",
|
|
15
|
+
ttf: "font/ttf",
|
|
16
|
+
otf: "font/otf",
|
|
17
|
+
};
|
|
18
|
+
/** Browsers report fonts as octet-stream or empty; the backend whitelists real font MIME types. */
|
|
19
|
+
export function resolveBrandAssetMimeType(file) {
|
|
20
|
+
const extension = file.name.split(".").pop()?.toLowerCase();
|
|
21
|
+
const fontMime = extension ? FONT_MIME_BY_EXTENSION[extension] : undefined;
|
|
22
|
+
if (fontMime)
|
|
23
|
+
return fontMime;
|
|
24
|
+
if (file.type && file.type !== "application/octet-stream")
|
|
25
|
+
return file.type;
|
|
26
|
+
return "application/octet-stream";
|
|
27
|
+
}
|
|
28
|
+
function isRecord(value) {
|
|
29
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
const OUTCOMES = new Set(["pending", "pass", "reject", "unsure"]);
|
|
32
|
+
function parseAsset(value) {
|
|
33
|
+
if (!isRecord(value) ||
|
|
34
|
+
typeof value.id !== "string" ||
|
|
35
|
+
typeof value.slot !== "string" ||
|
|
36
|
+
typeof value.fileUrl !== "string") {
|
|
37
|
+
throw new BrandAssetsRequestError(200, "INVALID_RESPONSE", "Unexpected brand asset response");
|
|
38
|
+
}
|
|
39
|
+
const outcome = value.moderationOutcome;
|
|
40
|
+
return {
|
|
41
|
+
id: value.id,
|
|
42
|
+
slot: value.slot,
|
|
43
|
+
fileUrl: value.fileUrl,
|
|
44
|
+
fileName: typeof value.fileName === "string" ? value.fileName : "",
|
|
45
|
+
mimeType: typeof value.mimeType === "string" ? value.mimeType : "",
|
|
46
|
+
fileSize: typeof value.fileSize === "number" ? value.fileSize : 0,
|
|
47
|
+
width: typeof value.width === "number" ? value.width : null,
|
|
48
|
+
height: typeof value.height === "number" ? value.height : null,
|
|
49
|
+
outcome: typeof outcome === "string" && OUTCOMES.has(outcome) ? outcome : null,
|
|
50
|
+
publishedAt: typeof value.publishedAt === "string" ? value.publishedAt : null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function errorFields(body, fallback) {
|
|
54
|
+
if (!isRecord(body))
|
|
55
|
+
return { code: "UNKNOWN", message: fallback };
|
|
56
|
+
const nested = isRecord(body.error) ? body.error : body;
|
|
57
|
+
return {
|
|
58
|
+
code: typeof nested.code === "string" ? nested.code : "UNKNOWN",
|
|
59
|
+
message: typeof nested.message === "string" ? nested.message : fallback,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** The shared HTTP implementation of {@link BrandAssetsApi}. */
|
|
63
|
+
export function createBrandAssetsClient(options) {
|
|
64
|
+
const base = options.baseUrl.replace(/\/+$/, "");
|
|
65
|
+
const assetsPath = (profileId) => `/brand-profiles/${encodeURIComponent(profileId)}/assets`;
|
|
66
|
+
async function request(method, path, fallback, body) {
|
|
67
|
+
const doFetch = options.fetch ?? fetch;
|
|
68
|
+
const send = async (token) => {
|
|
69
|
+
const headers = { Accept: "application/json" };
|
|
70
|
+
if (body !== undefined)
|
|
71
|
+
headers["Content-Type"] = "application/json";
|
|
72
|
+
if (token)
|
|
73
|
+
headers.Authorization = `Bearer ${token}`;
|
|
74
|
+
return doFetch(`${base}${path}`, {
|
|
75
|
+
method,
|
|
76
|
+
headers,
|
|
77
|
+
...(options.getAccessToken ? {} : { credentials: "include" }),
|
|
78
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
let response = await send(options.getAccessToken ? await options.getAccessToken() : undefined);
|
|
82
|
+
if (response.status === 401 && options.getAccessToken && !options.fetchRefreshesSession) {
|
|
83
|
+
response = await send(await options.getAccessToken());
|
|
84
|
+
}
|
|
85
|
+
let parsed;
|
|
86
|
+
if (response.status !== 204) {
|
|
87
|
+
try {
|
|
88
|
+
parsed = await response.json();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
parsed = undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const { code, message } = errorFields(parsed, fallback);
|
|
96
|
+
throw new BrandAssetsRequestError(response.status, code, message);
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
async function list(profileId) {
|
|
101
|
+
const data = await request("GET", assetsPath(profileId), "Failed to list brand assets");
|
|
102
|
+
if (!isRecord(data) || !Array.isArray(data.assets)) {
|
|
103
|
+
throw new BrandAssetsRequestError(200, "INVALID_RESPONSE", "Unexpected brand asset list");
|
|
104
|
+
}
|
|
105
|
+
return data.assets.map(parseAsset);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
list,
|
|
109
|
+
async upload({ profileId, slot, file }) {
|
|
110
|
+
const mimeType = resolveBrandAssetMimeType(file);
|
|
111
|
+
const target = await request("POST", `${assetsPath(profileId)}/upload-url`, "Failed to start brand asset upload", { slot, fileName: file.name, mimeType, fileSize: file.size });
|
|
112
|
+
if (!isRecord(target) ||
|
|
113
|
+
typeof target.uploadUrl !== "string" ||
|
|
114
|
+
typeof target.uploadId !== "string") {
|
|
115
|
+
throw new BrandAssetsRequestError(200, "INVALID_RESPONSE", "Unexpected upload target");
|
|
116
|
+
}
|
|
117
|
+
const requiredHeaders = isRecord(target.requiredHeaders)
|
|
118
|
+
? target.requiredHeaders
|
|
119
|
+
: {};
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
const timeoutId = setTimeout(() => controller.abort(), options.uploadTimeoutMs ?? 30_000);
|
|
122
|
+
try {
|
|
123
|
+
let stored;
|
|
124
|
+
try {
|
|
125
|
+
// Storage is a different origin with a presigned URL: plain fetch,
|
|
126
|
+
// never the authenticated one, so no bearer token leaks to it.
|
|
127
|
+
stored = await fetch(target.uploadUrl, {
|
|
128
|
+
method: "PUT",
|
|
129
|
+
headers: requiredHeaders,
|
|
130
|
+
body: file,
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new BrandAssetsRequestError(0, controller.signal.aborted ? "BRAND_ASSET_UPLOAD_TIMEOUT" : "BRAND_ASSET_UPLOAD_NETWORK", controller.signal.aborted
|
|
136
|
+
? "Upload timed out. Please try again."
|
|
137
|
+
: "Network error while uploading. Please check your connection and try again.");
|
|
138
|
+
}
|
|
139
|
+
if (!stored.ok) {
|
|
140
|
+
throw new BrandAssetsRequestError(stored.status, "BRAND_ASSET_UPLOAD_FAILED", "Failed to upload file to storage");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
clearTimeout(timeoutId);
|
|
145
|
+
}
|
|
146
|
+
const confirmed = await request("POST", `${assetsPath(profileId)}/confirm`, "Failed to confirm brand asset upload", { uploadId: target.uploadId, slot, fileName: file.name, mimeType, fileSize: file.size });
|
|
147
|
+
return parseAsset(confirmed);
|
|
148
|
+
},
|
|
149
|
+
async remove(profileId, assetId) {
|
|
150
|
+
await request("DELETE", `${assetsPath(profileId)}/${encodeURIComponent(assetId)}`, "Failed to delete brand asset");
|
|
151
|
+
},
|
|
152
|
+
async fetchModeration(profileId, assetId) {
|
|
153
|
+
const asset = (await list(profileId)).find((candidate) => candidate.id === assetId);
|
|
154
|
+
if (!asset)
|
|
155
|
+
return null;
|
|
156
|
+
return { identity: asset.id, outcome: asset.outcome, published: asset.publishedAt !== null };
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Safe read of the moderation fields off a plain {@link BrandAsset}. Assets that
|
|
162
|
+
* came through {@link BrandAssetsApi} are {@link BrandAssetRecord}s, but
|
|
163
|
+
* components type them as the narrower `BrandAsset`. Returns `null` (not "no
|
|
164
|
+
* verdict yet") when the fields are genuinely absent, e.g. a hand-built fixture.
|
|
165
|
+
*/
|
|
166
|
+
export function readBrandAssetModeration(asset) {
|
|
167
|
+
const record = asset;
|
|
168
|
+
if (record.outcome === undefined)
|
|
169
|
+
return null;
|
|
170
|
+
return { outcome: record.outcome, publishedAt: record.publishedAt ?? null };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=brand-assets-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-assets-api.js","sourceRoot":"","sources":["../../src/brand/brand-assets-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { BrandAssetUploaderCopy } from "./brand-asset-uploader-contract.js";
|
|
2
|
+
import type { ModerationNoticeCopy } from "../moderation/ModerationSurfaceNotice.js";
|
|
3
|
+
/** Copy for the brand font control. Every string is host-overridable. */
|
|
4
|
+
export interface BrandFontUploadCopy {
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle: string;
|
|
7
|
+
inputLabel: string;
|
|
8
|
+
tooLarge: (maxMegabytes: number) => string;
|
|
9
|
+
needsFamilyName: string;
|
|
10
|
+
uploading: string;
|
|
11
|
+
replace: string;
|
|
12
|
+
removing: string;
|
|
13
|
+
remove: string;
|
|
14
|
+
upload: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_BRAND_FONT_UPLOAD_COPY: BrandFontUploadCopy;
|
|
17
|
+
/** Copy for the scan-page banner control. Every string is host-overridable. */
|
|
18
|
+
export interface ScanPageBannerCopy {
|
|
19
|
+
title: string;
|
|
20
|
+
subtitle: string;
|
|
21
|
+
inputLabel: string;
|
|
22
|
+
tooLarge: (maxMegabytes: number) => string;
|
|
23
|
+
unsupported: (mimeType: string) => string;
|
|
24
|
+
previewAlt: string;
|
|
25
|
+
previewUnavailable: string;
|
|
26
|
+
fromWebsite: string;
|
|
27
|
+
uploading: string;
|
|
28
|
+
replace: string;
|
|
29
|
+
removing: string;
|
|
30
|
+
remove: string;
|
|
31
|
+
upload: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const DEFAULT_SCAN_PAGE_BANNER_COPY: ScanPageBannerCopy;
|
|
34
|
+
/** Copy for {@link BrandAssetsSection}. Omitted keys keep the English default. */
|
|
35
|
+
export interface BrandAssetsSectionCopy {
|
|
36
|
+
assetsTitle: string;
|
|
37
|
+
assetsSubtitle: string;
|
|
38
|
+
logoUploaded: string;
|
|
39
|
+
logoRemoved: string;
|
|
40
|
+
logoRemoveFailed: string;
|
|
41
|
+
bannerUploaded: string;
|
|
42
|
+
bannerRemoved: string;
|
|
43
|
+
bannerRemoveFailed: string;
|
|
44
|
+
fontUploaded: string;
|
|
45
|
+
fontRemoved: string;
|
|
46
|
+
fontRemoveFailed: string;
|
|
47
|
+
uploadTimeout: string;
|
|
48
|
+
uploadNetwork: string;
|
|
49
|
+
uploadFailed: string;
|
|
50
|
+
logoPendingHint: string;
|
|
51
|
+
bannerPendingHint: string;
|
|
52
|
+
font: BrandFontUploadCopy;
|
|
53
|
+
scanBanner: ScanPageBannerCopy;
|
|
54
|
+
uploader?: Partial<BrandAssetUploaderCopy>;
|
|
55
|
+
moderation?: Partial<ModerationNoticeCopy>;
|
|
56
|
+
}
|
|
57
|
+
export type BrandAssetsSectionCopyOverrides = Partial<Omit<BrandAssetsSectionCopy, "font" | "scanBanner">> & {
|
|
58
|
+
font?: Partial<BrandFontUploadCopy>;
|
|
59
|
+
scanBanner?: Partial<ScanPageBannerCopy>;
|
|
60
|
+
};
|
|
61
|
+
export declare const DEFAULT_BRAND_ASSETS_SECTION_COPY: BrandAssetsSectionCopy;
|
|
62
|
+
export declare function resolveBrandAssetsSectionCopy(overrides: BrandAssetsSectionCopyOverrides | undefined): BrandAssetsSectionCopy;
|
|
63
|
+
//# sourceMappingURL=brand-assets-section-copy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-assets-section-copy.d.ts","sourceRoot":"","sources":["../../src/brand/brand-assets-section-copy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAElF,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,8BAA8B,EAAE,mBAa5C,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,6BAA6B,EAAE,kBAe3C,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC5C;AAED,MAAM,MAAM,+BAA+B,GAAG,OAAO,CACnD,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,YAAY,CAAC,CACpD,GAAG;IACF,IAAI,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAC1C,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,sBAoB/C,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,+BAA+B,GAAG,SAAS,GACrD,sBAAsB,CAOxB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const DEFAULT_BRAND_FONT_UPLOAD_COPY = {
|
|
2
|
+
title: "Brand font",
|
|
3
|
+
subtitle: "A custom font for your scan pages. WOFF2 is recommended.",
|
|
4
|
+
inputLabel: "Upload brand font",
|
|
5
|
+
tooLarge: (mb) => `File is too large (max ${mb} MB). If this is a CJK TrueType font, please subset it or convert to WOFF2.`,
|
|
6
|
+
needsFamilyName: "This font is stored but not in use yet. Add a font family name to your brand details so scan pages can apply it.",
|
|
7
|
+
uploading: "Uploading...",
|
|
8
|
+
replace: "Replace",
|
|
9
|
+
removing: "Removing...",
|
|
10
|
+
remove: "Remove",
|
|
11
|
+
upload: "Upload font file",
|
|
12
|
+
};
|
|
13
|
+
export const DEFAULT_SCAN_PAGE_BANNER_COPY = {
|
|
14
|
+
title: "Scan page banner",
|
|
15
|
+
subtitle: "The image across the top of the page people see when they scan your QR code. It applies to your whole brand, not one product.",
|
|
16
|
+
inputLabel: "Upload scan page banner",
|
|
17
|
+
tooLarge: (mb) => `Image is too large (max ${mb} MB)`,
|
|
18
|
+
unsupported: (type) => `File type ${type || "unknown"} is unsupported`,
|
|
19
|
+
previewAlt: "Scan page banner preview",
|
|
20
|
+
previewUnavailable: "Banner preview unavailable",
|
|
21
|
+
fromWebsite: "Taken from your website",
|
|
22
|
+
uploading: "Uploading...",
|
|
23
|
+
replace: "Replace",
|
|
24
|
+
removing: "Removing...",
|
|
25
|
+
remove: "Remove",
|
|
26
|
+
upload: "Upload banner image",
|
|
27
|
+
};
|
|
28
|
+
export const DEFAULT_BRAND_ASSETS_SECTION_COPY = {
|
|
29
|
+
assetsTitle: "Logos & assets",
|
|
30
|
+
assetsSubtitle: "Upload logo variations used across your e-labels and exports.",
|
|
31
|
+
logoUploaded: "Logo uploaded",
|
|
32
|
+
logoRemoved: "Logo removed",
|
|
33
|
+
logoRemoveFailed: "Failed to remove logo",
|
|
34
|
+
bannerUploaded: "Banner updated",
|
|
35
|
+
bannerRemoved: "Banner removed",
|
|
36
|
+
bannerRemoveFailed: "Failed to remove banner",
|
|
37
|
+
fontUploaded: "Font updated",
|
|
38
|
+
fontRemoved: "Font removed",
|
|
39
|
+
fontRemoveFailed: "Failed to remove font",
|
|
40
|
+
uploadTimeout: "Upload timed out. Please try again.",
|
|
41
|
+
uploadNetwork: "Network error while uploading. Please check your connection and try again.",
|
|
42
|
+
uploadFailed: "Upload failed. Please try again.",
|
|
43
|
+
logoPendingHint: "Your current logo stays up while we check the new one; usually under a minute.",
|
|
44
|
+
bannerPendingHint: "Your current banner stays up while we check the new one; usually under a minute.",
|
|
45
|
+
font: DEFAULT_BRAND_FONT_UPLOAD_COPY,
|
|
46
|
+
scanBanner: DEFAULT_SCAN_PAGE_BANNER_COPY,
|
|
47
|
+
};
|
|
48
|
+
export function resolveBrandAssetsSectionCopy(overrides) {
|
|
49
|
+
return {
|
|
50
|
+
...DEFAULT_BRAND_ASSETS_SECTION_COPY,
|
|
51
|
+
...overrides,
|
|
52
|
+
font: { ...DEFAULT_BRAND_FONT_UPLOAD_COPY, ...overrides?.font },
|
|
53
|
+
scanBanner: { ...DEFAULT_SCAN_PAGE_BANNER_COPY, ...overrides?.scanBanner },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=brand-assets-section-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-assets-section-copy.js","sourceRoot":"","sources":["../../src/brand/brand-assets-section-copy.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,8BAA8B,GAAwB;IACjE,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,0DAA0D;IACpE,UAAU,EAAE,mBAAmB;IAC/B,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CACf,0BAA0B,EAAE,6EAA6E;IAC3G,eAAe,EACb,kHAAkH;IACpH,SAAS,EAAE,cAAc;IACzB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,aAAa;IACvB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,kBAAkB;CAC3B,CAAC;AAmBF,MAAM,CAAC,MAAM,6BAA6B,GAAuB;IAC/D,KAAK,EAAE,kBAAkB;IACzB,QAAQ,EACN,+HAA+H;IACjI,UAAU,EAAE,yBAAyB;IACrC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,2BAA2B,EAAE,MAAM;IACrD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,IAAI,IAAI,SAAS,iBAAiB;IACtE,UAAU,EAAE,0BAA0B;IACtC,kBAAkB,EAAE,4BAA4B;IAChD,WAAW,EAAE,yBAAyB;IACtC,SAAS,EAAE,cAAc;IACzB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,aAAa;IACvB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,qBAAqB;CAC9B,CAAC;AAiCF,MAAM,CAAC,MAAM,iCAAiC,GAA2B;IACvE,WAAW,EAAE,gBAAgB;IAC7B,cAAc,EAAE,+DAA+D;IAC/E,YAAY,EAAE,eAAe;IAC7B,WAAW,EAAE,cAAc;IAC3B,gBAAgB,EAAE,uBAAuB;IACzC,cAAc,EAAE,gBAAgB;IAChC,aAAa,EAAE,gBAAgB;IAC/B,kBAAkB,EAAE,yBAAyB;IAC7C,YAAY,EAAE,cAAc;IAC5B,WAAW,EAAE,cAAc;IAC3B,gBAAgB,EAAE,uBAAuB;IACzC,aAAa,EAAE,qCAAqC;IACpD,aAAa,EAAE,4EAA4E;IAC3F,YAAY,EAAE,kCAAkC;IAChD,eAAe,EAAE,gFAAgF;IACjG,iBAAiB,EACf,kFAAkF;IACpF,IAAI,EAAE,8BAA8B;IACpC,UAAU,EAAE,6BAA6B;CAC1C,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAC3C,SAAsD;IAEtD,OAAO;QACL,GAAG,iCAAiC;QACpC,GAAG,SAAS;QACZ,IAAI,EAAE,EAAE,GAAG,8BAA8B,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE;QAC/D,UAAU,EAAE,EAAE,GAAG,6BAA6B,EAAE,GAAG,SAAS,EAAE,UAAU,EAAE;KAC3E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ModerationSurfaceNoticeProps } from "../moderation/ModerationSurfaceNotice.js";
|
|
2
|
+
import { type BrandAssetsApi } from "./brand-assets-api.js";
|
|
3
|
+
import { type BrandAssetsSectionCopyOverrides } from "./brand-assets-section-copy.js";
|
|
4
|
+
import { type BrandVisualFontSources } from "./brand-font-family.js";
|
|
5
|
+
import { type BrandAsset } from "./types.js";
|
|
6
|
+
/** The slots whose scraped value lives on the profile and must be cleared with the asset. */
|
|
7
|
+
export type BrandScanPageSlot = "favicon" | "banner";
|
|
8
|
+
/** The data seam of {@link BrandAssetsSection}. Cache invalidation stays with the host. */
|
|
9
|
+
export interface BrandAssetsSectionApi extends Pick<BrandAssetsApi, "upload" | "remove" | "fetchModeration"> {
|
|
10
|
+
/**
|
|
11
|
+
* Clear a slot whose value also exists as a scraped profile field (favicon,
|
|
12
|
+
* banner): empties the profile field AND deletes the asset when `assetId` is
|
|
13
|
+
* set. Omit it and `favicon` falls back to `remove`, `banner` to `remove` of
|
|
14
|
+
* the stored asset only.
|
|
15
|
+
*/
|
|
16
|
+
clearScanPageSlot?(args: {
|
|
17
|
+
slot: BrandScanPageSlot;
|
|
18
|
+
assetId: string | null;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/** The profile fields the section reads. */
|
|
22
|
+
export interface BrandAssetsSectionVisual extends BrandVisualFontSources {
|
|
23
|
+
readonly bannerImage?: {
|
|
24
|
+
readonly value?: string | null;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
27
|
+
export type BrandAssetsNotice = {
|
|
28
|
+
readonly tone: "success" | "error";
|
|
29
|
+
readonly message: string;
|
|
30
|
+
};
|
|
31
|
+
export interface BrandAssetsSectionProps {
|
|
32
|
+
profileId: string;
|
|
33
|
+
/** `profileData.visual`: source of the font family and the scraped banner. */
|
|
34
|
+
visual?: BrandAssetsSectionVisual | null;
|
|
35
|
+
assets?: readonly BrandAsset[];
|
|
36
|
+
api: BrandAssetsSectionApi;
|
|
37
|
+
/** Passed to the logo previews; non-https deployments (localhost) must list their API origin. */
|
|
38
|
+
allowedImageOrigins?: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Render the scan-page banner. Default true. Surfaces without QR scan pages
|
|
41
|
+
* (importers) pass false: the control would be dead.
|
|
42
|
+
*/
|
|
43
|
+
showScanPageBanner?: boolean;
|
|
44
|
+
/** Called after any successful upload, remove or clear: invalidate the assets/profile caches here. */
|
|
45
|
+
onAssetsChanged?: () => void;
|
|
46
|
+
/** Toast adapter. The section never imports a toast library. */
|
|
47
|
+
onNotify?: (notice: BrandAssetsNotice) => void;
|
|
48
|
+
/** Host transport for the moderation appeal CTA. Hidden when absent. */
|
|
49
|
+
requestModerationReview?: ModerationSurfaceNoticeProps["requestReview"];
|
|
50
|
+
copy?: BrandAssetsSectionCopyOverrides;
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The brand "Logos & assets" workspace section: logo slots with remove, the
|
|
55
|
+
* optional scan-page banner, the brand font, and per-asset moderation notices.
|
|
56
|
+
* Producer and importer mount the same component; only the injected
|
|
57
|
+
* {@link BrandAssetsSectionApi} and copy differ. Mount it under a
|
|
58
|
+
* `ModerationScope` (`@cellarnode/ui/moderation/scope`) so verdicts show live;
|
|
59
|
+
* outside a provider the moderation notices stay silent.
|
|
60
|
+
*/
|
|
61
|
+
export declare function BrandAssetsSection({ profileId, visual, assets, api, allowedImageOrigins, showScanPageBanner, onAssetsChanged, onNotify, requestModerationReview, copy: copyOverrides, className, }: BrandAssetsSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
//# sourceMappingURL=brand-assets-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-assets-section.d.ts","sourceRoot":"","sources":["../../src/brand/brand-assets-section.tsx"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAG1F,OAAO,EAAoB,KAAK,UAAU,EAAsB,MAAM,SAAS,CAAC;AAEhF,6FAA6F;AAC7F,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErD,2FAA2F;AAC3F,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CACjD,cAAc,EACd,QAAQ,GAAG,QAAQ,GAAG,iBAAiB,CACxC;IACC;;;;;OAKG;IACH,iBAAiB,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9F;AAED,4CAA4C;AAC5C,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAClE;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjG,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,GAAG,EAAE,qBAAqB,CAAC;IAC3B,iGAAiG;IACjG,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sGAAsG;IACtG,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/C,wEAAwE;IACxE,uBAAuB,CAAC,EAAE,4BAA4B,CAAC,eAAe,CAAC,CAAC;IACxE,IAAI,CAAC,EAAE,+BAA+B,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,MAAM,EACN,MAAW,EACX,GAAG,EACH,mBAAmB,EACnB,kBAAyB,EACzB,eAAe,EACf,QAAQ,EACR,uBAAuB,EACvB,IAAI,EAAE,aAAa,EACnB,SAAS,GACV,EAAE,uBAAuB,2CAqPzB"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { focusUploadControl } from "../moderation/focus-upload-control.js";
|
|
5
|
+
import { useModerationCheckControls } from "../moderation/moderation-store.js";
|
|
6
|
+
import { ModerationSurfaceNotice, } from "../moderation/ModerationSurfaceNotice.js";
|
|
7
|
+
import { BrandAssetUploader } from "./brand-asset-uploader.js";
|
|
8
|
+
import { readBrandAssetModeration, } from "./brand-assets-api.js";
|
|
9
|
+
import { resolveBrandAssetsSectionCopy, } from "./brand-assets-section-copy.js";
|
|
10
|
+
import { resolveBrandFontFamily } from "./brand-font-family.js";
|
|
11
|
+
import { BrandFontUpload } from "./brand-font-upload.js";
|
|
12
|
+
import { ScanPageBanner } from "./scan-page-banner.js";
|
|
13
|
+
import { BRAND_LOGO_SLOTS } from "./types.js";
|
|
14
|
+
/**
|
|
15
|
+
* CEL-1681: wires an asset's durable, id-keyed moderation outcome into the
|
|
16
|
+
* notice for one slot. A replacement upload changes the asset id, which is what
|
|
17
|
+
* tells the operation to cancel the stale poll; an unrelated re-render must not.
|
|
18
|
+
*/
|
|
19
|
+
function BrandAssetModerationNotice({ profileId, asset, fetchModeration, ...noticeProps }) {
|
|
20
|
+
const assetId = asset?.id;
|
|
21
|
+
const fetchPersisted = useMemo(() => (assetId ? () => fetchModeration(profileId, assetId) : undefined), [fetchModeration, profileId, assetId]);
|
|
22
|
+
const initialOutcome = asset ? (readBrandAssetModeration(asset)?.outcome ?? null) : null;
|
|
23
|
+
return (_jsx(ModerationSurfaceNotice, { ...noticeProps, identity: assetId, fetchPersisted: fetchPersisted, initialOutcome: initialOutcome }));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The brand "Logos & assets" workspace section: logo slots with remove, the
|
|
27
|
+
* optional scan-page banner, the brand font, and per-asset moderation notices.
|
|
28
|
+
* Producer and importer mount the same component; only the injected
|
|
29
|
+
* {@link BrandAssetsSectionApi} and copy differ. Mount it under a
|
|
30
|
+
* `ModerationScope` (`@cellarnode/ui/moderation/scope`) so verdicts show live;
|
|
31
|
+
* outside a provider the moderation notices stay silent.
|
|
32
|
+
*/
|
|
33
|
+
export function BrandAssetsSection({ profileId, visual, assets = [], api, allowedImageOrigins, showScanPageBanner = true, onAssetsChanged, onNotify, requestModerationReview, copy: copyOverrides, className, }) {
|
|
34
|
+
const copy = useMemo(() => resolveBrandAssetsSectionCopy(copyOverrides), [copyOverrides]);
|
|
35
|
+
const { beginOperation, markChecking } = useModerationCheckControls();
|
|
36
|
+
const bannerSectionRef = useRef(null);
|
|
37
|
+
const logoSectionRef = useRef(null);
|
|
38
|
+
// One pending indicator per mutation kind, latest call wins: the same
|
|
39
|
+
// semantics as producer's one-mutation-at-a-time `useMutation` state, so a
|
|
40
|
+
// second upload into another slot moves the spinner instead of stacking.
|
|
41
|
+
const [pending, setPending] = useState({
|
|
42
|
+
upload: null,
|
|
43
|
+
remove: null,
|
|
44
|
+
clear: null,
|
|
45
|
+
});
|
|
46
|
+
const latestCall = useRef({ upload: 0, remove: 0, clear: 0 });
|
|
47
|
+
const [slotErrors, setSlotErrors] = useState({});
|
|
48
|
+
const [bannerError, setBannerError] = useState(null);
|
|
49
|
+
const [fontError, setFontError] = useState(null);
|
|
50
|
+
const bannerSubject = useMemo(() => ({ brandProfileId: profileId, slot: "banner" }), [profileId]);
|
|
51
|
+
const logoSubjects = useMemo(() => BRAND_LOGO_SLOTS.map((slot) => ({
|
|
52
|
+
slot,
|
|
53
|
+
match: { brandProfileId: profileId, slot },
|
|
54
|
+
asset: assets.find((candidate) => candidate.slot === slot),
|
|
55
|
+
})), [profileId, assets]);
|
|
56
|
+
const bannerAsset = useMemo(() => assets.find((asset) => asset.slot === "banner") ?? null, [assets]);
|
|
57
|
+
const fontAsset = useMemo(() => assets.find((asset) => asset.slot === "font") ?? null, [assets]);
|
|
58
|
+
const scrapedBanner = visual?.bannerImage?.value ?? null;
|
|
59
|
+
const fontFamilyName = resolveBrandFontFamily(visual);
|
|
60
|
+
const track = useCallback(async (kind, key, run) => {
|
|
61
|
+
const call = ++latestCall.current[kind];
|
|
62
|
+
setPending((current) => ({ ...current, [kind]: key }));
|
|
63
|
+
try {
|
|
64
|
+
return await run();
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (latestCall.current[kind] === call)
|
|
68
|
+
setPending((current) => ({ ...current, [kind]: null }));
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
// The write already succeeded when this runs: a throwing host refresh must
|
|
72
|
+
// never be reported as an upload/remove failure.
|
|
73
|
+
const notifyChanged = useCallback(() => {
|
|
74
|
+
try {
|
|
75
|
+
onAssetsChanged?.();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error("[brand-assets] onAssetsChanged threw after a successful write", error);
|
|
79
|
+
}
|
|
80
|
+
}, [onAssetsChanged]);
|
|
81
|
+
const notify = useCallback((tone, message) => onNotify?.({ tone, message }), [onNotify]);
|
|
82
|
+
const uploadErrorMessage = (error) => {
|
|
83
|
+
const code = error?.code;
|
|
84
|
+
if (code === "BRAND_ASSET_UPLOAD_TIMEOUT")
|
|
85
|
+
return copy.uploadTimeout;
|
|
86
|
+
if (code === "BRAND_ASSET_UPLOAD_NETWORK")
|
|
87
|
+
return copy.uploadNetwork;
|
|
88
|
+
return copy.uploadFailed;
|
|
89
|
+
};
|
|
90
|
+
const upload = useCallback(async (slot, file, handlers) => {
|
|
91
|
+
// CEL-1681: captured BEFORE the request goes out. The backend has an
|
|
92
|
+
// inline-verdict path, so a verdict can arrive while the response is
|
|
93
|
+
// still in flight; threading this into `markChecking` stops it being
|
|
94
|
+
// rejected as pre-existing.
|
|
95
|
+
// Fonts are not moderated: like producer, no check operation for them.
|
|
96
|
+
const startSeq = slot === "font" ? 0 : beginOperation();
|
|
97
|
+
try {
|
|
98
|
+
const asset = await track("upload", slot, () => api.upload({ profileId, slot, file }));
|
|
99
|
+
if (slot !== "font")
|
|
100
|
+
markChecking({ brandProfileId: profileId, slot }, asset?.id, startSeq);
|
|
101
|
+
notifyChanged();
|
|
102
|
+
handlers.onSuccess(asset);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
handlers.onError(error);
|
|
106
|
+
}
|
|
107
|
+
}, [api, beginOperation, markChecking, notifyChanged, profileId, track]);
|
|
108
|
+
const handleUpload = (slot, file) => {
|
|
109
|
+
setSlotErrors((current) => {
|
|
110
|
+
if (current[slot] === undefined)
|
|
111
|
+
return current;
|
|
112
|
+
const next = { ...current };
|
|
113
|
+
delete next[slot];
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
void upload(slot, file, {
|
|
117
|
+
onSuccess: () => notify("success", copy.logoUploaded),
|
|
118
|
+
onError: (error) => setSlotErrors((current) => ({ ...current, [slot]: uploadErrorMessage(error) })),
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const clearSlot = useCallback(async (slot, assetId) => {
|
|
122
|
+
await track("clear", slot, async () => {
|
|
123
|
+
if (api.clearScanPageSlot) {
|
|
124
|
+
await api.clearScanPageSlot({ slot, assetId });
|
|
125
|
+
}
|
|
126
|
+
else if (assetId) {
|
|
127
|
+
await api.remove(profileId, assetId);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Nothing the host lets us clear: fail rather than report a false success.
|
|
131
|
+
throw new Error("clearScanPageSlot is required to clear a scraped-only value");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
notifyChanged();
|
|
135
|
+
}, [api, notifyChanged, profileId, track]);
|
|
136
|
+
const handleDelete = (slot, assetId) => {
|
|
137
|
+
const run = slot === "favicon" ? clearSlot("favicon", assetId) : removeAsset(assetId);
|
|
138
|
+
run.then(() => notify("success", copy.logoRemoved), () => notify("error", copy.logoRemoveFailed));
|
|
139
|
+
};
|
|
140
|
+
function removeAsset(assetId) {
|
|
141
|
+
return track("remove", assetId, () => api.remove(profileId, assetId)).then(notifyChanged);
|
|
142
|
+
}
|
|
143
|
+
const handleBannerClear = () => {
|
|
144
|
+
setBannerError(null);
|
|
145
|
+
clearSlot("banner", bannerAsset?.id ?? null).then(() => notify("success", copy.bannerRemoved), () => setBannerError(copy.bannerRemoveFailed));
|
|
146
|
+
};
|
|
147
|
+
const handleFontClear = () => {
|
|
148
|
+
if (!fontAsset)
|
|
149
|
+
return;
|
|
150
|
+
setFontError(null);
|
|
151
|
+
removeAsset(fontAsset.id).then(() => notify("success", copy.fontRemoved), () => setFontError(copy.fontRemoveFailed));
|
|
152
|
+
};
|
|
153
|
+
const uploading = {};
|
|
154
|
+
const uploadingSlot = pending.upload;
|
|
155
|
+
for (const slot of BRAND_LOGO_SLOTS)
|
|
156
|
+
if (uploadingSlot === slot)
|
|
157
|
+
uploading[slot] = true;
|
|
158
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6", className), "data-testid": "brand-assets-section", children: [_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-foreground", children: copy.assetsTitle }), _jsx("p", { className: "text-sm text-muted-foreground", children: copy.assetsSubtitle })] }), _jsx("div", { ref: logoSectionRef, "data-testid": "brand-logo-slots", children: _jsx(BrandAssetUploader, { assets: assets, slots: BRAND_LOGO_SLOTS, allowedImageOrigins: allowedImageOrigins, onUpload: handleUpload, onDelete: handleDelete, uploading: uploading, slotErrors: slotErrors, copy: copy.uploader }) }), _jsx("div", { className: "flex flex-col gap-3", children: logoSubjects.map(({ slot, match, asset }) => (_jsx(BrandAssetModerationNotice, { profileId: profileId, asset: asset, fetchModeration: api.fetchModeration, match: match, pendingHint: copy.logoPendingHint, onReupload: () => focusUploadControl(logoSectionRef.current), copy: copy.moderation, requestReview: requestModerationReview }, slot))) })] }), showScanPageBanner ? (_jsxs("div", { ref: bannerSectionRef, className: "flex flex-col gap-3", children: [_jsx(ScanPageBanner, { asset: bannerAsset, scrapedUrl: scrapedBanner, copy: copy.scanBanner, allowedImageOrigins: allowedImageOrigins, canClear: Boolean(api.clearScanPageSlot) || bannerAsset !== null, onUpload: (file) => {
|
|
159
|
+
setBannerError(null);
|
|
160
|
+
void upload("banner", file, {
|
|
161
|
+
onSuccess: () => notify("success", copy.bannerUploaded),
|
|
162
|
+
onError: (error) => setBannerError(uploadErrorMessage(error)),
|
|
163
|
+
});
|
|
164
|
+
}, onClear: handleBannerClear, uploading: pending.upload === "banner", clearing: pending.clear === "banner", error: bannerError }), _jsx(BrandAssetModerationNotice, { profileId: profileId, asset: bannerAsset ?? undefined, fetchModeration: api.fetchModeration, match: bannerSubject, pendingHint: copy.bannerPendingHint, onReupload: () => focusUploadControl(bannerSectionRef.current), copy: copy.moderation, requestReview: requestModerationReview })] })) : null, _jsx(BrandFontUpload, { asset: fontAsset, familyName: fontFamilyName, copy: copy.font, onUpload: (file) => {
|
|
165
|
+
setFontError(null);
|
|
166
|
+
void upload("font", file, {
|
|
167
|
+
onSuccess: () => notify("success", copy.fontUploaded),
|
|
168
|
+
onError: (error) => setFontError(uploadErrorMessage(error)),
|
|
169
|
+
});
|
|
170
|
+
}, onClear: handleFontClear, uploading: pending.upload === "font", clearing: Boolean(fontAsset && pending.remove === fontAsset.id), error: fontError })] }));
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=brand-assets-section.js.map
|