Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
050ebae
fix: add loading state and disabled guard to Google OAuth buttons on …
hafiz-ahtasham-ali Feb 23, 2026
68acd8c
feat(dashboard): build habit dashboard UI with donut chart, heatmap, …
hafiz-ahtasham-ali Feb 23, 2026
6b33620
feat(dashboard): add AI processing pipeline section with animated 3-s…
hafiz-ahtasham-ali Feb 23, 2026
63de060
Fix division by zero in HabitHeatmap average calculation when no acti…
hafiz-ahtasham-ali Feb 24, 2026
70a77db
Fix lint errors: refactor DonutChart variable reassignment, suppress …
hafiz-ahtasham-ali Feb 24, 2026
9720d96
Rename "Avg score" and "Best day" labels to "Avg." and "Best" in habi…
hafiz-ahtasham-ali Feb 24, 2026
cd99033
Polish monthly habit heatmap with responsive scaling, streak emphasis…
hafiz-ahtasham-ali Feb 24, 2026
2b85ff5
feat(dashboard): replace structured JSON output with animated live da…
hafiz-ahtasham-ali Feb 24, 2026
7d44e63
feat(features): move AI pipeline demo to features page and remove it …
hafiz-ahtasham-ali Feb 24, 2026
0abf2e8
Improve dashboard-to-logs navigation with contextual links, breadcrum…
hafiz-ahtasham-ali Feb 24, 2026
d48adda
Polish nav UI: better arrow on View All Logs, remove redundant Back t…
hafiz-ahtasham-ali Feb 24, 2026
93cd9bd
Comment out Goals sort toggle and Needs attention summary
hafiz-ahtasham-ali Feb 24, 2026
f2c9dc6
Remove Duration label from AI Extraction concept tags
hafiz-ahtasham-ali Feb 24, 2026
649df64
fix: resolve eslint errors and warnings in dashboard and logs components
hafiz-ahtasham-ali Feb 24, 2026
55ad983
Wrap logs page in Suspense for useSearchParams
hafiz-ahtasham-ali Feb 25, 2026
1bbe58c
Layout: make Month Snapshot card fill horizontal space and match cale…
hafiz-ahtasham-ali Feb 25, 2026
9eec587
Add /stats page with mock analytics, charts, and event inspect modal
hafiz-ahtasham-ali Feb 25, 2026
1056634
Add dual dashboard modes: new compact view and legacy classic view
hafiz-ahtasham-ali Feb 25, 2026
1971e1a
Remove logs page breadcrumb to match statistics navigation
hafiz-ahtasham-ali Feb 25, 2026
8119c04
Remove Account Settings breadcrumb for sidebar-only dashboard navigation
hafiz-ahtasham-ali Feb 25, 2026
0cbf3b2
fix: align dashboard top spacing with logs, stats, and account pages
hafiz-ahtasham-ali Feb 25, 2026
e04d54e
Add Support & Feedback form with Resend integration and premium modal UI
hafiz-ahtasham-ali Feb 25, 2026
4fac801
fix: resolve ESLint issues (useEffect deps, prefer-const)
hafiz-ahtasham-ali Feb 25, 2026
a0fe88f
fix: point next-env.d.ts at .next/dev/types/routes.d.ts for Next 16
hafiz-ahtasham-ali Feb 26, 2026
72cf70e
feat: drive logs, dashboard, and stats from generated test-logs data
hafiz-ahtasham-ali Feb 26, 2026
1cbeb86
fix(LogsPage): resolve lint by deriving URL/view/date in render and k…
hafiz-ahtasham-ali Feb 26, 2026
dd04fe0
Add file attachment to Help & Feedback form
hafiz-ahtasham-ali Feb 26, 2026
cc4240e
Improve Help & Feedback modal UI: layout, inputs, dropzone, and polish
hafiz-ahtasham-ali Feb 26, 2026
a2378ba
fix: Today's Overview always uses today's date and shows empty state …
hafiz-ahtasham-ali Feb 27, 2026
4cae934
Logs Explorer: scrollable panel with dark-themed scrollbar and pagina…
hafiz-ahtasham-ali Feb 27, 2026
781b9d0
Add goals pages and integrate goals navigation into dashboard
hafiz-ahtasham-ali Feb 27, 2026
1d1c9ac
feat: align goals pages UI with dashboard non-compact/logs/stats style
hafiz-ahtasham-ali Feb 27, 2026
0694b69
add optional goal metadata to generated test logs for goals pages
hafiz-ahtasham-ali Feb 27, 2026
0d490d4
replace hardcoded goals pages data with test-logs derived goals data
hafiz-ahtasham-ali Feb 27, 2026
c4f7fa3
Add reusable goals radar chart with test-log-derived data and inferre…
hafiz-ahtasham-ali Feb 27, 2026
a74bcde
Add themed Logs Explorer-style scroll container to goal detail Timeline
hafiz-ahtasham-ali Feb 27, 2026
8158a6f
feat(goals): add log-driven Goal Insights with progress, milestones, …
hafiz-ahtasham-ali Feb 27, 2026
61dd056
add taxonomy-based goal/timeline tags with local persistence and fix …
hafiz-ahtasham-ali Feb 27, 2026
75e84ab
fix: remove unused dashboard imports and align goal tag color typing
hafiz-ahtasham-ali Feb 27, 2026
6129cf6
fix: lazily initialize Resend in support API to prevent CI build fail…
hafiz-ahtasham-ali Feb 27, 2026
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
101 changes: 101 additions & 0 deletions website/app/components/dashboard/AIPipeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import RawInputCard from "./RawInputCard";
import AIProcessingCard from "./AIProcessingCard";
import StructuredOutputCard from "./StructuredOutputCard";

