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",
|
||||
"loadMore": "Mehr laden",
|
||||
"processing": "Verarbeite …",
|
||||
"select": "Auswählen",
|
||||
"close": "Schließen",
|
||||
"reset": "Zurücksetzen"
|
||||
},
|
||||
|
||||
@@ -174,6 +174,7 @@
|
||||
"all": "All",
|
||||
"loadMore": "Load more",
|
||||
"processing": "Processing…",
|
||||
"select": "Select",
|
||||
"close": "Close",
|
||||
"reset": "Reset"
|
||||
},
|
||||
|
||||
@@ -98,6 +98,11 @@ export default function MobileEventPhotosPage() {
|
||||
const surface = String(theme.surface?.val ?? '#ffffff');
|
||||
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(() => {
|
||||
if (lightboxId === null) {
|
||||
return -1;
|
||||
@@ -106,12 +111,12 @@ export default function MobileEventPhotosPage() {
|
||||
}, [photos, lightboxId]);
|
||||
const lightbox = lightboxIndex >= 0 ? photos[lightboxIndex] : null;
|
||||
const parsedPhotoId = React.useMemo(() => {
|
||||
if (!photoIdParam) {
|
||||
if (!sourcePhotoParam) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(photoIdParam);
|
||||
const parsed = Number(sourcePhotoParam);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}, [photoIdParam]);
|
||||
}, [sourcePhotoParam]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (lightboxId !== null && lightboxIndex === -1 && !loading && pendingPhotoId !== lightboxId) {
|
||||
@@ -144,7 +149,8 @@ export default function MobileEventPhotosPage() {
|
||||
}, [lightbox]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!photoIdParam) {
|
||||
if (!sourcePhotoParam) {
|
||||
setLightboxId(null);
|
||||
setPendingPhotoId(null);
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +162,7 @@ export default function MobileEventPhotosPage() {
|
||||
|
||||
setLightboxId(parsedPhotoId);
|
||||
setPendingPhotoId(parsedPhotoId);
|
||||
}, [parsedPhotoId, photoIdParam]);
|
||||
}, [parsedPhotoId, sourcePhotoParam]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (slugParam && activeEvent?.slug !== slugParam) {
|
||||
@@ -333,9 +339,26 @@ export default function MobileEventPhotosPage() {
|
||||
}
|
||||
}, [online, syncQueuedActions]);
|
||||
|
||||
const setLightboxWithUrl = React.useCallback((photoId: number | null) => {
|
||||
setLightboxId(photoId);
|
||||
}, []);
|
||||
const setLightboxWithUrl = React.useCallback(
|
||||
(photoId: number | null) => {
|
||||
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(
|
||||
async (action: PhotoModerationAction['action'], photo: TenantPhoto) => {
|
||||
@@ -641,23 +664,6 @@ export default function MobileEventPhotosPage() {
|
||||
onBack={back}
|
||||
headerActions={
|
||||
<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')}>
|
||||
<RefreshCcw size={18} color={text} />
|
||||
</HeaderActionButton>
|
||||
@@ -707,6 +713,27 @@ export default function MobileEventPhotosPage() {
|
||||
</MobileCard>
|
||||
) : 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
|
||||
type="search"
|
||||
value={search}
|
||||
@@ -808,7 +835,6 @@ export default function MobileEventPhotosPage() {
|
||||
position="relative"
|
||||
>
|
||||
<motion.img
|
||||
layoutId={`photo-${photo.id}`}
|
||||
src={photo.thumbnail_url ?? photo.url ?? undefined}
|
||||
alt={photo.caption ?? 'Photo'}
|
||||
loading="lazy"
|
||||
@@ -973,7 +999,6 @@ export default function MobileEventPhotosPage() {
|
||||
style={{ touchAction: 'none' }}
|
||||
>
|
||||
<motion.img
|
||||
layoutId={`photo-${lightbox.id}`}
|
||||
src={lightboxImageSrc ?? undefined}
|
||||
alt={lightbox.caption ?? 'Photo'}
|
||||
loading="eager"
|
||||
|
||||
Reference in New Issue
Block a user