Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import ProtectedRoute from './pages/ProtectedRoute'
import AppShell from './components/layout/AppShell'
import LoginPage from './pages/LoginPage'
import SignUpPage from './pages/SignUpPage'
import CheckEmailPage from './pages/CheckEmailPage'
import VerifyEmailPage from './pages/VerifyEmailPage'
import LinkBankPage from './pages/LinkBankPage'
import DashboardPage from './pages/DashboardPage'
import BudgetPage from './pages/BudgetPage'
import SavingsPage from './pages/SavingsPage'
import InvestmentsPage from './pages/InvestmentsPage'
import DebtsPage from './pages/DebtsPage'
import ProposalsPage from './pages/ProposalsPage'
import ProfilePage from './pages/ProfilePage'

Expand All @@ -18,6 +22,8 @@ function App() {
{/* Public */}
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignUpPage />} />
<Route path="/check-email" element={<CheckEmailPage />} />
<Route path="/verify-email" element={<VerifyEmailPage />} />

{/* Post-signup onboarding */}
<Route
Expand All @@ -40,6 +46,8 @@ function App() {
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/budget" element={<BudgetPage />} />
<Route path="/savings" element={<SavingsPage />} />
<Route path="/investments" element={<InvestmentsPage />} />
<Route path="/debts" element={<DebtsPage />} />
<Route path="/proposals" element={<ProposalsPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Route>
Expand Down
2 changes: 2 additions & 0 deletions client/src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const PAGE_TITLES: Record<string, string> = {
'/dashboard': 'Dashboard',
'/budget': 'Budget',
'/savings': 'Savings',
'/investments': 'Investments',
'/debts': 'Debts',
'/proposals': 'Proposals',
'/profile': 'Profile',
}
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
LayoutDashboard,
Wallet,
PiggyBank,
TrendingUp,
CreditCard,
FileText,
User,
LogOut,
Expand All @@ -19,6 +21,8 @@ const NAV_ITEMS: NavItem[] = [
{ to: '/dashboard', icon: <LayoutDashboard size={20} />, label: 'Dashboard' },
{ to: '/budget', icon: <Wallet size={20} />, label: 'Budget' },
{ to: '/savings', icon: <PiggyBank size={20} />, label: 'Savings' },
{ to: '/investments', icon: <TrendingUp size={20} />, label: 'Investments' },
{ to: '/debts', icon: <CreditCard size={20} />, label: 'Debts' },
{ to: '/proposals', icon: <FileText size={20} />, label: 'Proposals' },
{ to: '/profile', icon: <User size={20} />, label: 'Profile' },
]
Expand Down
29 changes: 29 additions & 0 deletions client/src/pages/BudgetPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@
margin: 0;
}

.budget-page__goals {
display: flex;
flex-direction: column;
gap: var(--space-3);
}

.budget-page__goals-header {
display: flex;
align-items: center;
gap: var(--space-2);
}

.budget-page__goals-icon {
color: var(--color-text-muted);
}

.budget-page__goals-title {
font-size: var(--text-base);
font-weight: var(--weight-semibold);
color: var(--color-text-primary);
margin: 0;
}

.budget-page__goals-list {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
}

@media (max-width: 1024px) {
.budget-page__stat-grid { grid-template-columns: repeat(2, 1fr); }
}
Expand Down
16 changes: 16 additions & 0 deletions client/src/pages/BudgetPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EmptyState from '../components/ui/EmptyState'
import Spinner from '../components/ui/Spinner'
import Button from '../components/ui/Button'
import Card from '../components/ui/Card'
import Badge from '../components/ui/Badge'
import {
DollarSign,
Home,
Expand All @@ -21,6 +22,7 @@ import {
Shield,
Clapperboard,
HeartPulse,
Target,
} from 'lucide-react'
import './BudgetPage.css'

Expand Down Expand Up @@ -116,6 +118,20 @@ export default function BudgetPage() {
/>
</div>

{budget.goals.length > 0 && (
<Card className="budget-page__goals">
<div className="budget-page__goals-header">
<Target size={18} className="budget-page__goals-icon" />
<h3 className="budget-page__goals-title">Financial Goals</h3>
</div>
<div className="budget-page__goals-list">
{budget.goals.map((goal) => (
<Badge key={goal} variant="info">{goal}</Badge>
))}
</div>
</Card>
)}

<Card>
<div className="budget-page__categories-header">
<h3 className="budget-page__categories-title">Budget Categories</h3>
Expand Down
104 changes: 104 additions & 0 deletions client/src/pages/CheckEmailPage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
.check-email-page {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 100vh;
}

.check-email-page__hero {
background: var(--gradient-cta);
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-12);
}

.check-email-page__hero-content { max-width: 400px; }

.check-email-page__hero-title {
font-family: var(--font-heading);
font-size: var(--text-4xl);
font-weight: var(--weight-extrabold);
color: #fff;
margin-bottom: var(--space-4);
}

.check-email-page__hero-sub {
font-size: var(--text-lg);
color: rgba(255, 255, 255, 0.8);
line-height: var(--leading-relaxed);
}

.check-email-page__form-side {
background: var(--color-bg-body);
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-8);
}

.check-email-page__box {
width: 100%;
max-width: 440px;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-5);
text-align: center;
}

