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
2,347 changes: 613 additions & 1,734 deletions package-lock.json

Large diffs are not rendered by default.

54 changes: 2 additions & 52 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,6 @@
"use client"
import { Percent, Briefcase, MessageSquare } from "lucide-react"
import MetricCard from "@/components/dashboard/MetricCard"
import QuickActions from "@/components/dashboard/QuickActions"
import SeekerOverview from "@/components/dashboard/SeekerOverview"

export default function DashboardHome() {
const metrics = [
{
label: "Total Earnings",
value: "$1,500",
icon: Percent,
iconColor: "text-[#22d3ee]",
iconBgColor: "bg-[#22d3ee]/10",
},
{
label: "Number of Enrolled Students",
value: "12",
icon: Briefcase,
iconColor: "text-[#9d50ff]",
iconBgColor: "bg-[#9d50ff]/10",
},
{
label: "Total Courses Published",
value: "12",
icon: Briefcase,
iconColor: "text-[#9d50ff]",
iconBgColor: "bg-[#9d50ff]/10",
},
{
label: "Messages",
value: "10+",
icon: MessageSquare,
iconColor: "text-[#ffa500]",
iconBgColor: "bg-[#ffa500]/10",
},
]

return (
<div className="space-y-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{metrics.map((metric) => (
<MetricCard
key={metric.label}
label={metric.label}
value={metric.value}
icon={metric.icon}
iconColor={metric.iconColor}
iconBgColor={metric.iconBgColor}
/>
))}
</div>

