Skip to content

Commit 85d42d1

Browse files
feat: updated initial web app layout, marketing pages, dashboard group views, and TiltCard UI component.
1 parent ccae025 commit 85d42d1

19 files changed

Lines changed: 1736 additions & 427 deletions
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ExpenseDto, GroupDto, GroupMemberSummaryDto, PaginatedExpensesResponseDto } from '@fairshare/shared-types';
2+
import { notFound } from 'next/navigation';
3+
import { Suspense } from 'react';
4+
5+
import { DashboardLayout } from '../../../../src/components/layout';
6+
import { ExpenseTable, GroupActions, MemberList } from '../../../../src/components/groups';
7+
import { backendFetch } from '../../../../src/lib/backend';
8+
9+
interface GroupDetailPageProps {
10+
params: {
11+
groupId: string;
12+
};
13+
}
14+
15+
export default async function GroupDetailPage({ params }: GroupDetailPageProps) {
16+
const { groupId } = params;
17+
18+
try {
19+
const [group, members, expenses] = await Promise.all([
20+
backendFetch<GroupDto>(`/groups/${groupId}`),
21+
backendFetch<GroupMemberSummaryDto[]>(`/groups/${groupId}/members`),
22+
backendFetch<PaginatedExpensesResponseDto>(`/groups/${groupId}/expenses?limit=50`),
23+
]);
24+
25+
const totalExpenses = expenses.items.reduce((sum, expense) => sum + Number(expense.totalAmountCents), 0) / 100;
26+
27+
return (
28+
<DashboardLayout>
29+
<div className="space-y-10">
30+
<header className="rounded-3xl border border-[var(--fs-border)] bg-[var(--fs-card)] p-8 shadow-[var(--fs-shadow-soft)]">
31+
<div className="flex items-start justify-between gap-4 flex-wrap">
32+
<div className="space-y-2">
33+
<p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--fs-text-muted)]">Group</p>
34+
<h1 className="text-3xl font-extrabold tracking-tight text-[var(--fs-text-primary)]">{group.name}</h1>
35+
<p className="text-[12px] font-medium text-[var(--fs-text-muted)]">
36+
Created {new Date(group.createdAt).toLocaleDateString()} � ID {groupId.slice(0, 8)}
37+
</p>
38+
</div>
39+
<div className="flex items-center gap-3">
40+
<div className="rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)]/60 px-4 py-3 text-right">
41+
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--fs-text-muted)]">Currency</p>
42+
<p className="text-lg font-extrabold text-[var(--fs-text-primary)]">{group.currency}</p>
43+
</div>
44+
<div className="rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)]/60 px-4 py-3 text-right">
45+
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--fs-text-muted)]">Members</p>
46+
<p className="text-lg font-extrabold text-[var(--fs-text-primary)]">{members.length}</p>
47+
</div>
48+
<div className="rounded-xl border border-[var(--fs-border)] bg-[var(--fs-background)]/60 px-4 py-3 text-right">
49+
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-[var(--fs-text-muted)]">Total spend</p>
50+
<p className="text-lg font-extrabold text-[var(--fs-text-primary)]">
51+
{totalExpenses.toLocaleString(undefined, { style: 'currency', currency: group.currency })}
52+
</p>
53+
</div>
54+
</div>
55+
</div>
56+
</header>
57+
58+
<div className="grid gap-10 lg:grid-cols-[1fr_360px]">
59+
<div className="space-y-10">
60+
<ExpenseTable expenses={expenses.items as ExpenseDto[]} members={members} />
61+
</div>
62+
63+
<aside className="space-y-10">
64+
<Suspense fallback={<div className="h-52 rounded-3xl border border-[var(--fs-border)] bg-[var(--fs-card)] animate-pulse" />}>
65+
<MemberList groupId={groupId} members={members} />
66+
</Suspense>
67+
<Suspense fallback={<div className="h-40 rounded-3xl border border-[var(--fs-border)] bg-[var(--fs-card)] animate-pulse" />}>
68+
<GroupActions
69+
groupId={groupId}
70+
currency={group.currency}
71+
members={members}
72+
defaultSplitPreference={group.defaultSplitPreference}
73+
/>
74+
</Suspense>
75+
</aside>
76+
</div>
77+
</div>
78+
</DashboardLayout>
79+
);
80+
} catch (error) {
81+
console.error('Failed to fetch group details:', error);
82+
return notFound();
83+
}
84+
}

