Skip to content
Open
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
53 changes: 53 additions & 0 deletions MOBILE_PROMPT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Mobile Responsiveness Implementation Prompt

## Context
The current dashboard application (`frontend/src/app/dashboard`) is designed for desktop viewports and breaks on mobile devices. The goal is to refactor the key pages to be "mobile-first", utilizing horizontal scrolling patterns and responsive grid/flex layouts.

## Objectives

### 1. Dashboard Overview (`frontend/src/app/dashboard/page.tsx`)
The overview page contains 4 metric cards, a donut chart, and two lists (Locations, Sources).
- **Metric Cards**:
- **Current**: Vertical stack on mobile (1 column).
- **Requirement**: Implement a **horizontal scroll snap container** (Carousel) for mobile.
- **Implementation**:
- Use `flex overflow-x-auto snap-x snap-mandatory` on the container for mobile.
- Reset to `grid` on `md` and larger screens.
- Ensure cards have a minimum width (e.g., `min-w-[280px]`) on mobile to look good.
- Hide scrollbars if possible or style them.
- **Intent Distribution**:
- **Current**: Flex row that overflows/cramps on mobile.
- **Requirement**: Stack the Chart and Legend vertically on mobile (`flex-col`), side-by-side on desktop (`lg:flex-row`).
- **Layout**:
- Ensure all `grid-cols` switch to `1` on mobile and higher on desktop.
- Verify padding (`p-4` vs `p-8`).

### 2. Sessions List (`frontend/src/app/dashboard/sessions/page.tsx`)
The sessions list is currently a strict 12-column grid table.
- **Structure**:
- **Current**: `grid-cols-12` with fixed spans.
- **Requirement**: Responsive Card Layout for mobile.
- **Implementation**:
- **Header**: Hide the table header row on mobile (`hidden md:grid`).
- **Rows**:
- Change container to `flex flex-col` or `block` on mobile.
- Inside each row, use a flexible layout where:
- "Customer" name/avatar is prominent at the top.
- "Contact Info" stacks below.
- "Action" buttons (Lead toggle) are easily tappable.
- "First Seen" can be small text or hidden if less important.
- Restore `grid grid-cols-12` on `md` screens.
- **Search Bar**: Stack the title and search input vertically on mobile (`flex-col items-start`).

### 3. Guest Detail (`frontend/src/app/dashboard/sessions/[guestId]/page.tsx`)
- **Header**: Ensure the title, "Lead" badge, and action buttons wrap correctly on small screens (`flex-wrap`).

## Tech Stack
- Next.js 14+ (App Router)
- Tailwind CSS (Use standard responsive modifiers: `hidden`, `block`, `md:flex`, `lg:grid`, etc.)
- Lucide React Icons

