Compare commits
No commits in common. "94064e66f7a81633383c9fdb9228658e1b949dc1" and "9d2d2b0af776a6d8c067b028a0b4f271ea8b5fd1" have entirely different histories.
94064e66f7
...
9d2d2b0af7
33
App.tsx
33
App.tsx
|
|
@ -1,29 +1,20 @@
|
||||||
import './global.css';
|
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import { PortalHost } from '@rn-primitives/portal';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 items-center justify-center gap-6 bg-white p-6">
|
<View style={styles.container}>
|
||||||
<Text className="text-xl font-bold text-gray-900">
|
<Text>Open up App.tsx to start working on your app!</Text>
|
||||||
Yaltopia Tickets App
|
|
||||||
</Text>
|
|
||||||
<Card className="w-full max-w-sm">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Scan. Send. Reconcile.</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Button>
|
|
||||||
<Text>Get started</Text>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
<PortalHost />
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
31
app.json
31
app.json
|
|
@ -1 +1,30 @@
|
||||||
{"expo":{"name":"Yaltopia Tickets App","slug":"yaltopia-tickets-app","version":"1.0.0","orientation":"portrait","icon":"./assets/icon.png","userInterfaceStyle":"light","newArchEnabled":true,"splash":{"image":"./assets/splash-icon.png","resizeMode":"contain","backgroundColor":"#ffffff"},"ios":{"supportsTablet":true},"android":{"adaptiveIcon":{"foregroundImage":"./assets/adaptive-icon.png","backgroundColor":"#ffffff"},"edgeToEdgeEnabled":true,"predictiveBackGestureEnabled":false},"web":{"favicon":"./assets/favicon.png","bundler":"metro"},"scheme":"yaltopia-tickets"}}
|
{
|
||||||
|
"expo": {
|
||||||
|
"name": "Yaltopia-Tickets-App",
|
||||||
|
"slug": "Yaltopia-Tickets-App",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"orientation": "portrait",
|
||||||
|
"icon": "./assets/icon.png",
|
||||||
|
"userInterfaceStyle": "light",
|
||||||
|
"newArchEnabled": true,
|
||||||
|
"splash": {
|
||||||
|
"image": "./assets/splash-icon.png",
|
||||||
|
"resizeMode": "contain",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"ios": {
|
||||||
|
"supportsTablet": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"adaptiveIcon": {
|
||||||
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
|
"backgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"edgeToEdgeEnabled": true,
|
||||||
|
"predictiveBackGestureEnabled": false
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"favicon": "./assets/favicon.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import { Tabs } from 'expo-router';
|
|
||||||
import { View } from 'react-native';
|
|
||||||
|
|
||||||
const NAV_BG = '#2d2d2d';
|
|
||||||
const ACTIVE_TINT = '#ea580c';
|
|
||||||
const INACTIVE_TINT = '#a1a1aa';
|
|
||||||
|
|
||||||
export default function TabsLayout() {
|
|
||||||
return (
|
|
||||||
<Tabs
|
|
||||||
screenOptions={{
|
|
||||||
headerStyle: { backgroundColor: NAV_BG },
|
|
||||||
headerTintColor: '#ffffff',
|
|
||||||
headerTitleStyle: { fontWeight: '600' },
|
|
||||||
tabBarStyle: { backgroundColor: NAV_BG },
|
|
||||||
tabBarActiveTintColor: ACTIVE_TINT,
|
|
||||||
tabBarInactiveTintColor: INACTIVE_TINT,
|
|
||||||
tabBarLabelStyle: { fontSize: 12 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tabs.Screen
|
|
||||||
name="index"
|
|
||||||
options={{
|
|
||||||
title: 'Home',
|
|
||||||
tabBarLabel: 'Home',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tabs.Screen
|
|
||||||
name="scan"
|
|
||||||
options={{
|
|
||||||
title: 'Scan Invoice',
|
|
||||||
tabBarLabel: 'Scan',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tabs.Screen
|
|
||||||
name="proforma"
|
|
||||||
options={{
|
|
||||||
title: 'Proforma',
|
|
||||||
tabBarLabel: 'Proforma',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tabs.Screen
|
|
||||||
name="payments"
|
|
||||||
options={{
|
|
||||||
title: 'Payments',
|
|
||||||
tabBarLabel: 'Payments',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Tabs.Screen
|
|
||||||
name="profile"
|
|
||||||
options={{
|
|
||||||
title: 'Profile',
|
|
||||||
tabBarLabel: 'Profile',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
import { View, ScrollView, Pressable } from 'react-native';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { EARNINGS_SUMMARY, MOCK_INVOICES, MOCK_USER } from '@/lib/mock-data';
|
|
||||||
import { router } from 'expo-router';
|
|
||||||
|
|
||||||
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: 16, paddingBottom: 32 }}>
|
|
||||||
<View className="mb-4">
|
|
||||||
<Text className="text-2xl font-bold text-gray-900">Hi {MOCK_USER.name},</Text>
|
|
||||||
<Text className="text-muted-foreground mt-1">Take a look at your last activity.</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Card className="mb-4 overflow-hidden border-0">
|
|
||||||
<View className="bg-primary/10 p-4">
|
|
||||||
<Text className="text-muted-foreground text-sm">Earnings balance</Text>
|
|
||||||
<Text className="text-2xl font-bold text-gray-900">${EARNINGS_SUMMARY.balance.toLocaleString()}</Text>
|
|
||||||
</View>
|
|
||||||
<CardContent className="flex-row border-t border-border">
|
|
||||||
<Pressable className="flex-1 py-3" onPress={() => router.push('/(tabs)/payments')}>
|
|
||||||
<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>
|
|
||||||
</Pressable>
|
|
||||||
<View className="w-px bg-border" />
|
|
||||||
<Pressable className="flex-1 py-3">
|
|
||||||
<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>
|
|
||||||
</Pressable>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<View className="mb-4 flex-row gap-3">
|
|
||||||
<Button className="flex-1 bg-primary" onPress={() => router.push('/(tabs)/scan')}>
|
|
||||||
<Text className="text-primary-foreground font-medium">Scan invoice</Text>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="flex-1" onPress={() => router.push('/(tabs)/proforma')}>
|
|
||||||
<Text className="font-medium">Send proforma</Text>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className="mb-2 flex-row gap-2">
|
|
||||||
{['All', 'Draft', 'Waiting', 'Paid', 'Unpaid'].map((filter) => (
|
|
||||||
<View
|
|
||||||
key={filter}
|
|
||||||
className={`rounded-full px-3 py-1.5 ${filter === 'Waiting' ? 'bg-primary' : 'bg-muted'}`}
|
|
||||||
>
|
|
||||||
<Text className={filter === 'Waiting' ? 'text-primary-foreground text-sm font-medium' : 'text-muted-foreground text-sm'}>
|
|
||||||
{filter}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 text-sm">Today</Text>
|
|
||||||
{MOCK_INVOICES.filter((i) => i.status === 'Waiting').map((inv) => (
|
|
||||||
<Card key={inv.id} className="mb-2">
|
|
||||||
<CardContent className="flex-row items-center justify-between py-3">
|
|
||||||
<View className="flex-1">
|
|
||||||
<Text className="font-semibold text-gray-900">{inv.recipient}</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">Invoice #{inv.invoiceNumber} - Due {inv.dueDate}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="items-end">
|
|
||||||
<Text className="font-semibold text-gray-900">${inv.amount.toLocaleString()}</Text>
|
|
||||||
<View className={`mt-1 rounded-full px-2 py-0.5 ${statusColor[inv.status]}`}>
|
|
||||||
<Text className="text-xs font-medium">{inv.status}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 mt-4 text-sm">Yesterday</Text>
|
|
||||||
{MOCK_INVOICES.filter((i) => i.status === 'Paid').map((inv) => (
|
|
||||||
<Card key={inv.id} className="mb-2">
|
|
||||||
<CardContent className="flex-row items-center justify-between py-3">
|
|
||||||
<View className="flex-1">
|
|
||||||
<Text className="font-semibold text-gray-900">{inv.recipient}</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">Invoice #{inv.invoiceNumber} - {inv.dueDate}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="items-end">
|
|
||||||
<Text className="font-semibold text-gray-900">${inv.amount.toLocaleString()}</Text>
|
|
||||||
<View className={`mt-1 rounded-full px-2 py-0.5 ${statusColor[inv.status]}`}>
|
|
||||||
<Text className="text-xs font-medium">{inv.status}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import { View, ScrollView } from 'react-native';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
|
||||||
import { MOCK_PAYMENTS } from '@/lib/mock-data';
|
|
||||||
|
|
||||||
export default function PaymentsScreen() {
|
|
||||||
const matched = MOCK_PAYMENTS.filter((p) => p.matched);
|
|
||||||
const pending = MOCK_PAYMENTS.filter((p) => !p.matched);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Text className="text-muted-foreground mb-4">
|
|
||||||
Match payment SMS (e.g. bank or Telebirr) to invoices for quick reconciliation.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Button className="mb-4 bg-primary">
|
|
||||||
<Text className="text-primary-foreground font-medium">Scan SMS now</Text>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 text-sm">Pending match</Text>
|
|
||||||
{pending.map((pay) => (
|
|
||||||
<Card key={pay.id} className="mb-2 border-amber-500/30">
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<View>
|
|
||||||
<Text className="font-semibold text-gray-900">${pay.amount.toLocaleString()}</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">{pay.source} · {pay.date}</Text>
|
|
||||||
</View>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Text className="font-medium">Match to invoice</Text>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 mt-4 text-sm">Reconciled</Text>
|
|
||||||
{matched.map((pay) => (
|
|
||||||
<Card key={pay.id} className="mb-2">
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<View>
|
|
||||||
<Text className="font-semibold text-gray-900">${pay.amount.toLocaleString()}</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">
|
|
||||||
{pay.source} · {pay.date} {pay.reference && `· ${pay.reference}`}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className="rounded-full bg-emerald-500/20 px-2 py-0.5">
|
|
||||||
<Text className="text-xs font-medium text-emerald-700">Matched</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import { View, ScrollView, Pressable } from 'react-native';
|
|
||||||
import { 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 { MOCK_USER } from '@/lib/mock-data';
|
|
||||||
|
|
||||||
export default function ProfileScreen() {
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<View className="mb-6 items-center">
|
|
||||||
<View className="mb-3 h-20 w-20 items-center justify-center rounded-full bg-primary">
|
|
||||||
<Text className="text-3xl font-bold text-primary-foreground">{MOCK_USER.name[0]}</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="text-xl font-semibold text-gray-900">{MOCK_USER.name}</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">{MOCK_USER.email}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Account</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="gap-2">
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Email</Text>
|
|
||||||
<Text className="text-gray-900">{MOCK_USER.email}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Language</Text>
|
|
||||||
<Text className="text-gray-900">English</Text>
|
|
||||||
</View>
|
|
||||||
<Pressable onPress={() => router.push('/notifications')} className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Notifications</Text>
|
|
||||||
<Text className="text-primary font-medium">Manage</Text>
|
|
||||||
</Pressable>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>About</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Text className="text-muted-foreground text-sm">
|
|
||||||
Yaltopia Tickets App — Scan. Send. Reconcile. Companion to the Yaltopia Tickets web app.
|
|
||||||
</Text>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Button variant="outline" className="mt-2" onPress={() => router.push('/login')}>
|
|
||||||
<Text className="font-medium">Sign in (different account)</Text>
|
|
||||||
</Button>
|
|
||||||
<Button variant="destructive" className="mt-2">
|
|
||||||
<Text className="font-medium">Log out</Text>
|
|
||||||
</Button>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { View, ScrollView, Pressable } from 'react-native';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from '@/components/ui/card';
|
|
||||||
import { MOCK_PROFORMA } from '@/lib/mock-data';
|
|
||||||
import { router } from 'expo-router';
|
|
||||||
|
|
||||||
export default function ProformaScreen() {
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Text className="text-muted-foreground mb-4">
|
|
||||||
Create or select proforma requests and share with contacts via email or SMS.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Button className="mb-4 bg-primary" onPress={() => {}}>
|
|
||||||
<Text className="text-primary-foreground font-medium">Create new proforma</Text>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 text-sm">Your proforma requests</Text>
|
|
||||||
{MOCK_PROFORMA.map((pf) => (
|
|
||||||
<Card key={pf.id} className="mb-3">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{pf.title}</CardTitle>
|
|
||||||
<CardDescription>{pf.description}</CardDescription>
|
|
||||||
<Text className="text-muted-foreground mt-1 text-xs">Deadline: {pf.deadline} · {pf.itemCount} items</Text>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex-row justify-between">
|
|
||||||
<Text className="text-muted-foreground text-sm">Sent to {pf.sentCount} contacts</Text>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<Text className="font-medium">Send to contacts</Text>
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { View, ScrollView } from 'react-native';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
|
|
||||||
export default function ScanScreen() {
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Text className="text-muted-foreground mb-4">
|
|
||||||
Capture paper or digital invoices with your camera. We'll extract vendor, amount, date, and line items.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Card className="mb-4 border-2 border-dashed border-border">
|
|
||||||
<CardContent className="items-center justify-center py-16">
|
|
||||||
<View className="mb-4 h-20 w-20 items-center justify-center rounded-full bg-primary/10">
|
|
||||||
<Text className="text-4xl">📷</Text>
|
|
||||||
</View>
|
|
||||||
<Text className="mb-2 text-center text-lg font-semibold text-gray-900">Scan invoice</Text>
|
|
||||||
<Text className="text-muted-foreground mb-4 text-center text-sm">
|
|
||||||
Tap below to open camera and capture an invoice
|
|
||||||
</Text>
|
|
||||||
<Button className="bg-primary min-w-[200]">
|
|
||||||
<Text className="text-primary-foreground font-medium">Open camera</Text>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mb-2 text-sm">Recent scans</Text>
|
|
||||||
<Card className="mb-2">
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<View>
|
|
||||||
<Text className="font-medium text-gray-900">Acme Corp - Invoice #101</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">Scanned Sep 12, 2022 · $1,240</Text>
|
|
||||||
</View>
|
|
||||||
<View className="rounded-full bg-amber-500/20 px-2 py-0.5">
|
|
||||||
<Text className="text-xs font-medium text-amber-700">Pending</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="mb-2">
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<View>
|
|
||||||
<Text className="font-medium text-gray-900">Tech Supplies Ltd - Invoice #88</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">Scanned Sep 11, 2022 · $890</Text>
|
|
||||||
</View>
|
|
||||||
<View className="rounded-full bg-emerald-500/20 px-2 py-0.5">
|
|
||||||
<Text className="text-xs font-medium text-emerald-700">Saved</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import '../global.css';
|
|
||||||
import { Stack } from 'expo-router';
|
|
||||||
import { StatusBar } from 'expo-status-bar';
|
|
||||||
import { PortalHost } from '@rn-primitives/portal';
|
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
||||||
import { View } from 'react-native';
|
|
||||||
|
|
||||||
export default function RootLayout() {
|
|
||||||
return (
|
|
||||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
||||||
<SafeAreaProvider>
|
|
||||||
<View className="flex-1 bg-background">
|
|
||||||
<StatusBar style="light" />
|
|
||||||
<Stack
|
|
||||||
screenOptions={{
|
|
||||||
headerStyle: { backgroundColor: '#2d2d2d' },
|
|
||||||
headerTintColor: '#ffffff',
|
|
||||||
headerTitleStyle: { fontWeight: '600' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
||||||
<Stack.Screen name="proforma/[id]" options={{ title: 'Proforma request' }} />
|
|
||||||
<Stack.Screen name="payments/[id]" options={{ title: 'Payment' }} />
|
|
||||||
<Stack.Screen name="notifications/index" options={{ title: 'Notifications' }} />
|
|
||||||
<Stack.Screen name="notifications/settings" options={{ title: 'Notification settings' }} />
|
|
||||||
<Stack.Screen name="login" options={{ title: 'Sign in', headerShown: false }} />
|
|
||||||
</Stack>
|
|
||||||
<PortalHost />
|
|
||||||
</View>
|
|
||||||
</SafeAreaProvider>
|
|
||||||
</GestureHandlerRootView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { View, ScrollView } from 'react-native';
|
|
||||||
import { router } from 'expo-router';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
||||||
|
|
||||||
export default function LoginScreen() {
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingVertical: 48 }}>
|
|
||||||
<Text className="mb-6 text-center text-2xl font-bold text-gray-900">Yaltopia Tickets</Text>
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Sign in</CardTitle>
|
|
||||||
<CardDescription>Use the same account as the web app.</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="gap-3">
|
|
||||||
<Button className="bg-primary">
|
|
||||||
<Text className="text-primary-foreground font-medium">Email & password</Text>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline">
|
|
||||||
<Text className="font-medium">Continue with Google</Text>
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Button variant="ghost" onPress={() => router.back()}>
|
|
||||||
<Text className="font-medium">Back</Text>
|
|
||||||
</Button>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { View, ScrollView, Pressable } from 'react-native';
|
|
||||||
import { router } from 'expo-router';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
|
||||||
|
|
||||||
const MOCK_NOTIFICATIONS = [
|
|
||||||
{ id: '1', title: 'Invoice reminder', body: 'Invoice #2 to Robin Murray is due in 2 days.', time: '2h ago', read: false },
|
|
||||||
{ id: '2', title: 'Payment received', body: 'Payment of $500 received for Invoice #4.', time: '1d ago', read: true },
|
|
||||||
{ id: '3', title: 'Proforma submission', body: 'Vendor A submitted a quote for Marketing Landing Page.', time: '2d ago', read: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function NotificationsScreen() {
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<View className="mb-4 flex-row items-center justify-between">
|
|
||||||
<Text className="text-xl font-semibold text-gray-900">Notifications</Text>
|
|
||||||
<Pressable onPress={() => router.push('/notifications/settings')}>
|
|
||||||
<Text className="text-primary font-medium">Settings</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{MOCK_NOTIFICATIONS.map((n) => (
|
|
||||||
<Card key={n.id} className={`mb-2 ${!n.read ? 'border-primary/30' : ''}`}>
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<Text className="font-semibold text-gray-900">{n.title}</Text>
|
|
||||||
<Text className="text-muted-foreground mt-1 text-sm">{n.body}</Text>
|
|
||||||
<Text className="text-muted-foreground mt-1 text-xs">{n.time}</Text>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { View, ScrollView, Switch } from 'react-native';
|
|
||||||
import { router } from 'expo-router';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { Text } from '@/components/ui/text';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
|
|
||||||
export default function NotificationSettingsScreen() {
|
|
||||||
const [invoiceReminders, setInvoiceReminders] = useState(true);
|
|
||||||
const [daysBeforeDue, setDaysBeforeDue] = useState(2);
|
|
||||||
const [newsAlerts, setNewsAlerts] = useState(true);
|
|
||||||
const [reportReady, setReportReady] = useState(true);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Notification settings</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="gap-4">
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<Text className="text-gray-900">Invoice reminders</Text>
|
|
||||||
<Switch value={invoiceReminders} onValueChange={setInvoiceReminders} />
|
|
||||||
</View>
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<Text className="text-gray-900">News & announcements</Text>
|
|
||||||
<Switch value={newsAlerts} onValueChange={setNewsAlerts} />
|
|
||||||
</View>
|
|
||||||
<View className="flex-row items-center justify-between">
|
|
||||||
<Text className="text-gray-900">Report ready</Text>
|
|
||||||
<Switch value={reportReady} onValueChange={setReportReady} />
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Button variant="outline" onPress={() => router.back()}>
|
|
||||||
<Text className="font-medium">Back</Text>
|
|
||||||
</Button>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
||||||
export default function PaymentDetailScreen() {
|
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Payment #{id ?? '—'}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="gap-2">
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Amount</Text>
|
|
||||||
<Text className="font-semibold text-gray-900">$2,000.00</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Source</Text>
|
|
||||||
<Text className="text-gray-900">Telebirr</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Date</Text>
|
|
||||||
<Text className="text-gray-900">Sep 11, 2022</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between py-2">
|
|
||||||
<Text className="text-muted-foreground">Associated invoice</Text>
|
|
||||||
<Text className="text-amber-600">Not linked</Text>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Button className="mb-3 bg-primary" onPress={() => {}}>
|
|
||||||
<Text className="text-primary-foreground font-medium">Associate to invoice</Text>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" onPress={() => router.back()}>
|
|
||||||
<Text className="font-medium">Back to payments</Text>
|
|
||||||
</Button>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
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, CardDescription } from '@/components/ui/card';
|
|
||||||
|
|
||||||
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 },
|
|
||||||
];
|
|
||||||
const MOCK_SUBTOTAL = 1400;
|
|
||||||
const MOCK_TAX = 140;
|
|
||||||
const MOCK_TOTAL = 1540;
|
|
||||||
|
|
||||||
export default function ProformaDetailScreen() {
|
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView className="flex-1 bg-background" contentContainerStyle={{ padding: 16, paddingBottom: 32 }}>
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Proforma Request #{id ?? '—'}</CardTitle>
|
|
||||||
<CardDescription>Marketing Landing Page Package</CardDescription>
|
|
||||||
<Text className="text-muted-foreground mt-1 text-sm">Deadline: Sep 20, 2022 · OPEN</Text>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="gap-2">
|
|
||||||
{MOCK_ITEMS.map((item, i) => (
|
|
||||||
<View key={i} className="flex-row justify-between py-2">
|
|
||||||
<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-2">
|
|
||||||
<View className="flex-row justify-between">
|
|
||||||
<Text className="text-muted-foreground">Subtotal</Text>
|
|
||||||
<Text className="text-gray-900">${MOCK_SUBTOTAL.toLocaleString()}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between">
|
|
||||||
<Text className="text-muted-foreground">Tax (10%)</Text>
|
|
||||||
<Text className="text-gray-900">${MOCK_TAX.toLocaleString()}</Text>
|
|
||||||
</View>
|
|
||||||
<View className="flex-row justify-between">
|
|
||||||
<Text className="font-semibold text-gray-900">Total</Text>
|
|
||||||
<Text className="font-semibold text-gray-900">${MOCK_TOTAL.toLocaleString()}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Button className="mb-3 bg-primary" onPress={() => {}}>
|
|
||||||
<Text className="text-primary-foreground font-medium">Send to contacts</Text>
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" onPress={() => router.back()}>
|
|
||||||
<Text className="font-medium">Back to list</Text>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Text className="text-muted-foreground mt-6 mb-2 text-sm">Submissions (mock)</Text>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="py-3">
|
|
||||||
<Text className="font-medium text-gray-900">Vendor A — $1,450</Text>
|
|
||||||
<Text className="text-muted-foreground text-sm">Submitted Sep 15, 2022</Text>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = function (api) {
|
|
||||||
api.cache(true);
|
|
||||||
return {
|
|
||||||
presets: [
|
|
||||||
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
|
||||||
'nativewind/babel',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
|
||||||
"style": "new-york",
|
|
||||||
"rsc": false,
|
|
||||||
"tsx": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "tailwind.config.js",
|
|
||||||
"css": "global.css",
|
|
||||||
"baseColor": "neutral",
|
|
||||||
"cssVariables": true
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/components/ui",
|
|
||||||
"lib": "@/lib",
|
|
||||||
"hooks": "@/hooks"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import { TextClassContext } from '@/components/ui/text';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
|
||||||
import { Platform, Pressable } from 'react-native';
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
cn(
|
|
||||||
'group shrink-0 flex-row items-center justify-center gap-2 rounded-md shadow-none',
|
|
||||||
Platform.select({
|
|
||||||
web: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
||||||
})
|
|
||||||
),
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: cn(
|
|
||||||
'bg-primary active:bg-primary/90 shadow-sm shadow-black/5',
|
|
||||||
Platform.select({ web: 'hover:bg-primary/90' })
|
|
||||||
),
|
|
||||||
destructive: cn(
|
|
||||||
'bg-destructive active:bg-destructive/90 dark:bg-destructive/60 shadow-sm shadow-black/5',
|
|
||||||
Platform.select({
|
|
||||||
web: 'hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
outline: cn(
|
|
||||||
'border-border bg-background active:bg-accent dark:bg-input/30 dark:border-input dark:active:bg-input/50 border shadow-sm shadow-black/5',
|
|
||||||
Platform.select({
|
|
||||||
web: 'hover:bg-accent dark:hover:bg-input/50',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
secondary: cn(
|
|
||||||
'bg-secondary active:bg-secondary/80 shadow-sm shadow-black/5',
|
|
||||||
Platform.select({ web: 'hover:bg-secondary/80' })
|
|
||||||
),
|
|
||||||
ghost: cn(
|
|
||||||
'active:bg-accent dark:active:bg-accent/50',
|
|
||||||
Platform.select({ web: 'hover:bg-accent dark:hover:bg-accent/50' })
|
|
||||||
),
|
|
||||||
link: '',
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: cn('h-10 px-4 py-2 sm:h-9', Platform.select({ web: 'has-[>svg]:px-3' })),
|
|
||||||
sm: cn('h-9 gap-1.5 rounded-md px-3 sm:h-8', Platform.select({ web: 'has-[>svg]:px-2.5' })),
|
|
||||||
lg: cn('h-11 rounded-md px-6 sm:h-10', Platform.select({ web: 'has-[>svg]:px-4' })),
|
|
||||||
icon: 'h-10 w-10 sm:h-9 sm:w-9',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
size: 'default',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttonTextVariants = cva(
|
|
||||||
cn(
|
|
||||||
'text-foreground text-sm font-medium',
|
|
||||||
Platform.select({ web: 'pointer-events-none transition-colors' })
|
|
||||||
),
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: 'text-primary-foreground',
|
|
||||||
destructive: 'text-white',
|
|
||||||
outline: cn(
|
|
||||||
'group-active:text-accent-foreground',
|
|
||||||
Platform.select({ web: 'group-hover:text-accent-foreground' })
|
|
||||||
),
|
|
||||||
secondary: 'text-secondary-foreground',
|
|
||||||
ghost: 'group-active:text-accent-foreground',
|
|
||||||
link: cn(
|
|
||||||
'text-primary group-active:underline',
|
|
||||||
Platform.select({ web: 'underline-offset-4 hover:underline group-hover:underline' })
|
|
||||||
),
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: '',
|
|
||||||
sm: '',
|
|
||||||
lg: '',
|
|
||||||
icon: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
size: 'default',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type ButtonProps = React.ComponentProps<typeof Pressable> &
|
|
||||||
React.RefAttributes<typeof Pressable> &
|
|
||||||
VariantProps<typeof buttonVariants>;
|
|
||||||
|
|
||||||
function Button({ className, variant, size, ...props }: ButtonProps) {
|
|
||||||
return (
|
|
||||||
<TextClassContext.Provider value={buttonTextVariants({ variant, size })}>
|
|
||||||
<Pressable
|
|
||||||
className={cn(props.disabled && 'opacity-50', buttonVariants({ variant, size }), className)}
|
|
||||||
role="button"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</TextClassContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Button, buttonTextVariants, buttonVariants };
|
|
||||||
export type { ButtonProps };
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import { Text, TextClassContext } from '@/components/ui/text';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { View, type ViewProps } from 'react-native';
|
|
||||||
|
|
||||||
function Card({ className, ...props }: ViewProps & React.RefAttributes<View>) {
|
|
||||||
return (
|
|
||||||
<TextClassContext.Provider value="text-card-foreground">
|
|
||||||
<View
|
|
||||||
className={cn(
|
|
||||||
'bg-card border-border flex flex-col gap-6 rounded-xl border py-6 shadow-sm shadow-black/5',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</TextClassContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: ViewProps & React.RefAttributes<View>) {
|
|
||||||
return <View className={cn('flex flex-col gap-1.5 px-6', className)} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardTitle({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
role="heading"
|
|
||||||
aria-level={3}
|
|
||||||
className={cn('font-semibold leading-none', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardDescription({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof Text> & React.RefAttributes<Text>) {
|
|
||||||
return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardContent({ className, ...props }: ViewProps & React.RefAttributes<View>) {
|
|
||||||
return <View className={cn('px-6', className)} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: ViewProps & React.RefAttributes<View>) {
|
|
||||||
return <View className={cn('flex flex-row items-center px-6', className)} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import * as Slot from '@rn-primitives/slot';
|
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { Platform, Text as RNText, type Role } from 'react-native';
|
|
||||||
|
|
||||||
const textVariants = cva(
|
|
||||||
cn(
|
|
||||||
'text-foreground text-base',
|
|
||||||
Platform.select({
|
|
||||||
web: 'select-text',
|
|
||||||
})
|
|
||||||
),
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: '',
|
|
||||||
h1: cn(
|
|
||||||
'text-center text-4xl font-extrabold tracking-tight',
|
|
||||||
Platform.select({ web: 'scroll-m-20 text-balance' })
|
|
||||||
),
|
|
||||||
h2: cn(
|
|
||||||
'border-border border-b pb-2 text-3xl font-semibold tracking-tight',
|
|
||||||
Platform.select({ web: 'scroll-m-20 first:mt-0' })
|
|
||||||
),
|
|
||||||
h3: cn('text-2xl font-semibold tracking-tight', Platform.select({ web: 'scroll-m-20' })),
|
|
||||||
h4: cn('text-xl font-semibold tracking-tight', Platform.select({ web: 'scroll-m-20' })),
|
|
||||||
p: 'mt-3 leading-7 sm:mt-6',
|
|
||||||
blockquote: 'mt-4 border-l-2 pl-3 italic sm:mt-6 sm:pl-6',
|
|
||||||
code: cn(
|
|
||||||
'bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold'
|
|
||||||
),
|
|
||||||
lead: 'text-muted-foreground text-xl',
|
|
||||||
large: 'text-lg font-semibold',
|
|
||||||
small: 'text-sm font-medium leading-none',
|
|
||||||
muted: 'text-muted-foreground text-sm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type TextVariantProps = VariantProps<typeof textVariants>;
|
|
||||||
|
|
||||||
type TextVariant = NonNullable<TextVariantProps['variant']>;
|
|
||||||
|
|
||||||
const ROLE: Partial<Record<TextVariant, Role>> = {
|
|
||||||
h1: 'heading',
|
|
||||||
h2: 'heading',
|
|
||||||
h3: 'heading',
|
|
||||||
h4: 'heading',
|
|
||||||
blockquote: Platform.select({ web: 'blockquote' as Role }),
|
|
||||||
code: Platform.select({ web: 'code' as Role }),
|
|
||||||
};
|
|
||||||
|
|
||||||
const ARIA_LEVEL: Partial<Record<TextVariant, string>> = {
|
|
||||||
h1: '1',
|
|
||||||
h2: '2',
|
|
||||||
h3: '3',
|
|
||||||
h4: '4',
|
|
||||||
};
|
|
||||||
|
|
||||||
const TextClassContext = React.createContext<string | undefined>(undefined);
|
|
||||||
|
|
||||||
function Text({
|
|
||||||
className,
|
|
||||||
asChild = false,
|
|
||||||
variant = 'default',
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof RNText> &
|
|
||||||
TextVariantProps &
|
|
||||||
React.RefAttributes<RNText> & {
|
|
||||||
asChild?: boolean;
|
|
||||||
}) {
|
|
||||||
const textClass = React.useContext(TextClassContext);
|
|
||||||
const Component = asChild ? Slot.Text : RNText;
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
className={cn(textVariants({ variant }), textClass, className)}
|
|
||||||
role={variant ? ROLE[variant] : undefined}
|
|
||||||
aria-level={variant ? ARIA_LEVEL[variant] : undefined}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Text, TextClassContext };
|
|
||||||
60
global.css
60
global.css
|
|
@ -1,60 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 0 0% 3.9%;
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 0 0% 3.9%;
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 0 0% 3.9%;
|
|
||||||
--primary: 24 90% 48%;
|
|
||||||
--primary-foreground: 0 0% 100%;
|
|
||||||
--secondary: 0 0% 96.1%;
|
|
||||||
--secondary-foreground: 0 0% 9%;
|
|
||||||
--muted: 0 0% 96.1%;
|
|
||||||
--muted-foreground: 0 0% 45.1%;
|
|
||||||
--accent: 0 0% 96.1%;
|
|
||||||
--accent-foreground: 0 0% 9%;
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
|
||||||
--destructive-foreground: 0 0% 98%;
|
|
||||||
--border: 0 0% 89.8%;
|
|
||||||
--input: 0 0% 89.8%;
|
|
||||||
--ring: 0 0% 63%;
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--chart-1: 12 76% 61%;
|
|
||||||
--chart-2: 173 58% 39%;
|
|
||||||
--chart-3: 197 37% 24%;
|
|
||||||
--chart-4: 43 74% 66%;
|
|
||||||
--chart-5: 27 87% 67%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark:root {
|
|
||||||
--background: 0 0% 3.9%;
|
|
||||||
--foreground: 0 0% 98%;
|
|
||||||
--card: 0 0% 3.9%;
|
|
||||||
--card-foreground: 0 0% 98%;
|
|
||||||
--popover: 0 0% 3.9%;
|
|
||||||
--popover-foreground: 0 0% 98%;
|
|
||||||
--primary: 0 0% 98%;
|
|
||||||
--primary-foreground: 0 0% 9%;
|
|
||||||
--secondary: 0 0% 14.9%;
|
|
||||||
--secondary-foreground: 0 0% 98%;
|
|
||||||
--muted: 0 0% 14.9%;
|
|
||||||
--muted-foreground: 0 0% 63.9%;
|
|
||||||
--accent: 0 0% 14.9%;
|
|
||||||
--accent-foreground: 0 0% 98%;
|
|
||||||
--destructive: 0 70.9% 59.4%;
|
|
||||||
--destructive-foreground: 0 0% 98%;
|
|
||||||
--border: 0 0% 14.9%;
|
|
||||||
--input: 0 0% 14.9%;
|
|
||||||
--ring: 300 0% 45%;
|
|
||||||
--chart-1: 220 70% 50%;
|
|
||||||
--chart-2: 160 60% 45%;
|
|
||||||
--chart-3: 30 80% 55%;
|
|
||||||
--chart-4: 280 65% 60%;
|
|
||||||
--chart-5: 340 75% 55%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
export const MOCK_USER = {
|
|
||||||
name: 'Octavia',
|
|
||||||
email: 'octavia@yaltopia.com',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MOCK_INVOICES = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
recipient: 'Robin Murray',
|
|
||||||
recipientEmail: 'robinmurray@email.com',
|
|
||||||
invoiceNumber: '2',
|
|
||||||
dueDate: 'Sep 11, 2022',
|
|
||||||
amount: 1540,
|
|
||||||
status: 'Waiting',
|
|
||||||
createdAt: 'Sep 8, 2022',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
recipient: 'Sophie Shonia',
|
|
||||||
recipientEmail: 'sophie@email.com',
|
|
||||||
invoiceNumber: '4',
|
|
||||||
dueDate: 'Sep 9, 2022',
|
|
||||||
amount: 500,
|
|
||||||
status: 'Paid',
|
|
||||||
createdAt: 'Sep 9, 2022',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
recipient: 'Atlantis Limited',
|
|
||||||
recipientEmail: 'contact@atlantis.com',
|
|
||||||
invoiceNumber: '3',
|
|
||||||
dueDate: 'Sep 9, 2022',
|
|
||||||
amount: 2000,
|
|
||||||
status: 'Paid',
|
|
||||||
createdAt: 'Sep 9, 2022',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const MOCK_PROFORMA = [
|
|
||||||
{
|
|
||||||
id: 'pf1',
|
|
||||||
title: 'Marketing Landing Page Package',
|
|
||||||
description: 'Landing page design and development',
|
|
||||||
deadline: 'Sep 20, 2022',
|
|
||||||
itemCount: 2,
|
|
||||||
sentCount: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pf2',
|
|
||||||
title: 'Q4 Brand Assets',
|
|
||||||
description: 'Logo variants and social templates',
|
|
||||||
deadline: 'Sep 25, 2022',
|
|
||||||
itemCount: 5,
|
|
||||||
sentCount: 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const MOCK_PAYMENTS = [
|
|
||||||
{
|
|
||||||
id: 'pay1',
|
|
||||||
date: 'Sep 12, 2022',
|
|
||||||
amount: 1540,
|
|
||||||
reference: 'INV-002',
|
|
||||||
source: 'Telebirr',
|
|
||||||
matched: true,
|
|
||||||
invoiceId: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pay2',
|
|
||||||
date: 'Sep 10, 2022',
|
|
||||||
amount: 500,
|
|
||||||
reference: 'INV-004',
|
|
||||||
source: 'Bank Transfer',
|
|
||||||
matched: true,
|
|
||||||
invoiceId: '2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pay3',
|
|
||||||
date: 'Sep 11, 2022',
|
|
||||||
amount: 2000,
|
|
||||||
reference: null,
|
|
||||||
source: 'Telebirr',
|
|
||||||
matched: false,
|
|
||||||
invoiceId: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const EARNINGS_SUMMARY = {
|
|
||||||
balance: 6432,
|
|
||||||
waitingAmount: 1540,
|
|
||||||
waitingCount: 1,
|
|
||||||
paidThisMonth: 4120,
|
|
||||||
paidCount: 4,
|
|
||||||
};
|
|
||||||
81
lib/theme.ts
81
lib/theme.ts
|
|
@ -1,81 +0,0 @@
|
||||||
import { DarkTheme, DefaultTheme, type Theme } from '@react-navigation/native';
|
|
||||||
|
|
||||||
export const THEME = {
|
|
||||||
light: {
|
|
||||||
background: 'hsl(0 0% 100%)',
|
|
||||||
foreground: 'hsl(0 0% 3.9%)',
|
|
||||||
card: 'hsl(0 0% 100%)',
|
|
||||||
cardForeground: 'hsl(0 0% 3.9%)',
|
|
||||||
popover: 'hsl(0 0% 100%)',
|
|
||||||
popoverForeground: 'hsl(0 0% 3.9%)',
|
|
||||||
primary: 'hsl(24 90% 48%)',
|
|
||||||
primaryForeground: 'hsl(0 0% 100%)',
|
|
||||||
secondary: 'hsl(0 0% 96.1%)',
|
|
||||||
secondaryForeground: 'hsl(0 0% 9%)',
|
|
||||||
muted: 'hsl(0 0% 96.1%)',
|
|
||||||
mutedForeground: 'hsl(0 0% 45.1%)',
|
|
||||||
accent: 'hsl(0 0% 96.1%)',
|
|
||||||
accentForeground: 'hsl(0 0% 9%)',
|
|
||||||
destructive: 'hsl(0 84.2% 60.2%)',
|
|
||||||
border: 'hsl(0 0% 89.8%)',
|
|
||||||
input: 'hsl(0 0% 89.8%)',
|
|
||||||
ring: 'hsl(0 0% 63%)',
|
|
||||||
radius: '0.625rem',
|
|
||||||
chart1: 'hsl(12 76% 61%)',
|
|
||||||
chart2: 'hsl(173 58% 39%)',
|
|
||||||
chart3: 'hsl(197 37% 24%)',
|
|
||||||
chart4: 'hsl(43 74% 66%)',
|
|
||||||
chart5: 'hsl(27 87% 67%)',
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
background: 'hsl(0 0% 3.9%)',
|
|
||||||
foreground: 'hsl(0 0% 98%)',
|
|
||||||
card: 'hsl(0 0% 3.9%)',
|
|
||||||
cardForeground: 'hsl(0 0% 98%)',
|
|
||||||
popover: 'hsl(0 0% 3.9%)',
|
|
||||||
popoverForeground: 'hsl(0 0% 98%)',
|
|
||||||
primary: 'hsl(0 0% 98%)',
|
|
||||||
primaryForeground: 'hsl(0 0% 9%)',
|
|
||||||
secondary: 'hsl(0 0% 14.9%)',
|
|
||||||
secondaryForeground: 'hsl(0 0% 98%)',
|
|
||||||
muted: 'hsl(0 0% 14.9%)',
|
|
||||||
mutedForeground: 'hsl(0 0% 63.9%)',
|
|
||||||
accent: 'hsl(0 0% 14.9%)',
|
|
||||||
accentForeground: 'hsl(0 0% 98%)',
|
|
||||||
destructive: 'hsl(0 70.9% 59.4%)',
|
|
||||||
border: 'hsl(0 0% 14.9%)',
|
|
||||||
input: 'hsl(0 0% 14.9%)',
|
|
||||||
ring: 'hsl(300 0% 45%)',
|
|
||||||
radius: '0.625rem',
|
|
||||||
chart1: 'hsl(220 70% 50%)',
|
|
||||||
chart2: 'hsl(160 60% 45%)',
|
|
||||||
chart3: 'hsl(30 80% 55%)',
|
|
||||||
chart4: 'hsl(280 65% 60%)',
|
|
||||||
chart5: 'hsl(340 75% 55%)',
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const NAV_THEME: Record<'light' | 'dark', Theme> = {
|
|
||||||
light: {
|
|
||||||
...DefaultTheme,
|
|
||||||
colors: {
|
|
||||||
background: THEME.light.background,
|
|
||||||
border: THEME.light.border,
|
|
||||||
card: THEME.light.card,
|
|
||||||
notification: THEME.light.destructive,
|
|
||||||
primary: THEME.light.primary,
|
|
||||||
text: THEME.light.foreground,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dark: {
|
|
||||||
...DarkTheme,
|
|
||||||
colors: {
|
|
||||||
background: THEME.dark.background,
|
|
||||||
border: THEME.dark.border,
|
|
||||||
card: THEME.dark.card,
|
|
||||||
notification: THEME.dark.destructive,
|
|
||||||
primary: THEME.dark.primary,
|
|
||||||
text: THEME.dark.foreground,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { clsx, type ClassValue } from 'clsx';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs));
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
const { getDefaultConfig } = require('expo/metro-config');
|
|
||||||
const { withNativeWind } = require('nativewind/metro');
|
|
||||||
|
|
||||||
const config = getDefaultConfig(__dirname);
|
|
||||||
|
|
||||||
module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 });
|
|
||||||
1
nativewind-env.d.ts
vendored
1
nativewind-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
||||||
/// <reference types="nativewind/types" />
|
|
||||||
11405
package-lock.json
generated
11405
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "yaltopia-tickets-app",
|
"name": "yaltopia-tickets-app",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "expo-router/entry",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android",
|
||||||
|
|
@ -9,34 +9,13 @@
|
||||||
"web": "expo start --web"
|
"web": "expo start --web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-runtime": "~6.1.2",
|
|
||||||
"@react-navigation/native": "^7.1.28",
|
|
||||||
"@rn-primitives/portal": "^1.3.0",
|
|
||||||
"@rn-primitives/slot": "^1.2.0",
|
|
||||||
"babel-preset-expo": "^54.0.10",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"expo": "~54.0.33",
|
"expo": "~54.0.33",
|
||||||
"expo-constants": "^18.0.13",
|
|
||||||
"expo-linking": "^8.0.11",
|
|
||||||
"expo-router": "^6.0.23",
|
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
"nativewind": "^4.2.2",
|
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-native": "0.81.5"
|
||||||
"react-native": "0.81.5",
|
|
||||||
"react-native-gesture-handler": "^2.30.0",
|
|
||||||
"react-native-reanimated": "^4.2.2",
|
|
||||||
"react-native-safe-area-context": "^5.6.2",
|
|
||||||
"react-native-screens": "^4.23.0",
|
|
||||||
"react-native-web": "^0.21.0",
|
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tailwindcss-animate": "^1.0.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
|
||||||
"tailwindcss": "^3.4.19",
|
|
||||||
"typescript": "~5.9.2"
|
"typescript": "~5.9.2"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
|
|
||||||
11540
swagger.json
11540
swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -1,73 +0,0 @@
|
||||||
const { hairlineWidth } = require('nativewind/theme');
|
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
darkMode: 'class',
|
|
||||||
content: ['./App.tsx', './index.ts', './components/**/*.{js,jsx,ts,tsx}', './app/**/*.{js,jsx,ts,tsx}'],
|
|
||||||
presets: [require('nativewind/preset')],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
border: 'hsl(var(--border))',
|
|
||||||
input: 'hsl(var(--input))',
|
|
||||||
ring: 'hsl(var(--ring))',
|
|
||||||
background: 'hsl(var(--background))',
|
|
||||||
foreground: 'hsl(var(--foreground))',
|
|
||||||
primary: {
|
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
|
||||||
foreground: 'hsl(var(--secondary-foreground))',
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
|
||||||
foreground: 'hsl(var(--destructive-foreground))',
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
|
||||||
foreground: 'hsl(var(--muted-foreground))',
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
|
||||||
foreground: 'hsl(var(--accent-foreground))',
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
|
||||||
foreground: 'hsl(var(--popover-foreground))',
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
DEFAULT: 'hsl(var(--card))',
|
|
||||||
foreground: 'hsl(var(--card-foreground))',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: 'var(--radius)',
|
|
||||||
md: 'calc(var(--radius) - 2px)',
|
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
|
||||||
},
|
|
||||||
borderWidth: {
|
|
||||||
hairline: hairlineWidth(),
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
'accordion-down': {
|
|
||||||
from: { height: '0' },
|
|
||||||
to: { height: 'var(--radix-accordion-content-height)' },
|
|
||||||
},
|
|
||||||
'accordion-up': {
|
|
||||||
from: { height: 'var(--radix-accordion-content-height)' },
|
|
||||||
to: { height: '0' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
future: {
|
|
||||||
hoverOnlyWhenSupported: true,
|
|
||||||
},
|
|
||||||
plugins: [require('tailwindcss-animate')],
|
|
||||||
};
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "expo/tsconfig.base",
|
"extends": "expo/tsconfig.base",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"strict": true
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"include": ["**/*.ts", "**/*.tsx", "nativewind-env.d.ts"]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user