diff --git a/e2e/dashboard-widgets.spec.js b/e2e/dashboard-widgets.spec.js
index 8612d2e3..7ba431bc 100644
--- a/e2e/dashboard-widgets.spec.js
+++ b/e2e/dashboard-widgets.spec.js
@@ -179,6 +179,27 @@ test.beforeEach(async ({ page }) => {
body: "data: {}\n\n",
});
});
+
+ await page.route("**/api/user/dashboard-layout**", async (route) => {
+ if (route.request().method() === "GET") {
+ await route.fulfill({
+ contentType: "application/json",
+ body: JSON.stringify({ layout: null }),
+ });
+ } else {
+ await route.fulfill({
+ contentType: "application/json",
+ body: JSON.stringify({ ok: true }),
+ });
+ }
+ });
+
+ await page.route("**/api/daily-note**", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ body: JSON.stringify({ note: null }),
+ });
+ });
});
test("dashboard widgets render with mocked metrics", async ({ page }) => {
await page.goto("/dashboard", { waitUntil: "load" });
@@ -329,7 +350,7 @@ function mockMetricResponse(url) {
};
}
if (url.includes("/api/streak/freeze")) {
- return { freezes: [] };
+ return { hasFreeze: false, freezeDate: null };
}
if (url.includes("/api/integrations/jira")) {
return null;
diff --git a/e2e/notifications.spec.js b/e2e/notifications.spec.js
index be4320a0..a92717b4 100644
--- a/e2e/notifications.spec.js
+++ b/e2e/notifications.spec.js
@@ -153,6 +153,10 @@ test.beforeEach(async ({ page }) => {
});
});
+ await page.route("**/api/notifications**", async (route) => {
+ await route.fulfill({ contentType: "application/json", body: JSON.stringify({ notifications: [], unreadCount: 0 }) });
+ });
+
await page.route("**/api/user/github-accounts", async (route) => {
await route.fulfill({
contentType: "application/json",
@@ -255,6 +259,10 @@ test.beforeEach(async ({ page }) => {
});
}
+ await page.route("**/api/streak/freeze**", async (route) => {
+ await route.fulfill({ contentType: "application/json", body: JSON.stringify({ hasFreeze: false, freezeDate: null }) });
+ });
+
await page.route("**/api/stream**", async (route) => {
await route.fulfill({
status: 200,
@@ -262,6 +270,18 @@ test.beforeEach(async ({ page }) => {
body: "data: {}\n\n",
});
});
+
+ await page.route("**/api/user/dashboard-layout**", async (route) => {
+ if (route.request().method() === "GET") {
+ await route.fulfill({ contentType: "application/json", body: JSON.stringify({ layout: null }) });
+ } else {
+ await route.fulfill({ contentType: "application/json", body: JSON.stringify({ ok: true }) });
+ }
+ });
+
+ await page.route("**/api/daily-note**", async (route) => {
+ await route.fulfill({ contentType: "application/json", body: JSON.stringify({ note: null }) });
+ });
});
test("notification bell opens and closes drawer", async ({ page }) => {
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 3c7a6a7d..3ee7eec7 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -21,19 +21,19 @@ export default async function DashboardPage() {
{/* Quick actions */}
-
+
{/* Left side actions */}
Year in Code
Settings
@@ -44,32 +44,35 @@ export default async function DashboardPage() {
-
+ {/* Info Banners */}
+
-
+ {/* Today Focus Section */}
+
-
-
-
+ {/* Featured Section */}
+
+
+
-
+
New Feature
-
+
AI Resume Generator
-
+
Generate an ATS-Friendly CV Backed by Your Real Code
-
+
Analyze your GitHub contributions, merged PRs, and lines of code
changed to automatically generate professional bullet points for
your target roles.
@@ -78,10 +81,10 @@ export default async function DashboardPage() {
Build Resume
-
+
diff --git a/src/components/dashboard/CustomizableDashboard.tsx b/src/components/dashboard/CustomizableDashboard.tsx
index 95353860..01fd4003 100644
--- a/src/components/dashboard/CustomizableDashboard.tsx
+++ b/src/components/dashboard/CustomizableDashboard.tsx
@@ -204,17 +204,17 @@ const SECTION_ANCHOR_IDS: Record
= {
};
const SECTION_ACCENT_CLASSES: Record = {
- overview: "bg-[var(--accent)] shadow-[0_0_15px_var(--accent)]",
- activity: "bg-emerald-500 shadow-[0_0_15px_rgba(16,185,129,0.5)]",
- analytics: "bg-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.5)]",
- goals: "bg-purple-500 shadow-[0_0_15px_rgba(168,85,247,0.5)]",
+ overview: "h-1 bg-gradient-to-r from-[var(--accent)] to-[var(--accent)]/60 rounded-full shadow-md",
+ activity: "h-1 bg-gradient-to-r from-emerald-500 to-emerald-500/60 rounded-full shadow-md",
+ analytics: "h-1 bg-gradient-to-r from-blue-500 to-blue-500/60 rounded-full shadow-md",
+ goals: "h-1 bg-gradient-to-r from-purple-500 to-purple-500/60 rounded-full shadow-md",
};
const SECTION_GRID_CLASSES: Record = {
- overview: "grid grid-cols-1 xl:grid-cols-2 gap-6 w-full",
- activity: "grid grid-cols-1 xl:grid-cols-3 gap-6 w-full",
- analytics: "grid grid-cols-1 lg:grid-cols-2 gap-6 w-full",
- goals: "grid grid-cols-1 xl:grid-cols-3 gap-6 w-full",
+ overview: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6 w-full",
+ activity: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full",
+ analytics: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6 w-full",
+ goals: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full",
};
const WIDGET_SPAN_CLASSES: Partial> = {
@@ -584,7 +584,7 @@ export default function CustomizableDashboard() {
};
return (
-
+
-
+
-
- {DASHBOARD_SECTION_LABELS[sectionId]}
-
+
+
+ {DASHBOARD_SECTION_LABELS[sectionId]}
+
+
+ {sectionId === "overview" && "Quick summary of your development profile"}
+ {sectionId === "activity" && "Your coding patterns and contributions"}
+ {sectionId === "analytics" && "In-depth analysis of your repositories and code"}
+ {sectionId === "goals" && "Track progress, milestones, and insights"}
+
+
-
+
{sectionWidgets.map((widgetId) => (
-
-
-
- Dashboard Layout
+
+
+
+
+
+ Customize Your Dashboard
-
- Reorder widgets, hide unused cards, and reset the dashboard anytime.
+
+ Reorder widgets by dragging, hide cards you don't need, and reset to default anytime.
-
+
{isEditing ? (
- Reset
+ Reset Layout
) : null}
onEditingChange(!isEditing)}
- className="inline-flex items-center gap-2 rounded-lg border border-[var(--accent)] bg-[var(--accent)]/10 px-4 py-2 text-sm font-semibold text-[var(--accent)] transition hover:bg-[var(--accent)]/20 focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
+ className="inline-flex items-center gap-2 rounded-lg border border-[var(--accent)] bg-[var(--accent)]/10 px-4 py-2.5 text-sm font-semibold text-[var(--accent)] transition-all hover:bg-[var(--accent)]/20 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)] active:scale-95"
>
{isEditing ? (
<>
- Done
+ Done Editing
>
) : (
<>
@@ -66,19 +67,21 @@ export default function DashboardLayoutToolbar({
{isEditing ? (
-
-
- Hidden widgets
+
+
+
+ Hidden Widgets ({hiddenWidgets.length})
{hiddenWidgets.length > 0 ? (
-
+
{hiddenWidgets.map((widgetId) => (
onShowWidget(widgetId)}
- className="inline-flex items-center gap-2 rounded-full border border-[var(--border)] bg-[var(--control)] px-3 py-1.5 text-xs font-medium text-[var(--foreground)] transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
+ className="inline-flex items-center gap-2 rounded-full border border-[var(--border)] bg-[var(--card)]/70 px-3 py-2 text-xs font-medium text-[var(--foreground)] transition-all hover:bg-[var(--accent)]/20 hover:border-[var(--accent)]/50 hover:shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)] active:scale-95"
+ title={`Click to show ${DASHBOARD_WIDGET_LABELS[widgetId]}`}
>
Show {DASHBOARD_WIDGET_LABELS[widgetId]}
@@ -86,8 +89,8 @@ export default function DashboardLayoutToolbar({
))}
) : (
-
- No widgets are hidden.
+
+ ✓ All widgets are visible. No hidden widgets to restore.
)}
diff --git a/src/components/dashboard/SortableDashboardWidget.tsx b/src/components/dashboard/SortableDashboardWidget.tsx
index 8f4acb31..56bb74e4 100644
--- a/src/components/dashboard/SortableDashboardWidget.tsx
+++ b/src/components/dashboard/SortableDashboardWidget.tsx
@@ -46,16 +46,16 @@ export default function SortableDashboardWidget({
ref={setNodeRef}
style={style}
className={`relative min-w-0 ${className} ${
- isDragging ? "opacity-70" : ""
- }`}
+ isDragging ? "opacity-60 scale-95" : ""
+ } transition-all duration-150`}
>
{isEditing ? (
-
+
@@ -67,7 +67,7 @@ export default function SortableDashboardWidget({
aria-label={`Hide widget: ${title}`}
title={`Hide ${title}`}
onClick={() => onHide(id)}
- className="rounded-lg border border-[var(--border)] bg-[var(--card)]/95 p-2 text-[var(--muted-foreground)] shadow-sm backdrop-blur transition hover:text-red-400 focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
+ className="rounded-lg border border-[var(--border)] bg-[var(--card)]/95 p-2 text-[var(--muted-foreground)] shadow-sm backdrop-blur transition-all hover:text-red-500 hover:shadow-md hover:border-red-500/50 focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
>
@@ -77,8 +77,8 @@ export default function SortableDashboardWidget({
{children}