## Design Principles
- **Touch Targets**: Ensure buttons are large enough for touch.
- **Spacing**: Reduce outer padding on mobile to maximize screen real estate.
- **Scroll**: Use native scrolling with snap points for horizontal sections.
4 changes: 2 additions & 2 deletions frontend/src/app/dashboard/analytics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ const TrendChart = ({ days, setDays }: { days: number, setDays: (d: number) => v
))}
</div>
</div>
<div className="h-48 flex items-end gap-1 pt-4 border-b border-[var(--border-subtle)] pb-1">
<div className="h-48 flex items-end gap-1 pt-4 border-b border-[var(--border-subtle)] pb-1 overflow-x-auto">
{chartData.map((item) => (
<div key={item.date} className="flex-1 flex flex-col items-center gap-1 group relative h-full justify-end">
<div key={item.date} className="flex-1 flex flex-col items-center gap-1 group relative h-full justify-end min-w-[8px]">
<div
className="w-full bg-[var(--brand-primary)]/80 rounded-t-sm hover:bg-[var(--brand-primary)] transition-all min-w-[4px] relative"
style={{ height: `${(item.count / max) * 100}%` }}
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/app/dashboard/business/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,12 @@ export default function BusinessProfilePage() {

{/* Agent Configuration Section */}
<div className="space-y-4">
<h3 className="text-lg font-space font-semibold text-[var(--brand-primary)] border-b border-[var(--border-subtle)] pb-2 flex justify-between items-center">
Agent Configuration
<div className="border-b border-[var(--border-subtle)] pb-2 flex flex-col gap-2 md:flex-row md:justify-between md:items-center">
<h3 className="text-lg font-space font-semibold text-[var(--brand-primary)]">
Agent Configuration
</h3>
{editing && (
<div className="flex gap-2">
<div className="flex gap-2 flex-wrap">
{['professional', 'sales', 'support'].map(p => (
<button
key={p}
Expand All @@ -336,7 +338,7 @@ export default function BusinessProfilePage() {
))}
</div>
)}
</h3>
</div>

<div>
<label className="block text-[var(--text-secondary)] text-[13px] font-medium mb-2">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/dashboard/documents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default function DocumentsPage() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="flex items-center justify-between"
className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"
>
<div className="flex items-center gap-3">
<div className="p-3 bg-[var(--success)]/10 rounded-[var(--radius-squircle)]">
Expand All @@ -132,7 +132,7 @@ export default function DocumentsPage() {
</p>
</div>
</div>
<Button variant="secondary" onClick={fetchDocuments}>
<Button variant="secondary" onClick={fetchDocuments} className="self-start md:self-auto">
<RefreshCw className="w-4 h-4" />
Refresh
</Button>
Expand Down
30 changes: 16 additions & 14 deletions frontend/src/app/dashboard/handoff/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default function HandoffPage() {

const RequestsView = (
<div className="space-y-6">
<div className="flex gap-4 items-center">
<div className="flex flex-col md:flex-row gap-4 md:items-center">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] w-4 h-4" />
<input
Expand All @@ -121,19 +121,21 @@ export default function HandoffPage() {
className="w-full pl-10 pr-4 py-2 bg-[var(--bg-primary)] border border-[var(--border-subtle)] rounded-[var(--radius-md)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--brand-primary)]/20"
/>
</div>
<select
className="bg-[var(--bg-primary)] border border-[var(--border-subtle)] rounded-[var(--radius-md)] px-3 py-2 text-sm focus:outline-none"
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
>
<option value="all">All Status</option>
<option value="pending">Pending</option>
<option value="in_progress">In Progress</option>
<option value="resolved">Resolved</option>
</select>
<Button variant="secondary" onClick={fetchBusinessAndEscalations}>
Refresh
</Button>
<div className="flex gap-2">
<select
className="flex-1 md:flex-none bg-[var(--bg-primary)] border border-[var(--border-subtle)] rounded-[var(--radius-md)] px-3 py-2 text-sm focus:outline-none"
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
>
<option value="all">All Status</option>
<option value="pending">Pending</option>
<option value="in_progress">In Progress</option>
<option value="resolved">Resolved</option>
</select>
<Button variant="secondary" onClick={fetchBusinessAndEscalations} className="flex-1 md:flex-none justify-center">
Refresh
</Button>
</div>
</div>

{loadingEscalations ? (
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { AnalyticsOverview, IntentStat, TrafficSource, LocationStat } from '@/li

// --- Components ---

const MetricCard = ({ title, value, delta, label, icon: Icon, delay }: { title: string, value: string, delta?: string, label: string, icon: React.ElementType, delay: number }) => (
const MetricCard = ({ title, value, delta, label, icon: Icon, delay, className }: { title: string, value: string, delta?: string, label: string, icon: React.ElementType, delay: number, className?: string }) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay }}
className="bg-white p-6 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] shadow-[var(--shadow-sm)] flex flex-col justify-between h-full hover:shadow-[var(--shadow-md)] transition-shadow"
className={cn("bg-white p-6 rounded-[var(--radius-lg)] border border-[var(--border-subtle)] shadow-[var(--shadow-sm)] flex flex-col justify-between h-full hover:shadow-[var(--shadow-md)] transition-shadow", className)}
>
<div className="flex justify-between items-start mb-4">
<div className="p-2 bg-[var(--bg-tertiary)] rounded-lg">
Expand All @@ -40,7 +40,7 @@ const IntentDonut = ({ intents }: { intents: IntentStat[] }) => {
let cumulative = 0;

return (
<div className="flex items-center gap-8 h-full">
<div className="flex flex-col lg:flex-row items-center gap-8 h-full">
<div className="relative w-40 h-40 flex-shrink-0">
<svg viewBox="0 0 100 100" className="transform -rotate-90 w-full h-full">
{intents.length > 0 ? intents.map((intent, i) => {
Expand Down Expand Up @@ -124,34 +124,38 @@ export default function DashboardOverview() {
<p className="text-[var(--text-secondary)]">Welcome back. Here&apos;s what&apos;s happening today.</p>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="flex overflow-x-auto snap-x snap-mandatory gap-4 pb-4 -mx-4 px-4 md:grid md:grid-cols-2 lg:grid-cols-4 md:pb-0 md:mx-0 md:px-0 scrollbar-hide">
<MetricCard
title="Total Sessions"
value={metrics?.total_sessions.toLocaleString() || "0"}
label="Total Sessions"
icon={MessageSquare}
delay={0.1}
className="min-w-[280px] snap-center md:min-w-0"
/>
<MetricCard
title="Unique Guests"
value={metrics?.total_guests.toLocaleString() || "0"}
label="Unique Guests"
icon={Users}
delay={0.2}
className="min-w-[280px] snap-center md:min-w-0"
/>
<MetricCard
title="Leads Captured"
value={metrics?.leads_captured.toLocaleString() || "0"}
label="Leads Captured"
icon={Target}
delay={0.3}
className="min-w-[280px] snap-center md:min-w-0"
/>
<MetricCard
title="Avg Duration"
value={`${metrics ? Math.floor(metrics.avg_session_duration / 60) : 0}m ${metrics ? metrics.avg_session_duration % 60 : 0}s`}
label="Avg Session Duration"
icon={Clock}
delay={0.4}
className="min-w-[280px] snap-center md:min-w-0"
/>
</div>

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/app/dashboard/sessions/[guestId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ export default function GuestSessionsList() {

return (
<div className="max-w-[1200px] mx-auto h-[calc(100vh-140px)] flex flex-col">
<div className="mb-6 flex-shrink-0 flex items-center justify-between">
<div className="mb-6 flex-shrink-0 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex items-center gap-4">
<Button variant="secondary" onClick={() => router.back()} className="rounded-full px-3">
<ArrowLeft className="w-5 h-5 text-[var(--text-secondary)]" />
</Button>
<div>
<div className="flex items-center gap-3">
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-2xl font-space font-bold text-[var(--brand-primary)]">
{guest?.name || 'Guest History'}
</h1>
Expand All @@ -84,7 +84,7 @@ export default function GuestSessionsList() {
onClick={handleToggleLead}
disabled={togglingLead}
className={cn(
"flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-full transition-all",
"flex items-center justify-center w-full md:w-auto gap-2 px-4 py-2 text-sm font-medium rounded-full transition-all",
guest.is_lead
? "bg-[var(--status-success)]/10 text-[var(--status-success)] hover:bg-[var(--status-success)]/20 border border-[var(--status-success)]/20"
: "bg-[var(--bg-tertiary)] text-[var(--text-secondary)] hover:bg-[var(--border-subtle)] border border-[var(--border-subtle)]"
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/app/dashboard/sessions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ export default function SessionsGuestList() {

return (
<div className="max-w-[1600px] mx-auto h-[calc(100vh-140px)] flex flex-col">
<div className="mb-6 flex-shrink-0 flex justify-between items-end">
<div className="mb-6 flex-shrink-0 flex flex-col items-start gap-4 md:flex-row md:items-end md:gap-0">
<div>
<h1 className="text-2xl font-space font-bold text-[var(--brand-primary)]">Customer Sessions</h1>
<p className="text-[var(--text-secondary)]">Browse interactions by customer.</p>
</div>
<div className="relative w-64">
<div className="relative w-full md:w-64">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-tertiary)]" />
<input
type="text"
Expand All @@ -70,7 +70,7 @@ export default function SessionsGuestList() {

<div className="flex-1 bg-white rounded-[var(--radius-lg)] border border-[var(--border-subtle)] shadow-sm overflow-hidden flex flex-col">
{/* Header Row */}
<div className="grid grid-cols-12 gap-4 p-4 border-b border-[var(--border-subtle)] bg-[var(--bg-secondary)]/50 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
<div className="hidden md:grid md:grid-cols-12 gap-4 p-4 border-b border-[var(--border-subtle)] bg-[var(--bg-secondary)]/50 text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
<div className="col-span-4 pl-2">Customer</div>
<div className="col-span-3">Contact Info</div>
<div className="col-span-2">First Seen</div>
Expand All @@ -92,9 +92,9 @@ export default function SessionsGuestList() {
<div
key={guest.id}
onClick={() => router.push(`/dashboard/sessions/${guest.id}`)}
className="grid grid-cols-12 gap-4 p-4 border-b border-[var(--border-subtle)] hover:bg-[var(--bg-primary)] transition-colors cursor-pointer group items-center"
className="flex flex-col gap-2 md:grid md:grid-cols-12 md:gap-4 p-4 border-b border-[var(--border-subtle)] hover:bg-[var(--bg-primary)] transition-colors cursor-pointer group items-start md:items-center"
>
<div className="col-span-4 pl-2 flex items-center gap-3">
<div className="w-full md:col-span-4 pl-2 flex items-center gap-3">
<div className={cn(
"w-10 h-10 rounded-full text-white flex items-center justify-center font-bold shadow-sm",
guest.is_lead ? "bg-[var(--status-success)]" : "bg-[var(--brand-primary)]"
Expand All @@ -113,7 +113,7 @@ export default function SessionsGuestList() {
<p className="text-xs text-[var(--text-tertiary)] font-mono truncate max-w-[150px]">{guest.id.substring(0, 8)}...</p>
</div>
</div>
<div className="col-span-3 space-y-1">
<div className="w-full md:col-span-3 space-y-1 pl-12 md:pl-0">
{guest.email && (
<div className="flex items-center text-sm text-[var(--text-secondary)]">
<Mail className="w-3.5 h-3.5 mr-2 text-[var(--text-tertiary)]" /> {guest.email}
Expand All @@ -126,11 +126,11 @@ export default function SessionsGuestList() {
)}
{!guest.email && !guest.phone && <span className="text-xs text-[var(--text-tertiary)] italic">No contact info</span>}
</div>
<div className="col-span-2 text-sm text-[var(--text-secondary)] flex items-center">
<div className="w-full md:col-span-2 text-sm text-[var(--text-secondary)] flex items-center pl-12 md:pl-0">
<Calendar className="w-3.5 h-3.5 mr-2 text-[var(--text-tertiary)]" />
{new Date(guest.created_at).toLocaleDateString()}
</div>
<div className="col-span-2 flex justify-center">
<div className="w-full md:col-span-2 flex md:justify-center pl-12 md:pl-0 mt-2 md:mt-0">
<button
onClick={(e) => handleToggleLead(e, guest)}
disabled={togglingId === guest.id}
Expand All @@ -149,7 +149,7 @@ export default function SessionsGuestList() {
{guest.is_lead ? "Lead" : "Mark as Lead"}
</button>
</div>
<div className="col-span-1 flex justify-end pr-4">
<div className="hidden md:flex md:col-span-1 justify-end pr-4">
<button className="p-2 rounded-full hover:bg-[var(--border-subtle)] text-[var(--text-tertiary)] group-hover:text-[var(--brand-primary)] transition-colors">
<ChevronRight className="w-5 h-5" />
</button>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/dashboard/widget-settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export default function WidgetSettingsPage() {
{/* Settings Form - Left Panel (Scrollable) */}
<div className="lg:col-span-7 flex flex-col h-full bg-white rounded-[var(--radius-lg)] border border-[var(--border-subtle)] overflow-hidden shadow-sm">
{/* Tabs Header */}
<div className="flex border-b border-[var(--border-subtle)] px-6 pt-4 gap-6 bg-[var(--bg-secondary)]/30">
<div className="flex border-b border-[var(--border-subtle)] px-6 pt-4 gap-6 bg-[var(--bg-secondary)]/30 overflow-x-auto scrollbar-hide">
{tabs.map(tab => (
<button
key={tab.id}
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@ a[aria-disabled="true"] {
width: 40px;
background: linear-gradient(to right, transparent, var(--bg-primary));
}

/* Scrollbar Hide */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

.scrollbar-hide::-webkit-scrollbar {
display: none;
}
}

/* ===================================
Expand Down