From b5bf214d7fdd7f37a57400369404f48788bb2de3 Mon Sep 17 00:00:00 2001 From: TheOnlyMace <0815cracky@gmail.com> Date: Sat, 25 Jul 2026 16:24:18 +0200 Subject: [PATCH] Fix ticket category saving and synchronization error handling - Updated the ticket category saving logic to handle empty description and emoji fields, ensuring they are set to null if trimmed. - Enhanced error handling in the `maybeSyncTicketPanel` function to prevent UI errors when synchronization fails, allowing category CRUD operations to succeed. - Updated the response handling to include support role IDs in the UI after saving, improving data consistency. - Added validation for support role IDs in the dashboard schemas to ensure correct data types are used. --- .../modules/ticket-categories-manager.tsx | 13 ++++++++++--- apps/webui/src/lib/module-configs/tickets.ts | 9 ++++++++- docs/PHASE-TRACKING.md | 15 +++++++++++++++ packages/shared/src/dashboard.test.ts | 17 +++++++++++++++++ packages/shared/src/dashboard.ts | 2 +- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/apps/webui/src/components/modules/ticket-categories-manager.tsx b/apps/webui/src/components/modules/ticket-categories-manager.tsx index 735d1e8..7b1d06a 100644 --- a/apps/webui/src/components/modules/ticket-categories-manager.tsx +++ b/apps/webui/src/components/modules/ticket-categories-manager.tsx @@ -94,18 +94,25 @@ export function TicketCategoriesManager({ guildId, initialCategories }: TicketCa headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: category.name, - description: category.description, - emoji: category.emoji, + description: category.description?.trim() ? category.description : null, + emoji: category.emoji?.trim() ? category.emoji : null, supportRoleIds: category.supportRoleIds }) }); const body = (await response.json().catch(() => null)) as | (TicketCategoryDashboard & { error?: string; code?: string }) | null; - if (!response.ok || !body) { + if (!response.ok || !body || !body.id) { toast.error(panelSyncErrorMessage(body?.code, body?.error, t)); return; } + setCategories((prev) => + prev.map((entry) => + entry.clientKey === category.clientKey + ? { ...body, clientKey: entry.clientKey, saving: false } + : entry + ) + ); toast.success(t('common.saveSuccess')); } catch { toast.error(t('common.saveError')); diff --git a/apps/webui/src/lib/module-configs/tickets.ts b/apps/webui/src/lib/module-configs/tickets.ts index 42a12a0..965720e 100644 --- a/apps/webui/src/lib/module-configs/tickets.ts +++ b/apps/webui/src/lib/module-configs/tickets.ts @@ -105,7 +105,14 @@ async function maybeSyncTicketPanel(guildId: string): Promise { if (!config?.enabled || !config.panelChannelId) { return; } - await syncTicketPanel(guildId); + // Category CRUD must succeed even if Discord panel sync fails — otherwise + // support roles / category fields appear "unsaved" in the UI while already + // persisted in Postgres. + try { + await syncTicketPanel(guildId); + } catch (error) { + console.warn('[tickets] panel sync after category change failed', { guildId, error }); + } } export async function updateTicketConfigDashboard( diff --git a/docs/PHASE-TRACKING.md b/docs/PHASE-TRACKING.md index feb1746..11cf984 100644 --- a/docs/PHASE-TRACKING.md +++ b/docs/PHASE-TRACKING.md @@ -533,6 +533,21 @@ - [ ] Close aus Dashboard → Ticket verschwindet aus der Liste, Kanal/Thread wird geschlossen - [ ] Refresh-Button aktualisiert die Liste +## Post-Phase – Ticket Support-Rollen Speichern (Bugfix) + +### Abgeschlossen (Code) + +- Root cause: Kategorie-Save warf Fehler wenn Panel-Sync scheiterte → UI „Speichern fehlgeschlagen“, obwohl `supportRoleIds` schon in Postgres lagen +- `maybeSyncTicketPanel` fängt Sync-Fehler ab; Kategorie-CRUD bleibt erfolgreich +- Nach Speichern: Response inkl. `supportRoleIds` in die UI übernommen +- Zod: `supportRoleIds` jetzt `SnowflakeSchema[]` + +### Manuell testen + +- [ ] Kategorie: Support-Rolle wählen → Speichern → Erfolgstoast +- [ ] Seite neu laden → Rolle ist noch ausgewählt +- [ ] Neues Ticket → Support-Rolle wird gepingt / in Thread aufgenommen + ## Post-Phase – Ban/Kick-Reason Logging + Verification Fail Event (Status: implementiert) ### Abgeschlossen (Code) diff --git a/packages/shared/src/dashboard.test.ts b/packages/shared/src/dashboard.test.ts index 21df457..e71e77f 100644 --- a/packages/shared/src/dashboard.test.ts +++ b/packages/shared/src/dashboard.test.ts @@ -15,6 +15,7 @@ import { SuggestionActionSchema, TagDashboardCreateSchema, TicketCategoryDashboardCreateSchema, + TicketCategoryDashboardUpdateSchema, TicketConfigDashboardPatchSchema, WelcomeConfigDashboardPatchSchema } from './dashboard.js'; @@ -99,6 +100,22 @@ describe('dashboard schemas', () => { expect(parsed.supportRoleIds).toHaveLength(1); }); + it('validates ticket category update with support roles', () => { + const parsed = TicketCategoryDashboardUpdateSchema.parse({ + supportRoleIds: ['123456789012345678', '234567890123456789'] + }); + expect(parsed.supportRoleIds).toEqual(['123456789012345678', '234567890123456789']); + }); + + it('rejects invalid support role ids on ticket category create', () => { + expect(() => + TicketCategoryDashboardCreateSchema.parse({ + name: 'Support', + supportRoleIds: ['not-a-snowflake'] + }) + ).toThrow(); + }); + it('validates giveaway creation input', () => { const parsed = GiveawayCreateDashboardSchema.parse({ channelId: '123456789012345678', diff --git a/packages/shared/src/dashboard.ts b/packages/shared/src/dashboard.ts index 51b3240..68ee564 100644 --- a/packages/shared/src/dashboard.ts +++ b/packages/shared/src/dashboard.ts @@ -403,7 +403,7 @@ export const TicketCategoryDashboardSchema = z.object({ name: z.string().min(1).max(100), description: z.string().max(500).nullable().optional(), emoji: z.string().max(80).nullable().optional(), - supportRoleIds: z.array(z.string()) + supportRoleIds: z.array(SnowflakeSchema) }); export type TicketCategoryDashboard = z.infer;