fix
This commit is contained in:
@@ -285,36 +285,15 @@ export type FixCatalogResult = SyncA1Result;
|
||||
/** @deprecated Use SyncA1Response */
|
||||
export type FixCatalogResponse = SyncA1Response;
|
||||
|
||||
/** Sync A1: optional wp_product_categories.sql upload + dump category backfill + Fix hygiene. */
|
||||
/** Sync A1: dump category backfill + Fix hygiene; category prompts load from repo seed. */
|
||||
export async function syncAdminCompanyA1(
|
||||
companyId: string,
|
||||
opts?: {
|
||||
backfillCategories?: boolean;
|
||||
reprocessSampleLimit?: number;
|
||||
skipDumpBackfill?: boolean;
|
||||
wpProductCategoriesFile?: File | null;
|
||||
}
|
||||
): Promise<SyncA1Response> {
|
||||
const file = opts?.wpProductCategoriesFile ?? null;
|
||||
if (file) {
|
||||
const body = new FormData();
|
||||
body.append("confirm", "true");
|
||||
if (opts?.backfillCategories === false) {
|
||||
body.append("backfill_categories", "false");
|
||||
}
|
||||
if (typeof opts?.reprocessSampleLimit === "number") {
|
||||
body.append("reprocess_sample_limit", String(opts.reprocessSampleLimit));
|
||||
}
|
||||
if (opts?.skipDumpBackfill) {
|
||||
body.append("skip_dump_backfill", "true");
|
||||
}
|
||||
body.append("wp_product_categories", file, file.name || "wp_product_categories.sql");
|
||||
return api<SyncA1Response>(ADMIN_SYNC_A1_PATH(companyId), {
|
||||
method: "POST",
|
||||
body
|
||||
});
|
||||
}
|
||||
|
||||
const body: {
|
||||
confirm: true;
|
||||
backfill_categories?: boolean;
|
||||
|
||||
@@ -829,14 +829,11 @@ export const de: MessageDict = {
|
||||
"admin.users.syncA1": "A1 synchronisieren",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1-Katalog synchronisieren",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Abbrechen",
|
||||
"admin.users.syncA1Confirm": "A1 synchronisieren",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Datei entfernen",
|
||||
"admin.users.noUsers": "Keine Benutzer entsprechen diesem Filter.",
|
||||
"admin.users.noCompanies": "Keine Unternehmen entsprechen diesem Filter.",
|
||||
"admin.users.assignRoleTitle": "Mitarbeiterrolle zuweisen",
|
||||
|
||||
@@ -856,14 +856,11 @@ export const en: MessageDict = {
|
||||
"admin.users.syncA1": "Sync A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sync A1 catalog",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Cancel",
|
||||
"admin.users.syncA1Confirm": "Sync A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Clear file",
|
||||
"admin.users.noUsers": "No users match this filter.",
|
||||
"admin.users.noCompanies": "No companies match this filter.",
|
||||
"admin.users.assignRoleTitle": "Assign staff role",
|
||||
@@ -2109,8 +2106,8 @@ export const en: MessageDict = {
|
||||
"settings.emailAlertsHelp": "Email alert preferences are coming soon. Failure alerts will default on (like in-app); completion emails stay off unless you opt in.",
|
||||
"settings.emailAlertsBody": "For now, configure transactional mail under Company → Email integration. In-app alerts above still apply.",
|
||||
"settings.apiKeysLegacyNotice": "Legacy API keys were not migrated. Create a new key to restore API access — the secret is shown only once.",
|
||||
"settings.apiKeysAdminOnly": "Only company admins can create or revoke API keys. You can view existing key prefixes.",
|
||||
"settings.apiKeysCopyUnavailableNotice": "Copy unavailable for existing keys — only the prefix is stored. Create a new key to copy the full secret once, then revoke the old key if needed.",
|
||||
"settings.apiKeysAdminOnly": "Only company admins can create, rename, or revoke API keys. You can view existing key prefixes.",
|
||||
"settings.apiKeysCopyUnavailableNotice": "After you create a key, copy the full secret immediately (or from the row menu while this page stays open). Existing keys only store a prefix — create a new key if you lost the secret.",
|
||||
"settings.apiKeysForbidden": "You don't have permission to view API keys. Ask a company admin for help.",
|
||||
"settings.noApiKeys": "No API keys yet",
|
||||
"settings.noApiKeysHelpAdmin": "Legacy API keys were not migrated. Create a new key to authenticate Descrybe API requests (X-API-Key). The full secret is shown only once.",
|
||||
@@ -2118,13 +2115,20 @@ export const en: MessageDict = {
|
||||
"settings.unnamedKey": "Unnamed Key",
|
||||
"settings.neverUsed": "Never used",
|
||||
"settings.apiKeyActions": "API key actions",
|
||||
"settings.renameApiKey": "Rename",
|
||||
"settings.renameApiKeyTitle": "Rename API key",
|
||||
"settings.renameApiKeyHelp": "Update the display name for this key. The secret itself does not change.",
|
||||
"settings.saveRename": "Save name",
|
||||
"settings.copyApiKey": "Copy secret",
|
||||
"settings.copyUnavailable": "Copy unavailable",
|
||||
"settings.copyUnavailableTitle": "Full secret is shown only once when the key is created",
|
||||
"settings.copyUnavailableTitle": "Full secret is shown only once when the key is created (kept copyable on this page until you refresh)",
|
||||
"settings.revoke": "Revoke",
|
||||
"settings.apiKeyNameRequired": "Enter a name for this API key.",
|
||||
"settings.apiKeyCreatedOnce": "API key created. Copy it now - it won't be shown again.",
|
||||
"settings.apiKeyCreated": "API key created.",
|
||||
"settings.apiKeyCreateFailed": "Could not create API key",
|
||||
"settings.apiKeyRenamed": "API key renamed.",
|
||||
"settings.apiKeyRenameFailed": "Could not rename API key",
|
||||
"settings.apiKeyRevokeConfirm": "Revoke this API key? Requests using it will stop working.",
|
||||
"settings.apiKeyRevoked": "API key revoked.",
|
||||
"settings.apiKeyRevokeFailed": "Could not revoke API key",
|
||||
@@ -2282,6 +2286,7 @@ export const en: MessageDict = {
|
||||
"flash.settings.apiKeyCreatedFailed": "Could not create API key",
|
||||
"flash.settings.apiKeyRevoked": "API key revoked.",
|
||||
"flash.settings.apiKeyRevokeFailed": "Could not revoke API key",
|
||||
"flash.settings.apiKeyRenameFailed": "Could not rename API key",
|
||||
"flash.settings.copyFailed": "Could not copy. Select the text and copy manually.",
|
||||
"flash.products.accepted": "Accepted {count} product(s) — moved to Processed.",
|
||||
"flash.products.updated": "Product updated.",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const es: MessageDict = {
|
||||
"admin.users.syncA1": "Sincronizar A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizar catálogo A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Cancelar",
|
||||
"admin.users.syncA1Confirm": "Sincronizar A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Quitar archivo",
|
||||
"admin.users.noUsers": "Ningún usuario coincide con este filtro.",
|
||||
"admin.users.noCompanies": "Ninguna empresa coincide con este filtro.",
|
||||
"admin.users.assignRoleTitle": "Asignar rol de personal",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const fr: MessageDict = {
|
||||
"admin.users.syncA1": "Synchroniser A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Synchroniser le catalogue A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Annuler",
|
||||
"admin.users.syncA1Confirm": "Synchroniser A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Effacer le fichier",
|
||||
"admin.users.noUsers": "Aucun utilisateur ne correspond àce filtre.",
|
||||
"admin.users.noCompanies": "Aucune entreprise ne correspond àce filtre.",
|
||||
"admin.users.assignRoleTitle": "Assigner un rôle du personnel",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const it: MessageDict = {
|
||||
"admin.users.syncA1": "Sincronizza A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizza catalogo A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Annulla",
|
||||
"admin.users.syncA1Confirm": "Sincronizza A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Rimuovi file",
|
||||
"admin.users.noUsers": "Nessun utente corrisponde a questo filtro.",
|
||||
"admin.users.noCompanies": "Nessuna azienda corrisponde a questo filtro.",
|
||||
"admin.users.assignRoleTitle": "Assegna ruolo staff",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const ja: MessageDict = {
|
||||
"admin.users.syncA1": "A1ã‚’åŒæœŸ",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1ã‚«ã‚¿ãƒã‚°ã‚’åŒæœŸ",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "ã‚ャンセル",
|
||||
"admin.users.syncA1Confirm": "A1ã‚’åŒæœŸ",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "ファイルをクリア",
|
||||
"admin.users.noUsers": "ã“ã®フィルタã«一致ã™るユーザーã¯ã„ã¾ã›ん。",
|
||||
"admin.users.noCompanies": "ã“ã®フィルタã«一致ã™る会社ã¯ã‚りã¾ã›ん。",
|
||||
"admin.users.assignRoleTitle": "スタッフãƒÂールを割り当ã¦",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const nl: MessageDict = {
|
||||
"admin.users.syncA1": "A1 synchroniseren",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "A1-catalogus synchroniseren",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Annuleren",
|
||||
"admin.users.syncA1Confirm": "A1 synchroniseren",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Bestand wissen",
|
||||
"admin.users.noUsers": "Geen gebruikers komen overeen met dit filter.",
|
||||
"admin.users.noCompanies": "Geen bedrijven komen overeen met dit filter.",
|
||||
"admin.users.assignRoleTitle": "Medewerkerrol toewijzen",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const pl: MessageDict = {
|
||||
"admin.users.syncA1": "Synchronizuj A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Synchronizuj katalog A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Anuluj",
|
||||
"admin.users.syncA1Confirm": "Synchronizuj A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Wyczyść plik",
|
||||
"admin.users.noUsers": "Żaden użytkownik nie pasuje do tego filtra.",
|
||||
"admin.users.noCompanies": "Żadna firma nie pasuje do tego filtra.",
|
||||
"admin.users.assignRoleTitle": "Przypisz rolÄ™ personelu",
|
||||
|
||||
@@ -829,14 +829,11 @@ export const pt: MessageDict = {
|
||||
"admin.users.syncA1": "Sincronizar A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sincronizar catálogo A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "Cancelar",
|
||||
"admin.users.syncA1Confirm": "Sincronizar A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Limpar ficheiro",
|
||||
"admin.users.noUsers": "Nenhum utilizador corresponde a este filtro.",
|
||||
"admin.users.noCompanies": "Nenhuma empresa corresponde a este filtro.",
|
||||
"admin.users.assignRoleTitle": "Atribuir função de equipa",
|
||||
|
||||
@@ -18,14 +18,11 @@ export const sl: MessageDict = {
|
||||
"admin.users.syncA1": "Sinhroniziraj A1",
|
||||
"admin.users.syncA1Aria": "Sync A1 categories from dump and repair catalog hygiene for {name}",
|
||||
"admin.users.syncA1Title": "Sinhroniziraj katalog A1",
|
||||
"admin.users.syncA1Desc": "Uploads wp_product_categories.sql to force-apply Title/Description/Meta/Attributes category prompts, optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Desc": "Applies Title/Description/Meta/Attributes category prompts from the repo seed (scripts/seed/wp_product_categories.sql), optionally backfills mapped categories from a MySQL product dump on the API host, then runs catalog hygiene. Does not wipe or reimport the full catalog.",
|
||||
"admin.users.syncA1Company": "Company: {name}",
|
||||
"admin.users.syncA1Warning": "Upload wp_product_categories.sql below (primary). Auto-detect on the API host is a fallback when no file is chosen. Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Warning": "Category prompts load automatically from scripts/seed/wp_product_categories.sql on the API host (SEED_A1_WP_CATEGORIES overrides). Optional: place descrybe_new.sql on the API server for product-dump category coverage. No mass reprocess.",
|
||||
"admin.users.syncA1Cancel": "PrekliÄi",
|
||||
"admin.users.syncA1Confirm": "Sinhroniziraj A1",
|
||||
"admin.users.syncA1WpUpload": "wp_product_categories.sql",
|
||||
"admin.users.syncA1WpUploadHint": "Choose the WordPress category prompts dump. Max 16 MiB. When uploaded, Sync A1 force-applies sectioned prompts for title and description enhance.",
|
||||
"admin.users.syncA1WpUploadClear": "Počisti datoteko",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} with mapped category.",
|
||||
"flash.admin.syncA1Success": "Synced A1 for {name}: dump_mapped {dump_mapped} ({dump_status}), prompts {prompts}, hashes {hashes}, processedâ†mapped {categories}, mappedâ†processed {mapped_backfilled}. Coverage: {mapped_with}/{mapped_total} ({taxonomy} taxonomy).",
|
||||
"flash.admin.syncA1Error": "Sinhronizacija A1 za {name} ni uspela.",
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
|
||||
let syncOpen = $state(false);
|
||||
let syncCompany = $state<AdminOrgCompany | null>(null);
|
||||
let syncWpCategoriesFile = $state<File | null>(null);
|
||||
|
||||
const cloneDestLabel = $derived.by(() => {
|
||||
const selected = cloneDestOptions.find((c) => c.id === cloneDestId);
|
||||
@@ -409,7 +408,6 @@
|
||||
|
||||
function openSyncDialog(company: AdminOrgCompany) {
|
||||
syncCompany = company;
|
||||
syncWpCategoriesFile = null;
|
||||
syncOpen = true;
|
||||
error = "";
|
||||
success = "";
|
||||
@@ -471,8 +469,7 @@
|
||||
const targetName = syncCompany.name;
|
||||
try {
|
||||
const res = await syncAdminCompanyA1(syncCompany.id, {
|
||||
reprocessSampleLimit: 25,
|
||||
wpProductCategoriesFile: syncWpCategoriesFile
|
||||
reprocessSampleLimit: 25
|
||||
});
|
||||
const r = res.result;
|
||||
const mappedWith = Number(r.mapped_with_category ?? 0);
|
||||
@@ -493,7 +490,6 @@
|
||||
});
|
||||
syncOpen = false;
|
||||
syncCompany = null;
|
||||
syncWpCategoriesFile = null;
|
||||
} catch (err) {
|
||||
error = failureMessage(err, i18n.t("flash.admin.syncA1Error", { name: targetName }));
|
||||
} finally {
|
||||
@@ -1130,39 +1126,6 @@
|
||||
{i18n.t("admin.users.syncA1Company", { name: syncCompany.name })}
|
||||
</p>
|
||||
{/if}
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-foreground" for="sync-a1-wp-categories">
|
||||
{i18n.t("admin.users.syncA1WpUpload")}
|
||||
</label>
|
||||
<input
|
||||
id="sync-a1-wp-categories"
|
||||
type="file"
|
||||
accept=".sql,text/plain,application/sql"
|
||||
class="block w-full text-sm text-foreground file:mr-3 file:rounded-md file:border-0 file:bg-muted file:px-3 file:py-1.5 file:text-sm file:font-medium"
|
||||
disabled={busy}
|
||||
onchange={(e) => {
|
||||
const input = e.currentTarget;
|
||||
syncWpCategoriesFile = input.files?.[0] ?? null;
|
||||
}}
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">{i18n.t("admin.users.syncA1WpUploadHint")}</p>
|
||||
{#if syncWpCategoriesFile}
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>{syncWpCategoriesFile.name}</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={busy}
|
||||
onclick={() => {
|
||||
syncWpCategoriesFile = null;
|
||||
}}
|
||||
>
|
||||
{i18n.t("admin.users.syncA1WpUploadClear")}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{i18n.t("admin.users.syncA1Warning")}
|
||||
</p>
|
||||
@@ -1174,7 +1137,6 @@
|
||||
onclick={() => {
|
||||
syncOpen = false;
|
||||
syncCompany = null;
|
||||
syncWpCategoriesFile = null;
|
||||
}}
|
||||
>
|
||||
{i18n.t("admin.users.syncA1Cancel")}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
Mail,
|
||||
Bell,
|
||||
MoreVertical,
|
||||
Pencil,
|
||||
Save,
|
||||
Trash2,
|
||||
User as UserIcon,
|
||||
@@ -196,6 +197,11 @@
|
||||
let createKeyOpen = $state(false);
|
||||
let apiKeyName = $state("");
|
||||
let newApiKey = $state<string | null>(null);
|
||||
/** Full secrets shown after create — kept in-session only so they stay copyable until refresh. */
|
||||
let createdApiKeySecrets = $state<Record<string, string>>({});
|
||||
let renameKeyOpen = $state(false);
|
||||
let renameKeyId = $state<string | null>(null);
|
||||
let renameKeyName = $state("");
|
||||
let settingsAbort: AbortController | null = null;
|
||||
let alertPrefs = $state<AlertPrefs>(readAlertPrefs());
|
||||
let currentPassword = $state("");
|
||||
@@ -704,6 +710,9 @@
|
||||
});
|
||||
apiKeys = [created, ...apiKeys];
|
||||
newApiKey = created.key ?? null;
|
||||
if (created.id && created.key) {
|
||||
createdApiKeySecrets = { ...createdApiKeySecrets, [created.id]: created.key };
|
||||
}
|
||||
apiKeyName = "";
|
||||
trackEvent("api_key_created");
|
||||
success = newApiKey
|
||||
@@ -717,6 +726,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openRenameApiKey(key: ApiKey) {
|
||||
renameKeyId = key.id;
|
||||
renameKeyName = (key.name ?? "").trim();
|
||||
clearFormFeedback();
|
||||
renameKeyOpen = true;
|
||||
}
|
||||
|
||||
async function renameApiKey() {
|
||||
if (!canAdmin || !planCapabilities.can("settings.api_keys")) {
|
||||
error = i18n.t("settings.apiKeysAdminOnly");
|
||||
notifyError(error);
|
||||
return;
|
||||
}
|
||||
const id = renameKeyId;
|
||||
const name = renameKeyName.trim();
|
||||
if (!id) return;
|
||||
if (!name) {
|
||||
error = i18n.t("flash.settings.apiKeyNameRequired");
|
||||
fieldErrors = { apiKeyName: error };
|
||||
notifyError(error);
|
||||
return;
|
||||
}
|
||||
saving = true;
|
||||
clearFormFeedback();
|
||||
try {
|
||||
const updated = await api<ApiKey>(`/api/api-keys/${id}`, {
|
||||
method: "PATCH",
|
||||
body: { name }
|
||||
});
|
||||
apiKeys = apiKeys.map((k) => (k.id === id ? { ...k, ...updated, name: updated.name ?? name } : k));
|
||||
renameKeyOpen = false;
|
||||
renameKeyId = null;
|
||||
renameKeyName = "";
|
||||
success = i18n.t("settings.apiKeyRenamed");
|
||||
notifySuccess(success);
|
||||
} catch (err) {
|
||||
error = applyApiFormError(err, i18n.t("flash.settings.apiKeyRenameFailed"), "rename API keys");
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function revokeApiKey(keyId: string) {
|
||||
if (!confirm(i18n.t("confirm.revokeApiKey"))) {
|
||||
return;
|
||||
@@ -726,6 +777,8 @@
|
||||
try {
|
||||
await api(`/api/api-keys/${keyId}`, { method: "DELETE" });
|
||||
apiKeys = apiKeys.filter((k) => k.id !== keyId);
|
||||
const { [keyId]: _removed, ...rest } = createdApiKeySecrets;
|
||||
createdApiKeySecrets = rest;
|
||||
success = i18n.t("flash.settings.apiKeyRevoked");
|
||||
notifySuccess(success);
|
||||
} catch (err) {
|
||||
@@ -735,6 +788,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function sessionSecretFor(keyId: string): string | null {
|
||||
return createdApiKeySecrets[keyId] ?? null;
|
||||
}
|
||||
|
||||
|
||||
async function transferOwnership(member: TeamMember) {
|
||||
const userId = member.user_id ?? member.id;
|
||||
@@ -1418,22 +1475,37 @@
|
||||
: i18n.t("settings.neverUsed")}
|
||||
</TableCell>
|
||||
<TableCell stickyRight>
|
||||
{#if canAdmin}
|
||||
<div class="relative">
|
||||
<DropdownMenu class="min-w-[10rem]">
|
||||
{#snippet trigger({ open, toggle })}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={saving}
|
||||
aria-label={i18n.t("settings.apiKeyActions")}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
onclick={() => toggle()}
|
||||
>
|
||||
<MoreVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
{#if canAdmin}
|
||||
{@const sessionSecret = sessionSecretFor(key.id)}
|
||||
<div class="relative">
|
||||
<DropdownMenu class="min-w-[10rem]">
|
||||
{#snippet trigger({ open, toggle })}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={saving}
|
||||
aria-label={i18n.t("settings.apiKeyActions")}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
onclick={() => toggle()}
|
||||
>
|
||||
<MoreVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
<DropdownMenuItem onclick={() => openRenameApiKey(key)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
{i18n.t("settings.renameApiKey")}
|
||||
</DropdownMenuItem>
|
||||
{#if sessionSecret}
|
||||
<DropdownMenuItem
|
||||
onclick={() =>
|
||||
copyText(sessionSecret, i18n.t("settings.apiKeyCopied"))
|
||||
}
|
||||
>
|
||||
<Copy class="mr-2 h-4 w-4" />
|
||||
{i18n.t("settings.copyApiKey")}
|
||||
</DropdownMenuItem>
|
||||
{:else}
|
||||
<DropdownMenuItem
|
||||
disabled
|
||||
class="text-muted-foreground"
|
||||
@@ -1442,18 +1514,19 @@
|
||||
<Copy class="mr-2 h-4 w-4" />
|
||||
{i18n.t("settings.copyUnavailable")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
class="text-destructive focus:text-destructive"
|
||||
onclick={() => revokeApiKey(key.id)}
|
||||
>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{i18n.t("settings.revoke")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-sm text-muted-foreground">—</span>
|
||||
{/if}
|
||||
{/if}
|
||||
<DropdownMenuItem
|
||||
class="text-destructive focus:text-destructive"
|
||||
onclick={() => revokeApiKey(key.id)}
|
||||
>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{i18n.t("settings.revoke")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-sm text-muted-foreground">—</span>
|
||||
{/if}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
@@ -1756,7 +1829,16 @@
|
||||
<div class="space-y-2">
|
||||
<Label>{i18n.t("settings.apiKeyLabel")}</Label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Input value={newApiKey} readonly autocomplete="off" spellcheck={false} class="font-mono text-sm" aria-label={i18n.t("settings.newApiKeyAria")} />
|
||||
<Input
|
||||
value={newApiKey}
|
||||
readonly
|
||||
autocomplete="off"
|
||||
spellcheck={false}
|
||||
class="font-mono text-sm"
|
||||
aria-label={i18n.t("settings.newApiKeyAria")}
|
||||
onclick={(e) => (e.currentTarget as HTMLInputElement).select()}
|
||||
onfocus={(e) => (e.currentTarget as HTMLInputElement).select()}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
@@ -1822,4 +1904,52 @@
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
|
||||
<!-- Rename API Key Dialog -->
|
||||
<Dialog
|
||||
bind:open={renameKeyOpen}
|
||||
title={i18n.t("settings.renameApiKeyTitle")}
|
||||
description={i18n.t("settings.renameApiKeyHelp")}
|
||||
class="sm:max-w-md sm:min-w-0"
|
||||
onClose={() => {
|
||||
renameKeyId = null;
|
||||
renameKeyName = "";
|
||||
}}
|
||||
>
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="renameApiKeyName">{i18n.t("settings.keyName")}</Label>
|
||||
<Input
|
||||
id="renameApiKeyName"
|
||||
placeholder={i18n.t("settings.keyNamePlaceholder")}
|
||||
bind:value={renameKeyName}
|
||||
disabled={saving}
|
||||
aria-invalid={fieldInvalid(fieldErrors, "apiKeyName")}
|
||||
aria-describedby={fieldDescribedBy(fieldErrors, "apiKeyName", SETTINGS_ERROR_ID)}
|
||||
/>
|
||||
{#if fieldErrors.apiKeyName}
|
||||
<p class="text-sm text-destructive">{fieldErrors.apiKeyName}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#snippet footer()}
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={saving}
|
||||
onclick={() => {
|
||||
renameKeyOpen = false;
|
||||
renameKeyId = null;
|
||||
renameKeyName = "";
|
||||
}}
|
||||
>
|
||||
{i18n.t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={saving || !renameKeyName.trim()}
|
||||
loading={saving}
|
||||
onclick={renameApiKey}
|
||||
>
|
||||
{i18n.t("settings.saveRename")}
|
||||
</Button>
|
||||
{/snippet}
|
||||
</Dialog>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user