Moved the Select/Filter controls into the photo list area and added the missing “Select” translation
This commit is contained in:
@@ -178,6 +178,7 @@
|
|||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"loadMore": "Mehr laden",
|
"loadMore": "Mehr laden",
|
||||||
"processing": "Verarbeite …",
|
"processing": "Verarbeite …",
|
||||||
|
"select": "Auswählen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"reset": "Zurücksetzen"
|
"reset": "Zurücksetzen"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -174,6 +174,7 @@
|
|||||||
"all": "All",
|
"all": "All",
|
||||||
"loadMore": "Load more",
|
"loadMore": "Load more",
|
||||||
"processing": "Processing…",
|
"processing": "Processing…",
|
||||||
|
"select": "Select",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"reset": "Reset"
|
"reset": "Reset"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ export default function MobileEventPhotosPage() {
|
|||||||
const surface = String(theme.surface?.val ?? '#ffffff');
|
const surface = String(theme.surface?.val ?? '#ffffff');
|
||||||
const backdrop = String(theme.gray12?.val ?? '#0f172a');
|
const backdrop = String(theme.gray12?.val ?? '#0f172a');
|
||||||
|
|
||||||
|
const basePhotosPath = slug ? adminPath(`/mobile/events/${slug}/photos`) : adminPath('/mobile/events');
|
||||||
|
const photoQuery = React.useMemo(() => {
|
||||||
|
return new URLSearchParams(location.search).get('photo');
|
||||||
|
}, [location.search]);
|
||||||
|
const sourcePhotoParam = photoQuery ?? photoIdParam ?? null;
|
||||||
const lightboxIndex = React.useMemo(() => {
|
const lightboxIndex = React.useMemo(() => {
|
||||||
if (lightboxId === null) {
|
if (lightboxId === null) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -106,12 +111,12 @@ export default function MobileEventPhotosPage() {
|
|||||||
}, [photos, lightboxId]);
|
}, [photos, lightboxId]);
|
||||||
const lightbox = lightboxIndex >= 0 ? photos[lightboxIndex] : null;
|
const lightbox = lightboxIndex >= 0 ? photos[lightboxIndex] : null;
|
||||||
const parsedPhotoId = React.useMemo(() => {
|
const parsedPhotoId = React.useMemo(() => {
|
||||||
if (!photoIdParam) {
|
if (!sourcePhotoParam) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const parsed = Number(photoIdParam);
|
const parsed = Number(sourcePhotoParam);
|
||||||
return Number.isFinite(parsed) ? parsed : null;
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
}, [photoIdParam]);
|
}, [sourcePhotoParam]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (lightboxId !== null && lightboxIndex === -1 && !loading && pendingPhotoId !== lightboxId) {
|
if (lightboxId !== null && lightboxIndex === -1 && !loading && pendingPhotoId !== lightboxId) {
|
||||||
@@ -144,7 +149,8 @@ export default function MobileEventPhotosPage() {
|
|||||||
}, [lightbox]);
|
}, [lightbox]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!photoIdParam) {
|
if (!sourcePhotoParam) {
|
||||||
|
setLightboxId(null);
|
||||||
setPendingPhotoId(null);
|
setPendingPhotoId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,7 +162,7 @@ export default function MobileEventPhotosPage() {
|
|||||||
|
|
||||||
setLightboxId(parsedPhotoId);
|
setLightboxId(parsedPhotoId);
|
||||||
setPendingPhotoId(parsedPhotoId);
|
setPendingPhotoId(parsedPhotoId);
|
||||||
}, [parsedPhotoId, photoIdParam]);
|
}, [parsedPhotoId, sourcePhotoParam]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (slugParam && activeEvent?.slug !== slugParam) {
|
if (slugParam && activeEvent?.slug !== slugParam) {
|
||||||
@@ -333,9 +339,26 @@ export default function MobileEventPhotosPage() {
|
|||||||
}
|
}
|
||||||
}, [online, syncQueuedActions]);
|
}, [online, syncQueuedActions]);
|
||||||
|
|
||||||
const setLightboxWithUrl = React.useCallback((photoId: number | null) => {
|
const setLightboxWithUrl = React.useCallback(
|
||||||
|
(photoId: number | null) => {
|
||||||
setLightboxId(photoId);
|
setLightboxId(photoId);
|
||||||
}, []);
|
if (typeof window === 'undefined' || !slug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
if (photoId) {
|
||||||
|
params.set('photo', String(photoId));
|
||||||
|
} else {
|
||||||
|
params.delete('photo');
|
||||||
|
}
|
||||||
|
const nextSearch = params.toString();
|
||||||
|
const nextPath = `${basePhotosPath}${nextSearch ? `?${nextSearch}` : ''}`;
|
||||||
|
if (`${window.location.pathname}${window.location.search}` !== nextPath) {
|
||||||
|
window.history.replaceState(null, '', nextPath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[basePhotosPath, slug],
|
||||||
|
);
|
||||||
|
|
||||||
const handleModerationAction = React.useCallback(
|
const handleModerationAction = React.useCallback(
|
||||||
async (action: PhotoModerationAction['action'], photo: TenantPhoto) => {
|
async (action: PhotoModerationAction['action'], photo: TenantPhoto) => {
|
||||||
@@ -641,23 +664,6 @@ export default function MobileEventPhotosPage() {
|
|||||||
onBack={back}
|
onBack={back}
|
||||||
headerActions={
|
headerActions={
|
||||||
<XStack space="$3">
|
<XStack space="$3">
|
||||||
<HeaderActionButton
|
|
||||||
onPress={() => {
|
|
||||||
if (selectionMode) {
|
|
||||||
clearSelection();
|
|
||||||
} else {
|
|
||||||
setSelectionMode(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
ariaLabel={selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
|
|
||||||
>
|
|
||||||
<Text fontSize="$xs" fontWeight="800" color={text}>
|
|
||||||
{selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
|
|
||||||
</Text>
|
|
||||||
</HeaderActionButton>
|
|
||||||
<HeaderActionButton onPress={() => setShowFilters(true)} ariaLabel={t('mobilePhotos.filtersTitle', 'Filter')}>
|
|
||||||
<Filter size={18} color={text} />
|
|
||||||
</HeaderActionButton>
|
|
||||||
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
|
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
|
||||||
<RefreshCcw size={18} color={text} />
|
<RefreshCcw size={18} color={text} />
|
||||||
</HeaderActionButton>
|
</HeaderActionButton>
|
||||||
@@ -707,6 +713,27 @@ export default function MobileEventPhotosPage() {
|
|||||||
</MobileCard>
|
</MobileCard>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<XStack space="$2">
|
||||||
|
<CTAButton
|
||||||
|
label={selectionMode ? t('common.done', 'Done') : t('common.select', 'Select')}
|
||||||
|
tone={selectionMode ? 'solid' : 'ghost'}
|
||||||
|
fullWidth={false}
|
||||||
|
onPress={() => {
|
||||||
|
if (selectionMode) {
|
||||||
|
clearSelection();
|
||||||
|
} else {
|
||||||
|
setSelectionMode(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CTAButton
|
||||||
|
label={t('mobilePhotos.filtersTitle', 'Filter')}
|
||||||
|
tone="ghost"
|
||||||
|
fullWidth={false}
|
||||||
|
onPress={() => setShowFilters(true)}
|
||||||
|
/>
|
||||||
|
</XStack>
|
||||||
|
|
||||||
<MobileInput
|
<MobileInput
|
||||||
type="search"
|
type="search"
|
||||||
value={search}
|
value={search}
|
||||||
@@ -808,7 +835,6 @@ export default function MobileEventPhotosPage() {
|
|||||||
position="relative"
|
position="relative"
|
||||||
>
|
>
|
||||||
<motion.img
|
<motion.img
|
||||||
layoutId={`photo-${photo.id}`}
|
|
||||||
src={photo.thumbnail_url ?? photo.url ?? undefined}
|
src={photo.thumbnail_url ?? photo.url ?? undefined}
|
||||||
alt={photo.caption ?? 'Photo'}
|
alt={photo.caption ?? 'Photo'}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@@ -973,7 +999,6 @@ export default function MobileEventPhotosPage() {
|
|||||||
style={{ touchAction: 'none' }}
|
style={{ touchAction: 'none' }}
|
||||||
>
|
>
|
||||||
<motion.img
|
<motion.img
|
||||||
layoutId={`photo-${lightbox.id}`}
|
|
||||||
src={lightboxImageSrc ?? undefined}
|
src={lightboxImageSrc ?? undefined}
|
||||||
alt={lightbox.caption ?? 'Photo'}
|
alt={lightbox.caption ?? 'Photo'}
|
||||||
loading="eager"
|
loading="eager"
|
||||||
|
|||||||
Reference in New Issue
Block a user