neues Admin UI Layout eingeführt. Alle Tests auf den neusten Stand gebracht.
This commit is contained in:
@@ -15,6 +15,7 @@ import { MobileSheet } from './components/Sheet';
|
||||
import toast from 'react-hot-toast';
|
||||
import { adminPath } from '../constants';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { ADMIN_COLORS, ADMIN_GRADIENTS, useAdminTheme } from './theme';
|
||||
|
||||
type BrandingForm = {
|
||||
primary: string;
|
||||
@@ -54,11 +55,12 @@ export default function MobileBrandingPage() {
|
||||
const slug = slugParam ?? null;
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('management');
|
||||
const { textStrong, muted, subtle, border, primary, accentSoft, danger, surfaceMuted, surface } = useAdminTheme();
|
||||
|
||||
const [event, setEvent] = React.useState<TenantEvent | null>(null);
|
||||
const [form, setForm] = React.useState<BrandingForm>({
|
||||
primary: '#007AFF',
|
||||
accent: '#5AD2F4',
|
||||
primary: ADMIN_COLORS.primary,
|
||||
accent: ADMIN_COLORS.accent,
|
||||
headingFont: '',
|
||||
bodyFont: '',
|
||||
logoDataUrl: '',
|
||||
@@ -118,8 +120,8 @@ export default function MobileBrandingPage() {
|
||||
}, [showFontsSheet, fontsLoaded]);
|
||||
|
||||
const previewTitle = event ? renderName(event.name) : t('events.placeholders.untitled', 'Unbenanntes Event');
|
||||
const previewHeadingFont = form.headingFont || 'Montserrat';
|
||||
const previewBodyFont = form.bodyFont || 'Montserrat';
|
||||
const previewHeadingFont = form.headingFont || 'Fraunces';
|
||||
const previewBodyFont = form.bodyFont || 'Manrope';
|
||||
const watermarkAllowed = event?.package?.watermark_allowed !== false;
|
||||
const brandingAllowed = isBrandingAllowed(event ?? null);
|
||||
const watermarkLocked = watermarkAllowed && !brandingAllowed;
|
||||
@@ -229,7 +231,7 @@ export default function MobileBrandingPage() {
|
||||
return (
|
||||
<>
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{t('events.watermark.previewTitle', 'Watermark Preview')}
|
||||
</Text>
|
||||
<WatermarkPreview
|
||||
@@ -244,7 +246,7 @@ export default function MobileBrandingPage() {
|
||||
|
||||
{disabled ? (
|
||||
<InfoBadge
|
||||
icon={<Lock size={16} color="#b91c1c" />}
|
||||
icon={<Lock size={16} color={danger} />}
|
||||
text={t('events.watermark.lockedDisabled', 'Kein Wasserzeichen in diesem Paket.')}
|
||||
tone="danger"
|
||||
/>
|
||||
@@ -252,13 +254,13 @@ export default function MobileBrandingPage() {
|
||||
|
||||
{watermarkLocked ? (
|
||||
<InfoBadge
|
||||
icon={<Lock size={16} color="#111827" />}
|
||||
icon={<Lock size={16} color={textStrong} />}
|
||||
text={t('events.watermark.lockedBranding', 'Custom-Wasserzeichen ist im aktuellen Paket gesperrt. Standard wird genutzt.')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.watermark.title', 'Wasserzeichen')}
|
||||
</Text>
|
||||
|
||||
@@ -291,7 +293,7 @@ export default function MobileBrandingPage() {
|
||||
|
||||
{mode === 'custom' && !controlsLocked ? (
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{t('events.watermark.upload', 'Wasserzeichen hochladen')}
|
||||
</Text>
|
||||
<Pressable onPress={() => document.getElementById('watermark-upload-input')?.click()}>
|
||||
@@ -302,11 +304,11 @@ export default function MobileBrandingPage() {
|
||||
paddingVertical="$2.5"
|
||||
borderRadius={12}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
backgroundColor="white"
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<UploadCloud size={18} color="#007AFF" />
|
||||
<Text fontSize="$sm" color="#007AFF" fontWeight="700">
|
||||
<UploadCloud size={18} color={primary} />
|
||||
<Text fontSize="$sm" color={primary} fontWeight="700">
|
||||
{watermarkForm.assetPath
|
||||
? t('events.watermark.replace', 'Wasserzeichen ersetzen')
|
||||
: t('events.watermark.uploadCta', 'PNG/SVG/JPG (max. 3 MB)')}
|
||||
@@ -334,7 +336,7 @@ export default function MobileBrandingPage() {
|
||||
reader.readAsDataURL(file);
|
||||
}}
|
||||
/>
|
||||
<Text fontSize="$xs" color="#6b7280">
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('events.watermark.uploadHint', 'PNG mit transparenter Fläche empfohlen.')}
|
||||
</Text>
|
||||
</YStack>
|
||||
@@ -342,7 +344,7 @@ export default function MobileBrandingPage() {
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.watermark.placement', 'Position & Größe')}
|
||||
</Text>
|
||||
<PositionGrid
|
||||
@@ -408,13 +410,13 @@ export default function MobileBrandingPage() {
|
||||
onBack={back}
|
||||
headerActions={
|
||||
<HeaderActionButton onPress={() => handleSave()} ariaLabel={t('common.save', 'Save')}>
|
||||
<Save size={18} color="#007AFF" />
|
||||
<Save size={18} color={primary} />
|
||||
</HeaderActionButton>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<MobileCard>
|
||||
<Text fontWeight="700" color="#b91c1c">
|
||||
<Text fontWeight="700" color={danger}>
|
||||
{error}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
@@ -430,17 +432,17 @@ export default function MobileBrandingPage() {
|
||||
{activeTab === 'branding' ? (
|
||||
<>
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{t('events.branding.previewTitle', 'Guest App Preview')}
|
||||
</Text>
|
||||
<YStack borderRadius={16} borderWidth={1} borderColor="#e5e7eb" backgroundColor="#f8fafc" padding="$3" space="$2" alignItems="center">
|
||||
<YStack width="100%" borderRadius={12} backgroundColor="white" borderWidth={1} borderColor="#e5e7eb" overflow="hidden">
|
||||
<YStack borderRadius={16} borderWidth={1} borderColor={border} backgroundColor={surfaceMuted} padding="$3" space="$2" alignItems="center">
|
||||
<YStack width="100%" borderRadius={12} backgroundColor={surface} borderWidth={1} borderColor={border} overflow="hidden">
|
||||
<YStack backgroundColor={form.primary} height={64} />
|
||||
<YStack padding="$3" space="$1.5">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827" style={{ fontFamily: previewHeadingFont }}>
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong} style={{ fontFamily: previewHeadingFont }}>
|
||||
{previewTitle}
|
||||
</Text>
|
||||
<Text fontSize="$sm" color="#4b5563" style={{ fontFamily: previewBodyFont }}>
|
||||
<Text fontSize="$sm" color={muted} style={{ fontFamily: previewBodyFont }}>
|
||||
{t('events.branding.previewSubtitle', 'Aktuelle Farben & Schriften')}
|
||||
</Text>
|
||||
<XStack space="$2" marginTop="$1">
|
||||
@@ -453,7 +455,7 @@ export default function MobileBrandingPage() {
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.branding.colors', 'Colors')}
|
||||
</Text>
|
||||
<ColorField
|
||||
@@ -469,13 +471,13 @@ export default function MobileBrandingPage() {
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.branding.fonts', 'Fonts')}
|
||||
</Text>
|
||||
<InputField
|
||||
label={t('events.branding.headingFont', 'Headline Font')}
|
||||
value={form.headingFont}
|
||||
placeholder="SF Pro Display"
|
||||
placeholder={t('events.branding.headingFontPlaceholder', 'SF Pro Display')}
|
||||
onChange={(value) => setForm((prev) => ({ ...prev, headingFont: value }))}
|
||||
onPicker={() => {
|
||||
setFontField('heading');
|
||||
@@ -485,7 +487,7 @@ export default function MobileBrandingPage() {
|
||||
<InputField
|
||||
label={t('events.branding.bodyFont', 'Body Font')}
|
||||
value={form.bodyFont}
|
||||
placeholder="SF Pro Text"
|
||||
placeholder={t('events.branding.bodyFontPlaceholder', 'SF Pro Text')}
|
||||
onChange={(value) => setForm((prev) => ({ ...prev, bodyFont: value }))}
|
||||
onPicker={() => {
|
||||
setFontField('body');
|
||||
@@ -495,14 +497,14 @@ export default function MobileBrandingPage() {
|
||||
</MobileCard>
|
||||
|
||||
<MobileCard space="$3">
|
||||
<Text fontSize="$md" fontWeight="800" color="#111827">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.branding.logo', 'Logo')}
|
||||
</Text>
|
||||
<YStack
|
||||
borderRadius={14}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
backgroundColor="#f8fafc"
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
padding="$3"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
@@ -510,7 +512,11 @@ export default function MobileBrandingPage() {
|
||||
>
|
||||
{form.logoDataUrl ? (
|
||||
<>
|
||||
<img src={form.logoDataUrl} alt="Logo" style={{ maxHeight: 80, maxWidth: '100%', objectFit: 'contain' }} />
|
||||
<img
|
||||
src={form.logoDataUrl}
|
||||
alt={t('events.branding.logoAlt', 'Logo')}
|
||||
style={{ maxHeight: 80, maxWidth: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.branding.replaceLogo', 'Replace logo')}
|
||||
@@ -524,10 +530,10 @@ export default function MobileBrandingPage() {
|
||||
paddingVertical="$2"
|
||||
borderRadius={12}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
borderColor={border}
|
||||
>
|
||||
<Trash2 size={16} color="#b91c1c" />
|
||||
<Text fontSize="$sm" color="#b91c1c" fontWeight="700">
|
||||
<Trash2 size={16} color={danger} />
|
||||
<Text fontSize="$sm" color={danger} fontWeight="700">
|
||||
{t('events.branding.removeLogo', 'Remove')}
|
||||
</Text>
|
||||
</XStack>
|
||||
@@ -536,8 +542,8 @@ export default function MobileBrandingPage() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ImageIcon size={28} color="#94a3b8" />
|
||||
<Text fontSize="$sm" color="#4b5563" textAlign="center">
|
||||
<ImageIcon size={28} color={subtle} />
|
||||
<Text fontSize="$sm" color={muted} textAlign="center">
|
||||
{t('events.branding.logoHint', 'Upload a logo to brand guest invites and QR posters.')}
|
||||
</Text>
|
||||
<Pressable onPress={() => document.getElementById('branding-logo-input')?.click()}>
|
||||
@@ -548,11 +554,11 @@ export default function MobileBrandingPage() {
|
||||
paddingVertical="$2.5"
|
||||
borderRadius={12}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
backgroundColor="white"
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<UploadCloud size={18} color="#007AFF" />
|
||||
<Text fontSize="$sm" color="#007AFF" fontWeight="700">
|
||||
<UploadCloud size={18} color={primary} />
|
||||
<Text fontSize="$sm" color={primary} fontWeight="700">
|
||||
{t('events.branding.uploadLogo', 'Upload logo (max. 1 MB)')}
|
||||
</Text>
|
||||
</XStack>
|
||||
@@ -600,13 +606,13 @@ export default function MobileBrandingPage() {
|
||||
borderRadius={14}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
backgroundColor="white"
|
||||
backgroundColor={surface}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
borderColor={border}
|
||||
space="$2"
|
||||
>
|
||||
<RefreshCcw size={16} color="#111827" />
|
||||
<Text fontSize="$sm" color="#111827" fontWeight="700">
|
||||
<RefreshCcw size={16} color={textStrong} />
|
||||
<Text fontSize="$sm" color={textStrong} fontWeight="700">
|
||||
{t('events.branding.reset', 'Reset to Defaults')}
|
||||
</Text>
|
||||
</XStack>
|
||||
@@ -624,7 +630,7 @@ export default function MobileBrandingPage() {
|
||||
{fontsLoading ? (
|
||||
Array.from({ length: 4 }).map((_, idx) => <SkeletonCard key={`font-sk-${idx}`} height={48} />)
|
||||
) : fonts.length === 0 ? (
|
||||
<Text fontSize="$sm" color="#4b5563">
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('events.branding.noFonts', 'Keine Schriftarten gefunden.')}
|
||||
</Text>
|
||||
) : (
|
||||
@@ -641,17 +647,17 @@ export default function MobileBrandingPage() {
|
||||
>
|
||||
<XStack alignItems="center" justifyContent="space-between" paddingVertical="$2">
|
||||
<YStack>
|
||||
<Text fontSize="$sm" color="#111827" style={{ fontFamily: font.family }}>
|
||||
<Text fontSize="$sm" color={textStrong} style={{ fontFamily: font.family }}>
|
||||
{font.family}
|
||||
</Text>
|
||||
{font.variants?.length ? (
|
||||
<Text fontSize="$xs" color="#6b7280" style={{ fontFamily: font.family }}>
|
||||
<Text fontSize="$xs" color={muted} style={{ fontFamily: font.family }}>
|
||||
{font.variants.map((v) => v.style ?? v.weight ?? '').filter(Boolean).join(', ')}
|
||||
</Text>
|
||||
) : null}
|
||||
</YStack>
|
||||
{form[fontField === 'heading' ? 'headingFont' : 'bodyFont'] === font.family ? (
|
||||
<Text fontSize="$xs" color="#007AFF">
|
||||
<Text fontSize="$xs" color={primary}>
|
||||
{t('common.active', 'Active')}
|
||||
</Text>
|
||||
) : null}
|
||||
@@ -677,8 +683,8 @@ function extractBranding(event: TenantEvent): BrandingForm {
|
||||
return typeof value === 'string' ? value : '';
|
||||
};
|
||||
return {
|
||||
primary: readColor('primary_color', '#007AFF'),
|
||||
accent: readColor('accent_color', '#5AD2F4'),
|
||||
primary: readColor('primary_color', ADMIN_COLORS.primary),
|
||||
accent: readColor('accent_color', ADMIN_COLORS.accent),
|
||||
headingFont: readText('heading_font'),
|
||||
bodyFont: readText('body_font'),
|
||||
logoDataUrl: readText('logo_data_url'),
|
||||
@@ -757,9 +763,10 @@ function renderName(name: TenantEvent['name']): string {
|
||||
}
|
||||
|
||||
function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (next: string) => void }) {
|
||||
const { textStrong, muted, border, surface } = useAdminTheme();
|
||||
return (
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{label}
|
||||
</Text>
|
||||
<XStack alignItems="center" space="$2">
|
||||
@@ -767,9 +774,9 @@ function ColorField({ label, value, onChange }: { label: string; value: string;
|
||||
type="color"
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
style={{ width: 52, height: 52, borderRadius: 12, border: '1px solid #e5e7eb', background: 'white' }}
|
||||
style={{ width: 52, height: 52, borderRadius: 12, border: `1px solid ${border}`, background: surface }}
|
||||
/>
|
||||
<Text fontSize="$sm" color="#4b5563">
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{value}
|
||||
</Text>
|
||||
</XStack>
|
||||
@@ -778,10 +785,11 @@ function ColorField({ label, value, onChange }: { label: string; value: string;
|
||||
}
|
||||
|
||||
function ColorSwatch({ color, label }: { color: string; label: string }) {
|
||||
const { border, muted } = useAdminTheme();
|
||||
return (
|
||||
<YStack alignItems="center" space="$1">
|
||||
<YStack width={40} height={40} borderRadius={12} borderWidth={1} borderColor="#e5e7eb" backgroundColor={color} />
|
||||
<Text fontSize="$xs" color="#4b5563">
|
||||
<YStack width={40} height={40} borderRadius={12} borderWidth={1} borderColor={border} backgroundColor={color} />
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{label}
|
||||
</Text>
|
||||
</YStack>
|
||||
@@ -803,20 +811,21 @@ function InputField({
|
||||
onPicker?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const { textStrong, border, surface, primary } = useAdminTheme();
|
||||
return (
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{label}
|
||||
</Text>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
borderRadius={12}
|
||||
borderWidth={1}
|
||||
borderColor="#e5e7eb"
|
||||
borderColor={border}
|
||||
paddingLeft="$3"
|
||||
paddingRight="$2"
|
||||
height={48}
|
||||
backgroundColor="white"
|
||||
backgroundColor={surface}
|
||||
space="$2"
|
||||
>
|
||||
{children ?? (
|
||||
@@ -838,7 +847,7 @@ function InputField({
|
||||
)}
|
||||
{onPicker ? (
|
||||
<Pressable onPress={onPicker}>
|
||||
<ChevronDown size={16} color="#007AFF" />
|
||||
<ChevronDown size={16} color={primary} />
|
||||
</Pressable>
|
||||
) : null}
|
||||
</XStack>
|
||||
@@ -865,13 +874,14 @@ function LabeledSlider({
|
||||
disabled?: boolean;
|
||||
suffix?: string;
|
||||
}) {
|
||||
const { textStrong, muted } = useAdminTheme();
|
||||
return (
|
||||
<YStack space="$1.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color="#4b5563">
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{value}
|
||||
{suffix ? ` ${suffix}` : ''}
|
||||
</Text>
|
||||
@@ -899,6 +909,7 @@ function PositionGrid({
|
||||
onChange: (value: WatermarkPosition) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const { textStrong, primary, border, accentSoft, surface } = useAdminTheme();
|
||||
const positions: WatermarkPosition[] = [
|
||||
'top-left',
|
||||
'top-center',
|
||||
@@ -913,7 +924,7 @@ function PositionGrid({
|
||||
|
||||
return (
|
||||
<YStack space="$2">
|
||||
<Text fontSize="$sm" fontWeight="700" color="#111827">
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
Position
|
||||
</Text>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 8 }}>
|
||||
@@ -926,11 +937,11 @@ function PositionGrid({
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
border: value === pos ? '2px solid #007AFF' : '1px solid #e5e7eb',
|
||||
background: value === pos ? '#eff6ff' : '#fff',
|
||||
border: value === pos ? `2px solid ${primary}` : `1px solid ${border}`,
|
||||
background: value === pos ? accentSoft : surface,
|
||||
}}
|
||||
>
|
||||
<Text fontSize="$xs" color="#0f172a" textAlign="center">
|
||||
<Text fontSize="$xs" color={textStrong} textAlign="center">
|
||||
{pos.replace('-', ' ')}
|
||||
</Text>
|
||||
</button>
|
||||
@@ -955,6 +966,7 @@ function WatermarkPreview({
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}) {
|
||||
const { border, muted, textStrong, overlay } = useAdminTheme();
|
||||
const width = 280;
|
||||
const height = 180;
|
||||
const wmWidth = Math.max(24, Math.round(width * Math.min(1, Math.max(0.05, scale))));
|
||||
@@ -1001,11 +1013,11 @@ function WatermarkPreview({
|
||||
height,
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #e5e7eb',
|
||||
background: 'linear-gradient(135deg, #e0f2fe, #c7d2fe)',
|
||||
border: `1px solid ${border}`,
|
||||
background: ADMIN_GRADIENTS.softCard,
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg, #0f172a66, transparent)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: `linear-gradient(180deg, ${overlay}, transparent)` }} />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
@@ -1019,10 +1031,10 @@ function WatermarkPreview({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: Math.min(1, Math.max(0, opacity)),
|
||||
border: '1px dashed #64748b',
|
||||
border: `1px dashed ${muted}`,
|
||||
}}
|
||||
>
|
||||
<Droplets size={18} color="#0f172a" />
|
||||
<Droplets size={18} color={textStrong} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1030,11 +1042,12 @@ function WatermarkPreview({
|
||||
}
|
||||
|
||||
function InfoBadge({ icon, text, tone = 'info' }: { icon?: React.ReactNode; text: string; tone?: 'info' | 'danger' }) {
|
||||
const background = tone === 'danger' ? '#fef2f2' : '#f1f5f9';
|
||||
const color = tone === 'danger' ? '#991b1b' : '#0f172a';
|
||||
const { dangerBg, dangerText, surfaceMuted, textStrong, border } = useAdminTheme();
|
||||
const background = tone === 'danger' ? dangerBg : surfaceMuted;
|
||||
const color = tone === 'danger' ? dangerText : textStrong;
|
||||
|
||||
return (
|
||||
<MobileCard space="$2" backgroundColor={background} borderColor="#e2e8f0">
|
||||
<MobileCard space="$2" backgroundColor={background} borderColor={border}>
|
||||
<XStack space="$2" alignItems="center">
|
||||
{icon}
|
||||
<Text fontSize="$sm" color={color}>
|
||||
@@ -1046,6 +1059,7 @@ function InfoBadge({ icon, text, tone = 'info' }: { icon?: React.ReactNode; text
|
||||
}
|
||||
|
||||
function TabButton({ label, active, onPress }: { label: string; active: boolean; onPress: () => void }) {
|
||||
const { backdrop, surfaceMuted, border, surface } = useAdminTheme();
|
||||
return (
|
||||
<Pressable onPress={onPress} style={{ flex: 1 }}>
|
||||
<XStack
|
||||
@@ -1053,11 +1067,11 @@ function TabButton({ label, active, onPress }: { label: string; active: boolean;
|
||||
justifyContent="center"
|
||||
paddingVertical="$2.5"
|
||||
borderRadius={12}
|
||||
backgroundColor={active ? '#0f172a' : '#f1f5f9'}
|
||||
backgroundColor={active ? backdrop : surfaceMuted}
|
||||
borderWidth={1}
|
||||
borderColor={active ? '#0f172a' : '#e5e7eb'}
|
||||
borderColor={active ? backdrop : border}
|
||||
>
|
||||
<Text fontSize="$sm" color={active ? '#fff' : '#0f172a'} fontWeight="700">
|
||||
<Text fontSize="$sm" color={active ? surface : backdrop} fontWeight="700">
|
||||
{label}
|
||||
</Text>
|
||||
</XStack>
|
||||
|
||||
Reference in New Issue
Block a user