Fix TypeScript typecheck errors
This commit is contained in:
@@ -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'}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user