From 233af7fb3d0f2ce7683cf049f6adce800a94c8cb Mon Sep 17 00:00:00 2001 From: Mayur Shinde Date: Fri, 12 Jun 2026 20:58:46 +0530 Subject: [PATCH] customer login is working and customer dashboard created --- src/App.jsx | 8 +- src/components/Layout.jsx | 3 +- src/data/mockStore.jsx | 2 +- src/pages/CustomerDashboard.jsx | 205 ++++++++++++++++++++++++++++++++ src/pages/Login.jsx | 4 +- 5 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 src/pages/CustomerDashboard.jsx diff --git a/src/App.jsx b/src/App.jsx index 72ef02d..62db776 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import Dashboard from './pages/Dashboard'; import Maps from './pages/Maps'; import AdminSchedule from './pages/AdminSchedule'; import CustomerProfile from './pages/CustomerProfile'; +import CustomerDashboard from './pages/CustomerDashboard'; import LeaderboardPage from './pages/LeaderboardPage'; import ErrorBoundary from './components/ErrorBoundary'; import SmoothScroll from './components/SmoothScroll'; @@ -109,7 +110,12 @@ function App() { } /> - {/* Protected Field Agent Routes */} + {/* Customer Portal Routes */} + + + + } /> diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 841dd17..7fb7f51 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -219,7 +219,8 @@ const Layout = () => { ]; default: // Customer or Fallback return [ - { to: "/customer/profile", icon: LayoutDashboard, label: "Dashboard" }, + { to: "/portal/dashboard", icon: LayoutDashboard, label: "Dashboard" }, + { to: "/portal/profile", icon: User, label: "My Profile" }, ...commonItems, ]; } diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx index 59b663b..2723d1e 100644 --- a/src/data/mockStore.jsx +++ b/src/data/mockStore.jsx @@ -986,7 +986,7 @@ function generateMockMeetings() { status: 'Completed', agentName: 'Hannah Reyes', dealValue: 1500, - notes: 'Minor repairs completed. Payment received.', + notes: 'Minor repairs completed.', issueDescription: 'Leak in garage.', customerComments: 'Please call before arriving.', outcome: 'Repairs Completed' diff --git a/src/pages/CustomerDashboard.jsx b/src/pages/CustomerDashboard.jsx new file mode 100644 index 0000000..5e96f31 --- /dev/null +++ b/src/pages/CustomerDashboard.jsx @@ -0,0 +1,205 @@ +/** + * CustomerDashboard — the landing screen for logged-in customers (role: CUSTOMER). + * + * Rendered at /portal/dashboard and linked from the "Dashboard" item in the + * customer sidebar. Gives the homeowner an at-a-glance overview of their work + * with LynkedUp: upcoming inspections/visits, recent job history, what they've + * invested, plus quick links into Payments and their Profile. + * + * Data comes from the mock store (meetings) — the same source CustomerProfile uses. + */ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { useMockStore } from '../data/mockStore'; +import { SpotlightCard } from '../components/SpotlightCard'; +import { + Calendar, Clock, CheckCircle, DollarSign, Sparkles, User as UserIcon, + MessageSquare, Home, ArrowRight, Wrench, +} from 'lucide-react'; + +const CustomerDashboard = () => { + const { user } = useAuth(); + const { meetings } = useMockStore(); + + // My visits — match by explicit id link, fall back to name for legacy data + const myMeetings = (meetings || []).filter(m => + (m.customerId === user?.id) || (m.customerName === user?.name) + ); + + const startOfToday = () => { + const d = new Date(); + d.setHours(0, 0, 0, 0); + return d; + }; + + const upcomingVisits = myMeetings + .filter(m => new Date(m.date) >= startOfToday() && m.status !== 'Completed' && m.status !== 'Cancelled') + .sort((a, b) => new Date(a.date) - new Date(b.date)); + + const pastVisits = myMeetings + .filter(m => new Date(m.date) < startOfToday() || m.status === 'Completed') + .sort((a, b) => new Date(b.date) - new Date(a.date)); + + const totalInvested = pastVisits.reduce((sum, m) => sum + (m.dealValue || 0), 0); + const nextVisit = upcomingVisits[0]; + + const firstName = (user?.name || 'there').split(' ')[0]; + + const stats = [ + { label: 'Upcoming Visits', value: upcomingVisits.length, icon: Calendar, accent: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-900/20', ring: 'border-blue-100 dark:border-blue-500/20' }, + { label: 'Completed Jobs', value: pastVisits.length, icon: CheckCircle, accent: 'text-emerald-600 dark:text-emerald-400', bg: 'bg-emerald-50 dark:bg-emerald-900/20', ring: 'border-emerald-100 dark:border-emerald-500/20' }, + { label: 'Total Invested', value: `$${totalInvested.toLocaleString()}`, icon: DollarSign, accent: 'text-amber-600 dark:text-amber-400', bg: 'bg-amber-50 dark:bg-amber-900/20', ring: 'border-amber-100 dark:border-amber-500/20' }, + ]; + + const quickActions = [ + { to: '/estimate', label: 'Estimate Wizard', desc: 'Build a new roof estimate', icon: Sparkles, accent: 'text-amber-600 dark:text-amber-400', bg: 'bg-amber-50 dark:bg-amber-900/20' }, + { to: '/portal/profile', label: 'My Profile', desc: 'Update your details & property', icon: UserIcon, accent: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-50 dark:bg-blue-900/20' }, + { to: '/chat-assistant', label: 'Ask Assistant', desc: 'Get help anytime', icon: MessageSquare, accent: 'text-purple-600 dark:text-purple-400', bg: 'bg-purple-50 dark:bg-purple-900/20' }, + ]; + + return ( +
+
+ + {/* --- Welcome header --- */} +
+
+ {user?.name?.charAt(0) || 'C'} +
+
+

