@burdenoff/microfe-coolandlovely 2026.1031.1 → 2026.1031.2
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/coolandlovely/CoolAndLovelyRoutes.d.ts.map +1 -1
- package/dist/coolandlovely/CoolAndLovelyRoutes.js +42 -38
- package/dist/coolandlovely/CoolAndLovelyRoutes.js.map +1 -1
- package/dist/coolandlovely/components/Field.d.ts +3 -1
- package/dist/coolandlovely/components/Field.d.ts.map +1 -1
- package/dist/coolandlovely/components/Field.js +5 -4
- package/dist/coolandlovely/components/Field.js.map +1 -1
- package/dist/coolandlovely/hooks/useCatalogImport.d.ts +25 -0
- package/dist/coolandlovely/hooks/useCatalogImport.d.ts.map +1 -0
- package/dist/coolandlovely/hooks/useCatalogImport.js +113 -0
- package/dist/coolandlovely/hooks/useCatalogImport.js.map +1 -0
- package/dist/coolandlovely/hooks/useFiles.js +6 -3
- package/dist/coolandlovely/hooks/useFiles.js.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.d.ts.map +1 -1
- package/dist/coolandlovely/pages/brands/BrandsListPage.js +72 -60
- package/dist/coolandlovely/pages/brands/BrandsListPage.js.map +1 -1
- package/dist/coolandlovely/pages/brands/ImportCatalogPage.d.ts +17 -0
- package/dist/coolandlovely/pages/brands/ImportCatalogPage.d.ts.map +1 -0
- package/dist/coolandlovely/pages/brands/ImportCatalogPage.js +417 -0
- package/dist/coolandlovely/pages/brands/ImportCatalogPage.js.map +1 -0
- package/dist/coolandlovely/types/index.d.ts +80 -0
- package/dist/coolandlovely/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/coolandlovely/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — image upload + display via wspace-files-svc (NO duplication).\n * @module coolandlovely/hooks\n *\n * CoolAndLovely never stores blobs. Brand logos/covers, collection covers,\n * style-item photos, look photos, wardrobe photos and lookbook covers are\n * wspace-files `File` records referenced by id (the coo* inputs take *FileId;\n * the coo* types expose logo/cover/image: File resolved via federation).\n *\n * Upload uses the SAME graphqlFetch transport as every other coo* call — NOT\n * Apollo — driving the wspace-files 3-step presigned flow directly:\n * ensureFolderByPath -> initiateUpload -> HTTP PUT -> completeUpload\n * Display resolves a File id to a short-lived presigned URL via\n * getFileDownloadUrl.\n *\n * Two fleet gotchas are handled here:\n * (a) `fileSize` is REQUIRED on completeUpload even though this MFE's generated\n * `CompleteUploadInput` is stale and omits it. Because we send a hand-written\n * query with `Record<string, unknown>` variables (not typed against the\n * generated input) we simply include it and it works at runtime.\n * (b) `getFileDownloadUrl(expiresIn)` must be <= 604800 (S3 7-day cap → 500).\n * We use 21600 (6h); do NOT raise it.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql/fetch';\n\nimport { useCoolAndLovely } from '../context';\n\n/**\n * 6 hours. getFileDownloadUrl rejects TTLs > 604800 (S3 7-day cap → 500); the\n * safe fleet ceiling is 21600. Do NOT raise this.\n */\nconst DOWNLOAD_URL_TTL_SECONDS = 21_600;\n\n/** Per-entity folders in wspace-files. */\nexport const COO_FILE_FOLDERS = {\n brands: '/CoolAndLovely/Brands',\n collections: '/CoolAndLovely/Collections',\n items: '/CoolAndLovely/Items',\n looks: '/CoolAndLovely/Looks',\n wardrobe: '/CoolAndLovely/Wardrobe',\n lookbooks: '/CoolAndLovely/Lookbooks',\n} as const;\n\nexport type CooFileFolder = (typeof COO_FILE_FOLDERS)[keyof typeof COO_FILE_FOLDERS];\n\nexport interface UploadedImage {\n id: string;\n url: string;\n name: string;\n}\n\n/** Thin wrapper over the workspace gateway using the MFE's proven transport. */\nasync function filesFetch<TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Files request failed');\n }\n if (!response.data) {\n throw new Error('Files response did not include data');\n }\n // Drop fe-libs' ~15s query response cache after a mutation so a follow-up\n // getFileDownloadUrl reads fresh (mirrors coolQuery in useCoolAndLovelyApi).\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlFetchResponseCache();\n }\n return response.data;\n}\n\nconst ENSURE_FOLDER = `\n mutation CooEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }\n`;\n\nconst INITIATE_UPLOAD = `\n mutation CooInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD = `\n mutation CooCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = `\n query CooGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nfunction fileExtension(name: string, mimeType: string): string {\n const dot = name.lastIndexOf('.');\n if (dot > -1 && dot < name.length - 1) return name.slice(dot + 1).toLowerCase();\n return (mimeType.split('/')[1] ?? 'bin').toLowerCase();\n}\n\n/** Upload progress emitted by {@link uploadImageToFilesWithProgress}. */\nexport interface UploadProgress {\n /** Bytes transferred so far. */\n loaded: number;\n /** Total bytes to transfer. */\n total: number;\n /** Rounded percentage complete, 0–100. */\n pct: number;\n}\n\n/**\n * PUT the bytes straight to storage via XMLHttpRequest so `upload.onprogress`\n * can surface a real progress bar (the presigned upload URL points at object\n * storage, so a plain PUT is all that is needed — no auth header). Content-Type\n * must match what was sent to `initiateUpload`.\n */\nfunction putWithProgress(\n uploadUrl: string,\n file: File,\n mimeType: string,\n onProgress?: (progress: UploadProgress) => void\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mimeType);\n if (onProgress) {\n xhr.upload.onprogress = (event: ProgressEvent) => {\n const total = event.lengthComputable ? event.total : file.size;\n const loaded = Math.min(event.loaded, total);\n const pct = total > 0 ? Math.round((loaded / total) * 100) : 0;\n onProgress({ loaded, total, pct });\n };\n }\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n onProgress?.({ loaded: file.size, total: file.size, pct: 100 });\n resolve();\n } else {\n reject(new Error(`Upload to storage failed (${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Upload to storage failed — a network error occurred.'));\n xhr.onabort = () => reject(new Error('Upload was cancelled.'));\n xhr.send(file);\n });\n}\n\n/**\n * The full presigned upload flow (ensureFolder -> initiateUpload -> PUT ->\n * completeUpload -> download URL). Shared by both public upload helpers; the\n * PUT step reports progress when `onProgress` is supplied.\n */\nasync function runUpload(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items,\n onProgress?: (progress: UploadProgress) => void\n): Promise<UploadedImage> {\n const mimeType = file.type || 'application/octet-stream';\n const fileSize = String(file.size); // BigInt scalar — serialize as string.\n\n // 1. Resolve (or create) the destination folder. Best-effort: fall back to\n // the files root (folderId is optional on both initiate + complete).\n let folderId: string | undefined;\n try {\n const folder = await filesFetch<{ ensureFolderByPath?: { id: string } | null }>(\n ENSURE_FOLDER,\n { path: folderPath },\n baseUrl\n );\n folderId = folder.ensureFolderByPath?.id ?? undefined;\n } catch {\n folderId = undefined;\n }\n\n // 2. Open a presigned upload session.\n const initiated = await filesFetch<{\n initiateUpload?: { uploadUrl?: string | null; session: { id: string } } | null;\n }>(\n INITIATE_UPLOAD,\n {\n input: {\n fileName: file.name,\n extension: fileExtension(file.name, mimeType),\n fileSize, // required, BigInt-as-string\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n baseUrl\n );\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the bytes straight to storage (XHR → progress). Content-Type must\n // match initiateUpload.\n await putWithProgress(uploadUrl, file, mimeType, onProgress);\n\n // 4. Finalize — creates the File record. fileSize is REQUIRED here even though\n // the MFE's generated CompleteUploadInput is stale and omits it.\n const completed = await filesFetch<{\n completeUpload?: { fileId: string; file: { id: string; name: string } } | null;\n }>(\n COMPLETE_UPLOAD,\n {\n input: {\n sessionId,\n fileSize, // <-- stale-type gotcha: the live schema requires this\n ...(folderId ? { folderId } : {}),\n },\n },\n baseUrl\n );\n const record = completed.completeUpload?.file;\n const newId = completed.completeUpload?.fileId ?? record?.id;\n if (!newId) {\n throw new Error('The upload could not be finalized.');\n }\n\n // 5. Best-effort presigned URL for immediate preview.\n let url = '';\n try {\n const urlData = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n { id: newId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n baseUrl\n );\n url = urlData.getFileDownloadUrl ?? '';\n } catch {\n url = '';\n }\n\n return { id: newId, url, name: record?.name ?? file.name };\n}\n\n/**\n * Upload an image to wspace-files-svc and return its id + a presigned URL. The\n * caller persists `id` (e.g. cooUpdateBrand({ logoFileId })); `url` is for an\n * immediate preview. `baseUrl` is the shell's apiGatewayUrl.\n */\nexport async function uploadImageToFiles(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items\n): Promise<UploadedImage> {\n return runUpload(file, baseUrl, folderPath);\n}\n\n/**\n * Same as {@link uploadImageToFiles} but reports transfer progress via\n * `onProgress` (XHR `upload.onprogress`) so callers can render a live progress\n * bar. `baseUrl` is the shell's apiGatewayUrl.\n */\nexport async function uploadImageToFilesWithProgress(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items,\n onProgress?: (progress: UploadProgress) => void\n): Promise<UploadedImage> {\n return runUpload(file, baseUrl, folderPath, onProgress);\n}\n\n/** Resolve a wspace-files File id to a presigned URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useCoolAndLovely();\n return useQuery({\n queryKey: ['coolandlovely', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 20 * 60 * 1000, // refetch well inside the 6h TTL\n retry: 1,\n queryFn: async () => {\n const data = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n apiGatewayUrl\n );\n return data.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/coolandlovely/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — image upload + display via wspace-files-svc (NO duplication).\n * @module coolandlovely/hooks\n *\n * CoolAndLovely never stores blobs. Brand logos/covers, collection covers,\n * style-item photos, look photos, wardrobe photos and lookbook covers are\n * wspace-files `File` records referenced by id (the coo* inputs take *FileId;\n * the coo* types expose logo/cover/image: File resolved via federation).\n *\n * Upload uses the SAME graphqlFetch transport as every other coo* call — NOT\n * Apollo — driving the wspace-files 3-step presigned flow directly:\n * ensureFolderByPath -> initiateUpload -> HTTP PUT -> completeUpload\n * Display resolves a File id to a short-lived presigned URL via\n * getFileDownloadUrl.\n *\n * Two fleet gotchas are handled here:\n * (a) `fileSize` is REQUIRED on completeUpload even though this MFE's generated\n * `CompleteUploadInput` is stale and omits it. Because we send a hand-written\n * query with `Record<string, unknown>` variables (not typed against the\n * generated input) we simply include it and it works at runtime.\n * (b) `getFileDownloadUrl(expiresIn)` must be <= 604800 (S3 7-day cap → 500).\n * We use 21600 (6h); do NOT raise it.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql/fetch';\n\nimport { useCoolAndLovely } from '../context';\n\n/**\n * 6 hours. getFileDownloadUrl rejects TTLs > 604800 (S3 7-day cap → 500); the\n * safe fleet ceiling is 21600. Do NOT raise this.\n */\nconst DOWNLOAD_URL_TTL_SECONDS = 21_600;\n\n/** Per-entity folders in wspace-files. */\nexport const COO_FILE_FOLDERS = {\n brands: '/CoolAndLovely/Brands',\n collections: '/CoolAndLovely/Collections',\n items: '/CoolAndLovely/Items',\n looks: '/CoolAndLovely/Looks',\n wardrobe: '/CoolAndLovely/Wardrobe',\n lookbooks: '/CoolAndLovely/Lookbooks',\n} as const;\n\nexport type CooFileFolder = (typeof COO_FILE_FOLDERS)[keyof typeof COO_FILE_FOLDERS];\n\nexport interface UploadedImage {\n id: string;\n url: string;\n name: string;\n}\n\n/** Thin wrapper over the workspace gateway using the MFE's proven transport. */\nasync function filesFetch<TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Files request failed');\n }\n if (!response.data) {\n throw new Error('Files response did not include data');\n }\n // Drop fe-libs' ~15s query response cache after a mutation so a follow-up\n // getFileDownloadUrl reads fresh (mirrors coolQuery in useCoolAndLovelyApi).\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlFetchResponseCache();\n }\n return response.data;\n}\n\nconst ENSURE_FOLDER = `\n mutation CooEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }\n`;\n\nconst INITIATE_UPLOAD = `\n mutation CooInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD = `\n mutation CooCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = `\n query CooGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nfunction fileExtension(name: string, mimeType: string): string {\n const dot = name.lastIndexOf('.');\n if (dot > -1 && dot < name.length - 1) return name.slice(dot + 1).toLowerCase();\n return (mimeType.split('/')[1] ?? 'bin').toLowerCase();\n}\n\n/** Upload progress emitted by {@link uploadImageToFilesWithProgress}. */\nexport interface UploadProgress {\n /** Bytes transferred so far. */\n loaded: number;\n /** Total bytes to transfer. */\n total: number;\n /** Rounded percentage complete, 0–100. */\n pct: number;\n}\n\n/**\n * PUT the bytes straight to storage via XMLHttpRequest so `upload.onprogress`\n * can surface a real progress bar (the presigned upload URL points at object\n * storage, so a plain PUT is all that is needed — no auth header). Content-Type\n * must match what was sent to `initiateUpload`.\n */\nfunction putWithProgress(\n uploadUrl: string,\n file: File,\n mimeType: string,\n onProgress?: (progress: UploadProgress) => void\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', uploadUrl, true);\n xhr.setRequestHeader('Content-Type', mimeType);\n if (onProgress) {\n xhr.upload.onprogress = (event: ProgressEvent) => {\n const total = event.lengthComputable ? event.total : file.size;\n const loaded = Math.min(event.loaded, total);\n const pct = total > 0 ? Math.round((loaded / total) * 100) : 0;\n onProgress({ loaded, total, pct });\n };\n }\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n onProgress?.({ loaded: file.size, total: file.size, pct: 100 });\n resolve();\n } else {\n reject(new Error(`Upload to storage failed (${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Upload to storage failed — a network error occurred.'));\n xhr.onabort = () => reject(new Error('Upload was cancelled.'));\n xhr.send(file);\n });\n}\n\n/**\n * The full presigned upload flow (ensureFolder -> initiateUpload -> PUT ->\n * completeUpload -> download URL). Shared by both public upload helpers; the\n * PUT step reports progress when `onProgress` is supplied.\n */\nasync function runUpload(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items,\n onProgress?: (progress: UploadProgress) => void\n): Promise<UploadedImage> {\n const mimeType = file.type || 'application/octet-stream';\n const fileSize = String(file.size); // BigInt scalar — serialize as string.\n\n // 1. Resolve (or create) the destination folder. Best-effort: fall back to\n // the files root (folderId is optional on both initiate + complete).\n let folderId: string | undefined;\n try {\n const folder = await filesFetch<{ ensureFolderByPath?: { id: string } | null }>(\n ENSURE_FOLDER,\n { path: folderPath },\n baseUrl\n );\n folderId = folder.ensureFolderByPath?.id ?? undefined;\n } catch {\n folderId = undefined;\n }\n\n // 2. Open a presigned upload session.\n const initiated = await filesFetch<{\n initiateUpload?: { uploadUrl?: string | null; session: { id: string } } | null;\n }>(\n INITIATE_UPLOAD,\n {\n input: {\n fileName: file.name,\n extension: fileExtension(file.name, mimeType),\n fileSize, // required, BigInt-as-string\n mimeType,\n ...(folderId ? { folderId } : {}),\n },\n },\n baseUrl\n );\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the bytes straight to storage (XHR → progress). Content-Type must\n // match initiateUpload.\n await putWithProgress(uploadUrl, file, mimeType, onProgress);\n\n // 4. Finalize — creates the File record. fileSize is REQUIRED here even though\n // the MFE's generated CompleteUploadInput is stale and omits it.\n const completed = await filesFetch<{\n completeUpload?: { fileId: string; file: { id: string; name: string } } | null;\n }>(\n COMPLETE_UPLOAD,\n {\n input: {\n sessionId,\n fileSize, // <-- stale-type gotcha: the live schema requires this\n ...(folderId ? { folderId } : {}),\n },\n },\n baseUrl\n );\n const record = completed.completeUpload?.file;\n const newId = completed.completeUpload?.fileId ?? record?.id;\n if (!newId) {\n throw new Error('The upload could not be finalized.');\n }\n\n // 5. Best-effort presigned URL for immediate preview.\n let url = '';\n try {\n const urlData = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n { id: newId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n baseUrl\n );\n url = urlData.getFileDownloadUrl ?? '';\n } catch {\n url = '';\n }\n\n return { id: newId, url, name: record?.name ?? file.name };\n}\n\n/**\n * Upload an image to wspace-files-svc and return its id + a presigned URL. The\n * caller persists `id` (e.g. cooUpdateBrand({ logoFileId })); `url` is for an\n * immediate preview. `baseUrl` is the shell's apiGatewayUrl.\n */\nexport async function uploadImageToFiles(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items\n): Promise<UploadedImage> {\n return runUpload(file, baseUrl, folderPath);\n}\n\n/**\n * Same as {@link uploadImageToFiles} but reports transfer progress via\n * `onProgress` (XHR `upload.onprogress`) so callers can render a live progress\n * bar. `baseUrl` is the shell's apiGatewayUrl.\n */\nexport async function uploadImageToFilesWithProgress(\n file: File,\n baseUrl?: string,\n folderPath: string = COO_FILE_FOLDERS.items,\n onProgress?: (progress: UploadProgress) => void\n): Promise<UploadedImage> {\n return runUpload(file, baseUrl, folderPath, onProgress);\n}\n\n/** Resolve a wspace-files File id to a presigned URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useCoolAndLovely();\n return useQuery({\n queryKey: ['coolandlovely', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 20 * 60 * 1000, // refetch well inside the 6h TTL\n retry: 1,\n queryFn: async () => {\n const data = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n apiGatewayUrl\n );\n return data.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":";;;;;;AAkCA,IAAM,IAA2B,OAGpB,IAAmB;CAC9B,QAAQ;CACR,aAAa;CACb,OAAO;CACP,OAAO;CACP,UAAU;CACV,WAAW;CACZ;AAWD,eAAe,EACb,GACA,GACA,GACgB;CAChB,IAAM,IAAW,MAAM,EAAoB;EACzC,SAAS;EACT;EACA;EACA;EACA,gBAAgB;EACjB,CAAC;AACF,KAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,uBAAuB;AAExE,KAAI,CAAC,EAAS,KACZ,OAAU,MAAM,sCAAsC;AAOxD,QAHI,iBAAiB,KAAK,EAAM,IAC9B,GAAgC,EAE3B,EAAS;;AAGlB,IAAM,IAAgB,qGAMhB,IAAkB,4JASlB,IAAkB,2JASlB,IAAwB;AAM9B,SAAS,EAAc,GAAc,GAA0B;CAC7D,IAAM,IAAM,EAAK,YAAY,IAAI;AAEjC,QADI,IAAM,MAAM,IAAM,EAAK,SAAS,IAAU,EAAK,MAAM,IAAM,EAAE,CAAC,aAAa,IACvE,EAAS,MAAM,IAAI,CAAC,MAAM,OAAO,aAAa;;AAmBxD,SAAS,EACP,GACA,GACA,GACA,GACe;AACf,QAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,IAAM,IAAI,gBAAgB;AAqBhC,EApBA,EAAI,KAAK,OAAO,GAAW,GAAK,EAChC,EAAI,iBAAiB,gBAAgB,EAAS,EAC1C,MACF,EAAI,OAAO,cAAc,MAAyB;GAChD,IAAM,IAAQ,EAAM,mBAAmB,EAAM,QAAQ,EAAK,MACpD,IAAS,KAAK,IAAI,EAAM,QAAQ,EAAM;AAE5C,KAAW;IAAE;IAAQ;IAAO,KADhB,IAAQ,IAAI,KAAK,MAAO,IAAS,IAAS,IAAI,GAAG;IAC5B,CAAC;MAGtC,EAAI,eAAe;AACjB,GAAI,EAAI,UAAU,OAAO,EAAI,SAAS,OACpC,IAAa;IAAE,QAAQ,EAAK;IAAM,OAAO,EAAK;IAAM,KAAK;IAAK,CAAC,EAC/D,GAAS,IAET,EAAO,gBAAI,MAAM,6BAA6B,EAAI,OAAO,IAAI,CAAC;KAGlE,EAAI,gBAAgB,EAAO,gBAAI,MAAM,uDAAuD,CAAC,EAC7F,EAAI,gBAAgB,EAAO,gBAAI,MAAM,wBAAwB,CAAC,EAC9D,EAAI,KAAK,EAAK;GACd;;AAQJ,eAAe,EACb,GACA,GACA,IAAqB,EAAiB,OACtC,GACwB;CACxB,IAAM,IAAW,EAAK,QAAQ,4BACxB,IAAW,OAAO,EAAK,KAAK,EAI9B;AACJ,KAAI;AAMF,OALe,MAAM,EACnB,GACA,EAAE,MAAM,GAAY,EACpB,EACD,EACiB,oBAAoB,MAAM,KAAA;SACtC;AACN,MAAW,KAAA;;CAIb,IAAM,IAAY,MAAM,EAGtB,GACA,EACE,OAAO;EACL,UAAU,EAAK;EACf,WAAW,EAAc,EAAK,MAAM,EAAS;EAC7C;EACA;EACA,GAAI,IAAW,EAAE,aAAU,GAAG,EAAE;EACjC,EACF,EACD,EACD,EACK,IAAY,EAAU,gBAAgB,WACtC,IAAY,EAAU,gBAAgB,QAAQ;AACpD,KAAI,CAAC,KAAa,CAAC,EACjB,OAAU,MAAM,kDAAkD;AAKpE,OAAM,EAAgB,GAAW,GAAM,GAAU,EAAW;CAI5D,IAAM,IAAY,MAAM,EAGtB,GACA,EACE,OAAO;EACL;EACA;EACA,GAAI,IAAW,EAAE,aAAU,GAAG,EAAE;EACjC,EACF,EACD,EACD,EACK,IAAS,EAAU,gBAAgB,MACnC,IAAQ,EAAU,gBAAgB,UAAU,GAAQ;AAC1D,KAAI,CAAC,EACH,OAAU,MAAM,qCAAqC;CAIvD,IAAI,IAAM;AACV,KAAI;AAMF,OALgB,MAAM,EACpB,GACA;GAAE,IAAI;GAAO,WAAW;GAA0B,EAClD,EACD,EACa,sBAAsB;SAC9B;AACN,MAAM;;AAGR,QAAO;EAAE,IAAI;EAAO;EAAK,MAAM,GAAQ,QAAQ,EAAK;EAAM;;AAQ5D,eAAsB,EACpB,GACA,GACA,IAAqB,EAAiB,OACd;AACxB,QAAO,EAAU,GAAM,GAAS,EAAW;;AAQ7C,eAAsB,EACpB,GACA,GACA,IAAqB,EAAiB,OACtC,GACwB;AACxB,QAAO,EAAU,GAAM,GAAS,GAAY,EAAW;;AAIzD,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,qBAAkB,GAAkB;AAC5C,QAAO,EAAS;EACd,UAAU;GAAC;GAAiB;GAAY,KAAU;GAAG;EACrD,SAAS,EAAQ;EACjB,WAAW,OAAU;EACrB,OAAO;EACP,SAAS,aACM,MAAM,EACjB,GACA;GAAE,IAAI;GAAQ,WAAW;GAA0B,EACnD,EACD,EACW,sBAAsB;EAErC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrandsListPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/brands/BrandsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,wBAAgB,cAAc,
|
|
1
|
+
{"version":3,"file":"BrandsListPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/brands/BrandsListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,wBAAgB,cAAc,gCAiK7B"}
|
|
@@ -8,113 +8,125 @@ import { ErrorView as a, LoadingView as o } from "../../components/StateViews.js
|
|
|
8
8
|
import { EmptyState as s } from "../../components/EmptyState.js";
|
|
9
9
|
import { BrandCard as c } from "../../components/BrandCard.js";
|
|
10
10
|
import { useMemo as l, useState as u } from "react";
|
|
11
|
-
import { ChevronLeft as d, ChevronRight as f,
|
|
12
|
-
import { Fragment as
|
|
11
|
+
import { ChevronLeft as d, ChevronRight as f, Download as p, Plus as m, Star as h, Store as g } from "lucide-react";
|
|
12
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
13
13
|
//#region src/coolandlovely/pages/brands/BrandsListPage.tsx
|
|
14
|
-
var
|
|
14
|
+
var b = 24, x = [
|
|
15
15
|
"PUBLISHED",
|
|
16
16
|
"DRAFT",
|
|
17
17
|
"PENDING_REVIEW",
|
|
18
18
|
"SUSPENDED",
|
|
19
19
|
"ARCHIVED"
|
|
20
20
|
];
|
|
21
|
-
function
|
|
22
|
-
let { navigateTo:
|
|
21
|
+
function S() {
|
|
22
|
+
let { navigateTo: S } = e(), C = i(), [w, T] = u("ALL"), [E, D] = u(!1), [O, k] = u(0), A = l(() => [{
|
|
23
23
|
value: "ALL",
|
|
24
|
-
label:
|
|
25
|
-
}, ...
|
|
24
|
+
label: C("coolandlovely.filters.all", "All")
|
|
25
|
+
}, ...x.map((e) => ({
|
|
26
26
|
value: e,
|
|
27
|
-
label:
|
|
28
|
-
}))], [
|
|
29
|
-
status:
|
|
30
|
-
featured:
|
|
27
|
+
label: C(`coolandlovely.status.${e.toLowerCase()}`, e.replace(/_/g, " ").replace(/\b\w/g, (e) => e.toUpperCase()))
|
|
28
|
+
}))], [C]), j = r({
|
|
29
|
+
status: w === "ALL" ? void 0 : w,
|
|
30
|
+
featured: E ? !0 : void 0,
|
|
31
31
|
pagination: {
|
|
32
|
-
limit:
|
|
33
|
-
offset:
|
|
32
|
+
limit: b,
|
|
33
|
+
offset: O
|
|
34
34
|
}
|
|
35
|
-
}),
|
|
36
|
-
return /* @__PURE__ */
|
|
37
|
-
title:
|
|
38
|
-
description:
|
|
39
|
-
actions: /* @__PURE__ */
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
}), M = j.data?.items ?? [], N = j.data?.pageInfo.hasMore ?? !1;
|
|
36
|
+
return /* @__PURE__ */ y(n, {
|
|
37
|
+
title: C("coolandlovely.page.brandsTitle", "Brands"),
|
|
38
|
+
description: C("coolandlovely.page.brandsSubtitle", "Storefronts on the platform with a real publish lifecycle"),
|
|
39
|
+
actions: /* @__PURE__ */ y("div", {
|
|
40
|
+
className: "flex flex-wrap gap-2",
|
|
41
|
+
children: [/* @__PURE__ */ y("button", {
|
|
42
|
+
type: "button",
|
|
43
|
+
onClick: () => S("/brands/import"),
|
|
44
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ v(p, { className: "h-4 w-4" }),
|
|
47
|
+
" ",
|
|
48
|
+
C("coolandlovely.actions.importCatalog", "Import catalog")
|
|
49
|
+
]
|
|
50
|
+
}), /* @__PURE__ */ y("button", {
|
|
51
|
+
type: "button",
|
|
52
|
+
onClick: () => S("/brands/new"),
|
|
53
|
+
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ v(m, { className: "h-4 w-4" }),
|
|
56
|
+
" ",
|
|
57
|
+
C("coolandlovely.actions.newBrand", "New brand")
|
|
58
|
+
]
|
|
59
|
+
})]
|
|
48
60
|
}),
|
|
49
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ y("div", {
|
|
50
62
|
className: "mb-6 flex flex-wrap items-center gap-2",
|
|
51
|
-
children: [
|
|
63
|
+
children: [A.map((e) => /* @__PURE__ */ v("button", {
|
|
52
64
|
type: "button",
|
|
53
65
|
onClick: () => {
|
|
54
|
-
|
|
66
|
+
T(e.value), k(0);
|
|
55
67
|
},
|
|
56
|
-
className: t("rounded-full px-3 py-1.5 text-sm font-medium transition-colors",
|
|
68
|
+
className: t("rounded-full px-3 py-1.5 text-sm font-medium transition-colors", w === e.value ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
57
69
|
children: e.label
|
|
58
|
-
}, e.value)), /* @__PURE__ */
|
|
70
|
+
}, e.value)), /* @__PURE__ */ y("button", {
|
|
59
71
|
type: "button",
|
|
60
72
|
onClick: () => {
|
|
61
|
-
|
|
73
|
+
D((e) => !e), k(0);
|
|
62
74
|
},
|
|
63
|
-
className: t("ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors",
|
|
75
|
+
className: t("ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors", E ? "bg-status-warning-bg-subtle text-status-warning-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
64
76
|
children: [
|
|
65
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ v(h, { className: t("h-3.5 w-3.5", E && "fill-status-warning-text") }),
|
|
66
78
|
" ",
|
|
67
|
-
|
|
79
|
+
C("coolandlovely.filters.featured", "Featured")
|
|
68
80
|
]
|
|
69
81
|
})]
|
|
70
|
-
}),
|
|
82
|
+
}), j.isLoading ? /* @__PURE__ */ v(o, { label: C("coolandlovely.loading.brands", "Loading brands…") }) : j.isError ? /* @__PURE__ */ v(a, { onRetry: () => void j.refetch() }) : M.length === 0 ? /* @__PURE__ */ v(s, {
|
|
71
83
|
illustration: "empty-data",
|
|
72
|
-
icon: /* @__PURE__ */
|
|
73
|
-
title:
|
|
74
|
-
description:
|
|
75
|
-
action: /* @__PURE__ */
|
|
84
|
+
icon: /* @__PURE__ */ v(g, { className: "h-10 w-10" }),
|
|
85
|
+
title: C("coolandlovely.empty.brandsTitle", "No brands yet"),
|
|
86
|
+
description: C("coolandlovely.empty.brandsDescription", "Launch the first storefront to start building the catalogue."),
|
|
87
|
+
action: /* @__PURE__ */ y("button", {
|
|
76
88
|
type: "button",
|
|
77
|
-
onClick: () =>
|
|
89
|
+
onClick: () => S("/brands/new"),
|
|
78
90
|
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text",
|
|
79
91
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ v(m, { className: "h-4 w-4" }),
|
|
81
93
|
" ",
|
|
82
|
-
|
|
94
|
+
C("coolandlovely.actions.newBrand", "New brand")
|
|
83
95
|
]
|
|
84
96
|
})
|
|
85
|
-
}) : /* @__PURE__ */
|
|
97
|
+
}) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
|
|
86
98
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
87
|
-
children:
|
|
99
|
+
children: M.map((e) => /* @__PURE__ */ v(c, {
|
|
88
100
|
brand: e,
|
|
89
|
-
onClick: () =>
|
|
101
|
+
onClick: () => S(`/brands/${e.id}`)
|
|
90
102
|
}, e.id))
|
|
91
|
-
}), (
|
|
103
|
+
}), (O > 0 || N) && /* @__PURE__ */ y("div", {
|
|
92
104
|
className: "mt-6 flex items-center justify-center gap-2",
|
|
93
105
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
106
|
+
/* @__PURE__ */ y("button", {
|
|
95
107
|
type: "button",
|
|
96
|
-
disabled:
|
|
97
|
-
onClick: () =>
|
|
108
|
+
disabled: O === 0,
|
|
109
|
+
onClick: () => k((e) => Math.max(0, e - b)),
|
|
98
110
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40",
|
|
99
111
|
children: [
|
|
100
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ v(d, { className: "h-4 w-4" }),
|
|
101
113
|
" ",
|
|
102
|
-
|
|
114
|
+
C("coolandlovely.pagination.previous", "Previous")
|
|
103
115
|
]
|
|
104
116
|
}),
|
|
105
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ v("span", {
|
|
106
118
|
className: "text-sm text-text-secondary",
|
|
107
|
-
children:
|
|
119
|
+
children: C("coolandlovely.pagination.page", "Page {{page}}", { page: O / b + 1 })
|
|
108
120
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ y("button", {
|
|
110
122
|
type: "button",
|
|
111
|
-
disabled: !
|
|
112
|
-
onClick: () =>
|
|
123
|
+
disabled: !N,
|
|
124
|
+
onClick: () => k((e) => e + b),
|
|
113
125
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40",
|
|
114
126
|
children: [
|
|
115
|
-
|
|
127
|
+
C("coolandlovely.pagination.next", "Next"),
|
|
116
128
|
" ",
|
|
117
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ v(f, { className: "h-4 w-4" })
|
|
118
130
|
]
|
|
119
131
|
})
|
|
120
132
|
]
|
|
@@ -122,6 +134,6 @@ function x() {
|
|
|
122
134
|
});
|
|
123
135
|
}
|
|
124
136
|
//#endregion
|
|
125
|
-
export {
|
|
137
|
+
export { S as BrandsListPage };
|
|
126
138
|
|
|
127
139
|
//# sourceMappingURL=BrandsListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrandsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandsListPage.tsx"],"sourcesContent":["/**\n * Brands List Page\n * @module coolandlovely/pages/brands\n *\n * Lists brand storefronts (cursor-paginated `cooListBrands`) with status &\n * featured filters. Each card links to the brand detail page.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Store, Plus, ChevronLeft, ChevronRight, Star } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useTr } from '../../hooks';\nimport { BrandCard } from '../../components/BrandCard';\nimport { cn } from '../../utils/cn';\nimport type { CooBrandStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst STATUS_ORDER: CooBrandStatus[] = [\n 'PUBLISHED',\n 'DRAFT',\n 'PENDING_REVIEW',\n 'SUSPENDED',\n 'ARCHIVED',\n];\n\nexport function BrandsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooBrandStatus | 'ALL'>('ALL');\n const [featuredOnly, setFeaturedOnly] = useState(false);\n const [offset, setOffset] = useState(0);\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(\n `coolandlovely.status.${value.toLowerCase()}`,\n value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n ),\n })),\n ],\n [tr]\n );\n\n const query = useBrands({\n status: status === 'ALL' ? undefined : status,\n featured: featuredOnly ? true : undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const brands = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.brandsTitle', 'Brands')}\n description={tr(\n 'coolandlovely.page.brandsSubtitle',\n 'Storefronts on the platform with a real publish lifecycle'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newBrand', 'New brand')}\n </button>\n }\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => {\n setStatus(opt.value);\n setOffset(0);\n }}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => {\n setFeaturedOnly((v) => !v);\n setOffset(0);\n }}\n className={cn(\n 'ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n featuredOnly\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n <Star className={cn('h-3.5 w-3.5', featuredOnly && 'fill-status-warning-text')} />{' '}\n {tr('coolandlovely.filters.featured', 'Featured')}\n </button>\n </div>\n\n {query.isLoading ? (\n <LoadingView label={tr('coolandlovely.loading.brands', 'Loading brands…')} />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : brands.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Store className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.brandsTitle', 'No brands yet')}\n description={tr(\n 'coolandlovely.empty.brandsDescription',\n 'Launch the first storefront to start building the catalogue.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newBrand', 'New brand')}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {brands.map((brand) => (\n <BrandCard\n key={brand.id}\n brand={brand}\n onClick={() => navigateTo(`/brands/${brand.id}`)}\n />\n ))}\n </div>\n\n {/* Pagination */}\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.pagination.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.pagination.page', 'Page {{page}}', {\n page: offset / PAGE_SIZE + 1,\n })}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n {tr('coolandlovely.pagination.next', 'Next')} <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,IAAM,IAAY,IAEZ,IAAiC;CACrC;CACA;CACA;CACA;CACA;CACD;AAED,SAAgB,IAAiB;CAC/B,IAAM,EAAE,kBAAe,GAAkB,EACnC,IAAK,GAAO,EACZ,CAAC,GAAQ,KAAa,EAAiC,MAAM,EAC7D,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAQ,KAAa,EAAS,EAAE,EAEjC,IAAgB,QACd,CACJ;EAAE,OAAO;EAAgB,OAAO,EAAG,6BAA6B,MAAM;EAAE,EACxE,GAAG,EAAa,KAAK,OAAW;EAC9B;EACA,OAAO,EACL,wBAAwB,EAAM,aAAa,IAC3C,EAAM,QAAQ,MAAM,IAAI,CAAC,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC,CAClE;EACF,EAAE,CACJ,EACD,CAAC,EAAG,CACL,EAEK,IAAQ,EAAU;EACtB,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC,UAAU,IAAe,KAAO,KAAA;EAChC,YAAY;GAAE,OAAO;GAAW;GAAQ;EACzC,CAAC,EAEI,IAAS,EAAM,MAAM,SAAS,EAAE,EAChC,IAAU,EAAM,MAAM,SAAS,WAAW;AAEhD,QACE,kBAAC,GAAD;EACE,OAAO,EAAG,kCAAkC,SAAS;EACrD,aAAa,EACX,qCACA,4DACD;EACD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,cAAc;GACxC,WAAU;aAHZ;IAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;;IAAE,EAAG,kCAAkC,YAAY;IACxE;;YAbb,CAiBE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAc,KAAK,MAClB,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe;AAEb,KADA,EAAU,EAAI,MAAM,EACpB,EAAU,EAAE;;IAEd,WAAW,EACT,kEACA,MAAW,EAAI,QACX,kDACA,2DACL;cAEA,EAAI;IACE,EAdF,EAAI,MAcF,CACT,EACF,kBAAC,UAAD;IACE,MAAK;IACL,eAAe;AAEb,KADA,GAAiB,MAAM,CAAC,EAAE,EAC1B,EAAU,EAAE;;IAEd,WAAW,EACT,2GACA,IACI,yDACA,2DACL;cAXH;KAaE,kBAAC,GAAD,EAAM,WAAW,EAAG,eAAe,KAAgB,2BAA2B,EAAI,CAAA;KAAC;KAClF,EAAG,kCAAkC,WAAW;KAC1C;MACL;MAEL,EAAM,YACL,kBAAC,GAAD,EAAa,OAAO,EAAG,gCAAgC,kBAAkB,EAAI,CAAA,GAC3E,EAAM,UACR,kBAAC,GAAD,EAAW,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GAChD,EAAO,WAAW,IACpB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GACrC,OAAO,EAAG,mCAAmC,gBAAgB;GAC7D,aAAa,EACX,yCACA,+DACD;GACD,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,cAAc;IACxC,WAAU;cAHZ;KAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;;KAAE,EAAG,kCAAkC,YAAY;KACxE;;GAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAO,KAAK,MACX,kBAAC,GAAD;IAES;IACP,eAAe,EAAW,WAAW,EAAM,KAAK;IAChD,EAHK,EAAM,GAGX,CACF;GACE,CAAA,GAGJ,IAAS,KAAK,MACd,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,MAAW;KACrB,eAAe,GAAW,MAAM,KAAK,IAAI,GAAG,IAAI,EAAU,CAAC;KAC3D,WAAU;eAJZ;MAME,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MAAC;MACnC,EAAG,qCAAqC,WAAW;MAC7C;;IACT,kBAAC,QAAD;KAAM,WAAU;eACb,EAAG,iCAAiC,iBAAiB,EACpD,MAAM,IAAS,IAAY,GAC5B,CAAC;KACG,CAAA;IACP,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,CAAC;KACX,eAAe,GAAW,MAAM,IAAI,EAAU;KAC9C,WAAU;eAJZ;MAMG,EAAG,iCAAiC,OAAO;MAAC;MAAC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;MAC3E;;IACL;KAEP,EAAA,CAAA,CAEM"}
|
|
1
|
+
{"version":3,"file":"BrandsListPage.js","names":[],"sources":["../../../../src/coolandlovely/pages/brands/BrandsListPage.tsx"],"sourcesContent":["/**\n * Brands List Page\n * @module coolandlovely/pages/brands\n *\n * Lists brand storefronts (cursor-paginated `cooListBrands`) with status &\n * featured filters. Each card links to the brand detail page.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Store, Plus, ChevronLeft, ChevronRight, Star, Download } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useCoolAndLovely } from '../../context/CoolAndLovelyContext';\nimport { useBrands, useTr } from '../../hooks';\nimport { BrandCard } from '../../components/BrandCard';\nimport { cn } from '../../utils/cn';\nimport type { CooBrandStatus } from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst STATUS_ORDER: CooBrandStatus[] = [\n 'PUBLISHED',\n 'DRAFT',\n 'PENDING_REVIEW',\n 'SUSPENDED',\n 'ARCHIVED',\n];\n\nexport function BrandsListPage() {\n const { navigateTo } = useCoolAndLovely();\n const tr = useTr();\n const [status, setStatus] = useState<CooBrandStatus | 'ALL'>('ALL');\n const [featuredOnly, setFeaturedOnly] = useState(false);\n const [offset, setOffset] = useState(0);\n\n const statusOptions = useMemo(\n () => [\n { value: 'ALL' as const, label: tr('coolandlovely.filters.all', 'All') },\n ...STATUS_ORDER.map((value) => ({\n value,\n label: tr(\n `coolandlovely.status.${value.toLowerCase()}`,\n value.replace(/_/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase())\n ),\n })),\n ],\n [tr]\n );\n\n const query = useBrands({\n status: status === 'ALL' ? undefined : status,\n featured: featuredOnly ? true : undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const brands = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title={tr('coolandlovely.page.brandsTitle', 'Brands')}\n description={tr(\n 'coolandlovely.page.brandsSubtitle',\n 'Storefronts on the platform with a real publish lifecycle'\n )}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands/import')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.actions.importCatalog', 'Import catalog')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newBrand', 'New brand')}\n </button>\n </div>\n }\n >\n {/* Filters */}\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">\n {statusOptions.map((opt) => (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() => {\n setStatus(opt.value);\n setOffset(0);\n }}\n className={cn(\n 'rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n status === opt.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {opt.label}\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => {\n setFeaturedOnly((v) => !v);\n setOffset(0);\n }}\n className={cn(\n 'ml-auto inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm font-medium transition-colors',\n featuredOnly\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n <Star className={cn('h-3.5 w-3.5', featuredOnly && 'fill-status-warning-text')} />{' '}\n {tr('coolandlovely.filters.featured', 'Featured')}\n </button>\n </div>\n\n {query.isLoading ? (\n <LoadingView label={tr('coolandlovely.loading.brands', 'Loading brands…')} />\n ) : query.isError ? (\n <ErrorView onRetry={() => void query.refetch()} />\n ) : brands.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Store className=\"h-10 w-10\" />}\n title={tr('coolandlovely.empty.brandsTitle', 'No brands yet')}\n description={tr(\n 'coolandlovely.empty.brandsDescription',\n 'Launch the first storefront to start building the catalogue.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/brands/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('coolandlovely.actions.newBrand', 'New brand')}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {brands.map((brand) => (\n <BrandCard\n key={brand.id}\n brand={brand}\n onClick={() => navigateTo(`/brands/${brand.id}`)}\n />\n ))}\n </div>\n\n {/* Pagination */}\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-center gap-2\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n <ChevronLeft className=\"h-4 w-4\" />{' '}\n {tr('coolandlovely.pagination.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {tr('coolandlovely.pagination.page', 'Page {{page}}', {\n page: offset / PAGE_SIZE + 1,\n })}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-40\"\n >\n {tr('coolandlovely.pagination.next', 'Next')} <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Catalog Page (BOFF-3384)
|
|
3
|
+
* @module coolandlovely/pages/brands
|
|
4
|
+
*
|
|
5
|
+
* Lets a brand bring its existing catalog over from Shopify, WooCommerce or
|
|
6
|
+
* BigCommerce in three steps:
|
|
7
|
+
*
|
|
8
|
+
* 1. Connect — pick the platform and paste read-only API credentials. They
|
|
9
|
+
* are sent once with `cooPreviewExternalCatalog` and never stored.
|
|
10
|
+
* 2. Review — the catalog comes back mapped onto a brand, collections and
|
|
11
|
+
* style items. The merchant confirms the brand name/handle and unticks
|
|
12
|
+
* anything they do not want.
|
|
13
|
+
* 3. Import — product images are copied into wspace-files and everything is
|
|
14
|
+
* created in one transaction with `cooBulkImportCatalog`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ImportCatalogPage(): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=ImportCatalogPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImportCatalogPage.d.ts","sourceRoot":"","sources":["../../../../src/coolandlovely/pages/brands/ImportCatalogPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA6GH,wBAAgB,iBAAiB,gCA6ehC"}
|