Limit-Status im Upload-Flow anzeigen (Warnbanner + Sperrzustände).
Upload-Fehlercodes auswerten und freundliche Dialoge zeigen.
This commit is contained in:
86
resources/js/guest/lib/__tests__/limitSummaries.test.ts
Normal file
86
resources/js/guest/lib/__tests__/limitSummaries.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { EventPackageLimits } from '../../services/eventApi';
|
||||
import { buildLimitSummaries } from '../limitSummaries';
|
||||
|
||||
const translations = new Map<string, string>([
|
||||
['upload.status.cards.photos.title', 'Fotos'],
|
||||
['upload.status.cards.photos.remaining', 'Noch {remaining} von {limit}'],
|
||||
['upload.status.cards.photos.unlimited', 'Unbegrenzte Uploads'],
|
||||
['upload.status.cards.guests.title', 'Gäste'],
|
||||
['upload.status.cards.guests.remaining', '{remaining} Gäste frei (max. {limit})'],
|
||||
['upload.status.cards.guests.unlimited', 'Unbegrenzte Gäste'],
|
||||
['upload.status.badges.ok', 'OK'],
|
||||
['upload.status.badges.warning', 'Warnung'],
|
||||
['upload.status.badges.limit_reached', 'Limit erreicht'],
|
||||
['upload.status.badges.unlimited', 'Unbegrenzt'],
|
||||
]);
|
||||
|
||||
const t = (key: string) => translations.get(key) ?? key;
|
||||
|
||||
describe('buildLimitSummaries', () => {
|
||||
it('builds photo summary with progress and warning tone', () => {
|
||||
const limits: EventPackageLimits = {
|
||||
photos: {
|
||||
limit: 100,
|
||||
used: 80,
|
||||
remaining: 20,
|
||||
percentage: 80,
|
||||
state: 'warning',
|
||||
threshold_reached: 80,
|
||||
next_threshold: 95,
|
||||
thresholds: [80, 95],
|
||||
},
|
||||
guests: null,
|
||||
gallery: null,
|
||||
can_upload_photos: true,
|
||||
can_add_guests: true,
|
||||
};
|
||||
|
||||
const cards = buildLimitSummaries(limits, t);
|
||||
|
||||
expect(cards).toHaveLength(1);
|
||||
const card = cards[0];
|
||||
expect(card.id).toBe('photos');
|
||||
expect(card.tone).toBe('warning');
|
||||
expect(card.progress).toBe(80);
|
||||
expect(card.valueLabel).toBe('80 / 100');
|
||||
expect(card.description).toBe('Noch 20 von 100');
|
||||
expect(card.badgeLabel).toBe('Warnung');
|
||||
});
|
||||
|
||||
it('builds unlimited guest summary without progress', () => {
|
||||
const limits: EventPackageLimits = {
|
||||
photos: null,
|
||||
guests: {
|
||||
limit: null,
|
||||
used: 5,
|
||||
remaining: null,
|
||||
percentage: null,
|
||||
state: 'unlimited',
|
||||
threshold_reached: null,
|
||||
next_threshold: null,
|
||||
thresholds: [],
|
||||
},
|
||||
gallery: null,
|
||||
can_upload_photos: true,
|
||||
can_add_guests: true,
|
||||
};
|
||||
|
||||
const cards = buildLimitSummaries(limits, t);
|
||||
|
||||
expect(cards).toHaveLength(1);
|
||||
const card = cards[0];
|
||||
expect(card.id).toBe('guests');
|
||||
expect(card.progress).toBeNull();
|
||||
expect(card.tone).toBe('neutral');
|
||||
expect(card.valueLabel).toBe('Unbegrenzt');
|
||||
expect(card.description).toBe('Unbegrenzte Gäste');
|
||||
expect(card.badgeLabel).toBe('Unbegrenzt');
|
||||
});
|
||||
|
||||
it('returns empty list when no limits are provided', () => {
|
||||
expect(buildLimitSummaries(null, t)).toEqual([]);
|
||||
expect(buildLimitSummaries(undefined, t)).toEqual([]);
|
||||
});
|
||||
});
|
||||
49
resources/js/guest/lib/__tests__/uploadErrorDialog.test.ts
Normal file
49
resources/js/guest/lib/__tests__/uploadErrorDialog.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { resolveUploadErrorDialog } from '../uploadErrorDialog';
|
||||
|
||||
const translations = new Map<string, string>([
|
||||
['upload.dialogs.photoLimit.title', 'Upload-Limit erreicht'],
|
||||
['upload.dialogs.photoLimit.description', 'Es wurden {used} von {limit} Fotos hochgeladen. Es bleiben {remaining}.'],
|
||||
['upload.dialogs.photoLimit.hint', 'Wende dich an das Team.'],
|
||||
['upload.dialogs.deviceLimit.title', 'Dieses Gerät ist voll'],
|
||||
['upload.dialogs.deviceLimit.description', 'Du hast das Geräte-Limit erreicht.'],
|
||||
['upload.dialogs.deviceLimit.hint', 'Nutze ein anderes Gerät oder kontaktiere das Team.'],
|
||||
['upload.dialogs.packageMissing.title', 'Event nicht bereit'],
|
||||
['upload.dialogs.packageMissing.description', 'Das Event akzeptiert aktuell keine Uploads.'],
|
||||
['upload.dialogs.packageMissing.hint', 'Frag die Veranstalter:innen nach dem Status.'],
|
||||
['upload.dialogs.galleryExpired.title', 'Galerie abgelaufen'],
|
||||
['upload.dialogs.galleryExpired.description', 'Uploads sind nicht mehr möglich.'],
|
||||
['upload.dialogs.galleryExpired.hint', 'Bitte wende dich an die Veranstalter:innen.'],
|
||||
['upload.dialogs.csrf.title', 'Sicherheitsabgleich erforderlich'],
|
||||
['upload.dialogs.csrf.description', 'Bitte lade die Seite neu und versuche es erneut.'],
|
||||
['upload.dialogs.csrf.hint', 'Aktualisiere die Seite.'],
|
||||
['upload.dialogs.generic.title', 'Upload fehlgeschlagen'],
|
||||
['upload.dialogs.generic.description', 'Der Upload konnte nicht abgeschlossen werden.'],
|
||||
['upload.dialogs.generic.hint', 'Versuche es später erneut.'],
|
||||
]);
|
||||
|
||||
const t = (key: string) => translations.get(key) ?? key;
|
||||
|
||||
describe('resolveUploadErrorDialog', () => {
|
||||
it('renders photo limit dialog with placeholders', () => {
|
||||
const dialog = resolveUploadErrorDialog(
|
||||
'photo_limit_exceeded',
|
||||
{ used: 120, limit: 120, remaining: 0 },
|
||||
t
|
||||
);
|
||||
|
||||
expect(dialog.title).toBe('Upload-Limit erreicht');
|
||||
expect(dialog.description).toBe('Es wurden 120 von 120 Fotos hochgeladen. Es bleiben 0.');
|
||||
expect(dialog.hint).toBe('Wende dich an das Team.');
|
||||
expect(dialog.tone).toBe('danger');
|
||||
});
|
||||
|
||||
it('falls back to generic dialog when code is unknown', () => {
|
||||
const dialog = resolveUploadErrorDialog('something_else', undefined, t);
|
||||
|
||||
expect(dialog.tone).toBe('info');
|
||||
expect(dialog.title).toBe('Upload fehlgeschlagen');
|
||||
expect(dialog.description).toBe('Der Upload konnte nicht abgeschlossen werden.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user