diff --git a/apps/mobile/app/(tabs)/dashboard.tsx b/apps/mobile/app/(tabs)/dashboard.tsx index 0007e099..a00c0a13 100644 --- a/apps/mobile/app/(tabs)/dashboard.tsx +++ b/apps/mobile/app/(tabs)/dashboard.tsx @@ -1,11 +1,265 @@ /** * Dashboard tab: escrow list + filters by status - * Features: status filter tabs, infinite scroll/pagination, skeleton loaders, pull-refresh + * Features: status filter tabs, infinite scroll/pagination, skeleton loaders, pull-to-refresh */ -import React, { useCallback, useEffect, useRef, useState } from 'react',"imaport {\n ActivityIndicator,\n FlatList,\n RefreshControl,\n StyleSheet,\n Text,\n TouchableOpacity,\n View,\n } from 'react-native'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { + ActivityIndicator, + FlatList, + RefreshControl, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; import { useRouter } from 'expo-router'; import { escrowApi } from '../../services/api'; import { Escrow, EscrowStatus } from '../../types/escrow'; import { OfflineBanner } from '../../components/OfflineBanner'; import { useNetworkStatus } from '../../hooks/useNetworkStatus'; import { toFriendlyError, isOfflineError } from '../../utils/errors'; + +const STATUS_FILTERS: Array<{ label: string; value: EscrowStatus | 'all' }> = [ + { label: 'All', value: 'all' }, + { label: 'Created', value: 'created' }, + { label: 'Funded', value: 'funded' }, + { label: 'Active', value: 'confirmed' }, + { label: 'Completed', value: 'completed' }, + { label: 'Disputed', value: 'disputed' }, + { label: 'Expired', value: 'expired' }, +]; + +const STATUS_COLORS: Record = { + created: '#6c63ff', + funded: '#00b4d8', + confirmed: '#06d6a0', + released: '#06d6a0', + completed: '#06d6a0', + cancelled: '#aaa', + disputed: '#ef476f', + expired: '#f77f00', +}; + +function SkeletonCard() { + return ( + + + + + + ); +} + +function EscrowCard({ escrow, onPress }: { escrow: Escrow; onPress: () => void }) { + const color = STATUS_COLORS[escrow.status] ?? '#aaa'; + + return ( + + + + {escrow.title} + + + {escrow.status.toUpperCase()} + + + + {escrow.amount} {escrow.asset} + + + Deadline: {new Date(escrow.deadline).toLocaleDateString()} + + + ); +} + +export default function DashboardScreen() { + const router = useRouter(); + const [activeFilter, setActiveFilter] = useState('all'); + const [escrows, setEscrows] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState<{ title: string; message: string } | null>(null); + const [refreshing, setRefreshing] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + const [hasNextPage, setHasNextPage] = useState(false); + const pageRef = useRef(1); + const { isOffline, markOffline, markOnline } = useNetworkStatus(); + + const fetchEscrows = useCallback( + async (status: EscrowStatus | 'all', page: number, append = false) => { + try { + const res = await escrowApi.list({ status, page, limit: 20 }); + setEscrows((prev) => (append ? [...prev, ...res.escrows] : res.escrows)); + setHasNextPage(res.hasNextPage); + pageRef.current = page; + setError(null); + markOnline(); + } catch (err) { + const friendly = toFriendlyError(err); + setError({ title: friendly.title, message: friendly.message }); + if (isOfflineError(err)) markOffline(); + } + }, + [markOnline, markOffline], + ); + + useEffect(() => { + setLoading(true); + fetchEscrows(activeFilter, 1).finally(() => setLoading(false)); + }, [activeFilter, fetchEscrows]); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + await fetchEscrows(activeFilter, 1); + setRefreshing(false); + }, [activeFilter, fetchEscrows]); + + const onLoadMore = useCallback(async () => { + if (!hasNextPage || loadingMore) return; + setLoadingMore(true); + await fetchEscrows(activeFilter, pageRef.current + 1, true); + setLoadingMore(false); + }, [hasNextPage, loadingMore, activeFilter, fetchEscrows]); + + return ( + + + + item.value} + showsHorizontalScrollIndicator={false} + contentContainerStyle={styles.filterRow} + renderItem={({ item }) => ( + setActiveFilter(item.value)} + accessibilityRole="tab" + accessibilityState={{ selected: activeFilter === item.value }} + > + + {item.label} + + + )} + /> + + {loading ? ( + + {[1, 2, 3, 4].map((key) => ( + + ))} + + ) : error ? ( + + ⚠️ + {error.title} + {error.message} + + Retry + + + ) : ( + item.id} + contentContainerStyle={styles.list} + refreshControl={ + + } + onEndReached={onLoadMore} + onEndReachedThreshold={0.3} + ListEmptyComponent={No escrows found.} + ListFooterComponent={ + loadingMore ? : null + } + renderItem={({ item }) => ( + router.push({ pathname: '/escrow/[id]', params: { id: item.id } })} + /> + )} + /> + )} + + router.push('/escrow/create')} + accessibilityRole="button" + accessibilityLabel="Create new escrow" + > + + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#12121f' }, + filterRow: { paddingHorizontal: 16, paddingVertical: 12, gap: 8 }, + filterTab: { + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 20, + backgroundColor: '#2d2d44', + marginRight: 8, + }, + filterTabActive: { backgroundColor: '#6c63ff' }, + filterTabText: { color: '#aaa', fontSize: 13, fontWeight: '500' }, + filterTabTextActive: { color: '#fff' }, + list: { paddingHorizontal: 16, paddingBottom: 100 }, + card: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 16, marginBottom: 12 }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8, + }, + cardTitle: { color: '#fff', fontWeight: '600', fontSize: 15, flex: 1, marginRight: 8 }, + badge: { borderRadius: 6, borderWidth: 1, paddingHorizontal: 8, paddingVertical: 2 }, + badgeText: { fontSize: 10, fontWeight: '700' }, + cardAmount: { color: '#6c63ff', fontWeight: '700', fontSize: 18, marginBottom: 4 }, + cardMeta: { color: '#888', fontSize: 12 }, + empty: { color: '#888', textAlign: 'center', marginTop: 60, fontSize: 15 }, + errorContainer: { alignItems: 'center', justifyContent: 'center', marginTop: 60, paddingHorizontal: 32 }, + errorEmoji: { fontSize: 36, marginBottom: 8 }, + errorTitle: { color: '#ef476f', fontSize: 16, fontWeight: '700', marginBottom: 6, textAlign: 'center' }, + errorMessage: { color: '#aaa', fontSize: 13, textAlign: 'center', lineHeight: 18, marginBottom: 16 }, + retryBtn: { backgroundColor: '#6c63ff', borderRadius: 10, paddingHorizontal: 24, paddingVertical: 10 }, + retryText: { color: '#fff', fontWeight: '600' }, + skeletonList: { padding: 16 }, + skeletonCard: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 16, marginBottom: 12 }, + skeletonTitle: { + height: 16, + backgroundColor: '#2d2d44', + borderRadius: 4, + marginBottom: 10, + width: '70%', + }, + skeletonLine: { + height: 12, + backgroundColor: '#2d2d44', + borderRadius: 4, + marginBottom: 8, + width: '90%', + }, + fab: { + position: 'absolute', + bottom: 28, + right: 24, + backgroundColor: '#6c63ff', + width: 56, + height: 56, + borderRadius: 28, + alignItems: 'center', + justifyContent: 'center', + elevation: 6, + }, + fabText: { color: '#fff', fontSize: 28, lineHeight: 32 }, +}); diff --git a/apps/mobile/app/dashboard.tsx b/apps/mobile/app/dashboard.tsx deleted file mode 100644 index cc8c839b..00000000 --- a/apps/mobile/app/dashboard.tsx +++ /dev/null @@ -1,208 +0,0 @@ -/** - * #314 – Mobile Dashboard: escrow list + filters by status - * Features: status filter tabs, infinite scroll/pagination, skeleton loaders, pull-to-refresh - */ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { - ActivityIndicator, - FlatList, - RefreshControl, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { useRouter } from 'expo-router'; -import { escrowApi } from '../services/api'; -import { Escrow, EscrowStatus } from '../types/escrow'; -import { OfflineBanner } from '../components/OfflineBanner'; -import { useNetworkStatus } from '../hooks/useNetworkStatus'; -import { toFriendlyError, isOfflineError } from '../utils/errors'; - -const STATUS_FILTERS: Array<{ label: string; value: EscrowStatus | 'all' }> = [ - { label: 'All', value: 'all' }, - { label: 'Created', value: 'created' }, - { label: 'Funded', value: 'funded' }, - { label: 'Active', value: 'confirmed' }, - { label: 'Completed', value: 'completed' }, - { label: 'Disputed', value: 'disputed' }, - { label: 'Expired', value: 'expired' }, -]; - -const STATUS_COLORS: Record = { - created: '#6c63ff', - funded: '#00b4d8', - confirmed: '#06d6a0', - released: '#06d6a0', - completed: '#06d6a0', - cancelled: '#aaa', - disputed: '#ef476f', - expired: '#f77f00', -}; - -function SkeletonCard() { - return ( - - - - - - ); -} - -function EscrowCard({ escrow, onPress }: { escrow: Escrow; onPress: () => void }) { - const color = STATUS_COLORS[escrow.status] ?? '#aaa'; - return ( - - - {escrow.title} - - {escrow.status.toUpperCase()} - - - {escrow.amount} {escrow.asset} - Deadline: {new Date(escrow.deadline).toLocaleDateString()} - - ); -} - -export default function DashboardScreen() { - const router = useRouter(); - const [activeFilter, setActiveFilter] = useState('all'); - const [escrows, setEscrows] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<{ title: string; message: string } | null>(null); - const [refreshing, setRefreshing] = useState(false); - const [loadingMore, setLoadingMore] = useState(false); - const [hasNextPage, setHasNextPage] = useState(false); - const pageRef = useRef(1); - const { isOffline, markOffline, markOnline } = useNetworkStatus(); - - const fetchEscrows = useCallback(async (status: EscrowStatus | 'all', page: number, append = false) => { - try { - const res = await escrowApi.list({ status, page, limit: 20 }); - setEscrows((prev) => (append ? [...prev, ...res.escrows] : res.escrows)); - setHasNextPage(res.hasNextPage); - pageRef.current = page; - setError(null); - markOnline(); - } catch (err) { - const friendly = toFriendlyError(err); - setError({ title: friendly.title, message: friendly.message }); - if (isOfflineError(err)) markOffline(); - } - }, [markOnline, markOffline]); - - useEffect(() => { - setLoading(true); - fetchEscrows(activeFilter, 1).finally(() => setLoading(false)); - }, [activeFilter, fetchEscrows]); - - const onRefresh = useCallback(async () => { - setRefreshing(true); - await fetchEscrows(activeFilter, 1); - setRefreshing(false); - }, [activeFilter, fetchEscrows]); - - const onLoadMore = useCallback(async () => { - if (!hasNextPage || loadingMore) return; - setLoadingMore(true); - await fetchEscrows(activeFilter, pageRef.current + 1, true); - setLoadingMore(false); - }, [hasNextPage, loadingMore, activeFilter, fetchEscrows]); - - return ( - - - - {/* Status filter tabs */} - item.value} - showsHorizontalScrollIndicator={false} - contentContainerStyle={styles.filterRow} - renderItem={({ item }) => ( - setActiveFilter(item.value)} - accessibilityRole="tab" - accessibilityState={{ selected: activeFilter === item.value }} - > - - {item.label} - - - )} - /> - - {/* Escrow list */} - {loading ? ( - - {[1, 2, 3, 4].map((k) => )} - - ) : error ? ( - - ⚠️ - {error.title} - {error.message} - - Retry - - - ) : ( - item.id} - contentContainerStyle={styles.list} - refreshControl={} - onEndReached={onLoadMore} - onEndReachedThreshold={0.3} - ListEmptyComponent={No escrows found.} - ListFooterComponent={loadingMore ? : null} - renderItem={({ item }) => ( - router.push({ pathname: '/escrow/[id]', params: { id: item.id } })} - /> - )} - /> - )} - - {/* FAB – create escrow */} - router.push('/escrow/create')} accessibilityRole="button" accessibilityLabel="Create new escrow"> - - - - ); -} - -const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#12121f' }, - filterRow: { paddingHorizontal: 16, paddingVertical: 12, gap: 8 }, - filterTab: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, backgroundColor: '#2d2d44', marginRight: 8 }, - filterTabActive: { backgroundColor: '#6c63ff' }, - filterTabText: { color: '#aaa', fontSize: 13, fontWeight: '500' }, - filterTabTextActive: { color: '#fff' }, - list: { paddingHorizontal: 16, paddingBottom: 100 }, - card: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 16, marginBottom: 12 }, - cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, - cardTitle: { color: '#fff', fontWeight: '600', fontSize: 15, flex: 1, marginRight: 8 }, - badge: { borderRadius: 6, borderWidth: 1, paddingHorizontal: 8, paddingVertical: 2 }, - badgeText: { fontSize: 10, fontWeight: '700' }, - cardAmount: { color: '#6c63ff', fontWeight: '700', fontSize: 18, marginBottom: 4 }, - cardMeta: { color: '#888', fontSize: 12 }, - empty: { color: '#888', textAlign: 'center', marginTop: 60, fontSize: 15 }, - errorContainer: { alignItems: 'center', justifyContent: 'center', marginTop: 60, paddingHorizontal: 32 }, - errorEmoji: { fontSize: 36, marginBottom: 8 }, - errorTitle: { color: '#ef476f', fontSize: 16, fontWeight: '700', marginBottom: 6, textAlign: 'center' }, - errorMessage: { color: '#aaa', fontSize: 13, textAlign: 'center', lineHeight: 18, marginBottom: 16 }, - retryBtn: { backgroundColor: '#6c63ff', borderRadius: 10, paddingHorizontal: 24, paddingVertical: 10 }, - retryText: { color: '#fff', fontWeight: '600' }, - skeletonList: { padding: 16 }, - skeletonCard: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 16, marginBottom: 12 }, - skeletonTitle: { height: 16, backgroundColor: '#2d2d44', borderRadius: 4, marginBottom: 10, width: '70%' }, - skeletonLine: { height: 12, backgroundColor: '#2d2d44', borderRadius: 4, marginBottom: 8, width: '90%' }, - fab: { position: 'absolute', bottom: 28, right: 24, backgroundColor: '#6c63ff', width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center', elevation: 6 }, - fabText: { color: '#fff', fontSize: 28, lineHeight: 32 }, -}); diff --git a/apps/mobile/app/escrow/create.tsx b/apps/mobile/app/escrow/create.tsx index 560ca3f3..be948780 100644 --- a/apps/mobile/app/escrow/create.tsx +++ b/apps/mobile/app/escrow/create.tsx @@ -167,7 +167,7 @@ export default function CreateEscrowScreen() { }); Alert.alert('Success', 'Escrow created!', [ { text: 'View', onPress: () => router.replace({ pathname: '/escrow/[id]', params: { id: created.id } }) }, - { text: 'Dashboard', onPress: () => router.replace('/dashboard') }, + { text: 'Dashboard', onPress: () => router.replace('/(tabs)/dashboard') }, ]); } catch (err) { const friendly = toFriendlyError(err); @@ -204,4 +204,4 @@ export default function CreateEscrowScreen() { {errors.milestoneTotal && {errors.milestoneTotal}} {errors.milestones && {errors.milestones}} - {form.milestones.map((m, i) => (\n \n \n Milestone {i + 1}\n {form.milestones.length > MIN_MILESTONES && (\n removeMilestone(i)}>\n Remove\n \n )}\n \n updateMilestone(i, 'title', v)} placeholder=\"Milestone title\" error={errors[`_title_${i}]} />\n updateMilestone(i, 'amount', v)} keyboardType=\"decimal-pad\" placeholder=\"0.00\" error={errors[`_amount_${i}]} />\n \n )}\n\n {form.milestones.length < MAX_MILESTONEEs && (\n \n + Add Milestone\n \n )}\n \n )}\n\n {/* Step 3: Deadline */}\n {step === 3 && (\n \n Deadline\n update('deadline', v)}\n placeholder=\"2026-12-31\"\n error={errors.deadline}\n />\n The escrow will expire if not completed by this date.\n \n )}\n\n {/* Step 4: Review */}\n {step === 4 && (\n \n Review & Submit\n \n \n \n \n \n \n \n By submitting, you agree to lock funds until milestones are released.\n \n )}\n\n {/* Navigation */}\n \n {step > 1 && (\n setStep((s) => s - 1)}>\n ← Back\n \n )}\n {step < 4 ? (\n \n Next →\n \n ) : (\n \n {submitting ? : Create Escrow}\n \n )}\n \n \n \n );\n}\n\nfunction ReviewRow({ label, value }: { label: string; value: string }) {\n return (\n \n {label}\n {value}\n \n );\n}\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#12121f' },\n content: { padding: 16, paddingBottom: 40 },\n stepRow: { flexDirection: 'row', justifyContent: 'center', gap: 6, marginBottom: 12 },\n stepDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: '#2d2d44' },\n stepDotDone: { backgroundColor: '#6c63ff' },\n stepDotActive: { backgroundColor: '#6c63ff', transform: [{ scale: 1.3 }] },\n stepLabel: { color: '#888', fontSize: 12, marginBottom: 8 },\n stepTitle: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 16 },\n hint: { color: '#888', fontSize: 12, marginBottom: 8 },\n field: { marginBottom: 16 },\n label: { color: '#aaa', fontSize: 13, marginBottom: 6 },\n input: {\n backgroundColor: '#1e1e30',\n borderRadius: 10,\n paddingHorizontal: 14,\n paddingVertical: 12,\n color: '#fff',\n fontSize: 15,\n },\n inputMulti: { minHeight: 80, textAlignVertical: 'top' },\n inputError: { borderWidth: 1, borderColor: '#ef476f' },\n errorText: { color: '#ef476f', fontSize: 12, marginTop: 4 },\n milestoneBlock: { backgroundColor: '#1a1a1e', borderRadius: 12, padding: 16, marginBottom: 12 },\n milestoneHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 },\n milestoneNum: { color: '#fff', fontWeight: '600' },\n removeText: { color: '#ef476f', fontSize: 13 },\n addBtn: { borderWidth: 1, borderColor: '#6c63ff', borderRadius: 10, paddingVertical: 12, alignItems: 'center', marginTop: 8 },\n addBtnText: { color: '#6c63ff', fontWeight: '600' },\n navRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 24 },\n backBtn: { backgroundColor: '#2d2d44', borderRadius: 10, paddingHorizontal: 20, paddingVertical: 12 },\n backBtnText: { color: '#fff', fontWeight: '600' },\n nextBtn: { backgroundColor: '#6c63ff', borderRadius: 10, paddingHorizontal: 24, paddingVertical: 12, alignItems: 'center', justifyContent: 'center' },\n nextBtnText: { color: '#fff', fontWeight: '600' },\n btnDisabled: { opacity: 0.6 },\n reviewCard: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 12, marginBottom: 16 },\n reviewRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#2d2d44' },\n reviewLabel: { color: '#888', fontSize: 13 },\n reviewValue: { color: '#fff', fontSize: 13, fontWeight: '500', maxWidth: '60%' },\n});\n \ No newline at end of file + {form.milestones.map((m, i) => (\n \n \n Milestone {i + 1}\n {form.milestones.length > MIN_MILESTONES && (\n removeMilestone(i)}>\n Remove\n \n )}\n \n updateMilestone(i, 'title', v)} placeholder=\"Milestone title\" error={errors[`_title_${i}]} />\n updateMilestone(i, 'amount', v)} keyboardType=\"decimal-pad\" placeholder=\"0.00\" error={errors[`_amount_${i}]} />\n \n )}\n\n {form.milestones.length < MAX_MILESTONEEs && (\n \n + Add Milestone\n \n )}\n \n )}\n\n {/* Step 3: Deadline */}\n {step === 3 && (\n \n Deadline\n update('deadline', v)}\n placeholder=\"2026-12-31\"\n error={errors.deadline}\n />\n The escrow will expire if not completed by this date.\n \n )}\n\n {/* Step 4: Review */}\n {step === 4 && (\n \n Review & Submit\n \n \n \n \n \n \n \n By submitting, you agree to lock funds until milestones are released.\n \n )}\n\n {/* Navigation */}\n \n {step > 1 && (\n setStep((s) => s - 1)}>\n ← Back\n \n )}\n {step < 4 ? (\n \n Next →\n \n ) : (\n \n {submitting ? : Create Escrow}\n \n )}\n \n \n \n );\n}\n\nfunction ReviewRow({ label, value }: { label: string; value: string }) {\n return (\n \n {label}\n {value}\n \n );\n}\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#12121f' },\n content: { padding: 16, paddingBottom: 40 },\n stepRow: { flexDirection: 'row', justifyContent: 'center', gap: 6, marginBottom: 12 },\n stepDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: '#2d2d44' },\n stepDotDone: { backgroundColor: '#6c63ff' },\n stepDotActive: { backgroundColor: '#6c63ff', transform: [{ scale: 1.3 }] },\n stepLabel: { color: '#888', fontSize: 12, marginBottom: 8 },\n stepTitle: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 16 },\n hint: { color: '#888', fontSize: 12, marginBottom: 8 },\n field: { marginBottom: 16 },\n label: { color: '#aaa', fontSize: 13, marginBottom: 6 },\n input: {\n backgroundColor: '#1e1e30',\n borderRadius: 10,\n paddingHorizontal: 14,\n paddingVertical: 12,\n color: '#fff',\n fontSize: 15,\n },\n inputMulti: { minHeight: 80, textAlignVertical: 'top' },\n inputError: { borderWidth: 1, borderColor: '#ef476f' },\n errorText: { color: '#ef476f', fontSize: 12, marginTop: 4 },\n milestoneBlock: { backgroundColor: '#1a1a1e', borderRadius: 12, padding: 16, marginBottom: 12 },\n milestoneHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 },\n milestoneNum: { color: '#fff', fontWeight: '600' },\n removeText: { color: '#ef476f', fontSize: 13 },\n addBtn: { borderWidth: 1, borderColor: '#6c63ff', borderRadius: 10, paddingVertical: 12, alignItems: 'center', marginTop: 8 },\n addBtnText: { color: '#6c63ff', fontWeight: '600' },\n navRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 24 },\n backBtn: { backgroundColor: '#2d2d44', borderRadius: 10, paddingHorizontal: 20, paddingVertical: 12 },\n backBtnText: { color: '#fff', fontWeight: '600' },\n nextBtn: { backgroundColor: '#6c63ff', borderRadius: 10, paddingHorizontal: 24, paddingVertical: 12, alignItems: 'center', justifyContent: 'center' },\n nextBtnText: { color: '#fff', fontWeight: '600' },\n btnDisabled: { opacity: 0.6 },\n reviewCard: { backgroundColor: '#1e1e30', borderRadius: 12, padding: 12, marginBottom: 16 },\n reviewRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#2d2d44' },\n reviewLabel: { color: '#888', fontSize: 13 },\n reviewValue: { color: '#fff', fontSize: 13, fontWeight: '500', maxWidth: '60%' },\n});\n diff --git a/apps/mobile/app/notifications.tsx b/apps/mobile/app/notifications.tsx deleted file mode 100644 index 23bfc556..00000000 --- a/apps/mobile/app/notifications.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native'; -import { useNotifications } from '../hooks/useNotifications'; -import { NotificationItem } from '../services/NotificationService'; - -export default function NotificationsScreen() { - const { notifications, unreadCount, markAsRead, markAllAsRead } = useNotifications(); - - const renderItem = ({ item }: { item: NotificationItem }) => ( - markAsRead(item.id)} - > - - {item.type.replace('_', ' ')} - {!item.isRead && } - - {item.message} - {new Date(item.createdAt).toLocaleDateString()} - - ); - - return ( - - - Notifications - {unreadCount > 0 && ( - - Mark all as read - - )} - - - item.id} - renderItem={renderItem} - contentContainerStyle={styles.listContent} - ListEmptyComponent={ - No notifications yet. - } - /> - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0F172A', - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - backgroundColor: '#1E293B', - }, - headerTitle: { - fontSize: 20, - fontWeight: 'bold', - color: '#FFFFFF', - }, - markAllText: { - color: '#3B82F6', - fontWeight: '600', - }, - listContent: { - padding: 16, - }, - notificationCard: { - backgroundColor: '#1E293B', - padding: 16, - borderRadius: 8, - marginBottom: 12, - }, - unreadCard: { - borderColor: '#3B82F6', - borderWidth: 1, - }, - cardHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 8, - }, - typeText: { - color: '#94A3B8', - fontSize: 12, - fontWeight: 'bold', - }, - unreadDot: { - width: 8, - height: 8, - borderRadius: 4, - backgroundColor: '#3B82F6', - }, - messageText: { - color: '#FFFFFF', - fontSize: 14, - marginBottom: 8, - }, - dateText: { - color: '#64748B', - fontSize: 12, - }, - emptyText: { - color: '#94A3B8', - textAlign: 'center', - marginTop: 32, - }, -});