photo visibility for demo events, hardened the demo mode. fixed dark/light mode toggle and notification bell toggle. fixed photo upload page sizes & header visibility.
This commit is contained in:
@@ -41,6 +41,7 @@ import { compressPhoto, formatBytes } from '../lib/image';
|
||||
import { useGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import { useEventData } from '../hooks/useEventData';
|
||||
import { isTaskModeEnabled } from '../lib/engagement';
|
||||
import { getDeviceId } from '../lib/device';
|
||||
|
||||
interface Task {
|
||||
id: number;
|
||||
@@ -130,6 +131,7 @@ export default function UploadPage() {
|
||||
const bodyFont = branding.typography?.body ?? branding.fontFamily ?? undefined;
|
||||
const uploadsRequireApproval =
|
||||
(event?.guest_upload_visibility as 'immediate' | 'review' | undefined) !== 'immediate';
|
||||
const demoReadOnly = Boolean(event?.demo_read_only);
|
||||
|
||||
const taskIdParam = searchParams.get('task');
|
||||
const emotionSlug = searchParams.get('emotion') || '';
|
||||
@@ -154,9 +156,11 @@ export default function UploadPage() {
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [uploadError, setUploadError] = useState<string | null>(null);
|
||||
const [uploadWarning, setUploadWarning] = useState<string | null>(null);
|
||||
const [immersiveMode, setImmersiveMode] = useState(false);
|
||||
const [immersiveMode, setImmersiveMode] = useState(true);
|
||||
const [showCelebration, setShowCelebration] = useState(false);
|
||||
const [showHeroOverlay, setShowHeroOverlay] = useState(true);
|
||||
const kpiChipsRef = useRef<HTMLDivElement | null>(null);
|
||||
const navSentinelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [errorDialog, setErrorDialog] = useState<UploadErrorDialog | null>(null);
|
||||
const [taskDetailsExpanded, setTaskDetailsExpanded] = useState(false);
|
||||
@@ -172,15 +176,35 @@ const [canUpload, setCanUpload] = useState(true);
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined') return undefined;
|
||||
const className = 'guest-immersive';
|
||||
if (immersiveMode) {
|
||||
document.body.classList.add(className);
|
||||
} else {
|
||||
document.body.classList.remove(className);
|
||||
}
|
||||
document.body.classList.add(className);
|
||||
document.body.classList.add('guest-nav-visible'); // show nav by default on upload page
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove(className);
|
||||
document.body.classList.remove('guest-nav-visible');
|
||||
};
|
||||
}, [immersiveMode]);
|
||||
}, []);
|
||||
|
||||
const updateNavVisibility = useCallback(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
// nav is always visible on upload page unless user explicitly toggles immersive off via button
|
||||
document.body.classList.add('guest-nav-visible');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure nav remains visible; hide only when immersive toggled off via the menu button
|
||||
updateNavVisibility();
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove('guest-nav-visible');
|
||||
};
|
||||
}, [updateNavVisibility]);
|
||||
|
||||
const [showPrimer, setShowPrimer] = useState<boolean>(() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
@@ -330,6 +354,13 @@ const [canUpload, setCanUpload] = useState(true);
|
||||
if (!eventKey) return;
|
||||
|
||||
const checkLimits = async () => {
|
||||
if (demoReadOnly) {
|
||||
setCanUpload(false);
|
||||
setUploadError(t('upload.demoReadOnly', 'Uploads sind in der Demo deaktiviert.'));
|
||||
setUploadWarning(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const pkg = await getEventPackage(eventKey);
|
||||
setEventPackage(pkg);
|
||||
@@ -374,7 +405,7 @@ const [canUpload, setCanUpload] = useState(true);
|
||||
};
|
||||
|
||||
checkLimits();
|
||||
}, [eventKey, t]);
|
||||
}, [demoReadOnly, eventKey, t]);
|
||||
|
||||
const stopStream = useCallback(() => {
|
||||
if (streamRef.current) {
|
||||
@@ -1111,7 +1142,7 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
<>
|
||||
<div
|
||||
ref={cameraShellRef as unknown as React.RefObject<HTMLDivElement>}
|
||||
className="relative flex min-h-screen flex-col gap-4 pb-[calc(env(safe-area-inset-bottom,0px)+12px)] pt-3"
|
||||
className="relative flex min-h-screen flex-col gap-4 pb-[calc(env(safe-area-inset-bottom,0px)+72px)] pt-3"
|
||||
style={bodyFont ? { fontFamily: bodyFont } : undefined}
|
||||
>
|
||||
{taskFloatingCard}
|
||||
@@ -1130,9 +1161,9 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
ref={cameraViewportRef}
|
||||
className="relative w-full"
|
||||
style={{
|
||||
height: 'calc(100vh - 160px)',
|
||||
minHeight: '70vh',
|
||||
maxHeight: '90vh',
|
||||
height: 'clamp(60vh, calc(100vh - 220px), 82vh)',
|
||||
minHeight: '60vh',
|
||||
maxHeight: '88vh',
|
||||
}}
|
||||
>
|
||||
<video
|
||||
@@ -1402,17 +1433,20 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[
|
||||
</div>
|
||||
|
||||
{socialChips.length > 0 && (
|
||||
<div className="mt-4 flex gap-3 overflow-x-auto pb-2">
|
||||
{socialChips.map((chip) => (
|
||||
<div
|
||||
key={chip.id}
|
||||
className="shrink-0 rounded-full border border-white/15 bg-white/80 px-4 py-2 text-xs font-semibold text-slate-800 shadow dark:border-white/10 dark:bg-white/10 dark:text-white"
|
||||
>
|
||||
<span className="block text-[10px] uppercase tracking-wide opacity-70">{chip.label}</span>
|
||||
<span className="text-sm">{chip.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div ref={navSentinelRef} data-testid="nav-visibility-sentinel" className="h-px w-full" />
|
||||
<div ref={kpiChipsRef} data-testid="upload-kpi-chips" className="mt-4 flex gap-3 overflow-x-auto pb-2">
|
||||
{socialChips.map((chip) => (
|
||||
<div
|
||||
key={chip.id}
|
||||
className="shrink-0 rounded-full border border-white/15 bg-white/80 px-4 py-2 text-xs font-semibold text-slate-800 shadow dark:border-white/10 dark:bg-white/10 dark:text-white"
|
||||
>
|
||||
<span className="block text-[10px] uppercase tracking-wide opacity-70">{chip.label}</span>
|
||||
<span className="text-sm">{chip.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{permissionState !== 'granted' && renderPermissionNotice()}
|
||||
|
||||
Reference in New Issue
Block a user