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.
This commit is contained in:
TheOnlyMace
2026-07-25 16:24:18 +02:00
parent 5c11b68fc0
commit b5bf214d7f
5 changed files with 51 additions and 5 deletions

View File

@@ -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'));

View File

@@ -105,7 +105,14 @@ async function maybeSyncTicketPanel(guildId: string): Promise<void> {
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(

View File

@@ -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)

View File

@@ -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',

View File

@@ -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<typeof TicketCategoryDashboardSchema>;