feat: Implement mobile application and lead processing utilities.
This commit is contained in:
@@ -1,87 +1,383 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
View, Text, ScrollView, TouchableOpacity, ActivityIndicator,
|
||||
StyleSheet, Platform,
|
||||
} from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router'
|
||||
import { useEffect } from 'react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useNewsReadStore } from '@/store/news.store'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
import { useNewsDetail } from '@/hooks/useNews'
|
||||
import { AttachmentRow } from '@/components/news/AttachmentRow'
|
||||
import { Badge } from '@/components/ui/Badge'
|
||||
import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared'
|
||||
import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared/types'
|
||||
import { format } from 'date-fns'
|
||||
import { de } from 'date-fns/locale'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lightweight markdown renderer (headings, bold, bullets, paragraphs)
|
||||
// ---------------------------------------------------------------------------
|
||||
function MarkdownBody({ source }: { source: string }) {
|
||||
const blocks = source.split(/\n\n+/)
|
||||
return (
|
||||
<View style={md.container}>
|
||||
{blocks.map((block, i) => {
|
||||
const trimmed = block.trim()
|
||||
if (!trimmed) return null
|
||||
|
||||
// H2 ##
|
||||
if (trimmed.startsWith('## ')) {
|
||||
return (
|
||||
<Text key={i} style={md.h2}>
|
||||
{trimmed.slice(3)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
// H3 ###
|
||||
if (trimmed.startsWith('### ')) {
|
||||
return (
|
||||
<Text key={i} style={md.h3}>
|
||||
{trimmed.slice(4)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
// H1 #
|
||||
if (trimmed.startsWith('# ')) {
|
||||
return (
|
||||
<Text key={i} style={md.h1}>
|
||||
{trimmed.slice(2)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
// Bullet list
|
||||
if (trimmed.startsWith('- ')) {
|
||||
const items = trimmed.split('\n').filter(Boolean)
|
||||
return (
|
||||
<View key={i} style={md.list}>
|
||||
{items.map((line, j) => {
|
||||
const text = line.replace(/^-\s+/, '')
|
||||
return (
|
||||
<View key={j} style={md.listItem}>
|
||||
<View style={md.bullet} />
|
||||
<Text style={md.listText}>{renderInline(text)}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
// Paragraph (with inline bold)
|
||||
return (
|
||||
<Text key={i} style={md.paragraph}>
|
||||
{renderInline(trimmed)}
|
||||
</Text>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
/** Render **bold** inline within a Text node */
|
||||
function renderInline(text: string): React.ReactNode[] {
|
||||
const parts = text.split(/(\*\*.*?\*\*)/)
|
||||
return parts.map((part, i) => {
|
||||
if (part.startsWith('**') && part.endsWith('**')) {
|
||||
return (
|
||||
<Text key={i} style={{ fontWeight: '700', color: '#0F172A' }}>
|
||||
{part.slice(2, -2)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
return <Text key={i}>{part}</Text>
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Screen
|
||||
// ---------------------------------------------------------------------------
|
||||
export default function NewsDetailScreen() {
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
const router = useRouter()
|
||||
const markRead = useNewsReadStore((s) => s.markRead)
|
||||
const markReadMutation = trpc.news.markRead.useMutation()
|
||||
const { data: news, isLoading, onOpen } = useNewsDetail(id)
|
||||
|
||||
const { data: news, isLoading } = trpc.news.byId.useQuery({ id })
|
||||
|
||||
useEffect(() => {
|
||||
if (news) {
|
||||
markRead(id)
|
||||
markReadMutation.mutate({ newsId: id })
|
||||
}
|
||||
}, [news?.id])
|
||||
useEffect(() => { if (news) onOpen() }, [news?.id])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#E63946" />
|
||||
<SafeAreaView style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#003B7E" />
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
if (!news) return null
|
||||
|
||||
const initials = (news.author?.name ?? 'I')
|
||||
.split(' ')
|
||||
.map((n) => n.charAt(0))
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-white" edges={['top']}>
|
||||
{/* Header */}
|
||||
<View className="flex-row items-center px-4 py-3 border-b border-gray-100">
|
||||
<TouchableOpacity onPress={() => router.back()} className="mr-3">
|
||||
<Text className="text-brand-500 text-base">← Zurück</Text>
|
||||
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
||||
{/* Nav bar */}
|
||||
<View style={styles.navBar}>
|
||||
<TouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
style={styles.backBtn}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name="chevron-back" size={22} color="#003B7E" />
|
||||
<Text style={styles.backText}>Neuigkeiten</Text>
|
||||
</TouchableOpacity>
|
||||
<Text className="font-semibold text-gray-900 flex-1" numberOfLines={1}>
|
||||
{news.title}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={{ padding: 16 }}>
|
||||
<Badge label={NEWS_KATEGORIE_LABELS[news.kategorie]} kategorie={news.kategorie} />
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* ── Hero header ────────────────────────────────────────── */}
|
||||
<View style={styles.hero}>
|
||||
<Badge
|
||||
label={NEWS_KATEGORIE_LABELS[news.kategorie]}
|
||||
kategorie={news.kategorie}
|
||||
/>
|
||||
|
||||
<Text className="text-2xl font-bold text-gray-900 mt-3 mb-2">
|
||||
{news.title}
|
||||
</Text>
|
||||
<Text style={styles.heroTitle}>{news.title}</Text>
|
||||
|
||||
<Text className="text-sm text-gray-500 mb-6">
|
||||
{news.author?.name ?? 'InnungsApp'} ·{' '}
|
||||
{news.publishedAt
|
||||
? format(new Date(news.publishedAt), 'dd. MMMM yyyy', { locale: de })
|
||||
: ''}
|
||||
</Text>
|
||||
{/* Author + date row */}
|
||||
<View style={styles.metaRow}>
|
||||
<View style={styles.avatarCircle}>
|
||||
<Text style={styles.avatarText}>{initials}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.authorName}>{news.author?.name ?? 'Innung'}</Text>
|
||||
{news.publishedAt && (
|
||||
<Text style={styles.dateText}>
|
||||
{format(new Date(news.publishedAt), 'dd. MMMM yyyy', { locale: de })}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Simple Markdown renderer — plain text for MVP */}
|
||||
<Text className="text-base text-gray-700 leading-7">
|
||||
{news.body.replace(/^#+\s/gm, '').replace(/\*\*(.*?)\*\*/g, '$1')}
|
||||
</Text>
|
||||
{/* ── Separator ──────────────────────────────────────────── */}
|
||||
<View style={styles.heroSeparator} />
|
||||
|
||||
{/* Attachments */}
|
||||
{/* ── Article body ───────────────────────────────────────── */}
|
||||
<MarkdownBody source={news.body} />
|
||||
|
||||
{/* ── Attachments ────────────────────────────────────────── */}
|
||||
{news.attachments.length > 0 && (
|
||||
<View className="mt-8 border-t border-gray-100 pt-4">
|
||||
<Text className="font-semibold text-gray-900 mb-3">Anhänge</Text>
|
||||
{news.attachments.map((a) => (
|
||||
<AttachmentRow key={a.id} attachment={a} />
|
||||
))}
|
||||
<View style={styles.attachmentsSection}>
|
||||
<View style={styles.attachmentsHeader}>
|
||||
<Ionicons name="attach" size={14} color="#64748B" />
|
||||
<Text style={styles.attachmentsLabel}>
|
||||
ANHÄNGE ({news.attachments.length})
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.attachmentsCard}>
|
||||
{news.attachments.map((a, idx) => (
|
||||
<View key={a.id}>
|
||||
{idx > 0 && <View style={styles.attachmentsDivider} />}
|
||||
<AttachmentRow attachment={a} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Bottom spacer */}
|
||||
<View style={{ height: 48 }} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles
|
||||
// ---------------------------------------------------------------------------
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#FAFAFA',
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: '#FAFAFA',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
// Nav
|
||||
navBar: {
|
||||
backgroundColor: '#FAFAFA',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: '#E2E8F0',
|
||||
},
|
||||
backBtn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
backText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
color: '#003B7E',
|
||||
},
|
||||
// Hero
|
||||
hero: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 22,
|
||||
paddingBottom: 20,
|
||||
gap: 12,
|
||||
},
|
||||
heroTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '800',
|
||||
color: '#0F172A',
|
||||
letterSpacing: -0.5,
|
||||
lineHeight: 32,
|
||||
marginTop: 4,
|
||||
},
|
||||
metaRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
marginTop: 4,
|
||||
},
|
||||
avatarCircle: {
|
||||
width: 38,
|
||||
height: 38,
|
||||
borderRadius: 19,
|
||||
backgroundColor: '#003B7E',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
avatarText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 13,
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
authorName: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#0F172A',
|
||||
lineHeight: 18,
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 12,
|
||||
color: '#94A3B8',
|
||||
marginTop: 1,
|
||||
},
|
||||
heroSeparator: {
|
||||
height: 4,
|
||||
backgroundColor: '#F1F5F9',
|
||||
},
|
||||
// Scroll
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
// Attachments
|
||||
attachmentsSection: {
|
||||
marginHorizontal: 20,
|
||||
marginTop: 28,
|
||||
},
|
||||
attachmentsHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
marginBottom: 10,
|
||||
},
|
||||
attachmentsLabel: {
|
||||
fontSize: 11,
|
||||
fontWeight: '700',
|
||||
color: '#64748B',
|
||||
letterSpacing: 0.8,
|
||||
},
|
||||
attachmentsCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderRadius: 16,
|
||||
paddingHorizontal: 16,
|
||||
overflow: 'hidden',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: '#1C1917',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 10,
|
||||
},
|
||||
android: { elevation: 2 },
|
||||
}),
|
||||
},
|
||||
attachmentsDivider: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
backgroundColor: '#F1F5F9',
|
||||
},
|
||||
})
|
||||
|
||||
// Markdown styles
|
||||
const md = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 22,
|
||||
paddingBottom: 8,
|
||||
gap: 14,
|
||||
},
|
||||
h1: {
|
||||
fontSize: 22,
|
||||
fontWeight: '800',
|
||||
color: '#0F172A',
|
||||
letterSpacing: -0.3,
|
||||
lineHeight: 30,
|
||||
},
|
||||
h2: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
letterSpacing: -0.2,
|
||||
lineHeight: 26,
|
||||
marginTop: 8,
|
||||
paddingBottom: 6,
|
||||
borderBottomWidth: 2,
|
||||
borderBottomColor: '#EFF6FF',
|
||||
},
|
||||
h3: {
|
||||
fontSize: 16,
|
||||
fontWeight: '700',
|
||||
color: '#1E293B',
|
||||
lineHeight: 24,
|
||||
marginTop: 4,
|
||||
},
|
||||
paragraph: {
|
||||
fontSize: 16,
|
||||
color: '#334155',
|
||||
lineHeight: 28,
|
||||
letterSpacing: 0.1,
|
||||
},
|
||||
list: {
|
||||
gap: 8,
|
||||
},
|
||||
listItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 10,
|
||||
},
|
||||
bullet: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: '#003B7E',
|
||||
marginTop: 10,
|
||||
flexShrink: 0,
|
||||
},
|
||||
listText: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
color: '#334155',
|
||||
lineHeight: 28,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,100 +1,157 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
TouchableOpacity,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
View, Text, FlatList, TouchableOpacity, RefreshControl, ScrollView, StyleSheet,
|
||||
} from 'react-native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { useState } from 'react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { useNewsList } from '@/hooks/useNews'
|
||||
import { NewsCard } from '@/components/news/NewsCard'
|
||||
import { EmptyState } from '@/components/ui/EmptyState'
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner'
|
||||
import { NEWS_KATEGORIE_LABELS } from '@innungsapp/shared'
|
||||
|
||||
const FILTER_OPTIONS = [
|
||||
const FILTERS = [
|
||||
{ value: undefined, label: 'Alle' },
|
||||
{ value: 'Wichtig', label: 'Wichtig' },
|
||||
{ value: 'Pruefung', label: 'Prüfung' },
|
||||
{ value: 'Foerderung', label: 'Förderung' },
|
||||
{ value: 'Pruefung', label: 'Pruefung' },
|
||||
{ value: 'Foerderung', label: 'Foerderung' },
|
||||
{ value: 'Veranstaltung', label: 'Veranstaltung' },
|
||||
]
|
||||
|
||||
export default function NewsScreen() {
|
||||
const router = useRouter()
|
||||
const [kategorie, setKategorie] = useState<string | undefined>(undefined)
|
||||
const { data, isLoading, refetch, isRefetching } = trpc.news.list.useQuery({
|
||||
kategorie: kategorie as never,
|
||||
})
|
||||
const { data, isLoading, refetch, isRefetching } = useNewsList(kategorie)
|
||||
|
||||
const unreadCount = data?.filter((n) => !n.isRead).length ?? 0
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-gray-50" edges={['top']}>
|
||||
{/* Header */}
|
||||
<View className="bg-white px-4 py-3 border-b border-gray-100">
|
||||
<Text className="text-xl font-bold text-gray-900">News</Text>
|
||||
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
||||
<View style={styles.header}>
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={styles.screenTitle}>Aktuelles</Text>
|
||||
{unreadCount > 0 && (
|
||||
<View style={styles.unreadBadge}>
|
||||
<Text style={styles.unreadBadgeText}>{unreadCount} neu</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.filterScroll}
|
||||
>
|
||||
{FILTERS.map((opt) => {
|
||||
const active = kategorie === opt.value
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={String(opt.value)}
|
||||
onPress={() => setKategorie(opt.value)}
|
||||
style={[styles.chip, active && styles.chipActive]}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
<Text style={[styles.chipLabel, active && styles.chipLabelActive]}>
|
||||
{opt.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Kategorie Filter */}
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
className="bg-white border-b border-gray-100"
|
||||
contentContainerStyle={{ paddingHorizontal: 12, paddingVertical: 10, gap: 8 }}
|
||||
>
|
||||
{FILTER_OPTIONS.map((opt) => (
|
||||
<TouchableOpacity
|
||||
key={String(opt.value)}
|
||||
onPress={() => setKategorie(opt.value)}
|
||||
className={`px-4 py-1.5 rounded-full border ${
|
||||
kategorie === opt.value
|
||||
? 'bg-brand-500 border-brand-500'
|
||||
: 'bg-white border-gray-200'
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-sm font-medium ${
|
||||
kategorie === opt.value ? 'text-white' : 'text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
<View style={styles.divider} />
|
||||
|
||||
{/* List */}
|
||||
{isLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<FlatList
|
||||
data={data ?? []}
|
||||
keyExtractor={(item) => item.id}
|
||||
contentContainerStyle={{ padding: 12, gap: 8 }}
|
||||
contentContainerStyle={styles.list}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
tintColor="#E63946"
|
||||
/>
|
||||
<RefreshControl refreshing={isRefetching} onRefresh={refetch} tintColor="#003B7E" />
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<NewsCard
|
||||
news={item}
|
||||
onPress={() => router.push(`/(app)/news/${item.id}`)}
|
||||
onPress={() => router.push(`/(app)/news/${item.id}` as never)}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
<EmptyState
|
||||
icon="📰"
|
||||
title="Keine News"
|
||||
subtitle="Noch keine Beiträge für diese Kategorie"
|
||||
/>
|
||||
<EmptyState icon="N" title="Keine News" subtitle="Noch keine Beitraege veroeffentlicht." />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
backgroundColor: '#F8FAFC',
|
||||
},
|
||||
header: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 14,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 14,
|
||||
},
|
||||
screenTitle: {
|
||||
fontSize: 28,
|
||||
fontWeight: '800',
|
||||
color: '#0F172A',
|
||||
letterSpacing: -0.5,
|
||||
},
|
||||
unreadBadge: {
|
||||
backgroundColor: '#EFF6FF',
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 99,
|
||||
},
|
||||
unreadBadgeText: {
|
||||
color: '#003B7E',
|
||||
fontSize: 12,
|
||||
fontWeight: '700',
|
||||
},
|
||||
filterScroll: {
|
||||
paddingBottom: 14,
|
||||
gap: 8,
|
||||
paddingRight: 20,
|
||||
},
|
||||
chip: {
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 7,
|
||||
borderRadius: 99,
|
||||
borderWidth: 1,
|
||||
borderColor: '#E2E8F0',
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
chipActive: {
|
||||
backgroundColor: '#003B7E',
|
||||
borderColor: '#003B7E',
|
||||
},
|
||||
chipLabel: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: '#64748B',
|
||||
},
|
||||
chipLabelActive: {
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: '#E2E8F0',
|
||||
},
|
||||
list: {
|
||||
padding: 16,
|
||||
gap: 10,
|
||||
paddingBottom: 30,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user