Fix PayPal billing flow and mobile admin UX
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-02-05 10:19:29 +01:00
parent c43327af74
commit 0d7a861875
39 changed files with 1630 additions and 253 deletions

View File

@@ -54,6 +54,7 @@ export default function BottomNav() {
const { event, status } = useEventData();
const { t } = useTranslation();
const { branding } = useEventBranding();
const navRef = React.useRef<HTMLDivElement | null>(null);
const radius = branding.buttons?.radius ?? 12;
const buttonStyle = branding.buttons?.style ?? 'filled';
const linkColor = branding.buttons?.linkColor ?? branding.secondaryColor;
@@ -83,9 +84,46 @@ export default function BottomNav() {
const compact = isUploadActive;
const navPaddingBottom = `calc(env(safe-area-inset-bottom, 0px) + ${compact ? 12 : 18}px)`;
const setBottomOffset = React.useCallback(() => {
if (typeof document === 'undefined' || !navRef.current) {
return;
}
const height = Math.ceil(navRef.current.getBoundingClientRect().height);
document.documentElement.style.setProperty('--guest-bottom-nav-offset', `${height}px`);
}, []);
React.useLayoutEffect(() => {
if (typeof window === 'undefined') {
return;
}
setBottomOffset();
const handleResize = () => setBottomOffset();
if (typeof ResizeObserver !== 'undefined' && navRef.current) {
const observer = new ResizeObserver(() => setBottomOffset());
observer.observe(navRef.current);
window.addEventListener('resize', handleResize);
return () => {
observer.disconnect();
window.removeEventListener('resize', handleResize);
document.documentElement.style.removeProperty('--guest-bottom-nav-offset');
};
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
document.documentElement.style.removeProperty('--guest-bottom-nav-offset');
};
}, [setBottomOffset, compact]);
return (
<div
ref={navRef}
className={`guest-bottom-nav fixed inset-x-0 bottom-0 z-30 border-t border-white/15 bg-gradient-to-t from-black/55 via-black/30 to-black/5 px-4 shadow-2xl backdrop-blur-xl transition-all duration-200 dark:border-white/10 dark:from-gray-950/85 dark:via-gray-900/55 dark:to-gray-900/20 ${
compact ? 'pt-1' : 'pt-2 pb-1'
}`}

View File

@@ -0,0 +1,104 @@
import React from 'react';
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import BottomNav from '../BottomNav';
const originalGetBoundingClientRect = HTMLElement.prototype.getBoundingClientRect;
const originalResizeObserver = globalThis.ResizeObserver;
vi.mock('../../hooks/useEventData', () => ({
useEventData: () => ({
status: 'ready',
event: {
id: 1,
default_locale: 'de',
},
}),
}));
vi.mock('../../i18n/useTranslation', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('../../context/EventBrandingContext', () => ({
useEventBranding: () => ({
branding: {
primaryColor: '#0f172a',
secondaryColor: '#38bdf8',
backgroundColor: '#ffffff',
palette: {
surface: '#ffffff',
},
buttons: {
radius: 12,
style: 'filled',
linkColor: '#0f172a',
},
},
}),
}));
vi.mock('../../lib/engagement', () => ({
isTaskModeEnabled: () => false,
}));
describe('BottomNav', () => {
beforeEach(() => {
HTMLElement.prototype.getBoundingClientRect = () =>
({
x: 0,
y: 0,
width: 0,
height: 80,
top: 0,
left: 0,
right: 0,
bottom: 80,
toJSON: () => ({}),
}) as DOMRect;
(globalThis as unknown as { ResizeObserver: typeof ResizeObserver }).ResizeObserver = class {
private callback: () => void;
constructor(callback: () => void) {
this.callback = callback;
}
observe() {
this.callback();
}
disconnect() {}
};
document.documentElement.style.removeProperty('--guest-bottom-nav-offset');
});
afterEach(() => {
HTMLElement.prototype.getBoundingClientRect = originalGetBoundingClientRect;
document.documentElement.style.removeProperty('--guest-bottom-nav-offset');
if (originalResizeObserver) {
globalThis.ResizeObserver = originalResizeObserver;
} else {
delete (globalThis as unknown as { ResizeObserver?: typeof ResizeObserver }).ResizeObserver;
}
});
it('sets the bottom nav offset CSS variable', async () => {
render(
<MemoryRouter initialEntries={['/e/demo']}>
<Routes>
<Route path="/e/:token/*" element={<BottomNav />} />
</Routes>
</MemoryRouter>
);
await waitFor(() => {
expect(document.documentElement.style.getPropertyValue('--guest-bottom-nav-offset')).toBe('80px');
});
});
});