apps/web/app/dashboard/groups/[groupId]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExpenseDto, GroupDto, GroupMemberSummaryDto, PaginatedExpensesResponseDto } from '@fairshare/shared-types';
1+
import { ExpenseDto, GroupDto, GroupMemberSummaryDto, PaginatedExpensesResponseDto } from '@fairshare/shared-types';
22
import { notFound } from 'next/navigation';
33
import { Suspense } from 'react';
44

@@ -33,7 +33,7 @@ export default async function GroupDetailPage({ params }: GroupDetailPageProps)
3333
<p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[var(--fs-text-muted)]">Group</p>
3434
<h1 className="text-3xl font-extrabold tracking-tight text-[var(--fs-text-primary)]">{group.name}</h1>
3535
<p className="text-[12px] font-medium text-[var(--fs-text-muted)]">
36-
Created {new Date(group.createdAt).toLocaleDateString()} ID {groupId.slice(0, 8)}
36+
Created {new Date(group.createdAt).toLocaleDateString()} ID {groupId.slice(0, 8)}
3737
</p>
3838
</div>
3939
<div className="flex items-center gap-3">

apps/web/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5353
<body className="min-h-screen bg-black text-white selection:bg-yellow-400 selection:text-black">
5454
<Providers>
5555
<Navbar />
56-
<div className="pt-16 sm:pt-20">{children}</div>
56+
<div>{children}</div>
5757
<Footer />
5858
</Providers>
5959
</body>

apps/web/app/page.tsx

Lines changed: 30 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -1,192 +1,39 @@
11
'use client';
22

3-
import { ArrowRight, Sparkles } from 'lucide-react';
4-
import Link from 'next/link';
5-
import { motion } from 'framer-motion';
6-
import { SectionContainer } from '../components/layout/SectionContainer';
7-
import { CTASection } from '../components/marketing/CTASection';
8-
import { FeatureCard } from '../components/marketing/FeatureCard';
3+
import {
4+
GridBackground,
5+
FloatingOrb,
6+
HeroSection,
7+
ProblemSolutionSection,
8+
HowItWorksSection,
9+
FeatureGridSection,
10+
SecurityStripSection,
11+
} from '../components/home';
12+
// import { TrustStatsSection } from '../components/marketing/TrustStatsSection';
913
import { ComingSoonAppSection } from '../components/marketing/ComingSoonAppSection';
10-
import { TrustStatsSection } from '../components/marketing/TrustStatsSection';
11-
import { GlassCard } from '../components/ui/GlassCard';
12-
import { AccentButton } from '../components/ui/AccentButton';
13-
14-
const previewFeatures = [
15-
{
16-
title: 'Track every shared expense',
17-
description: 'Add bills, assign who owes what, and keep the group ledger current without bouncing between messages.',
18-
iconName: 'ReceiptText' as const,
19-
eyebrow: 'Unified Ledger',
20-
},
21-
{
22-
title: 'Instant balance updates',
23-
description: 'Everyone stays aligned as new expenses land, so there is less backtracking when it is time to settle.',
24-
iconName: 'RefreshCw' as const,
25-
eyebrow: 'Live Sync',
26-
},
27-
{
28-
title: 'Contextual receipts',
29-
description: 'Keep proof attached to the expense so the group can verify details without digging through gallery screenshots.',
30-
iconName: 'ShieldCheck' as const,
31-
eyebrow: 'Verification',
32-
},
33-
{
34-
title: 'Simplify payments',
35-
description: 'FairShare reduces a messy web of debts into clear settle-up actions the group can complete quickly.',
36-
iconName: 'GitMerge' as const,
37-
eyebrow: 'Optimization',
38-
},
39-
];
14+
import { CTASection } from '../components/marketing/CTASection';
4015

