Fix TypeScript typecheck errors
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-30 15:56:06 +01:00
parent 916b204688
commit b1f9f7cee0
42 changed files with 324 additions and 72 deletions

View File

@@ -184,7 +184,7 @@ export const MobileInput = React.forwardRef<HTMLInputElement, React.ComponentPro
{...props}
{...({ type } as any)}
secureTextEntry={isPassword}
onChangeText={(value) => {
onChangeText={(value: string) => {
onChange?.({ target: { value } } as React.ChangeEvent<HTMLInputElement>);
}}
size={compact ? '$3' : '$4'}
@@ -222,7 +222,7 @@ export const MobileTextArea = React.forwardRef<
ref={ref as React.Ref<any>}
{...props}
{...({ minHeight: compact ? 72 : 96 } as any)}
onChangeText={(value) => {
onChangeText={(value: string) => {
onChange?.({ target: { value } } as React.ChangeEvent<HTMLTextAreaElement>);
}}
size={compact ? '$3' : '$4'}
@@ -292,7 +292,7 @@ export function MobileSelect({
<Select
value={selectValue}
defaultValue={selectValue === undefined ? selectDefault : undefined}
onValueChange={(next) => {
onValueChange={(next: string) => {
props.onChange?.({ target: { value: next } } as React.ChangeEvent<HTMLSelectElement>);
}}
size={compact ? '$3' : '$4'}

View File

@@ -107,7 +107,7 @@ export function LegalConsentSheet({
id="legal-terms"
size="$4"
checked={acceptedTerms}
onCheckedChange={(checked) => setAcceptedTerms(Boolean(checked))}
onCheckedChange={(checked: boolean) => setAcceptedTerms(Boolean(checked))}
borderWidth={1}
borderColor={border}
backgroundColor={surface}
@@ -135,7 +135,7 @@ export function LegalConsentSheet({
id="legal-waiver"
size="$4"
checked={acceptedWaiver}
onCheckedChange={(checked) => setAcceptedWaiver(Boolean(checked))}
onCheckedChange={(checked: boolean) => setAcceptedWaiver(Boolean(checked))}
borderWidth={1}
borderColor={border}
backgroundColor={surface}

View File

@@ -18,6 +18,7 @@ import { TenantEvent, getEvents } from '../../api';
import { setTabHistory } from '../lib/tabHistory';
import { loadPhotoQueue } from '../lib/photoModerationQueue';
import { countQueuedPhotoActions } from '../lib/queueStatus';
import { useDocumentTitle } from '../hooks/useDocumentTitle';
import { useAdminTheme } from '../theme';
import { useAuth } from '../../auth/context';
import { EventSwitcherSheet } from './EventSwitcherSheet';
@@ -25,13 +26,14 @@ import { UserMenuSheet } from './UserMenuSheet';
type MobileShellProps = {
title?: string;
subtitle?: string;
children: React.ReactNode;
activeTab: NavKey;
onBack?: () => void;
headerActions?: React.ReactNode;
};
export function MobileShell({ title, children, activeTab, onBack, headerActions }: MobileShellProps) {
export function MobileShell({ title, subtitle, children, activeTab, onBack, headerActions }: MobileShellProps) {
const { events, activeEvent, selectEvent } = useEventContext();
const { user } = useAuth();
const { go } = useMobileNav(activeEvent?.slug, activeTab);
@@ -40,6 +42,8 @@ export function MobileShell({ title, children, activeTab, onBack, headerActions
const { t } = useTranslation('mobile');
const { count: notificationCount } = useNotificationsBadge();
const online = useOnlineStatus();
useDocumentTitle(title);
const theme = useAdminTheme();
@@ -360,6 +364,18 @@ export function MobileShell({ title, children, activeTab, onBack, headerActions
) : null}
</MobileCard>
) : null}
{subtitle ? (
<YStack space="$1">
{title ? (
<Text fontSize="$lg" fontWeight="800" color={theme.textStrong}>
{title}
</Text>
) : null}
<Text fontSize="$sm" color={theme.muted}>
{subtitle}
</Text>
</YStack>
) : null}
{children}
</YStack>

View File

@@ -5,6 +5,7 @@ import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { useTranslation } from 'react-i18next';
import { useAdminTheme } from '../theme';
import { useDocumentTitle } from '../hooks/useDocumentTitle';
type OnboardingShellProps = {
eyebrow?: string;
@@ -34,6 +35,8 @@ export function OnboardingShell({
const resolvedBackLabel = backLabel ?? t('layout.back', 'Back');
const resolvedSkipLabel = skipLabel ?? t('layout.skip', 'Skip');
useDocumentTitle(title);
return (
<YStack minHeight="100vh" backgroundColor={background} alignItems="center">
<YStack

View File

@@ -98,7 +98,7 @@ export function CTAButton({
iconRight,
}: {
label: string;
onPress: () => void;
onPress?: () => void;
tone?: 'primary' | 'ghost' | 'danger';
fullWidth?: boolean;
disabled?: boolean;
@@ -110,7 +110,7 @@ export function CTAButton({
const { primary, surface, border, text, danger, glassSurfaceStrong } = useAdminTheme();
const isPrimary = tone === 'primary';
const isDanger = tone === 'danger';
const isDisabled = disabled || loading;
const isDisabled = disabled || loading || !onPress;
const backgroundColor = isDanger ? danger : isPrimary ? primary : glassSurfaceStrong ?? surface;
const borderColor = isPrimary || isDanger ? 'transparent' : border;
const labelColor = isPrimary || isDanger ? 'white' : text;

View File

@@ -66,8 +66,7 @@ export function SetupChecklist({
height={6}
>
<Progress.Indicator
backgroundColor={isAllComplete ? theme.success : theme.primary}
animation="bouncy"
backgroundColor={isAllComplete ? theme.successText : theme.primary}
/>
</Progress>
</YStack>

View File

@@ -17,7 +17,7 @@ import { MobileSelect } from './FormControls';
type UserMenuSheetProps = {
open: boolean;
onClose: () => void;
user?: { name?: string | null; email?: string | null; avatar_url?: string | null };
user?: { name?: string | null; email?: string | null; avatar_url?: string | null } | null;
isMember: boolean;
navigate: (path: string) => void;
};
@@ -245,7 +245,7 @@ export function UserMenuSheet({ open, onClose, user, isMember, navigate }: UserM
<Switch
size="$2"
checked={isDark}
onCheckedChange={(next) => updateAppearance(next ? 'dark' : 'light')}
onCheckedChange={(next: boolean) => updateAppearance(next ? 'dark' : 'light')}
aria-label={t('mobileProfile.theme', 'Dark Mode')}
>
<Switch.Thumb />

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { act, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import type { EventContextValue } from '../../../context/EventContext';
import type { TenantEvent } from '../../../api';
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string, fallback?: string) => fallback ?? key, i18n: { language: 'en-GB' } }),
@@ -55,12 +57,27 @@ vi.mock('../UserMenuSheet', () => ({
UserMenuSheet: () => <div data-testid="user-menu-sheet" />,
}));
const eventContext = {
const baseEvent: TenantEvent = {
id: 1,
name: 'Test Event',
slug: 'event-1',
event_date: '2024-01-01',
event_type_id: null,
event_type: null,
status: 'published',
settings: {},
};
const eventContext: EventContextValue = {
events: [],
activeEvent: { slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} },
isLoading: false,
isError: false,
activeEvent: baseEvent,
activeSlug: baseEvent.slug,
hasMultipleEvents: false,
hasEvents: true,
selectEvent: vi.fn(),
refetch: vi.fn(),
};
vi.mock('../../../context/EventContext', () => ({
@@ -129,9 +146,25 @@ describe('MobileShell', () => {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
});
document.title = '';
eventContext.events = [];
eventContext.hasMultipleEvents = false;
eventContext.activeEvent = { slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} };
eventContext.activeEvent = { ...baseEvent };
eventContext.activeSlug = baseEvent.slug;
});
it('sets the document title with the app prefix', async () => {
await act(async () => {
render(
<MemoryRouter>
<MobileShell activeTab="home" title="Dashboard">
<div>Body</div>
</MobileShell>
</MemoryRouter>
);
});
expect(document.title).toBe('Fotospiel.App Event Admin · Dashboard');
});
it('renders quick QR as icon-only button', async () => {
@@ -171,8 +204,14 @@ describe('MobileShell', () => {
it('shows the event switcher when multiple events are available', async () => {
eventContext.events = [
{ slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} },
{ slug: 'event-2', name: 'Second Event', event_date: '2024-02-01', status: 'active', settings: {} },
{ ...baseEvent },
{
...baseEvent,
id: 2,
slug: 'event-2',
name: 'Second Event',
event_date: '2024-02-01',
},
];
await act(async () => {
@@ -190,8 +229,14 @@ describe('MobileShell', () => {
it('hides the event switcher on the events list page', async () => {
eventContext.events = [
{ slug: 'event-1', name: 'Test Event', event_date: '2024-01-01', status: 'active', settings: {} },
{ slug: 'event-2', name: 'Second Event', event_date: '2024-02-01', status: 'active', settings: {} },
{ ...baseEvent },
{
...baseEvent,
id: 2,
slug: 'event-2',
name: 'Second Event',
event_date: '2024-02-01',
},
];
await act(async () => {