- 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>
148 lines
7.0 KiB
TypeScript
148 lines
7.0 KiB
TypeScript
import { View, ScrollView, Pressable } from 'react-native';
|
|
import { Text } from '@/components/ui/text';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { EARNINGS_SUMMARY, MOCK_INVOICES, MOCK_USER } from '@/lib/mock-data';
|
|
import { router } from 'expo-router';
|
|
import { Camera, Send, ChevronRight, Wallet, DollarSign, Clock } from '@/lib/icons';
|
|
|
|
const PRIMARY = '#ea580c';
|
|
const statusColor: Record<string, string> = {
|
|
Waiting: 'bg-amber-500/20 text-amber-700',
|
|
Paid: 'bg-emerald-500/20 text-emerald-700',
|
|
Draft: 'bg-gray-200 text-gray-700',
|
|
Unpaid: 'bg-red-500/20 text-red-700',
|
|
};
|
|
|
|
export default function HomeScreen() {
|
|
return (
|
|
<ScrollView
|
|
className="flex-1 bg-[#f5f5f5]"
|
|
contentContainerStyle={{ padding: 20, paddingBottom: 40 }}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
<View className="mb-5">
|
|
<Text className="text-2xl font-bold text-gray-900">Hi {MOCK_USER.name},</Text>
|
|
<Text className="text-muted-foreground mt-1 text-base">Take a look at your last activity.</Text>
|
|
</View>
|
|
|
|
<Card className="mb-5 overflow-hidden rounded-2xl border-0 shadow-sm">
|
|
<View className="bg-primary/10 px-5 py-5">
|
|
<Text className="text-muted-foreground text-sm">Earnings balance</Text>
|
|
<Text className="mt-1 text-3xl font-bold text-gray-900">${EARNINGS_SUMMARY.balance.toLocaleString()}</Text>
|
|
</View>
|
|
<View className="flex-row border-t border-border">
|
|
<Pressable
|
|
className="flex-1 flex-row items-center gap-3 px-5 py-4"
|
|
onPress={() => router.push('/(tabs)/payments')}
|
|
>
|
|
<View className="rounded-xl bg-primary/15 p-2">
|
|
<Clock color={PRIMARY} size={20} strokeWidth={2} />
|
|
</View>
|
|
<View>
|
|
<Text className="text-muted-foreground text-xs">Waiting for pay</Text>
|
|
<Text className="font-semibold text-gray-900">${EARNINGS_SUMMARY.waitingAmount.toLocaleString()}</Text>
|
|
<Text className="text-muted-foreground text-xs">{EARNINGS_SUMMARY.waitingCount} Waiting invoice</Text>
|
|
</View>
|
|
</Pressable>
|
|
<View className="w-px bg-border" />
|
|
<Pressable className="flex-1 flex-row items-center gap-3 px-5 py-4">
|
|
<View className="rounded-xl bg-emerald-500/15 p-2">
|
|
<DollarSign color="#059669" size={20} strokeWidth={2} />
|
|
</View>
|
|
<View>
|
|
<Text className="text-muted-foreground text-xs">Paid this month</Text>
|
|
<Text className="font-semibold text-gray-900">${EARNINGS_SUMMARY.paidThisMonth.toLocaleString()}</Text>
|
|
<Text className="text-muted-foreground text-xs">{EARNINGS_SUMMARY.paidCount} Paid invoice</Text>
|
|
</View>
|
|
</Pressable>
|
|
</View>
|
|
</Card>
|
|
|
|
<View className="mb-5 flex-row gap-3">
|
|
<Button className="min-h-12 flex-1 rounded-xl bg-primary" onPress={() => router.push('/(tabs)/scan')}>
|
|
<Camera color="#ffffff" size={20} strokeWidth={2} />
|
|
<Text className="ml-2 text-primary-foreground font-medium">Scan invoice</Text>
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
className="min-h-12 flex-1 rounded-xl border-border"
|
|
onPress={() => router.push('/(tabs)/proforma')}
|
|
>
|
|
<Send color={PRIMARY} size={20} strokeWidth={2} />
|
|
<Text className="ml-2 font-medium text-gray-700">Send proforma</Text>
|
|
</Button>
|
|
</View>
|
|
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} className="-mx-1 mb-4">
|
|
<View className="flex-row gap-2 px-1">
|
|
{['All', 'Draft', 'Waiting', 'Paid', 'Unpaid'].map((filter) => (
|
|
<Pressable
|
|
key={filter}
|
|
className={`rounded-full px-4 py-2.5 ${filter === 'Waiting' ? 'bg-primary' : 'bg-white'} border border-border`}
|
|
>
|
|
<Text
|
|
className={
|
|
filter === 'Waiting' ? 'text-primary-foreground text-sm font-medium' : 'text-muted-foreground text-sm'
|
|
}
|
|
>
|
|
{filter}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
</ScrollView>
|
|
|
|
<View className="mb-2 flex-row items-center gap-2">
|
|
<View className="h-px flex-1 bg-border" />
|
|
<Text className="text-muted-foreground text-xs font-medium">Today</Text>
|
|
<View className="h-px flex-1 bg-border" />
|
|
</View>
|
|
{MOCK_INVOICES.filter((i) => i.status === 'Waiting').map((inv) => (
|
|
<Pressable key={inv.id} onPress={() => router.push(`/invoices/${inv.id}`)}>
|
|
<Card className="mb-2.5 overflow-hidden rounded-xl border border-border bg-white">
|
|
<CardContent className="flex-row items-center justify-between py-4 pl-4 pr-3">
|
|
<View className="flex-1">
|
|
<Text className="font-semibold text-gray-900">{inv.recipient}</Text>
|
|
<Text className="text-muted-foreground mt-0.5 text-sm">Invoice #{inv.invoiceNumber} · Due {inv.dueDate}</Text>
|
|
</View>
|
|
<View className="items-end gap-1">
|
|
<Text className="font-semibold text-gray-900">${inv.amount.toLocaleString()}</Text>
|
|
<View className={`rounded-full px-2.5 py-1 ${statusColor[inv.status]}`}>
|
|
<Text className="text-xs font-medium">{inv.status}</Text>
|
|
</View>
|
|
</View>
|
|
<ChevronRight className="ml-2 text-muted-foreground" color="#71717a" size={20} strokeWidth={2} />
|
|
</CardContent>
|
|
</Card>
|
|
</Pressable>
|
|
))}
|
|
|
|
<View className="mb-2 mt-6 flex-row items-center gap-2">
|
|
<View className="h-px flex-1 bg-border" />
|
|
<Text className="text-muted-foreground text-xs font-medium">Yesterday</Text>
|
|
<View className="h-px flex-1 bg-border" />
|
|
</View>
|
|
{MOCK_INVOICES.filter((i) => i.status === 'Paid').map((inv) => (
|
|
<Pressable key={inv.id} onPress={() => router.push(`/invoices/${inv.id}`)}>
|
|
<Card className="mb-2.5 overflow-hidden rounded-xl border border-border bg-white">
|
|
<CardContent className="flex-row items-center justify-between py-4 pl-4 pr-3">
|
|
<View className="flex-1">
|
|
<Text className="font-semibold text-gray-900">{inv.recipient}</Text>
|
|
<Text className="text-muted-foreground mt-0.5 text-sm">Invoice #{inv.invoiceNumber} · {inv.dueDate}</Text>
|
|
</View>
|
|
<View className="items-end gap-1">
|
|
<Text className="font-semibold text-gray-900">${inv.amount.toLocaleString()}</Text>
|
|
<View className={`rounded-full px-2.5 py-1 ${statusColor[inv.status]}`}>
|
|
<Text className="text-xs font-medium">{inv.status}</Text>
|
|
</View>
|
|
</View>
|
|
<ChevronRight className="ml-2 text-muted-foreground" color="#71717a" size={20} strokeWidth={2} />
|
|
</CardContent>
|
|
</Card>
|
|
</Pressable>
|
|
))}
|
|
</ScrollView>
|
|
);
|
|
}
|