Changes

gpt-engineer-app[bot] committed Nov 27, 2025 at 11:13 UTC 42852dcbe8fa472612332bd7fbf0326baa94c0f0
10 files changed +545 -81
index.html
+3 -2
@@ -3,8 +3,9 @@
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 - <title>b956142d-8f42-4ed5-97c8-8198e2b51da7</title>
7 - <meta name="description" content="Lovable Generated Project" />
6 + <title>ExpensePay - Smart Expense Management</title>
7 + <meta name="description" content="Manage your expenses efficiently with ExpensePay - track, approve, and pay business expenses on the go" />
8 + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
9 <meta name="author" content="Lovable" />
10
11 <meta property="og:title" content="b956142d-8f42-4ed5-97c8-8198e2b51da7" />
src/App.tsx
+6 -2
@@ -3,7 +3,9 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
3 import { TooltipProvider } from "@/components/ui/tooltip";
4 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5 import { BrowserRouter, Routes, Route } from "react-router-dom";
6 -import Index from "./pages/Index";
6 +import Home from "./pages/Home";
7 +import Pay from "./pages/Pay";
8 +import Profile from "./pages/Profile";
9 import NotFound from "./pages/NotFound";
10
11 const queryClient = new QueryClient();
@@ -15,7 +17,9 @@ const App = () => (
17 <Sonner />
18 <BrowserRouter>
19 <Routes>
18 - <Route path="/" element={<Index />} />
20 + <Route path="/" element={<Home />} />
21 + <Route path="/pay" element={<Pay />} />
22 + <Route path="/profile" element={<Profile />} />
23 {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
24 <Route path="*" element={<NotFound />} />
25 </Routes>
src/components/BottomNav.tsx new
+39
@@ -0,0 +1,39 @@
1 +import { Home, CreditCard, User } from "lucide-react";
2 +import { NavLink } from "@/components/NavLink";
3 +
4 +const navItems = [
5 + { to: "/", icon: Home, label: "Home" },
6 + { to: "/pay", icon: CreditCard, label: "Pay" },
7 + { to: "/profile", icon: User, label: "Profile" },
8 +];
9 +
10 +const BottomNav = () => {
11 + return (
12 + <nav className="fixed bottom-0 left-0 right-0 z-50 bg-nav-bg border-t border-nav-border safe-area-bottom">
13 + <div className="flex justify-around items-center h-16 max-w-lg mx-auto">
14 + {navItems.map((item) => (
15 + <NavLink
16 + key={item.to}
17 + to={item.to}
18 + end
19 + className="flex flex-col items-center justify-center gap-1 px-6 py-2 text-muted-foreground transition-colors"
20 + activeClassName="text-nav-active"
21 + >
22 + {({ isActive }) => (
23 + <>
24 + <item.icon
25 + className={`h-6 w-6 transition-all ${
26 + isActive ? "scale-110" : ""
27 + }`}
28 + />
29 + <span className="text-xs font-medium">{item.label}</span>
30 + </>
31 + )}
32 + </NavLink>
33 + ))}
34 + </div>
35 + </nav>
36 + );
37 +};
38 +
39 +export default BottomNav;
src/components/Layout.tsx new
+22
@@ -0,0 +1,22 @@
1 +import { ReactNode } from "react";
2 +import BottomNav from "./BottomNav";
3 +import Toolbar from "./Toolbar";
4 +
5 +interface LayoutProps {
6 + children: ReactNode;
7 + title: string;
8 + showBack?: boolean;
9 + rightAction?: React.ReactNode;
10 +}
11 +
12 +const Layout = ({ children, title, showBack, rightAction }: LayoutProps) => {
13 + return (
14 + <div className="min-h-screen bg-background pb-16">
15 + <Toolbar title={title} showBack={showBack} rightAction={rightAction} />
16 + <main className="max-w-lg mx-auto">{children}</main>
17 + <BottomNav />
18 + </div>
19 + );
20 +};
21 +
22 +export default Layout;
src/components/Toolbar.tsx new
+38
@@ -0,0 +1,38 @@
1 +import { ArrowLeft } from "lucide-react";
2 +import { useNavigate } from "react-router-dom";
3 +import { Button } from "@/components/ui/button";
4 +
5 +interface ToolbarProps {
6 + title: string;
7 + showBack?: boolean;
8 + rightAction?: React.ReactNode;
9 +}
10 +
11 +const Toolbar = ({ title, showBack = false, rightAction }: ToolbarProps) => {
12 + const navigate = useNavigate();
13 +
14 + return (
15 + <header className="sticky top-0 z-50 bg-toolbar-bg border-b border-toolbar-border">
16 + <div className="flex items-center justify-between h-14 px-4">
17 + <div className="flex items-center gap-2 flex-1">
18 + {showBack && (
19 + <Button
20 + variant="ghost"
21 + size="icon"
22 + onClick={() => navigate(-1)}
23 + className="h-9 w-9"
24 + >
25 + <ArrowLeft className="h-5 w-5" />
26 + </Button>
27 + )}
28 + <h1 className="text-lg font-semibold text-foreground truncate">
29 + {title}
30 + </h1>
31 + </div>
32 + {rightAction && <div className="flex-shrink-0">{rightAction}</div>}
33 + </div>
34 + </header>
35 + );
36 +};
37 +
38 +export default Toolbar;
src/index.css
+66 -68
@@ -8,89 +8,87 @@ All colors MUST be HSL.
8
9 @layer base {
10 :root {
11 - --background: 0 0% 100%;
12 - --foreground: 222.2 84% 4.9%;
11 + --background: 210 20% 98%;
12 + --foreground: 215 25% 15%;
13
14 --card: 0 0% 100%;
15 - --card-foreground: 222.2 84% 4.9%;
15 + --card-foreground: 215 25% 15%;
16
17 --popover: 0 0% 100%;
18 - --popover-foreground: 222.2 84% 4.9%;
18 + --popover-foreground: 215 25% 15%;
19
20 - --primary: 222.2 47.4% 11.2%;
21 - --primary-foreground: 210 40% 98%;
20 + --primary: 195 85% 45%;
21 + --primary-foreground: 0 0% 100%;
22
23 - --secondary: 210 40% 96.1%;
24 - --secondary-foreground: 222.2 47.4% 11.2%;
23 + --secondary: 195 75% 96%;
24 + --secondary-foreground: 195 85% 35%;
25
26 - --muted: 210 40% 96.1%;
27 - --muted-foreground: 215.4 16.3% 46.9%;
26 + --muted: 210 20% 95%;
27 + --muted-foreground: 215 15% 50%;
28
29 - --accent: 210 40% 96.1%;
30 - --accent-foreground: 222.2 47.4% 11.2%;
29 + --accent: 175 70% 50%;
30 + --accent-foreground: 0 0% 100%;
31
32 - --destructive: 0 84.2% 60.2%;
33 - --destructive-foreground: 210 40% 98%;
32 + --destructive: 0 75% 55%;
33 + --destructive-foreground: 0 0% 100%;
34
35 - --border: 214.3 31.8% 91.4%;
36 - --input: 214.3 31.8% 91.4%;
37 - --ring: 222.2 84% 4.9%;
35 + --border: 210 20% 90%;
36 + --input: 210 20% 90%;
37 + --ring: 195 85% 45%;
38
39 - --radius: 0.5rem;
39 + --radius: 1rem;
40
41 - --sidebar-background: 0 0% 98%;
42 -
43 - --sidebar-foreground: 240 5.3% 26.1%;
44 -
45 - --sidebar-primary: 240 5.9% 10%;
46 -
47 - --sidebar-primary-foreground: 0 0% 98%;
48 -
49 - --sidebar-accent: 240 4.8% 95.9%;
50 -
51 - --sidebar-accent-foreground: 240 5.9% 10%;
52 -
53 - --sidebar-border: 220 13% 91%;
54 -
55 - --sidebar-ring: 217.2 91.2% 59.8%;
41 + --toolbar-bg: 0 0% 100%;
42 + --toolbar-border: 210 20% 90%;
43 +
44 + --nav-bg: 0 0% 100%;
45 + --nav-border: 210 20% 90%;
46 + --nav-active: 195 85% 45%;
47 +
48 + --expense-positive: 150 70% 45%;
49 + --expense-negative: 0 75% 55%;
50 +
51 + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
52 }
53
54 .dark {
59 - --background: 222.2 84% 4.9%;
60 - --foreground: 210 40% 98%;
61 -
62 - --card: 222.2 84% 4.9%;
63 - --card-foreground: 210 40% 98%;
64 -
65 - --popover: 222.2 84% 4.9%;
66 - --popover-foreground: 210 40% 98%;
67 -
68 - --primary: 210 40% 98%;
69 - --primary-foreground: 222.2 47.4% 11.2%;
70 -
71 - --secondary: 217.2 32.6% 17.5%;
72 - --secondary-foreground: 210 40% 98%;
73 -
74 - --muted: 217.2 32.6% 17.5%;
75 - --muted-foreground: 215 20.2% 65.1%;
76 -
77 - --accent: 217.2 32.6% 17.5%;
78 - --accent-foreground: 210 40% 98%;
79 -
80 - --destructive: 0 62.8% 30.6%;
81 - --destructive-foreground: 210 40% 98%;
82 -
83 - --border: 217.2 32.6% 17.5%;
84 - --input: 217.2 32.6% 17.5%;
85 - --ring: 212.7 26.8% 83.9%;
86 - --sidebar-background: 240 5.9% 10%;
87 - --sidebar-foreground: 240 4.8% 95.9%;
88 - --sidebar-primary: 224.3 76.3% 48%;
89 - --sidebar-primary-foreground: 0 0% 100%;
90 - --sidebar-accent: 240 3.7% 15.9%;
91 - --sidebar-accent-foreground: 240 4.8% 95.9%;
92 - --sidebar-border: 240 3.7% 15.9%;
93 - --sidebar-ring: 217.2 91.2% 59.8%;
55 + --background: 215 30% 8%;
56 + --foreground: 210 20% 95%;
57 +
58 + --card: 215 25% 12%;
59 + --card-foreground: 210 20% 95%;
60 +
61 + --popover: 215 25% 12%;
62 + --popover-foreground: 210 20% 95%;
63 +
64 + --primary: 195 85% 55%;
65 + --primary-foreground: 0 0% 100%;
66 +
67 + --secondary: 215 25% 18%;
68 + --secondary-foreground: 195 85% 75%;
69 +
70 + --muted: 215 25% 18%;
71 + --muted-foreground: 215 15% 60%;
72 +
73 + --accent: 175 70% 55%;
74 + --accent-foreground: 0 0% 100%;
75 +
76 + --destructive: 0 75% 60%;
77 + --destructive-foreground: 0 0% 100%;
78 +
79 + --border: 215 25% 20%;
80 + --input: 215 25% 20%;
81 + --ring: 195 85% 55%;
82 +
83 + --toolbar-bg: 215 25% 12%;
84 + --toolbar-border: 215 25% 20%;
85 +
86 + --nav-bg: 215 25% 12%;
87 + --nav-border: 215 25% 20%;
88 + --nav-active: 195 85% 55%;
89 +
90 + --expense-positive: 150 70% 50%;
91 + --expense-negative: 0 75% 60%;
92 }
93 }
94
src/pages/Home.tsx new
+121
@@ -0,0 +1,121 @@
1 +import Layout from "@/components/Layout";
2 +import { Card } from "@/components/ui/card";
3 +import { Badge } from "@/components/ui/badge";
4 +import { ArrowUpRight, ArrowDownRight, Plus } from "lucide-react";
5 +import { Button } from "@/components/ui/button";
6 +
7 +const transactions = [
8 + {
9 + id: 1,
10 + name: "Office Supplies",
11 + category: "Office",
12 + amount: -45.99,
13 + date: "Today",
14 + status: "pending",
15 + },
16 + {
17 + id: 2,
18 + name: "Client Lunch",
19 + category: "Food & Dining",
20 + amount: -128.50,
21 + date: "Yesterday",
22 + status: "approved",
23 + },
24 + {
25 + id: 3,
26 + name: "Reimbursement",
27 + category: "Income",
28 + amount: 250.00,
29 + date: "2 days ago",
30 + status: "completed",
31 + },
32 + {
33 + id: 4,
34 + name: "Software License",
35 + category: "Software",
36 + amount: -79.99,
37 + date: "3 days ago",
38 + status: "approved",
39 + },
40 +];
41 +
42 +const Home = () => {
43 + return (
44 + <Layout title="Expenses">
45 + <div className="p-4 space-y-6">
46 + {/* Balance Card */}
47 + <Card className="p-6 bg-gradient-to-br from-primary to-accent text-primary-foreground border-0">
48 + <div className="space-y-1">
49 + <p className="text-sm opacity-90">Available Balance</p>
50 + <h2 className="text-4xl font-bold">$2,450.00</h2>
51 + </div>
52 + <div className="flex gap-3 mt-6">
53 + <Button size="sm" variant="secondary" className="flex-1">
54 + <Plus className="h-4 w-4 mr-1" />
55 + Add Expense
56 + </Button>
57 + </div>
58 + </Card>
59 +
60 + {/* Stats */}
61 + <div className="grid grid-cols-2 gap-4">
62 + <Card className="p-4">
63 + <div className="flex items-start justify-between">
64 + <div>
65 + <p className="text-sm text-muted-foreground">This Month</p>
66 + <p className="text-2xl font-bold text-expense-negative">-$985</p>
67 + </div>
68 + <ArrowUpRight className="h-5 w-5 text-expense-negative" />
69 + </div>
70 + </Card>
71 + <Card className="p-4">
72 + <div className="flex items-start justify-between">
73 + <div>
74 + <p className="text-sm text-muted-foreground">Saved</p>
75 + <p className="text-2xl font-bold text-expense-positive">+$450</p>
76 + </div>
77 + <ArrowDownRight className="h-5 w-5 text-expense-positive" />
78 + </div>
79 + </Card>
80 + </div>
81 +
82 + {/* Recent Transactions */}
83 + <div className="space-y-3">
84 + <h3 className="text-lg font-semibold">Recent Transactions</h3>
85 + <div className="space-y-2">
86 + {transactions.map((transaction) => (
87 + <Card key={transaction.id} className="p-4 hover:shadow-md transition-shadow">
88 + <div className="flex items-center justify-between">
89 + <div className="flex-1">
90 + <div className="flex items-center gap-2">
91 + <p className="font-medium">{transaction.name}</p>
92 + {transaction.status === "pending" && (
93 + <Badge variant="outline" className="text-xs">
94 + Pending
95 + </Badge>
96 + )}
97 + </div>
98 + <p className="text-sm text-muted-foreground">
99 + {transaction.category} · {transaction.date}
100 + </p>
101 + </div>
102 + <p
103 + className={`text-lg font-semibold ${
104 + transaction.amount > 0
105 + ? "text-expense-positive"
106 + : "text-expense-negative"
107 + }`}
108 + >
109 + {transaction.amount > 0 ? "+" : ""}${Math.abs(transaction.amount).toFixed(2)}
110 + </p>
111 + </div>
112 + </Card>
113 + ))}
114 + </div>
115 + </div>
116 + </div>
117 + </Layout>
118 + );
119 +};
120 +
121 +export default Home;
src/pages/Pay.tsx new
+129
@@ -0,0 +1,129 @@
1 +import Layout from "@/components/Layout";
2 +import { Card } from "@/components/ui/card";
3 +import { CreditCard, Smartphone, QrCode, Building2 } from "lucide-react";
4 +
5 +const paymentMethods = [
6 + {
7 + id: 1,
8 + name: "Credit Card",
9 + description: "Pay with your linked card",
10 + icon: CreditCard,
11 + color: "text-primary",
12 + },
13 + {
14 + id: 2,
15 + name: "Mobile Wallet",
16 + description: "Apple Pay, Google Pay",
17 + icon: Smartphone,
18 + color: "text-accent",
19 + },
20 + {
21 + id: 3,
22 + name: "QR Code",
23 + description: "Scan to pay instantly",
24 + icon: QrCode,
25 + color: "text-expense-positive",
26 + },
27 + {
28 + id: 4,
29 + name: "Bank Transfer",
30 + description: "Direct bank transfer",
31 + icon: Building2,
32 + color: "text-muted-foreground",
33 + },
34 +];
35 +
36 +const linkedCards = [
37 + {
38 + id: 1,
39 + name: "Business Visa",
40 + last4: "4242",
41 + type: "Visa",
42 + primary: true,
43 + },
44 + {
45 + id: 2,
46 + name: "Corporate Amex",
47 + last4: "8888",
48 + type: "Amex",
49 + primary: false,
50 + },
51 +];
52 +
53 +const Pay = () => {
54 + return (
55 + <Layout title="Pay">
56 + <div className="p-4 space-y-6">
57 + {/* Quick Actions */}
58 + <div>
59 + <h3 className="text-lg font-semibold mb-3">Payment Methods</h3>
60 + <div className="grid grid-cols-2 gap-3">
61 + {paymentMethods.map((method) => (
62 + <Card
63 + key={method.id}
64 + className="p-4 hover:shadow-lg transition-all cursor-pointer hover:scale-105 active:scale-95"
65 + >
66 + <div className="space-y-3">
67 + <method.icon className={`h-8 w-8 ${method.color}`} />
68 + <div>
69 + <p className="font-medium text-sm">{method.name}</p>
70 + <p className="text-xs text-muted-foreground">
71 + {method.description}
72 + </p>
73 + </div>
74 + </div>
75 + </Card>
76 + ))}
77 + </div>
78 + </div>
79 +
80 + {/* Linked Cards */}
81 + <div>
82 + <h3 className="text-lg font-semibold mb-3">Linked Cards</h3>
83 + <div className="space-y-3">
84 + {linkedCards.map((card) => (
85 + <Card
86 + key={card.id}
87 + className={`p-4 cursor-pointer hover:shadow-md transition-shadow ${
88 + card.primary ? "border-primary" : ""
89 + }`}
90 + >
91 + <div className="flex items-center justify-between">
92 + <div className="flex items-center gap-3">
93 + <div className="h-12 w-12 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center">
94 + <CreditCard className="h-6 w-6 text-primary-foreground" />
95 + </div>
96 + <div>
97 + <p className="font-medium">{card.name}</p>
98 + <p className="text-sm text-muted-foreground">
99 + {card.type} •••• {card.last4}
100 + </p>
101 + </div>
102 + </div>
103 + {card.primary && (
104 + <span className="text-xs font-medium text-primary">
105 + Primary
106 + </span>
107 + )}
108 + </div>
109 + </Card>
110 + ))}
111 + </div>
112 + </div>
113 +
114 + {/* Quick Transfer */}
115 + <Card className="p-4 bg-secondary">
116 + <div className="text-center space-y-2">
117 + <QrCode className="h-16 w-16 mx-auto text-secondary-foreground" />
118 + <h4 className="font-semibold">Quick Pay QR Code</h4>
119 + <p className="text-sm text-muted-foreground">
120 + Scan this code to receive payments
121 + </p>
122 + </div>
123 + </Card>
124 + </div>
125 + </Layout>
126 + );
127 +};
128 +
129 +export default Pay;
src/pages/Profile.tsx new
+106
@@ -0,0 +1,106 @@
1 +import Layout from "@/components/Layout";
2 +import { Card } from "@/components/ui/card";
3 +import { Avatar, AvatarFallback } from "@/components/ui/avatar";
4 +import {
5 + Settings,
6 + Bell,
7 + Shield,
8 + HelpCircle,
9 + FileText,
10 + LogOut,
11 + ChevronRight,
12 +} from "lucide-react";
13 +import { Button } from "@/components/ui/button";
14 +
15 +const menuItems = [
16 + {
17 + section: "Account",
18 + items: [
19 + { icon: Settings, label: "Settings", to: "/settings" },
20 + { icon: Bell, label: "Notifications", to: "/notifications" },
21 + { icon: Shield, label: "Security", to: "/security" },
22 + ],
23 + },
24 + {
25 + section: "Support",
26 + items: [
27 + { icon: HelpCircle, label: "Help Center", to: "/help" },
28 + { icon: FileText, label: "Terms & Privacy", to: "/terms" },
29 + ],
30 + },
31 +];
32 +
33 +const Profile = () => {
34 + return (
35 + <Layout title="Profile">
36 + <div className="p-4 space-y-6">
37 + {/* Profile Header */}
38 + <Card className="p-6">
39 + <div className="flex items-center gap-4">
40 + <Avatar className="h-20 w-20">
41 + <AvatarFallback className="bg-gradient-to-br from-primary to-accent text-primary-foreground text-2xl font-bold">
42 + JD
43 + </AvatarFallback>
44 + </Avatar>
45 + <div className="flex-1">
46 + <h2 className="text-xl font-bold">John Doe</h2>
47 + <p className="text-sm text-muted-foreground">john.doe@company.com</p>
48 + <p className="text-sm text-muted-foreground mt-1">ID: EMP-001</p>
49 + </div>
50 + </div>
51 + </Card>
52 +
53 + {/* Stats */}
54 + <div className="grid grid-cols-3 gap-3">
55 + <Card className="p-4 text-center">
56 + <p className="text-2xl font-bold text-primary">24</p>
57 + <p className="text-xs text-muted-foreground mt-1">Expenses</p>
58 + </Card>
59 + <Card className="p-4 text-center">
60 + <p className="text-2xl font-bold text-expense-positive">$2.4K</p>
61 + <p className="text-xs text-muted-foreground mt-1">Approved</p>
62 + </Card>
63 + <Card className="p-4 text-center">
64 + <p className="text-2xl font-bold text-expense-negative">3</p>
65 + <p className="text-xs text-muted-foreground mt-1">Pending</p>
66 + </Card>
67 + </div>
68 +
69 + {/* Menu Sections */}
70 + {menuItems.map((section) => (
71 + <div key={section.section} className="space-y-2">
72 + <h3 className="text-sm font-semibold text-muted-foreground px-1">
73 + {section.section}
74 + </h3>
75 + <Card className="divide-y">
76 + {section.items.map((item) => (
77 + <button
78 + key={item.label}
79 + className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors"
80 + >
81 + <div className="flex items-center gap-3">
82 + <item.icon className="h-5 w-5 text-muted-foreground" />
83 + <span className="font-medium">{item.label}</span>
84 + </div>
85 + <ChevronRight className="h-5 w-5 text-muted-foreground" />
86 + </button>
87 + ))}
88 + </Card>
89 + </div>
90 + ))}
91 +
92 + {/* Logout Button */}
93 + <Button variant="outline" className="w-full" size="lg">
94 + <LogOut className="h-5 w-5 mr-2" />
95 + Log Out
96 + </Button>
97 +
98 + <div className="text-center text-sm text-muted-foreground pb-4">
99 + Version 1.0.0
100 + </div>
101 + </div>
102 + </Layout>
103 + );
104 +};
105 +
106 +export default Profile;
tailwind.config.ts
+15 -9
@@ -47,16 +47,22 @@ export default {
47 DEFAULT: "hsl(var(--card))",
48 foreground: "hsl(var(--card-foreground))",
49 },
50 - sidebar: {
51 - DEFAULT: "hsl(var(--sidebar-background))",
52 - foreground: "hsl(var(--sidebar-foreground))",
53 - primary: "hsl(var(--sidebar-primary))",
54 - "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
55 - accent: "hsl(var(--sidebar-accent))",
56 - "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
57 - border: "hsl(var(--sidebar-border))",
58 - ring: "hsl(var(--sidebar-ring))",
50 + toolbar: {
51 + bg: "hsl(var(--toolbar-bg))",
52 + border: "hsl(var(--toolbar-border))",
53 },
54 + nav: {
55 + bg: "hsl(var(--nav-bg))",
56 + border: "hsl(var(--nav-border))",
57 + active: "hsl(var(--nav-active))",
58 + },
59 + expense: {
60 + positive: "hsl(var(--expense-positive))",
61 + negative: "hsl(var(--expense-negative))",
62 + },
63 + },
64 + transitionProperty: {
65 + 'all': 'var(--transition)',
66 },
67 borderRadius: {
68 lg: "var(--radius)",