Add approve-and-live action for Live Show
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-05 14:16:27 +01:00
parent 148c075d58
commit 99186e8e2f
7 changed files with 172 additions and 10 deletions

View File

@@ -1587,6 +1587,20 @@ export async function approveLiveShowPhoto(
return normalizePhoto(data.data);
}
export async function approveAndLiveShowPhoto(
slug: string,
id: number,
payload: { priority?: number } = {}
): Promise<TenantPhoto> {
const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/approve-and-live`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to approve and add live show photo');
return normalizePhoto(data.data);
}
export async function rejectLiveShowPhoto(slug: string, id: number, reason?: string): Promise<TenantPhoto> {
const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/reject`, {
method: 'POST',

View File

@@ -2158,16 +2158,25 @@
"none": "Nicht vorgemerkt"
},
"galleryApproved": "Galerie freigegeben",
"galleryApprovedOnly": "Hier erscheinen nur bereits freigegebene Galerie-Fotos.",
"galleryApprovedOnly": "Galerie- und Live-Show-Freigaben sind getrennt. Ausstehende Fotos können hier freigegeben werden.",
"galleryStatus": {
"approved": "Galerie freigegeben",
"pending": "Galerie ausstehend",
"rejected": "Galerie abgelehnt",
"hidden": "Versteckt"
},
"offlineNotice": "Du bist offline. Live-Show-Aktionen sind deaktiviert.",
"empty": "Keine Fotos für die Live-Show in der Warteschlange.",
"loadFailed": "Live-Show-Warteschlange konnte nicht geladen werden.",
"approve": "Für Live-Show freigeben",
"approveAndLive": "Freigeben + Live",
"reject": "Ablehnen",
"clear": "Aus Live-Show entfernen",
"approveSuccess": "Foto für Live-Show freigegeben",
"approveAndLiveSuccess": "Foto freigegeben und zur Live-Show hinzugefügt",
"rejectSuccess": "Foto aus Live-Show entfernt",
"clearSuccess": "Live-Show-Freigabe entfernt",
"notEligible": "Nicht zulässig",
"actionFailed": "Live-Show-Aktion fehlgeschlagen."
},
"mobileProfile": {

View File

@@ -2162,16 +2162,25 @@
"none": "Not queued"
},
"galleryApproved": "Gallery approved",
"galleryApprovedOnly": "Only gallery-approved photos appear here.",
"galleryApprovedOnly": "Gallery and Live Show approvals are separate. Pending photos can be approved here.",
"galleryStatus": {
"approved": "Gallery approved",
"pending": "Gallery pending",
"rejected": "Gallery rejected",
"hidden": "Hidden"
},
"offlineNotice": "You are offline. Live Show actions are disabled.",
"empty": "No photos waiting for Live Show.",
"loadFailed": "Live Show queue could not be loaded.",
"approve": "Approve for Live Show",
"approveAndLive": "Approve + Live",
"reject": "Reject",
"clear": "Remove from Live Show",
"approveSuccess": "Photo approved for Live Show",
"approveAndLiveSuccess": "Photo approved and added to Live Show",
"rejectSuccess": "Photo removed from Live Show",
"clearSuccess": "Live Show approval removed",
"notEligible": "Not eligible",
"actionFailed": "Live Show update failed."
},
"mobileProfile": {

View File

@@ -9,6 +9,7 @@ import { MobileCard, CTAButton, PillBadge, SkeletonCard } from './components/Pri
import { MobileSelect } from './components/FormControls';
import { useEventContext } from '../context/EventContext';
import {
approveAndLiveShowPhoto,
approveLiveShowPhoto,
clearLiveShowPhoto,
getEvents,
@@ -123,6 +124,22 @@ export default function MobileEventLiveShowQueuePage() {
}
}
async function handleApproveAndLive(photo: TenantPhoto) {
if (!slug || busyId) return;
setBusyId(photo.id);
try {
const updated = await approveAndLiveShowPhoto(slug, photo.id);
setPhotos((prev) => prev.map((item) => (item.id === photo.id ? updated : item)));
toast.success(t('liveShowQueue.approveAndLiveSuccess', 'Photo approved and added to Live Show'));
} catch (err) {
if (!isAuthError(err)) {
toast.error(t('liveShowQueue.actionFailed', 'Live Show update failed.'));
}
} finally {
setBusyId(null);
}
}
async function handleReject(photo: TenantPhoto) {
if (!slug || busyId) return;
setBusyId(photo.id);
@@ -161,6 +178,17 @@ export default function MobileEventLiveShowQueuePage() {
return 'muted';
}
function resolveGalleryLabel(status?: string | null): string {
const fallbackMap: Record<string, string> = {
approved: 'Gallery approved',
pending: 'Gallery pending',
rejected: 'Gallery rejected',
hidden: 'Hidden',
};
const key = status ?? 'pending';
return t(`liveShowQueue.galleryStatus.${key}`, fallbackMap[key] ?? key);
}
return (
<MobileShell
activeTab="home"
@@ -175,7 +203,10 @@ export default function MobileEventLiveShowQueuePage() {
>
<MobileCard borderColor={border} backgroundColor="transparent">
<Text fontSize="$sm" color={muted}>
{t('liveShowQueue.galleryApprovedOnly', 'Only gallery-approved photos appear here.')}
{t(
'liveShowQueue.galleryApprovedOnly',
'Gallery and Live Show approvals are separate. Pending photos can be approved here.'
)}
</Text>
{!online ? (
<Text fontSize="$sm" color={danger}>
@@ -223,6 +254,11 @@ export default function MobileEventLiveShowQueuePage() {
{photos.map((photo) => {
const isBusy = busyId === photo.id;
const liveStatus = photo.live_status ?? 'pending';
const galleryStatus = photo.status ?? 'pending';
const canApproveGallery = galleryStatus === 'pending';
const canApproveLiveOnly = galleryStatus === 'approved';
const canApproveLive = canApproveGallery || canApproveLiveOnly;
const showApproveAction = liveStatus !== 'approved';
return (
<MobileCard key={photo.id}>
<XStack space="$3" alignItems="center">
@@ -241,8 +277,8 @@ export default function MobileEventLiveShowQueuePage() {
) : null}
<YStack flex={1} space="$2">
<XStack alignItems="center" space="$2">
<PillBadge tone="success">
{t('liveShowQueue.galleryApproved', 'Gallery approved')}
<PillBadge tone={resolveStatusTone(galleryStatus)}>
{resolveGalleryLabel(galleryStatus)}
</PillBadge>
<PillBadge tone={resolveStatusTone(liveStatus)}>
{t(`liveShowQueue.status.${liveStatus}`, liveStatus)}
@@ -254,11 +290,25 @@ export default function MobileEventLiveShowQueuePage() {
</YStack>
</XStack>
<XStack space="$2" marginTop="$2">
{liveStatus !== 'approved' ? (
{showApproveAction ? (
<CTAButton
label={t('liveShowQueue.approve', 'Approve for Live Show')}
onPress={() => handleApprove(photo)}
disabled={!online}
label={
canApproveGallery
? t('liveShowQueue.approveAndLive', 'Approve + Live')
: canApproveLiveOnly
? t('liveShowQueue.approve', 'Approve for Live Show')
: t('liveShowQueue.notEligible', 'Not eligible')
}
onPress={() => {
if (canApproveGallery) {
void handleApproveAndLive(photo);
return;
}
if (canApproveLiveOnly) {
void handleApprove(photo);
}
}}
disabled={!online || !canApproveLive}
loading={isBusy}
tone="primary"
/>