Align admin mobile colors with Tamagui v2 tokens

This commit is contained in:
Codex Agent
2026-02-04 11:44:07 +01:00
parent 0eacb5646c
commit 56a39d0535
8 changed files with 60 additions and 41 deletions

View File

@@ -316,6 +316,7 @@ function LifecycleHero({
const isPostEvent = phase === 'post'; const isPostEvent = phase === 'post';
const showQuickControls = canManage && !isPostEvent; const showQuickControls = canManage && !isPostEvent;
const displayStatus = event.status === 'archived' ? 'archived' : published ? 'published' : 'draft'; const displayStatus = event.status === 'archived' ? 'archived' : published ? 'published' : 'draft';
const liveGradient = `linear-gradient(135deg, ${theme.primary} 0%, ${theme.accent} 100%)`;
// Header Row // Header Row
const Header = () => ( const Header = () => (
@@ -338,13 +339,13 @@ function LifecycleHero({
padding={cardPadding} padding={cardPadding}
backgroundColor={theme.primary} backgroundColor={theme.primary}
borderColor="transparent" borderColor="transparent"
style={{ backgroundImage: 'linear-gradient(135deg, #4F46E5 0%, #4338CA 100%)' }} style={{ backgroundImage: liveGradient }}
> >
<YStack gap={isEmbedded ? '$2.5' : '$3'}> <YStack gap={isEmbedded ? '$2.5' : '$3'}>
<XStack alignItems="center" justifyContent="space-between"> <XStack alignItems="center" justifyContent="space-between">
<YStack gap="$1"> <YStack gap="$1">
<XStack alignItems="center" gap="$2"> <XStack alignItems="center" gap="$2">
<YStack width={8} height={8} borderRadius={4} backgroundColor="#22C55E" /> <YStack width={8} height={8} borderRadius={4} backgroundColor={theme.successText} />
<Text color="white" fontWeight="700" fontSize="$xs" textTransform="uppercase" letterSpacing={1}> <Text color="white" fontWeight="700" fontSize="$xs" textTransform="uppercase" letterSpacing={1}>
{t('dashboard:liveNow.status', 'Happening Now')} {t('dashboard:liveNow.status', 'Happening Now')}
</Text> </Text>

View File

@@ -148,9 +148,9 @@ function PhotoGridTile({
badges: string[]; badges: string[];
isBusy: boolean; isBusy: boolean;
}) { }) {
const { border, muted, surfaceMuted } = useAdminTheme(); const { border, muted, surfaceMuted, overlay } = useAdminTheme();
const overlayBg = withAlpha('#0f172a', 0.55); const overlayBg = overlay;
const actionBg = withAlpha('#0f172a', 0.55); const actionBg = overlay;
return ( return (
<div <div

View File

@@ -48,10 +48,14 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
const backgroundColor = theme.background; const backgroundColor = theme.background;
const [isCompactHeader, setIsCompactHeader] = React.useState(false); const [isCompactHeader, setIsCompactHeader] = React.useState(false);
// --- DARK HEADER --- const headerSurface = theme.surface;
const headerSurface = '#0F172A'; // Slate 900 const headerText = theme.text;
const headerMuted = theme.muted;
const actionSurface = theme.primary; const actionSurface = theme.primary;
const actionBorder = 'rgba(255, 255, 255, 0.1)'; const actionBorder = theme.glassBorder;
const pillSurface = theme.glassSurface;
const pillSurfacePress = theme.glassSurfaceStrong;
const pillBorder = theme.glassBorder;
const [fallbackEvents, setFallbackEvents] = React.useState<TenantEvent[]>([]); const [fallbackEvents, setFallbackEvents] = React.useState<TenantEvent[]>([]);
const [loadingEvents, setLoadingEvents] = React.useState(false); const [loadingEvents, setLoadingEvents] = React.useState(false);
@@ -144,7 +148,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
const EventContextPill = () => { const EventContextPill = () => {
if (!effectiveActive || isEventsIndex || isCompactHeader) { if (!effectiveActive || isEventsIndex || isCompactHeader) {
return ( return (
<Text fontSize="$md" fontWeight="700" fontFamily="$display" color="white"> <Text fontSize="$md" fontWeight="700" fontFamily="$display" color={headerText}>
{pageTitle} {pageTitle}
</Text> </Text>
); );
@@ -158,7 +162,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
fontSize="$lg" fontSize="$lg"
fontWeight="700" fontWeight="700"
fontFamily="$display" fontFamily="$display"
color="white" color={headerText}
numberOfLines={1} numberOfLines={1}
ellipsizeMode="tail" ellipsizeMode="tail"
> >
@@ -170,7 +174,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
return ( return (
<Pressable onPress={() => setSwitcherOpen(true)} aria-label={t('header.eventSwitcher', 'Switch event')}> <Pressable onPress={() => setSwitcherOpen(true)} aria-label={t('header.eventSwitcher', 'Switch event')}>
<XStack <XStack
backgroundColor="rgba(255, 255, 255, 0.12)" backgroundColor={pillSurface}
paddingHorizontal="$3" paddingHorizontal="$3"
paddingVertical="$1.5" paddingVertical="$1.5"
borderRadius={999} borderRadius={999}
@@ -178,20 +182,20 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
gap="$1.5" gap="$1.5"
maxWidth={220} maxWidth={220}
borderWidth={1} borderWidth={1}
borderColor="rgba(255, 255, 255, 0.08)" borderColor={pillBorder}
pressStyle={{ backgroundColor: 'rgba(255, 255, 255, 0.2)' }} pressStyle={{ backgroundColor: pillSurfacePress }}
> >
<Text <Text
fontSize="$sm" fontSize="$sm"
fontWeight="700" fontWeight="700"
color="white" color={headerText}
numberOfLines={1} numberOfLines={1}
ellipsizeMode="tail" ellipsizeMode="tail"
flexShrink={1} flexShrink={1}
> >
{displayName} {displayName}
</Text> </Text>
<ChevronsUpDown size={14} color="rgba(255,255,255,0.6)" /> <ChevronsUpDown size={14} color={headerMuted} />
</XStack> </XStack>
</Pressable> </Pressable>
); );
@@ -200,7 +204,7 @@ export function MobileShell({ title, subtitle, children, activeTab, onBack, head
const headerBackButton = onBack ? ( const headerBackButton = onBack ? (
<HeaderActionButton onPress={onBack} ariaLabel={t('actions.back', 'Back')}> <HeaderActionButton onPress={onBack} ariaLabel={t('actions.back', 'Back')}>
<XStack alignItems="center" gap="$1.5"> <XStack alignItems="center" gap="$1.5">
<ChevronLeft size={28} color="white" strokeWidth={2.5} /> <ChevronLeft size={28} color={headerText} strokeWidth={2.5} />
</XStack> </XStack>
</HeaderActionButton> </HeaderActionButton>
) : ( ) : (

View File

@@ -40,6 +40,10 @@ vi.mock('@tamagui/react-native-web-lite', () => ({
})); }));
vi.mock('tamagui', () => ({ vi.mock('tamagui', () => ({
YStack: ({ children, ...props }: { children: React.ReactNode }) => <div {...props}>{children}</div>,
XStack: ({ children, ...props }: { children: React.ReactNode }) => <div {...props}>{children}</div>,
SizableText: ({ children, ...props }: { children: React.ReactNode }) => <span {...props}>{children}</span>,
Image: ({ src, ...props }: { src?: string }) => <img src={src} alt="" {...props} />,
Separator: ({ children }: { children?: React.ReactNode }) => <div>{children}</div>, Separator: ({ children }: { children?: React.ReactNode }) => <div>{children}</div>,
Tabs: Object.assign(({ children }: { children: React.ReactNode }) => <div>{children}</div>, { Tabs: Object.assign(({ children }: { children: React.ReactNode }) => <div>{children}</div>, {
List: ({ children }: { children: React.ReactNode }) => <div>{children}</div>, List: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
@@ -50,6 +54,7 @@ vi.mock('tamagui', () => ({
vi.mock('../BottomNav', () => ({ vi.mock('../BottomNav', () => ({
BottomNav: () => <div data-testid="bottom-nav" />, BottomNav: () => <div data-testid="bottom-nav" />,
BOTTOM_NAV_HEIGHT: 70,
NavKey: {}, NavKey: {},
})); }));
@@ -57,6 +62,10 @@ vi.mock('../UserMenuSheet', () => ({
UserMenuSheet: () => <div data-testid="user-menu-sheet" />, UserMenuSheet: () => <div data-testid="user-menu-sheet" />,
})); }));
vi.mock('../EventSwitcherSheet', () => ({
EventSwitcherSheet: () => <div data-testid="event-switcher-sheet" />,
}));
const baseEvent: TenantEvent = { const baseEvent: TenantEvent = {
id: 1, id: 1,
name: 'Test Event', name: 'Test Event',

View File

@@ -1,8 +1,8 @@
import { useTheme, useThemeName } from '@tamagui/core'; import { useTheme, useThemeName } from '@tamagui/core';
export const ADMIN_COLORS = { export const ADMIN_COLORS = {
primary: '#4F46E5', // Indigo 600 primary: '#FF5A5F', // Brand Rose
primaryStrong: '#4338CA', // Indigo 700 primaryStrong: '#F43F5E', // Rose 500
accent: '#F43F5E', // Rose 500 accent: '#F43F5E', // Rose 500
accentSoft: '#E0E7FF', // Indigo 100 accentSoft: '#E0E7FF', // Indigo 100
accentWarm: '#FFE4E6', // Rose 100 accentWarm: '#FFE4E6', // Rose 100

View File

@@ -10,11 +10,12 @@ import { MobileCard, CTAButton } from '../components/Primitives';
import { ADMIN_HOME_PATH, ADMIN_WELCOME_BASE_PATH, ADMIN_WELCOME_PACKAGES_PATH, ADMIN_WELCOME_SUMMARY_PATH, adminPath } from '../../constants'; import { ADMIN_HOME_PATH, ADMIN_WELCOME_BASE_PATH, ADMIN_WELCOME_PACKAGES_PATH, ADMIN_WELCOME_SUMMARY_PATH, adminPath } from '../../constants';
import { getTenantPackagesOverview, trackOnboarding } from '../../api'; import { getTenantPackagesOverview, trackOnboarding } from '../../api';
import { getSelectedPackageId } from '../lib/onboardingSelection'; import { getSelectedPackageId } from '../lib/onboardingSelection';
import { ADMIN_COLORS } from '../theme'; import { useAdminTheme } from '../theme';
export default function WelcomeEventPage() { export default function WelcomeEventPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation('onboarding'); const { t } = useTranslation('onboarding');
const theme = useAdminTheme();
const selectedId = getSelectedPackageId(); const selectedId = getSelectedPackageId();
const { data: overview } = useQuery({ const { data: overview } = useQuery({
@@ -54,7 +55,7 @@ export default function WelcomeEventPage() {
<Text fontSize="$sm" fontWeight="800"> <Text fontSize="$sm" fontWeight="800">
{t('eventSetup.step.title', 'Event setup in minutes')} {t('eventSetup.step.title', 'Event setup in minutes')}
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{t( {t(
'eventSetup.step.description', 'eventSetup.step.description',
'We guide you through name, date, mood, and tasks. Afterwards you can moderate photos and support guests live.', 'We guide you through name, date, mood, and tasks. Afterwards you can moderate photos and support guests live.',
@@ -83,7 +84,7 @@ export default function WelcomeEventPage() {
<Text fontSize="$sm" fontWeight="800"> <Text fontSize="$sm" fontWeight="800">
{t('eventSetup.cta.heading', 'Ready for your first event?')} {t('eventSetup.cta.heading', 'Ready for your first event?')}
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{t( {t(
'eventSetup.cta.description', 'eventSetup.cta.description',
"You're switching to the event manager. Assign tasks, invite members, and test the gallery. You can always return to the welcome journey.", "You're switching to the event manager. Assign tasks, invite members, and test the gallery. You can always return to the welcome journey.",
@@ -120,23 +121,24 @@ function FeatureRow({
title: string; title: string;
body: string; body: string;
}) { }) {
const theme = useAdminTheme();
return ( return (
<XStack alignItems="center" gap="$2"> <XStack alignItems="center" gap="$2">
<XStack <XStack
width={34} width={34}
height={34} height={34}
borderRadius={12} borderRadius={12}
backgroundColor={ADMIN_COLORS.accentSoft} backgroundColor={theme.accentSoft}
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Icon size={16} color={ADMIN_COLORS.primary} /> <Icon size={16} color={theme.primary} />
</XStack> </XStack>
<YStack> <YStack>
<Text fontSize="$sm" fontWeight="700"> <Text fontSize="$sm" fontWeight="700">
{title} {title}
</Text> </Text>
<Text fontSize="$xs" color={ADMIN_COLORS.textMuted}> <Text fontSize="$xs" color={theme.muted}>
{body} {body}
</Text> </Text>
</YStack> </YStack>

View File

@@ -15,12 +15,13 @@ import {
ADMIN_WELCOME_PACKAGES_PATH, ADMIN_WELCOME_PACKAGES_PATH,
adminPath, adminPath,
} from '../../constants'; } from '../../constants';
import { ADMIN_COLORS } from '../theme'; import { useAdminTheme } from '../theme';
export default function WelcomeLandingPage() { export default function WelcomeLandingPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation('onboarding'); const { t } = useTranslation('onboarding');
const { hasEvents } = useEventContext(); const { hasEvents } = useEventContext();
const theme = useAdminTheme();
const { data: packagesData } = useQuery({ const { data: packagesData } = useQuery({
queryKey: ['mobile', 'onboarding', 'packages-overview'], queryKey: ['mobile', 'onboarding', 'packages-overview'],
@@ -53,7 +54,7 @@ export default function WelcomeLandingPage() {
<Text fontSize="$lg" fontWeight="900"> <Text fontSize="$lg" fontWeight="900">
{t('hero.title', 'Design the next Fotospiel experience')} {t('hero.title', 'Design the next Fotospiel experience')}
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{t( {t(
'hero.description', 'hero.description',
'In just a few steps you guide guests through a magical photo journey complete with storytelling, tasks, and a moderated gallery.', 'In just a few steps you guide guests through a magical photo journey complete with storytelling, tasks, and a moderated gallery.',
@@ -124,11 +125,11 @@ function FeatureCard({
width={36} width={36}
height={36} height={36}
borderRadius={12} borderRadius={12}
backgroundColor={ADMIN_COLORS.accentSoft} backgroundColor={theme.accentSoft}
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Icon size={18} color={ADMIN_COLORS.primary} /> <Icon size={18} color={theme.primary} />
</XStack> </XStack>
<Text fontSize="$sm" fontWeight="800"> <Text fontSize="$sm" fontWeight="800">
{title} {title}
@@ -136,7 +137,7 @@ function FeatureCard({
</XStack> </XStack>
{badge ? <PillBadge tone="muted">{badge}</PillBadge> : null} {badge ? <PillBadge tone="muted">{badge}</PillBadge> : null}
</XStack> </XStack>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{body} {body}
</Text> </Text>
</MobileCard> </MobileCard>

View File

@@ -10,7 +10,7 @@ import { MobileCard, CTAButton, PillBadge } from '../components/Primitives';
import { getPackages, getTenantPackagesOverview } from '../../api'; import { getPackages, getTenantPackagesOverview } from '../../api';
import { ADMIN_WELCOME_BASE_PATH, ADMIN_WELCOME_EVENT_PATH, ADMIN_WELCOME_PACKAGES_PATH, adminPath } from '../../constants'; import { ADMIN_WELCOME_BASE_PATH, ADMIN_WELCOME_EVENT_PATH, ADMIN_WELCOME_PACKAGES_PATH, adminPath } from '../../constants';
import { getSelectedPackageId } from '../lib/onboardingSelection'; import { getSelectedPackageId } from '../lib/onboardingSelection';
import { ADMIN_COLORS } from '../theme'; import { useAdminTheme } from '../theme';
type SummaryPackage = { type SummaryPackage = {
id: number; id: number;
@@ -25,6 +25,7 @@ type SummaryPackage = {
export default function WelcomeSummaryPage() { export default function WelcomeSummaryPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation('onboarding'); const { t } = useTranslation('onboarding');
const theme = useAdminTheme();
const selectedId = getSelectedPackageId(); const selectedId = getSelectedPackageId();
const { data: catalog, isLoading: catalogLoading } = useQuery({ const { data: catalog, isLoading: catalogLoading } = useQuery({
@@ -79,7 +80,7 @@ export default function WelcomeSummaryPage() {
> >
{loading ? ( {loading ? (
<MobileCard> <MobileCard>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{t('summary.state.loading', 'Checking available packages …')} {t('summary.state.loading', 'Checking available packages …')}
</Text> </Text>
</MobileCard> </MobileCard>
@@ -88,7 +89,7 @@ export default function WelcomeSummaryPage() {
<Text fontSize="$sm" fontWeight="800"> <Text fontSize="$sm" fontWeight="800">
{t('summary.state.missingTitle', 'No package selected')} {t('summary.state.missingTitle', 'No package selected')}
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{t('summary.state.missingDescription', 'Select a package first or refresh if data changed.')} {t('summary.state.missingDescription', 'Select a package first or refresh if data changed.')}
</Text> </Text>
<CTAButton label={t('summary.footer.back', 'Back to package selection')} onPress={() => navigate(ADMIN_WELCOME_PACKAGES_PATH)} /> <CTAButton label={t('summary.footer.back', 'Back to package selection')} onPress={() => navigate(ADMIN_WELCOME_PACKAGES_PATH)} />
@@ -101,17 +102,17 @@ export default function WelcomeSummaryPage() {
width={36} width={36}
height={36} height={36}
borderRadius={12} borderRadius={12}
backgroundColor={ADMIN_COLORS.accentSoft} backgroundColor={theme.accentSoft}
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<PackageIcon size={18} color={ADMIN_COLORS.primary} /> <PackageIcon size={18} color={theme.primary} />
</XStack> </XStack>
<YStack> <YStack>
<Text fontSize="$sm" fontWeight="800"> <Text fontSize="$sm" fontWeight="800">
{resolvedPackage.name} {resolvedPackage.name}
</Text> </Text>
<Text fontSize="$xs" color={ADMIN_COLORS.textMuted}> <Text fontSize="$xs" color={theme.muted}>
{resolvedPackage.active {resolvedPackage.active
? t('summary.details.section.statusActive', 'Already purchased') ? t('summary.details.section.statusActive', 'Already purchased')
: t('summary.details.section.statusInactive', 'Not purchased yet')} : t('summary.details.section.statusInactive', 'Not purchased yet')}
@@ -149,8 +150,8 @@ export default function WelcomeSummaryPage() {
{resolvedPackage.active ? ( {resolvedPackage.active ? (
<XStack alignItems="center" gap="$2"> <XStack alignItems="center" gap="$2">
<CheckCircle2 size={18} color={ADMIN_COLORS.success} /> <CheckCircle2 size={18} color={theme.successText} />
<Text fontSize="$sm" color={ADMIN_COLORS.success} fontWeight="700"> <Text fontSize="$sm" color={theme.successText} fontWeight="700">
{t('summary.details.section.statusActive', 'Already purchased')} {t('summary.details.section.statusActive', 'Already purchased')}
</Text> </Text>
</XStack> </XStack>
@@ -172,10 +173,10 @@ export default function WelcomeSummaryPage() {
], ],
}) as string[]).map((item) => ( }) as string[]).map((item) => (
<XStack key={item} gap="$2"> <XStack key={item} gap="$2">
<Text fontSize="$xs" color={ADMIN_COLORS.textMuted}> <Text fontSize="$xs" color={theme.muted}>
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{item} {item}
</Text> </Text>
</XStack> </XStack>
@@ -202,12 +203,13 @@ export default function WelcomeSummaryPage() {
} }
function SummaryRow({ label, value }: { label: string; value: string }) { function SummaryRow({ label, value }: { label: string; value: string }) {
const theme = useAdminTheme();
return ( return (
<XStack alignItems="center" justifyContent="space-between"> <XStack alignItems="center" justifyContent="space-between">
<Text fontSize="$sm" color={ADMIN_COLORS.text}> <Text fontSize="$sm" color={theme.text}>
{label} {label}
</Text> </Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}> <Text fontSize="$sm" color={theme.muted}>
{value} {value}
</Text> </Text>
</XStack> </XStack>