4116
export default function HomePage() {
4217
return (
43-
<main className="relative min-h-screen overflow-hidden bg-[#030303] text-white">
44-
{/* Background Ambience */}
45-
<div className="pointer-events-none absolute inset-0 grid-bg opacity-10" />
46-
<div className="pointer-events-none absolute top-0 left-1/2 -translate-x-1/2 h-[500px] w-full max-w-4xl bg-purple-600/10 blur-[120px]" />
47-
48-
{/* Hero Section */}
49-
<SectionContainer size="spacious" className="relative pt-20 sm:pt-32">
50-
<div className="flex flex-col items-center text-center">
51-
<motion.div
52-
initial={{ opacity: 0, scale: 0.95 }}
53-
animate={{ opacity: 1, scale: 1 }}
54-
className="eyebrow-label mb-8"
55-
>
56-
<Sparkles size={14} className="text-purple-400" />
57-
<span>The Premium Way to Split Expenses</span>
58-
</motion.div>
59-
60-
<h1 className="max-w-4xl text-5xl font-extrabold tracking-tight text-white sm:text-7xl lg:text-8xl">
61-
Split group expenses <span className="text-purple-500">without the chaos.</span>
62-
</h1>
63-
64-
<p className="mt-8 max-w-2xl text-lg leading-8 text-zinc-400 sm:text-xl">
65-
FairShare helps roommates, travelers, and teams log shared spending, track balances live, and settle up faster. No spreadsheets required.
66-
</p>
67-
68-
<div className="mt-12 flex flex-col items-center gap-4 sm:flex-row">
69-
<AccentButton href="/login" variant="primary" icon={<ArrowRight size={18} />}>
70-
Start Splitting Free
71-
</AccentButton>
72-
<AccentButton href="/features" variant="secondary">
73-
Explore Features
74-
</AccentButton>
75-
</div>
76-
77-
<p className="mt-10 text-xs font-bold uppercase tracking-[0.25em] text-zinc-600">
78-
Android & iOS App Coming Soon
79-
</p>
80-
</div>
81-
82-
{/* Product Teaser Dashboard Image */}
83-
<div className="mt-20 relative px-4 sm:px-10">
84-
<GlassCard className="!p-0 border-white/10 bg-black/40 shadow-[0_0_100px_rgba(168,85,247,0.1)]" hoverable={false}>
85-
<div className="aspect-[16/9] relative bg-gradient-to-br from-purple-900/10 via-transparent to-transparent">
86-
{/* Dashboard Mockup Representation */}
87-
<div className="absolute inset-0 p-8 sm:p-12 overflow-hidden">
88-
<div className="flex justify-between items-start mb-12">
89-
<div className="space-y-4">
90-
<div className="h-4 w-32 rounded-full bg-white/10" />
91-
<div className="h-10 w-64 rounded-xl bg-white/5" />
92-
</div>
93-
<div className="h-12 w-12 rounded-full bg-purple-500/20 border border-purple-500/30" />
94-
</div>
95-
<div className="grid grid-cols-3 gap-6">
96-
<div className="h-40 rounded-3xl bg-white/5 border border-white/10 p-6">
97-
<div className="h-3 w-1/2 rounded-full bg-purple-400/30 mb-4" />
98-
<div className="h-10 w-3/4 rounded-xl bg-white/10" />
99-
</div>
100-
<div className="h-40 rounded-3xl bg-white/5 border border-white/10 p-6">
101-
<div className="h-3 w-1/2 rounded-full bg-purple-400/30 mb-4" />
102-
<div className="h-10 w-3/4 rounded-xl bg-white/10" />
103-
</div>
104-
<div className="h-40 rounded-3xl bg-purple-600/10 border border-purple-500/20 p-6">
105-
<div className="h-3 w-1/2 rounded-full bg-purple-400/40 mb-4" />
106-
<div className="h-10 w-3/4 rounded-xl bg-white/10" />
107-
</div>
108-
</div>
109-
</div>
110-
</div>
111-
{/* Skeuomorphic Glass Light Leak */}
112-
<div className="absolute -top-px -left-px -right-px h-px bg-gradient-to-r from-transparent via-purple-500/40 to-transparent" />
113-
</GlassCard>
114-
115-
{/* Floating Accents */}
116-
<div className="absolute -top-12 -right-12 h-24 w-24 rounded-full bg-purple-500/20 blur-3xl" />
117-
<div className="absolute -bottom-12 -left-12 h-32 w-32 rounded-full bg-purple-900/30 blur-3xl" />
118-
</div>
119-
</SectionContainer>
120-
121-
{/* Social Proof */}
122-
<TrustStatsSection />
123-
124-
{/* Problem/Solution Card Grid */}
125-
<SectionContainer size="compact">
126-
<div className="grid gap-8 lg:grid-cols-2">
127-
<GlassCard className="p-8 sm:p-10 border-red-500/10 bg-red-500/[0.02]">
128-
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-red-400/60 mb-4 block">The Friction</span>
129-
<h2 className="text-3xl font-extrabold tracking-tight text-white sm:text-4xl">
130-
Manual tracking causes headaches for everyone.
131-
</h2>
132-
<ul className="mt-8 space-y-6">
133-
{[
134-
'Expenses get logged late, leading to balance drift.',
135-
'Lost receipts cause arguments over price details.',
136-
'Complex settle-up math adds unnecessary friction.'
137-
].map((item, i) => (
138-
<li key={i} className="flex gap-4 items-start text-zinc-400">
139-
<div className="mt-1.5 h-1.5 w-1.5 rounded-full bg-red-500/40 shrink-0" />
140-
<span className="text-base leading-relaxed">{item}</span>
141-
</li>
142-
))}
143-
</ul>
144-
</GlassCard>
145-
146-
<GlassCard className="p-8 sm:p-10 border-purple-500/20 bg-purple-500/[0.04]">
147-
<span className="text-[10px] font-bold uppercase tracking-[0.25em] text-purple-400 mb-4 block">The Solution</span>
148-
<h2 className="text-3xl font-extrabold tracking-tight text-white sm:text-4xl">
149-
FairShare automates the busywork of group spending.
150-
</h2>
151-
<ul className="mt-8 space-y-6">
152-
{[
153-
'Members see balance changes instantly after each log.',
154-
'Proof stays attached to the record for total transparency.',
155-
'Optimized settlements reduce the number of payments.'
156-
].map((item, i) => (
157-
<li key={i} className="flex gap-4 items-start text-zinc-300">
158-
<div className="mt-1.5 h-1.5 w-1.5 rounded-full bg-purple-500 shrink-0" />
159-
<span className="text-base leading-relaxed">{item}</span>
160-
</li>
161-
))}
162-
</ul>
163-
</GlassCard>
164-
</div>
165-
</SectionContainer>
166-
167-
{/* Feature Grid */}
168-
<SectionContainer id="features">
169-
<div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between mb-12">
170-
<div className="max-w-2xl">
171-
<span className="eyebrow-label mb-6">Core Features</span>
172-
<h2 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl">
173-
Everything you need, nothing you don&apos;t.
174-
</h2>
175-
</div>
176-
<Link href="/features" className="group flex items-center gap-2 text-sm font-bold text-purple-400 hover:text-purple-300 transition-colors">
177-
Explore Full Feature Set
178-
<ArrowRight size={16} className="transition-transform group-hover:translate-x-1" />
179-
</Link>
180-
</div>
181-
182-
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
183-
{previewFeatures.map((feature) => (
184-
<FeatureCard key={feature.title} {...feature} />
185-
))}
186-
</div>
187-
</SectionContainer>
188-
189-
{/* Mobile Teaser */}
18+
<main
19+
className="relative min-h-screen overflow-hidden bg-[#030303] text-white"
20+
style={{ perspective: '1200px' }}
21+
>
22+
{/* ── Persistent Grid Background ── */}
23+
<GridBackground />
24+
25+
{/* ── Floating Orbs ── */}
26+
<FloatingOrb className="top-[10%] left-[8%] h-72 w-72 bg-purple-600/8 blur-[100px]" delay={0} />
27+
<FloatingOrb className="top-[40%] right-[5%] h-96 w-96 bg-purple-800/6 blur-[120px]" delay={2} />
28+
<FloatingOrb className="top-[70%] left-[15%] h-64 w-64 bg-indigo-600/6 blur-[100px]" delay={4} />
29+
30+
{/* Sections */}
31+
<HeroSection />
32+
{/* <TrustStatsSection /> */}
33+
<ProblemSolutionSection />
34+
<HowItWorksSection />
35+
<FeatureGridSection />
36+
<SecurityStripSection />
19037
<ComingSoonAppSection />
19138

19239
{/* Final CTA */}

0 commit comments

Comments
 (0)