+ Welcome back, {firstName} +

+

Here's what's happening with your property.

+
+
+ + {/* --- Stat cards --- */} +
+ {stats.map((stat) => { + const Icon = stat.icon; + return ( + +
+
+ +
+
+
{stat.value}
+
{stat.label}
+
+
+
+ ); + })} +
+ + {/* --- Next visit highlight --- */} +
+

+ Your Next Visit +

+ {nextVisit ? ( + +
+
+
+ {new Date(nextVisit.date).toLocaleString('default', { month: 'short' })} + {new Date(nextVisit.date).getDate()} +
+
+

{nextVisit.notes || 'Scheduled Visit'}

+
+ {nextVisit.time || 'TBD'} + {nextVisit.agentName || 'Assigned Agent'} +
+
+
+ + {nextVisit.status} + +
+ {nextVisit.issueDescription && ( +
+ + {nextVisit.issueDescription} +
+ )} +
+ ) : ( +
+ No upcoming visits scheduled. We'll let you know when one is booked. +
+ )} +
+ + {/* --- Quick actions --- */} +
+

+ Quick Actions +

+
+ {quickActions.map((action) => { + const Icon = action.icon; + return ( + + +
+ +
+
+

{action.label}

+ +
+

{action.desc}

+
+ + ); + })} +
+
+ + {/* --- Recent activity --- */} +
+

+ Recent Activity +

+ {pastVisits.length > 0 ? ( +
+ {pastVisits.slice(0, 3).map((visit) => ( + +
+
+
+ +
+
+
+

{visit.agentName || 'Agent'}

+ • {new Date(visit.date).toLocaleDateString()} +
+

{visit.notes || 'No notes available.'}

+
+
+ {visit.outcome ? ( + + {visit.outcome} + + ) : null} +
+
+ ))} +
+ ) : ( +
+ No activity yet. +
+ )} +
+
+
+ ); +}; + +export default CustomerDashboard; diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 836da84..cf7c7ec 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -126,7 +126,7 @@ const Login = () => { // Role-based Redirect switch (result.role) { case 'CUSTOMER': - navigate('/'); + navigate('/portal/dashboard'); break; case 'OWNER': navigate('/owner/snapshot'); @@ -154,7 +154,7 @@ const Login = () => { const result = login(account.id, account.pw, account.loginType); if (result.success) { switch (result.role) { - case 'CUSTOMER': navigate('/'); break; + case 'CUSTOMER': navigate('/portal/dashboard'); break; case 'OWNER': navigate('/owner/snapshot'); break; case 'CONTRACTOR': navigate('/contractor/dashboard'); break; case 'VENDOR': navigate('/vendor/dashboard'); break;