- Add Lucide React Native icon library and use across tabs and screens
- Mobile-like design: rounded cards (xl/2xl), section dividers, icon chips, chevrons
- New pages from swagger: register, invoices/[id], reports, documents, settings
- Invoice detail: amount, bill to, items, Share/PDF actions (GET /invoices/{id})
- Register screen with link to login (POST /auth/register)
- Reports list with mock data and download (GET /reports)
- Documents list with upload CTA (GET /documents)
- Settings: notifications link, language, about
- Profile: links to Notifications, Reports, Documents, Settings
- Home: invoice rows navigate to /invoices/[id]
- Login ↔ Register navigation
- Keep orange (#ea580c) and dark navbar (#2d2d2d) theme throughout
- README: update screens table with new routes
Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
4.8 KiB
TypeScript
101 lines
4.8 KiB
TypeScript
import { View, ScrollView } from 'react-native';
|
||
import { useLocalSearchParams, router } from 'expo-router';
|
||
import { Text } from '@/components/ui/text';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { FileText, Calendar, User, Share2, Download, ChevronRight } from '@/lib/icons';
|
||
import { MOCK_INVOICES } from '@/lib/mock-data';
|
||
|
||
const PRIMARY = '#ea580c';
|
||
const MOCK_ITEMS = [
|
||
{ description: 'Marketing Landing Page Package', qty: 1, unitPrice: 1000, total: 1000 },
|
||
{ description: 'Instagram Post Initial Design', qty: 4, unitPrice: 100, total: 400 },
|
||
];
|
||
|
||
export default function InvoiceDetailScreen() {
|
||
const { id } = useLocalSearchParams<{ id: string }>();
|
||
const invoice = MOCK_INVOICES.find((i) => i.id === id);
|
||
|
||
return (
|
||
<ScrollView
|
||
className="flex-1 bg-[#f5f5f5]"
|
||
contentContainerStyle={{ padding: 20, paddingBottom: 40 }}
|
||
showsVerticalScrollIndicator={false}
|
||
>
|
||
<Card className="mb-4 overflow-hidden rounded-xl border border-border bg-white">
|
||
<CardContent className="p-5">
|
||
<View className="flex-row items-center justify-between">
|
||
<View className="flex-row items-center gap-2">
|
||
<FileText color={PRIMARY} size={22} strokeWidth={2} />
|
||
<Text className="font-semibold text-gray-900">Invoice #{invoice?.invoiceNumber ?? id}</Text>
|
||
</View>
|
||
<View className="rounded-full bg-amber-500/20 px-2.5 py-1">
|
||
<Text className="text-xs font-medium text-amber-700">{invoice?.status ?? 'Waiting'}</Text>
|
||
</View>
|
||
</View>
|
||
<Text className="text-muted-foreground mt-2 text-sm">Amount due</Text>
|
||
<Text className="mt-1 text-2xl font-bold text-gray-900">${invoice?.amount.toLocaleString() ?? '—'}</Text>
|
||
<View className="mt-3 flex-row gap-4">
|
||
<View className="flex-row items-center gap-1.5">
|
||
<Calendar color="#71717a" size={16} strokeWidth={2} />
|
||
<Text className="text-muted-foreground text-sm">Due {invoice?.dueDate ?? '—'}</Text>
|
||
</View>
|
||
<View className="flex-row items-center gap-1.5">
|
||
<Calendar color="#71717a" size={16} strokeWidth={2} />
|
||
<Text className="text-muted-foreground text-sm">Issued {invoice?.createdAt ?? '—'}</Text>
|
||
</View>
|
||
</View>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="mb-4 overflow-hidden rounded-xl border border-border bg-white">
|
||
<CardHeader className="pb-2">
|
||
<View className="flex-row items-center gap-2">
|
||
<User color="#71717a" size={18} strokeWidth={2} />
|
||
<CardTitle className="text-base">Bill to</CardTitle>
|
||
</View>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Text className="font-medium text-gray-900">{invoice?.recipient ?? '—'}</Text>
|
||
<Text className="text-muted-foreground text-sm">{invoice?.recipientEmail ?? '—'}</Text>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="mb-4 overflow-hidden rounded-xl border border-border bg-white">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-base">Items</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="gap-2">
|
||
{MOCK_ITEMS.map((item, i) => (
|
||
<View key={i} className="flex-row justify-between border-b border-border py-2 last:border-0">
|
||
<Text className="text-gray-700">{item.description} × {item.qty}</Text>
|
||
<Text className="font-medium text-gray-900">${item.total.toLocaleString()}</Text>
|
||
</View>
|
||
))}
|
||
<View className="mt-2 border-t border-border pt-3">
|
||
<View className="flex-row justify-between">
|
||
<Text className="font-semibold text-gray-900">Total</Text>
|
||
<Text className="font-semibold text-gray-900">${invoice?.amount.toLocaleString() ?? '1,540'}</Text>
|
||
</View>
|
||
</View>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<View className="flex-row gap-3">
|
||
<Button variant="outline" className="min-h-12 flex-1 rounded-xl border-border" onPress={() => {}}>
|
||
<Share2 color={PRIMARY} size={20} strokeWidth={2} />
|
||
<Text className="ml-2 font-medium text-gray-700">Share</Text>
|
||
</Button>
|
||
<Button variant="outline" className="min-h-12 flex-1 rounded-xl border-border" onPress={() => {}}>
|
||
<Download color={PRIMARY} size={20} strokeWidth={2} />
|
||
<Text className="ml-2 font-medium text-gray-700">PDF</Text>
|
||
</Button>
|
||
</View>
|
||
<Button variant="ghost" className="mt-4 rounded-xl" onPress={() => router.back()}>
|
||
<ChevronRight className="rotate-180" color="#71717a" size={20} strokeWidth={2} />
|
||
<Text className="ml-2 font-medium text-muted-foreground">Back</Text>
|
||
</Button>
|
||
</ScrollView>
|
||
);
|
||
}
|