.check-email-page__icon {
width: 72px;
height: 72px;
border-radius: 50%;
background: rgba(99, 102, 241, 0.12);
color: #6366F1;
display: flex;
align-items: center;
justify-content: center;
}

.check-email-page__heading {
font-family: var(--font-heading);
font-size: var(--text-2xl);
font-weight: var(--weight-bold);
color: var(--color-text-primary);
margin: 0;
}

.check-email-page__description {
font-size: var(--text-sm);
color: var(--color-text-muted);
line-height: var(--leading-relaxed);
margin: 0;
}

.check-email-page__error {
font-size: var(--text-sm);
color: var(--color-danger);
padding: var(--space-3) var(--space-4);
background: var(--color-danger-bg);
border-radius: var(--radius-md);
width: 100%;
margin: 0;
}

.check-email-page__success {
font-size: var(--text-sm);
color: #22C55E;
padding: var(--space-3) var(--space-4);
background: rgba(34, 197, 94, 0.08);
border-radius: var(--radius-md);
width: 100%;
margin: 0;
}

.check-email-page__footer {
font-size: var(--text-sm);
color: var(--color-text-muted);
}

@media (max-width: 767px) {
.check-email-page { grid-template-columns: 1fr; }
.check-email-page__hero { padding: var(--space-8); min-height: 160px; }
.check-email-page__hero-title { font-size: var(--text-2xl); }
}
74 changes: 74 additions & 0 deletions client/src/pages/CheckEmailPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useState } from 'react'
import { useSearchParams, Link } from 'react-router-dom'
import { Mail } from 'lucide-react'
import { api } from '../services/api'
import Button from '../components/ui/Button'
import './CheckEmailPage.css'

export default function CheckEmailPage() {
const [searchParams] = useSearchParams()
const email = searchParams.get('email') ?? ''
const [resending, setResending] = useState(false)
const [resent, setResent] = useState(false)
const [error, setError] = useState('')

async function handleResend() {
if (!email) return
setResending(true)
setError('')
setResent(false)
try {
await api.post('/api/auth/resend-verification', { email })
setResent(true)
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to resend email')
} finally {
setResending(false)
}
}

return (
<div className="check-email-page">
<div className="check-email-page__hero">
<div className="check-email-page__hero-content">
<h1 className="check-email-page__hero-title">Financial Assistant</h1>
<p className="check-email-page__hero-sub">
Connect your accounts. Get personalized budget insights powered by AI.
</p>
</div>
</div>

<div className="check-email-page__form-side">
<div className="check-email-page__box">
<div className="check-email-page__icon">
<Mail size={40} />
</div>
<h2 className="check-email-page__heading">Check your email</h2>
<p className="check-email-page__description">
We sent a verification link to{' '}
{email ? <strong>{email}</strong> : 'your email address'}.
Click the link in the email to verify your account.
</p>

{error && <p className="check-email-page__error" role="alert">{error}</p>}
{resent && <p className="check-email-page__success" role="status">Verification email sent!</p>}

{email && (
<Button
variant="secondary"
fullWidth
disabled={resending || resent}
onClick={handleResend}
>
{resending ? 'Sending...' : resent ? 'Email sent!' : 'Resend verification email'}
</Button>
)}

<p className="check-email-page__footer">
<Link to="/login">Back to login</Link>
</p>
</div>
</div>
</div>
)
}
Loading
Loading