// ─── Arrow between steps (responsive: horizontal on desktop, vertical on mobile)

function FlowArrow() {
return (
<>
{/* Desktop — horizontal */}
<div className="hidden lg:flex items-center flex-shrink-0 px-1 self-center">
<div className="flex items-center gap-0.5 text-slate-700">
<div className="w-6 h-px bg-gradient-to-r from-slate-800 to-slate-700" />
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M9 5l7 7-7 7"
/>
</svg>
</div>
</div>
{/* Mobile — vertical */}
<div className="lg:hidden flex justify-center py-0.5">
<svg
className="w-4 h-4 text-slate-700"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</>
);
}

// ─── AIPipeline ───────────────────────────────────────────────────────────────

export default function AIPipeline() {
return (
<div className="bg-slate-900/60 border border-slate-800/60 rounded-2xl mb-8 animate-fade-in-up">
{/* Header */}
<div className="px-6 py-5 border-b border-slate-800/50 flex items-center justify-between flex-wrap gap-3">
<div>
<h2 className="text-base font-semibold text-white">AI Processing Pipeline</h2>
<p className="text-sm text-slate-400 mt-1">
How raw journal input becomes structured dashboard data
</p>
</div>
<span className="flex items-center gap-1.5 px-3 py-1 rounded-lg bg-violet-500/10 text-violet-400 text-xs font-semibold border border-violet-500/20">
<span className="w-1.5 h-1.5 rounded-full bg-violet-400 animate-pulse" />
Live Demo
</span>
</div>

{/* 3-step pipeline body */}
<div className="p-6 flex flex-col lg:flex-row items-stretch gap-3">
<RawInputCard />
<FlowArrow />
<AIProcessingCard />
<FlowArrow />
<StructuredOutputCard />
</div>

{/* Connection footer — links output to dashboard sections */}
<div className="px-6 py-4 border-t border-slate-800/40 flex items-center justify-between flex-wrap gap-3">
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse flex-shrink-0" />
<span className="text-xs text-slate-400">
This output powers your dashboard below
</span>
</div>
<div className="flex items-center gap-4 text-[11px] text-slate-500 flex-wrap">
<span>
activities{" "}
<span className="text-emerald-400 font-semibold">→</span>{" "}
Activity Logs
</span>
<span className="text-slate-700 select-none">·</span>
<span>
summary{" "}
<span className="text-blue-400 font-semibold">→</span>{" "}
Donut Chart
</span>
</div>
</div>
</div>
);
}
95 changes: 95 additions & 0 deletions website/app/components/dashboard/AIProcessingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client";

import { useEffect, useState } from "react";

// ─── Mock processing steps (cycles on a timer) ────────────────────────────────

const PROCESSING_STEPS = [
"Identifying activity mentions...",
"Classifying by category...",
"Extracting duration metrics...",
"Building structured output...",
];

