Skip to content

Commit 45cfe0c

Browse files
authored
Merge pull request #58 from stenwire/main
deploy
2 parents 1b7d3c2 + 9d3b5fc commit 45cfe0c

7 files changed

Lines changed: 440 additions & 357 deletions

File tree

frontend/src/app/dashboard/documents/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export default function DocumentsPage() {
169169
<Card title="Upload Documents" subtitle="Add files to your knowledge base">
170170
<label
171171
htmlFor="file-upload"
172-
className={`block border-2 border-dashed rounded-[var(--radius-md)] p-8 text-center transition-colors cursor-pointer ${isDragging
172+
className={`block border-2 border-dashed rounded-[var(--radius-md)] p-4 lg:p-8 text-center transition-colors cursor-pointer ${isDragging
173173
? 'border-[var(--brand-primary)] bg-[var(--brand-primary)]/5'
174174
: 'border-[var(--border-subtle)] hover:border-[var(--border-strong)]'
175175
}`}
@@ -299,7 +299,7 @@ export default function DocumentsPage() {
299299
variant="ghost"
300300
size="sm"
301301
onClick={() => confirmDelete(doc.id)}
302-
className="text-[var(--text-tertiary)] hover:text-red-600 hover:bg-red-50 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-all"
302+
className="text-[var(--text-tertiary)] hover:text-red-600 hover:bg-red-50 flex-shrink-0 opacity-100 lg:opacity-0 lg:group-hover:opacity-100 transition-all"
303303
>
304304
<Trash2 className="w-4 h-4" />
305305
</Button>

frontend/src/app/dashboard/layout.tsx

Lines changed: 91 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React, { useState, useEffect } from 'react';
44
import { useRouter, usePathname } from 'next/navigation';
5-
import { LayoutDashboard, Building2, FileText, MessageSquare, LogOut, Menu, X, Settings, Users, AlertTriangle, Bot } from 'lucide-react';
5+
import { LayoutDashboard, Building2, FileText, MessageSquare, LogOut, Menu, X, Settings, Users, AlertTriangle, Bot, AlignLeft, ChevronLeft, ChevronRight } from 'lucide-react';
66
import Sidebar, { SidebarSection } from '@/components/ui/Sidebar';
77
import { useAuth } from '@/contexts/AuthContext';
88
import { useBusiness, BusinessProvider } from '@/contexts/BusinessContext';
@@ -67,85 +67,110 @@ function DashboardLayoutInner({
6767

6868
return (
6969
<ProtectedRoute>
70-
<div className="min-h-screen bg-[var(--bg-secondary)] flex font-sans">
70+
<div className="flex h-screen bg-[var(--bg-secondary)] font-sans overflow-hidden">
7171

7272
{/* Mobile Header */}
73-
<div className="lg:hidden fixed top-0 left-0 right-0 z-50 bg-[var(--bg-primary)] border-b border-[var(--border-subtle)] px-4 py-3 flex items-center justify-between">
74-
<h1 className="text-h2 font-space tracking-tight text-[var(--brand-primary)] font-bold">Taimako</h1>
73+
<div className="lg:hidden fixed top-0 left-0 right-0 z-50 bg-[var(--bg-primary)] border-b border-[var(--border-subtle)] px-4 py-3 flex items-center justify-between h-16">
74+
<h1 className="text-xl font-space tracking-tight text-[var(--brand-primary)] font-bold">Taimako</h1>
7575
<button
76-
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
76+
onClick={() => setMobileMenuOpen(true)}
7777
className="p-2 hover:bg-[var(--bg-secondary)] rounded-[var(--radius-sm)] transition-colors text-[var(--text-primary)]"
78+
aria-label="Open Menu"
7879
>
79-
{mobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
80+
<AlignLeft className="w-6 h-6" />
8081
</button>
8182
</div>
8283

8384
{/* Sidebar - Desktop */}
84-
<div className={sidebarCollapsed ? "hidden lg:block w-16 transition-all duration-300 relative z-40" : "hidden lg:block w-60 transition-all duration-300 relative z-40"}>
85-
<div className="fixed top-0 left-0 h-full bg-[var(--bg-primary)] border-r border-[var(--border-subtle)]">
86-
<div className="h-full flex flex-col w-full">
87-
{/* Sidebar Header */}
88-
<div className="p-6 border-b border-[var(--border-subtle)] flex items-center justify-between">
89-
{!sidebarCollapsed && (
90-
<h1 className="text-xl font-space font-bold text-[var(--brand-primary)] tracking-tight">Taimako</h1>
91-
)}
92-
<button onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1 hover:bg-[var(--bg-tertiary)] rounded text-[var(--text-secondary)]">
93-
{sidebarCollapsed ? <Menu className="w-4 h-4" /> : <Menu className="w-4 h-4" />}
94-
</button>
95-
</div>
85+
<aside
86+
className={`hidden lg:flex flex-col border-r border-[var(--border-subtle)] bg-[var(--bg-primary)] transition-all duration-300 ease-in-out relative z-40 ${sidebarCollapsed ? 'w-16' : 'w-64'
87+
}`}
88+
>
89+
{/* Sidebar Header */}
90+
<div className="h-16 flex items-center justify-between px-4 border-b border-[var(--border-subtle)] flex-shrink-0">
91+
{!sidebarCollapsed && (
92+
<h1 className="text-xl font-space font-bold text-[var(--brand-primary)] tracking-tight truncate">Taimako</h1>
93+
)}
94+
<button
95+
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
96+
className={`p-1.5 hover:bg-[var(--bg-tertiary)] rounded-md text-[var(--text-secondary)] transition-colors ${sidebarCollapsed ? 'mx-auto' : ''}`}
97+
aria-label={sidebarCollapsed ? "Expand Sidebar" : "Collapse Sidebar"}
98+
>
99+
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
100+
</button>
101+
</div>
96102

97-
{/* Sidebar Navigation */}
98-
<div className="flex-1 overflow-y-auto w-full">
99-
<Sidebar sections={sidebarSections} collapsed={sidebarCollapsed} />
100-
</div>
103+
{/* Sidebar Navigation */}
104+
<div className="flex-1 overflow-y-auto overflow-x-hidden custom-scrollbar">
105+
<Sidebar sections={sidebarSections} collapsed={sidebarCollapsed} />
106+
</div>
101107

102-
{/* Sidebar Footer */}
103-
<div className="p-4 border-t border-[var(--border-subtle)]">
104-
<div className="flex flex-col gap-2">
105-
<div className={`flex items-center gap-3 px-2 ${sidebarCollapsed ? 'justify-center' : ''}`}>
106-
<div className="w-8 h-8 rounded-full bg-[var(--brand-secondary)] flex items-center justify-center text-white font-bold text-xs flex-shrink-0">
107-
{user?.name?.[0] || user?.email?.[0] || 'U'}
108-
</div>
109-
{!sidebarCollapsed && (
110-
<div className="flex-1 overflow-hidden min-w-0">
111-
<div className="text-sm font-medium text-[var(--text-primary)] truncate" title={user?.name}>{user?.name || 'User'}</div>
112-
<div className="text-xs text-[var(--text-tertiary)] truncate" title={user?.email}>{user?.email}</div>
113-
</div>
114-
)}
115-
</div>
116-
<Button
117-
variant="ghost"
118-
className={`w-full ${sidebarCollapsed ? 'justify-center px-0' : 'justify-start'} mt-2 text-[var(--text-secondary)] hover:text-[var(--status-error)] hover:bg-[var(--bg-tertiary)]`}
119-
onClick={logout}
120-
>
121-
<LogOut className="w-4 h-4" />
122-
{!sidebarCollapsed && <span className="ml-2">Logout</span>}
123-
</Button>
108+
{/* Sidebar Footer */}
109+
<div className="p-4 border-t border-[var(--border-subtle)] flex-shrink-0">
110+
<div className="flex flex-col gap-2">
111+
<div className={`flex items-center gap-3 px-2 ${sidebarCollapsed ? 'justify-center' : ''}`}>
112+
<div className="w-8 h-8 rounded-full bg-[var(--brand-secondary)] flex items-center justify-center text-white font-bold text-xs flex-shrink-0 ring-2 ring-white">
113+
{user?.name?.[0] || user?.email?.[0] || 'U'}
124114
</div>
115+
{!sidebarCollapsed && (
116+
<div className="flex-1 overflow-hidden min-w-0">
117+
<div className="text-sm font-medium text-[var(--text-primary)] truncate" title={user?.name}>{user?.name || 'User'}</div>
118+
<div className="text-xs text-[var(--text-tertiary)] truncate" title={user?.email}>{user?.email}</div>
119+
</div>
120+
)}
125121
</div>
122+
<Button
123+
variant="ghost"
124+
className={`w-full ${sidebarCollapsed ? 'justify-center px-0' : 'justify-start'} mt-2 text-[var(--text-secondary)] hover:text-[var(--status-error)] hover:bg-[var(--bg-tertiary)]`}
125+
onClick={logout}
126+
>
127+
<LogOut className="w-4 h-4" />
128+
{!sidebarCollapsed && <span className="ml-2">Logout</span>}
129+
</Button>
126130
</div>
127131
</div>
128-
</div>
132+
</aside>
129133

130-
{/* Mobile Sidebar */}
134+
{/* Mobile Sidebar Overlay */}
131135
{mobileMenuOpen && (
132-
<div className="lg:hidden fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" onClick={() => setMobileMenuOpen(false)}>
136+
<div className="lg:hidden fixed inset-0 z-[60]">
137+
{/* Backdrop */}
133138
<div
134-
className="w-72 h-full bg-[var(--bg-primary)] border-r border-[var(--border-subtle)] shadow-2xl"
139+
className="absolute inset-0 bg-black/40 backdrop-blur-sm transition-opacity opacity-100"
140+
onClick={() => setMobileMenuOpen(false)}
141+
/>
142+
143+
{/* Drawer */}
144+
<div
145+
className="absolute top-0 left-0 bottom-0 w-72 bg-[var(--bg-primary)] shadow-2xl transform transition-transform duration-300 ease-out translate-x-0"
135146
onClick={(e) => e.stopPropagation()}
136147
>
137-
<div className="h-full flex flex-col">
138-
<div className="p-6 border-b border-[var(--border-subtle)] flex justify-between items-center">
139-
<h1 className="text-2xl font-space font-bold text-[var(--brand-primary)]">Taimako</h1>
140-
<button onClick={() => setMobileMenuOpen(false)}><X className="w-6 h-6 text-[var(--text-secondary)]" /></button>
148+
<div className="flex flex-col h-full">
149+
<div className="h-16 flex items-center justify-between px-6 border-b border-[var(--border-subtle)]">
150+
<h1 className="text-xl font-space font-bold text-[var(--brand-primary)]">Taimako</h1>
151+
<button
152+
onClick={() => setMobileMenuOpen(false)}
153+
className="p-2 -mr-2 text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)] rounded-md"
154+
>
155+
<X className="w-5 h-5" />
156+
</button>
141157
</div>
142158

143159
<div className="flex-1 overflow-y-auto">
144-
<Sidebar sections={sidebarSections} />
160+
<Sidebar sections={sidebarSections} onItemClick={() => setMobileMenuOpen(false)} />
145161
</div>
146162

147163
<div className="p-6 border-t border-[var(--border-subtle)]">
148-
<Button onClick={logout} variant="ghost" className="w-full justify-start text-[var(--text-secondary)]">
164+
<div className="flex items-center gap-3 mb-6">
165+
<div className="w-10 h-10 rounded-full bg-[var(--brand-secondary)] flex items-center justify-center text-white font-bold text-sm ring-2 ring-white">
166+
{user?.name?.[0] || user?.email?.[0] || 'U'}
167+
</div>
168+
<div className="flex-1 overflow-hidden">
169+
<div className="text-sm font-medium text-[var(--text-primary)] truncate">{user?.name || 'User'}</div>
170+
<div className="text-xs text-[var(--text-tertiary)] truncate">{user?.email}</div>
171+
</div>
172+
</div>
173+
<Button onClick={logout} variant="ghost" className="w-full justify-start text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]">
149174
<LogOut className="w-5 h-5 mr-3" /> Logout
150175
</Button>
151176
</div>
@@ -154,40 +179,35 @@ function DashboardLayoutInner({
154179
</div>
155180
)}
156181

157-
{/* Main Content */}
158-
<div className="flex-1 flex flex-col min-w-0 transition-all duration-300">
182+
{/* Main Content Area */}
183+
<main className="flex-1 flex flex-col min-w-0 bg-[var(--bg-secondary)] overflow-hidden relative">
159184

160-
{/* Missing Key Warning Banner */}
185+
{/* Missing Key Warning */}
161186
{apiKeyMissing && !checkingKey && (
162-
<div className="bg-[var(--warning-bg)] border-l-4 border-[var(--warning)] p-4 sticky top-0 z-30 shadow-md">
187+
<div className="bg-[var(--warning-bg)] border-l-4 border-[var(--warning)] p-4 shadow-sm z-30 flex-shrink-0">
163188
<div className="flex items-center">
164189
<div className="flex-shrink-0">
165190
<AlertTriangle className="h-5 w-5 text-[var(--warning)]" aria-hidden="true" />
166191
</div>
167192
<div className="ml-3">
168193
<p className="text-sm text-[var(--warning-text)]">
169194
<span className="font-bold">Action Required: </span>
170-
You must set your Google Gemini API Key in <span className="font-bold cursor-pointer underline" onClick={() => router.push('/dashboard/business')}>Settings</span> to use the AI features. Access to other pages is restricted until configured.
195+
You must set your Google Gemini API Key in <span className="font-bold cursor-pointer underline hover:text-opacity-80" onClick={() => router.push('/dashboard/business')}>Settings</span> to use AI features.
171196
</p>
172197
</div>
173198
</div>
174199
</div>
175200
)}
176201

177-
<div className={`h-full ${apiKeyMissing ? '' : 'pt-16 lg:pt-0'}`}>
178-
{/* If blocked, mobile header might overlap if we don't account for paddingTop.
179-
The original had pt-16 lg:pt-0. If we add banner, we shift content down.
180-
Actually, just keep original padding. Content is children.
181-
*/}
182-
<div className="pt-16 lg:pt-0 h-full">
183-
<main className="p-4 lg:p-8 h-full overflow-y-auto">
184-
<div className="max-w-[1440px] mx-auto w-full h-full">
185-
{children}
186-
</div>
187-
</main>
202+
{/* Scrollable Content */}
203+
<div className="flex-1 overflow-y-auto scroll-smooth">
204+
<div className="pt-16 lg:pt-0 min-h-full">
205+
<div className="p-4 lg:p-8 max-w-[1600px] mx-auto w-full">
206+
{children}
207+
</div>
188208
</div>
189209
</div>
190-
</div>
210+
</main>
191211
</div>
192212
</ProtectedRoute>
193213
);

frontend/src/app/dashboard/page.tsx

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -118,50 +118,62 @@ export default function DashboardOverview() {
118118
if (loading) return <div className="p-8">Loading dashboard...</div>;
119119

120120
return (
121+
121122
<div className="space-y-6">
122-
<div className="mb-8">
123+
<div className="mb-6 lg:mb-8">
123124
<h1 className="text-2xl font-space font-bold text-[var(--brand-primary)]">Overview</h1>
124125
<p className="text-[var(--text-secondary)]">Welcome back. Here&apos;s what&apos;s happening today.</p>
125126
</div>
126127

127-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
128-
<MetricCard
129-
title="Total Sessions"
130-
value={metrics?.total_sessions.toLocaleString() || "0"}
131-
label="Total Sessions"
132-
icon={MessageSquare}
133-
delay={0.1}
134-
/>
135-
<MetricCard
136-
title="Unique Guests"
137-
value={metrics?.total_guests.toLocaleString() || "0"}
138-
label="Unique Guests"
139-
icon={Users}
140-
delay={0.2}
141-
/>
142-
<MetricCard
143-
title="Leads Captured"
144-
value={metrics?.leads_captured.toLocaleString() || "0"}
145-
label="Leads Captured"
146-
icon={Target}
147-
delay={0.3}
148-
/>
149-
<MetricCard
150-
title="Avg Duration"
151-
value={`${metrics ? Math.floor(metrics.avg_session_duration / 60) : 0}m ${metrics ? metrics.avg_session_duration % 60 : 0}s`}
152-
label="Avg Session Duration"
153-
icon={Clock}
154-
delay={0.4}
155-
/>
128+
{/* Metrics Section: Horizontal Scroll on Mobile, Grid on Desktop */}
129+
<div className="flex overflow-x-auto snap-x snap-mandatory gap-4 pb-4 -mx-4 px-4 lg:grid lg:grid-cols-4 lg:gap-4 lg:pb-0 lg:mx-0 lg:px-0 scrollbar-hide">
130+
<div className="min-w-[260px] snap-center h-full">
131+
<MetricCard
132+
title="Total Sessions"
133+
value={metrics?.total_sessions.toLocaleString() || "0"}
134+
label="Total Sessions"
135+
icon={MessageSquare}
136+
delay={0.1}
137+
/>
138+
</div>
139+
<div className="min-w-[260px] snap-center h-full">
140+
<MetricCard
141+
title="Unique Guests"
142+
value={metrics?.total_guests.toLocaleString() || "0"}
143+
label="Unique Guests"
144+
icon={Users}
145+
delay={0.2}
146+
/>
147+
</div>
148+
<div className="min-w-[260px] snap-center h-full">
149+
<MetricCard
150+
title="Leads Captured"
151+
value={metrics?.leads_captured.toLocaleString() || "0"}
152+
label="Leads Captured"
153+
icon={Target}
154+
delay={0.3}
155+
/>
156+
</div>
157+
<div className="min-w-[260px] snap-center h-full">
158+
<MetricCard
159+
title="Avg Duration"
160+
value={`${metrics ? Math.floor(metrics.avg_session_duration / 60) : 0}m ${metrics ? metrics.avg_session_duration % 60 : 0}s`}
161+
label="Avg Session Duration"
162+
icon={Clock}
163+
delay={0.4}
164+
/>
165+
</div>
156166
</div>
157167

158168
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4 h-auto lg:h-[320px]">
159169
<div className="lg:col-span-8 bg-white p-6 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] shadow-[var(--shadow-sm)]">
160170
<h3 className="text-lg font-space font-semibold text-[var(--brand-primary)] mb-6">Intent Distribution</h3>
161-
<IntentDonut intents={intents} />
171+
<div className="h-[calc(100%-2rem)] flex items-center justify-center">
172+
<IntentDonut intents={intents} />
173+
</div>
162174
</div>
163175

164-
<div className="lg:col-span-4 bg-[var(--brand-primary)] text-white p-6 rounded-[var(--radius-lg)] shadow-[var(--shadow-glow)] flex flex-col justify-between relative overflow-hidden">
176+
<div className="lg:col-span-4 bg-[var(--brand-primary)] text-white p-6 rounded-[var(--radius-lg)] shadow-[var(--shadow-glow)] flex flex-col justify-between relative overflow-hidden min-h-[200px]">
165177
<div className="relative z-10">
166178
<h3 className="text-lg font-space font-semibold text-white/90">Returning Guests</h3>
167179
<p className="text-sm text-white/70 mt-1">Guests engaging more than once</p>
@@ -209,7 +221,7 @@ export default function DashboardOverview() {
209221

210222
<div className="lg:col-span-6 bg-white p-6 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] shadow-[var(--shadow-sm)] flex flex-col">
211223
<h3 className="text-lg font-space font-semibold text-[var(--brand-primary)] mb-4">Traffic Sources</h3>
212-
<div className="grid grid-cols-2 gap-4 flex-1">
224+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1">
213225
{sources.map((item, i) => (
214226
<div key={item.source} className="p-4 bg-[var(--bg-tertiary)]/30 rounded-[var(--radius-md)] flex flex-col justify-center">
215227
<div className={`w-2 h-2 rounded-full mb-3 bg-[var(--brand-primary)]`} />

0 commit comments

Comments
 (0)