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 showQuickControls = canManage && !isPostEvent;
const displayStatus = event.status === 'archived' ? 'archived' : published ? 'published' : 'draft';
const liveGradient = `linear-gradient(135deg, ${theme.primary} 0%, ${theme.accent} 100%)`;
// Header Row
const Header = () => (
@@ -338,13 +339,13 @@ function LifecycleHero({
padding={cardPadding}
backgroundColor={theme.primary}
borderColor="transparent"
style={{ backgroundImage: 'linear-gradient(135deg, #4F46E5 0%, #4338CA 100%)' }}
style={{ backgroundImage: liveGradient }}
>
<YStack gap={isEmbedded ? '$2.5' : '$3'}>
<XStack alignItems="center" justifyContent="space-between">
<YStack gap="$1">
<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}>
{t('dashboard:liveNow.status', 'Happening Now')}
</Text>

View File

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

View File

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

View File

@@ -40,6 +40,10 @@ vi.mock('@tamagui/react-native-web-lite', () => ({
}));
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>,
Tabs: Object.assign(({ 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', () => ({
BottomNav: () => <div data-testid="bottom-nav" />,
BOTTOM_NAV_HEIGHT: 70,
NavKey: {},
}));
@@ -57,6 +62,10 @@ vi.mock('../UserMenuSheet', () => ({
UserMenuSheet: () => <div data-testid="user-menu-sheet" />,
}));
vi.mock('../EventSwitcherSheet', () => ({
EventSwitcherSheet: () => <div data-testid="event-switcher-sheet" />,
}));
const baseEvent: TenantEvent = {
id: 1,
name: 'Test Event',

View File

@@ -1,8 +1,8 @@
import { useTheme, useThemeName } from '@tamagui/core';
export const ADMIN_COLORS = {
primary: '#4F46E5', // Indigo 600
primaryStrong: '#4338CA', // Indigo 700
primary: '#FF5A5F', // Brand Rose
primaryStrong: '#F43F5E', // Rose 500
accent: '#F43F5E', // Rose 500
accentSoft: '#E0E7FF', // Indigo 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 { getTenantPackagesOverview, trackOnboarding } from '../../api';
import { getSelectedPackageId } from '../lib/onboardingSelection';
import { ADMIN_COLORS } from '../theme';
import { useAdminTheme } from '../theme';
export default function WelcomeEventPage() {
const navigate = useNavigate();
const { t } = useTranslation('onboarding');
const theme = useAdminTheme();
const selectedId = getSelectedPackageId();
const { data: overview } = useQuery({
@@ -54,7 +55,7 @@ export default function WelcomeEventPage() {
<Text fontSize="$sm" fontWeight="800">
{t('eventSetup.step.title', 'Event setup in minutes')}
</Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}>
<Text fontSize="$sm" color={theme.muted}>
{t(
'eventSetup.step.description',
'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">
{t('eventSetup.cta.heading', 'Ready for your first event?')}
</Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}>
<Text fontSize="$sm" color={theme.muted}>
{t(
'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.",
@@ -120,23 +121,24 @@ function FeatureRow({
title: string;
body: string;
}) {
const theme = useAdminTheme();
return (
<XStack alignItems="center" gap="$2">
<XStack
width={34}
height={34}
borderRadius={12}
backgroundColor={ADMIN_COLORS.accentSoft}
backgroundColor={theme.accentSoft}
alignItems="center"
justifyContent="center"
>
<Icon size={16} color={ADMIN_COLORS.primary} />
<Icon size={16} color={theme.primary} />
</XStack>
<YStack>
<Text fontSize="$sm" fontWeight="700">
{title}
</Text>
<Text fontSize="$xs" color={ADMIN_COLORS.textMuted}>
<Text fontSize="$xs" color={theme.muted}>
{body}
</Text>
</YStack>

View File

@@ -15,12 +15,13 @@ import {
ADMIN_WELCOME_PACKAGES_PATH,
adminPath,
} from '../../constants';
import { ADMIN_COLORS } from '../theme';
import { useAdminTheme } from '../theme';
export default function WelcomeLandingPage() {
const navigate = useNavigate();
const { t } = useTranslation('onboarding');
const { hasEvents } = useEventContext();
const theme = useAdminTheme();
const { data: packagesData } = useQuery({
queryKey: ['mobile', 'onboarding', 'packages-overview'],
@@ -53,7 +54,7 @@ export default function WelcomeLandingPage() {
<Text fontSize="$lg" fontWeight="900">
{t('hero.title', 'Design the next Fotospiel experience')}
</Text>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}>
<Text fontSize="$sm" color={theme.muted}>
{t(
'hero.description',
'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}
height={36}
borderRadius={12}
backgroundColor={ADMIN_COLORS.accentSoft}
backgroundColor={theme.accentSoft}
alignItems="center"
justifyContent="center"
>
<Icon size={18} color={ADMIN_COLORS.primary} />
<Icon size={18} color={theme.primary} />
</XStack>
<Text fontSize="$sm" fontWeight="800">
{title}
@@ -136,7 +137,7 @@ function FeatureCard({
</XStack>
{badge ? <PillBadge tone="muted">{badge}</PillBadge> : null}
</XStack>
<Text fontSize="$sm" color={ADMIN_COLORS.textMuted}>
<Text fontSize="$sm" color={theme.muted}>
{body}
</Text>
</MobileCard>

View File

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