const CONCEPT_TAGS = [
{ label: "Health", style: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" },
{ label: "Work", style: "bg-blue-500/10 text-blue-400 border-blue-500/20" },
{ label: "Relationships", style: "bg-amber-500/10 text-amber-400 border-amber-500/20" },
{ label: "Duration", style: "bg-violet-500/10 text-violet-400 border-violet-500/20" },
];

// ─── AIProcessingCard ─────────────────────────────────────────────────────────

export default function AIProcessingCard() {
const [step, setStep] = useState(0);

useEffect(() => {
const id = setInterval(() => setStep((s) => (s + 1) % PROCESSING_STEPS.length), 1800);
return () => clearInterval(id);
}, []);

return (
<div className="flex-1 flex flex-col gap-3 animate-fade-in-up" style={{ animationDelay: "0.35s" }}>
{/* Step label */}
<div className="flex items-center gap-2">
<span className="w-5 h-5 rounded-full bg-violet-900/50 border border-violet-700/40 text-[10px] font-bold text-violet-400 flex items-center justify-center flex-shrink-0 select-none">
2
</span>
<span className="text-[11px] font-semibold text-slate-500 uppercase tracking-widest">
AI Extraction
</span>
</div>

{/* Card */}
<div
className="flex-1 bg-violet-950/20 border border-violet-800/25 rounded-2xl p-5
flex flex-col items-center justify-center gap-5"
>
{/* Brain / AI icon */}
<div className="w-11 h-11 rounded-xl bg-violet-500/15 border border-violet-500/25 flex items-center justify-center">
<svg
className="w-5 h-5 text-violet-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 002.25-2.25V6.75a2.25 2.25 0 00-2.25-2.25H6.75A2.25 2.25 0 004.5 6.75v10.5a2.25 2.25 0 002.25 2.25zm.75-12h9v9h-9v-9z"
/>
</svg>
</div>

{/* Pulsing indicator dots */}
<div className="flex items-center gap-2">
{[0, 150, 300].map((delay, i) => (
<div
key={i}
className="w-2.5 h-2.5 rounded-full bg-violet-400 animate-pulse"
style={{ animationDelay: `${delay}ms` }}
/>
))}
</div>

{/* Cycling step message */}
<p className="shimmer-text text-xs font-mono text-center px-2 min-h-[18px]">
{PROCESSING_STEPS[step]}
</p>

{/* Extracted concept tags */}
<div className="flex flex-wrap gap-1.5 justify-center">
{CONCEPT_TAGS.map(({ label, style }) => (
<span
key={label}
className={`text-[10px] font-semibold px-2 py-0.5 rounded-md border ${style}`}
>
{label}
</span>
))}
</div>
</div>
</div>
);
}
81 changes: 81 additions & 0 deletions website/app/components/dashboard/ActivityItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// ─── Shared types & config (used by ActivityLogList + ClassificationPanel) ────

export type LogCategory = "Work" | "Health" | "Relationships";

export interface ActivityLog {
id: string;
time: string; // "HH:MM"
text: string;
category: LogCategory;
}

export const CATEGORY_CONFIG: Record<
LogCategory,
{ badge: string; color: string; highlight: string }
> = {
Work: {
badge: "bg-blue-500/15 text-blue-300",
color: "#3b82f6",
highlight: "bg-blue-500/20 text-blue-200 not-italic",
},
Health: {
badge: "bg-emerald-500/15 text-emerald-300",
color: "#10b981",
highlight: "bg-emerald-500/20 text-emerald-200 not-italic",
},
Relationships: {
badge: "bg-amber-500/15 text-amber-300",
color: "#f59e0b",
highlight: "bg-amber-500/20 text-amber-200 not-italic",
},
};

// ─── ActivityItem ─────────────────────────────────────────────────────────────

interface ActivityItemProps {
log: ActivityLog;
isLast: boolean;
isLatest: boolean;
}

export default function ActivityItem({ log, isLast, isLatest }: ActivityItemProps) {
const config = CATEGORY_CONFIG[log.category];

return (
<div className="flex items-stretch gap-0">
{/* Left col: time + connecting line */}
<div className="flex flex-col items-end w-12 flex-shrink-0">
<span className="text-[11px] font-mono text-slate-500 leading-none pt-0.5">
{log.time}
</span>
{!isLast && (
<div className="flex-1 w-px bg-slate-800/50 mt-1.5 mx-auto" />
)}
</div>

{/* Dot */}
<div className="flex flex-col items-center px-3 flex-shrink-0">
<div
className={`w-2 h-2 rounded-full mt-0.5 flex-shrink-0 transition-transform duration-200 ${
isLatest ? "scale-125" : ""
}`}
style={{
backgroundColor: config.color,
boxShadow: isLatest ? `0 0 6px ${config.color}88` : "none",
}}
/>
{!isLast && <div className="flex-1 w-px bg-slate-800/50 mt-1.5" />}
</div>

{/* Content */}
<div className="flex items-start justify-between gap-3 pb-4 flex-1 min-w-0">
<p className="text-sm text-slate-200 leading-snug pt-0.5">{log.text}</p>
<span
className={`text-xs font-semibold px-2.5 py-0.5 rounded-lg flex-shrink-0 mt-0.5 ${config.badge}`}
>
{log.category}
</span>
</div>
</div>
);
}
43 changes: 43 additions & 0 deletions website/app/components/dashboard/ActivityList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export interface Activity {
id: string;
title: string;
category: "Work" | "Health" | "Relationships";
time: string;
icon: string;
}

interface ActivityListProps {
activities: Activity[];
}

const CATEGORY_STYLES: Record<Activity["category"], string> = {
Work: "bg-blue-500/15 text-blue-300",
Health: "bg-emerald-500/15 text-emerald-300",
Relationships: "bg-amber-500/15 text-amber-300",
};

export default function ActivityList({ activities }: ActivityListProps) {
return (
<div className="space-y-2">
{activities.map((activity) => (
<div
key={activity.id}
className="flex items-center gap-3 px-4 py-3 rounded-xl bg-slate-950/50 border border-slate-800/40
hover:border-slate-700/60 hover:bg-slate-800/30 hover:scale-[1.01] hover:-translate-y-px
transition-all duration-150 cursor-default"
>
<span className="text-base flex-shrink-0 select-none">{activity.icon}</span>
<div className="flex-1 min-w-0">
<p className="text-sm text-slate-100 font-medium truncate">{activity.title}</p>
<p className="text-xs text-slate-500 mt-0.5">{activity.time}</p>
</div>
<span
className={`text-xs font-medium px-2.5 py-0.5 rounded-lg flex-shrink-0 ${CATEGORY_STYLES[activity.category]}`}
>
{activity.category}
</span>
</div>
))}
</div>
);
}
56 changes: 56 additions & 0 deletions website/app/components/dashboard/ActivityLogList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import ActivityItem, { ActivityLog } from "./ActivityItem";

// ─── Mock data ────────────────────────────────────────────────────────────────

export const MOCK_LOGS: ActivityLog[] = [
{ id: "1", time: "07:00", text: "Morning workout at the gym", category: "Health" },
{ id: "2", time: "08:15", text: "Journaled thoughts for 15 minutes", category: "Health" },
{ id: "3", time: "09:30", text: "Team standup meeting", category: "Work" },
{ id: "4", time: "10:00", text: "Deep work on project proposal", category: "Work" },
{ id: "5", time: "12:30", text: "Lunch break — took a walk outside", category: "Health" },
{ id: "6", time: "13:15", text: "Called mum to check in", category: "Relationships" },
{ id: "7", time: "14:00", text: "Project review with the team", category: "Work" },
{ id: "8", time: "17:30", text: "Dinner with family at home", category: "Relationships" },
{ id: "9", time: "19:00", text: "Evening run — 5 km", category: "Health" },
{ id: "10", time: "21:00", text: "Read before bed", category: "Health" },
];

// ─── ActivityLogList ──────────────────────────────────────────────────────────

interface ActivityLogListProps {
logs?: ActivityLog[];
}

export default function ActivityLogList({ logs = MOCK_LOGS }: ActivityLogListProps) {
const categoryCount = logs.reduce<Record<string, number>>((acc, l) => {
acc[l.category] = (acc[l.category] ?? 0) + 1;
return acc;
}, {});

return (
<div>
<div className="flex items-center justify-between mb-5">
<p className="text-[11px] font-semibold text-slate-500 uppercase tracking-widest">Today&apos;s Log</p>
<div className="flex items-center gap-3">
{Object.entries(categoryCount).map(([cat, count]) => (
<span key={cat} className="text-[11px] font-medium text-slate-500">
{count} {cat}
</span>
))}
</div>
</div>

{/* Scrollable timeline */}
<div className="max-h-[420px] overflow-y-auto pr-1 scrollbar-thin">
{logs.map((log, i) => (
<ActivityItem
key={log.id}
log={log}
isLast={i === logs.length - 1}
isLatest={i === logs.length - 1}
/>
))}
</div>
</div>
);
}
Loading
Loading