<QuickActions />
</div>
)
return <SeekerOverview />
}
22 changes: 8 additions & 14 deletions src/components/CreateWalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ export default function CreateWalletModal({ open, onClose }: Props) {
setState("connecting");
// fake async
setTimeout(() => {
// deterministic success for MetaMask, error for Coinbase, random for WalletConnect
if (wallet === "MetaMask") setState("connected");
else if (wallet === "Coinbase Wallet") setState("error");
// deterministic success for Freighter, random for Albedo
if (wallet === "Freighter") setState("connected");
else setState(Math.random() > 0.4 ? "connected" : "error");
}, 1200);
Comment on lines 45 to 49
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear pending connection timeout to avoid stale state transitions.

On Line 45, the timer is not canceled. If the modal closes/reopens before it fires, the old callback can flip state in a later session.

Suggested fix
 export default function CreateWalletModal({ open, onClose }: Props) {
   const [state, setState] = useState<ModalState>("idle");
   const [method, setMethod] = useState<string | null>(null);
   const backdropRef = useRef<HTMLDivElement | null>(null);
+  const connectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

   // reset when opened
   useEffect(() => {
     if (open) {
       setState("idle");
       setMethod(null);
     }
+    return () => {
+      if (connectTimerRef.current) {
+        clearTimeout(connectTimerRef.current);
+        connectTimerRef.current = null;
+      }
+    };
   }, [open]);

   function simulateConnect(wallet: string) {
+    if (connectTimerRef.current) clearTimeout(connectTimerRef.current);
     setMethod(wallet);
     setState("connecting");
-    setTimeout(() => {
+    connectTimerRef.current = setTimeout(() => {
       // deterministic success for Freighter, random for Albedo
       if (wallet === "Freighter") setState("connected");
       else setState(Math.random() > 0.4 ? "connected" : "error");
     }, 1200);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout(() => {
// deterministic success for MetaMask, error for Coinbase, random for WalletConnect
if (wallet === "MetaMask") setState("connected");
else if (wallet === "Coinbase Wallet") setState("error");
// deterministic success for Freighter, random for Albedo
if (wallet === "Freighter") setState("connected");
else setState(Math.random() > 0.4 ? "connected" : "error");
}, 1200);
export default function CreateWalletModal({ open, onClose }: Props) {
const [state, setState] = useState<ModalState>("idle");
const [method, setMethod] = useState<string | null>(null);
const backdropRef = useRef<HTMLDivElement | null>(null);
const connectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// reset when opened
useEffect(() => {
if (open) {
setState("idle");
setMethod(null);
}
return () => {
if (connectTimerRef.current) {
clearTimeout(connectTimerRef.current);
connectTimerRef.current = null;
}
};
}, [open]);
function simulateConnect(wallet: string) {
if (connectTimerRef.current) clearTimeout(connectTimerRef.current);
setMethod(wallet);
setState("connecting");
connectTimerRef.current = setTimeout(() => {
// deterministic success for Freighter, random for Albedo
if (wallet === "Freighter") setState("connected");
else setState(Math.random() > 0.4 ? "connected" : "error");
}, 1200);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/CreateWalletModal.tsx` around lines 45 - 49, The connection
timer in CreateWalletModal uses setTimeout that can fire after the modal has
closed; store the timeout ID (e.g., in a ref or local variable) when you call
setTimeout for the wallet connection simulation and call clearTimeout(timeoutId)
in the component cleanup or whenever the modal is closed/restarted so the old
callback cannot call setState; update the logic around the setTimeout (the block
that checks wallet === "Freighter" / Math.random) to clear any existing timer
before starting a new one and to clear it on unmount (useEffect cleanup) to
prevent stale state transitions.

}
Expand All @@ -72,19 +71,14 @@ export default function CreateWalletModal({ open, onClose }: Props) {
{/* Default / Options */}
{state === "idle" && (
<div className="space-y-3">
<button onClick={() => simulateConnect("MetaMask")} className="w-full flex items-center justify-between px-4 py-3 bg-[#FA7F2B] text-white border border-white/10 rounded-lg">
<span>MetaMask</span>
<span className="text-xs text-white/90">Popular</span>
<button onClick={() => simulateConnect("Freighter")} className="w-full flex items-center justify-between px-4 py-3 bg-gradient-to-r from-[#4B0082] to-[#1E90FF] text-white border border-white/10 rounded-lg hover:opacity-90 transition-opacity">
<span>Freighter</span>
<span className="text-xs text-white/90">Recommended</span>
</button>

<button onClick={() => simulateConnect("WalletConnect")} className="w-full flex items-center justify-between px-4 py-3 bg-gradient-to-r from-purple-600 to-purple-700 text-white border border-white/10 rounded-lg">
<span>WalletConnect</span>
<span className="text-xs text-white/90">QR</span>
</button>

<button onClick={() => simulateConnect("Coinbase Wallet")} className="w-full flex items-center justify-between px-4 py-3 bg-[#613485] text-white border border-white/10 rounded-lg">
<span>Coinbase Wallet</span>
<span className="text-xs text-white/90">Hosted</span>
<button onClick={() => simulateConnect("Albedo")} className="w-full flex items-center justify-between px-4 py-3 bg-gradient-to-r from-[#1a1a2e] to-[#16213e] text-white border border-white/10 rounded-lg hover:opacity-90 transition-opacity">
<span>Albedo</span>
<span className="text-xs text-white/90">Secure</span>
</button>
</div>
)}
Expand Down
66 changes: 66 additions & 0 deletions src/components/dashboard/AvailabilityToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use client"
import { useState } from "react"
import { Card } from "@/components/ui/Card"

interface AvailabilityToggleProps {
initialAvailable?: boolean
onChange?: (available: boolean) => void
}

export default function AvailabilityToggle({
initialAvailable = false,
onChange,
}: AvailabilityToggleProps) {
const [isAvailable, setIsAvailable] = useState(initialAvailable)

const handleToggle = () => {
const newState = !isAvailable
setIsAvailable(newState)
onChange?.(newState)
}

return (
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold text-white mb-1">Availability Status</h3>
<p className="text-sm text-slate-400">
{isAvailable
? "You are currently available for sessions"
: "You are currently unavailable"}
</p>
</div>

<button
onClick={handleToggle}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
isAvailable ? "bg-green-500/30" : "bg-slate-700/30"
}`}
aria-label="Toggle availability"
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
isAvailable ? "translate-x-7" : "translate-x-1"
}`}
/>
</button>
</div>

{isAvailable && (
<div className="mt-4 p-3 rounded-lg bg-green-500/10 border border-green-500/20">
<p className="text-sm text-green-400">
✓ Experts can see your profile and book sessions with you
</p>
</div>
)}

{!isAvailable && (
<div className="mt-4 p-3 rounded-lg bg-slate-500/10 border border-slate-500/20">
<p className="text-sm text-slate-400">
No new session requests will be sent to you
</p>
</div>
)}
</Card>
)
}
229 changes: 229 additions & 0 deletions src/components/dashboard/ExpertDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import {
TrendingUp,
Award,
Zap,
Star,
MessageSquare,
Clock,
Users,
} from "lucide-react"
import { Card } from "@/components/ui/Card"
import { Badge } from "@/components/ui/Badge"
import { Button } from "@/components/ui/Button"
import AvailabilityToggle from "./AvailabilityToggle"
import { mockSessions, mockExperts } from "../../../utils/data/mock-data"

export default function ExpertDashboard() {
const router = useRouter()
const [isAvailable, setIsAvailable] = useState(true)

// Get current expert (for demo purposes, using the first expert)
const currentExpert = mockExperts[0]

// Calculate stats
const totalSessions = mockSessions.length
const completedSessions = mockSessions.filter(s => s.status === 'completed').length
const totalEarnings = "$12,750" // Mock data
const stakedAmount = "$5,000" // Mock data
const averageRating = currentExpert.rating

return (
<div className="space-y-8">
{/* Header */}
<div>
<h1 className="text-3xl font-bold text-white mb-2">Expert Dashboard</h1>
<p className="text-slate-400">
Welcome back, {currentExpert.name}! Here's your session and earnings overview.
</p>
</div>

{/* Key Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Total Earnings */}
<Card className="p-6 bg-gradient-to-br from-cyan-500/10 to-cyan-600/5 border-cyan-500/20 hover:border-cyan-500/40 transition-colors">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-slate-400 mb-2">Total Earnings</p>
<h3 className="text-2xl font-bold text-white">{totalEarnings}</h3>
<p className="text-xs text-cyan-400 mt-2">↑ 12% from last month</p>
</div>
<div className="p-3 rounded-lg bg-cyan-500/20">
<TrendingUp className="w-6 h-6 text-cyan-400" />
</div>
</div>
</Card>

{/* Staked Amount */}
<Card className="p-6 bg-gradient-to-br from-purple-500/10 to-purple-600/5 border-purple-500/20 hover:border-purple-500/40 transition-colors">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-slate-400 mb-2">Staked Amount</p>
<h3 className="text-2xl font-bold text-white">{stakedAmount}</h3>
<p className="text-xs text-purple-400 mt-2">Earning rewards</p>
</div>
<div className="p-3 rounded-lg bg-purple-500/20">
<Zap className="w-6 h-6 text-purple-400" />
</div>
</div>
</Card>

{/* Average Rating */}
<Card className="p-6 bg-gradient-to-br from-yellow-500/10 to-yellow-600/5 border-yellow-500/20 hover:border-yellow-500/40 transition-colors">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-slate-400 mb-2">Average Rating</p>
<div className="flex items-baseline gap-2">
<h3 className="text-2xl font-bold text-white">{averageRating}</h3>
<span className="text-sm text-slate-400">/5.0</span>
</div>
<div className="flex items-center gap-1 mt-2">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`w-3 h-3 ${
i < Math.round(averageRating)
? "fill-yellow-400 text-yellow-400"
: "text-slate-600"
}`}
/>
))}
</div>
</div>
<div className="p-3 rounded-lg bg-yellow-500/20">
<Award className="w-6 h-6 text-yellow-400" />
</div>
</div>
</Card>

{/* Total Sessions */}
<Card className="p-6 bg-gradient-to-br from-emerald-500/10 to-emerald-600/5 border-emerald-500/20 hover:border-emerald-500/40 transition-colors">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-slate-400 mb-2">Total Sessions</p>
<h3 className="text-2xl font-bold text-white">{totalSessions}</h3>
<p className="text-xs text-emerald-400 mt-2">
{completedSessions} completed
</p>
</div>
<div className="p-3 rounded-lg bg-emerald-500/20">
<Users className="w-6 h-6 text-emerald-400" />
</div>
</div>
</Card>
</div>

{/* Availability Toggle */}
<AvailabilityToggle
initialAvailable={isAvailable}
onChange={setIsAvailable}
/>

{/* Quick Actions */}
<div>
<h3 className="text-lg font-semibold text-white mb-4">Quick Actions</h3>
<div className="flex flex-wrap gap-3">
<Button
className="bg-cyan-500/20 hover:bg-cyan-500/30 text-cyan-400 border border-cyan-500/30"
onClick={() => router.push("/dashboard/earnings")}
>
<TrendingUp className="w-4 h-4 mr-2" />
View Detailed Earnings
</Button>
<Button
className="bg-purple-500/20 hover:bg-purple-500/30 text-purple-400 border border-purple-500/30"
onClick={() => router.push("/dashboard/learners")}
>
<Users className="w-4 h-4 mr-2" />
View Learners
</Button>
<Button
className="bg-orange-500/20 hover:bg-orange-500/30 text-orange-400 border border-orange-500/30"
onClick={() => router.push("/dashboard/messages")}
>
<MessageSquare className="w-4 h-4 mr-2" />
Messages
</Button>
</div>
</div>

{/* Recent Sessions */}
{mockSessions.length > 0 && (
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-white">Recent Sessions</h3>
<Button
variant="ghost"
className="text-slate-400 hover:text-white"
onClick={() => router.push("/dashboard")}
>
View All →
</Button>
</div>

<div className="space-y-3">
{mockSessions.slice(0, 3).map(session => (
<Card
key={session.id}
className="p-4 hover:border-white/20 transition-colors"
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="font-semibold text-white mb-1">
{session.title}
</h4>
<div className="flex items-center gap-3 flex-wrap text-sm text-slate-400">
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{session.date}
</div>
<div>{session.time}</div>
<Badge className="text-xs capitalize bg-white/5">
{session.status}
</Badge>
</div>
</div>

<div className="text-right">
<p className="font-semibold text-cyan-400">{session.price}</p>
<p className="text-xs text-slate-400 mt-1">
{session.duration}
</p>
</div>
</div>
</Card>
))}
</div>
</div>
)}

{/* Expert Stats Summary */}
<Card className="p-6 bg-gradient-to-br from-white/5 to-white/[0.02] border-white/10">
<h3 className="text-lg font-semibold text-white mb-4">
Performance Summary
</h3>

<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<p className="text-xs text-slate-400 mb-2">Response Time</p>
<p className="text-xl font-bold text-white">5m avg</p>
</div>
<div>
<p className="text-xs text-slate-400 mb-2">Session Completion</p>
<p className="text-xl font-bold text-white">{Math.round((completedSessions / totalSessions) * 100)}%</p>
</div>
Comment on lines +213 to +216
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard against division by zero.

completedSessions / totalSessions yields NaN when totalSessions === 0, rendering "NaN%". This card always renders (unlike Recent Sessions which is length-guarded), so add a fallback.

🛡️ Proposed guard
-            <p className="text-xl font-bold text-white">{Math.round((completedSessions / totalSessions) * 100)}%</p>
+            <p className="text-xl font-bold text-white">{totalSessions > 0 ? Math.round((completedSessions / totalSessions) * 100) : 0}%</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div>
<p className="text-xs text-slate-400 mb-2">Session Completion</p>
<p className="text-xl font-bold text-white">{Math.round((completedSessions / totalSessions) * 100)}%</p>
</div>
<div>
<p className="text-xs text-slate-400 mb-2">Session Completion</p>
<p className="text-xl font-bold text-white">{totalSessions > 0 ? Math.round((completedSessions / totalSessions) * 100) : 0}%</p>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/ExpertDashboard.tsx` around lines 213 - 216, The
Session Completion percentage can produce NaN when totalSessions === 0; inside
the ExpertDashboard component compute a guarded completion value (e.g. derive a
completionRate variable) that checks totalSessions > 0 before doing
Math.round((completedSessions / totalSessions) * 100) and falls back to a safe
value like 0 (or '--') and then render that variable in the JSX instead of the
raw expression; update the JSX block that currently contains
Math.round((completedSessions / totalSessions) * 100) to use the new guarded
completionRate.

<div>
<p className="text-xs text-slate-400 mb-2">Repeat Clients</p>
<p className="text-xl font-bold text-white">12</p>
</div>
<div>
<p className="text-xs text-slate-400 mb-2">Revenue This Month</p>
<p className="text-xl font-bold text-white">$3,200</p>
</div>
</div>
</Card>
</div>
)
}
Loading
Loading