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
387 changes: 317 additions & 70 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@upstash/redis": "^1.38.0",
"@vercel/analytics": "^2.0.1",
"@vercel/speed-insights": "^2.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"dompurify": "^3.1.6",
Expand All @@ -36,7 +37,8 @@
"recharts": "^2.12.7",
"rehype-sanitize": "^6.0.0",
"server-only": "^0.0.1",
"sonner": "^2.0.7"
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0"
},
"devDependencies": {
"@playwright/test": "1.60.0",
Expand Down
29 changes: 18 additions & 11 deletions src/components/CodingActivityInsightsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
} from "recharts";
import { Skeleton } from "@/components/Skeleton";
import { useAccount } from "@/components/AccountContext";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
formatHourRange,
type CodingActivityInsight,
Expand Down Expand Up @@ -233,20 +236,18 @@ export default function CodingActivityInsightsCard() {
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-1">
<div className="mb-4 flex items-start justify-between gap-3">
<div>
<h2 className="text-lg font-semibold text-[var(--card-foreground)]">
Coding Activity Insights
</h2>
<CardTitle>Coding Activity Insights</CardTitle>
{data?.weeklyTrend ? (
<div className="mt-2">
<div className="mt-2 mb-1">
<TrendBadge
direction={data.weeklyTrend.direction}
percentage={data.weeklyTrend.percentage}
/>
</div>
) : null}
<p className="mt-1 text-sm text-[var(--muted-foreground)]">
<CardDescription className="mt-1">
{subtitle}
</p>
</CardDescription>
</div>

<button aria-label="Refresh"
Expand Down Expand Up @@ -274,6 +275,10 @@ export default function CodingActivityInsightsCard() {
className="space-y-4"
>
<span className="sr-only">Loading coding activity insights</span>
<Skeleton className="h-[260px] rounded-lg" />
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3].map((item) => (
<Skeleton key={item} aria-hidden="true" className="h-16 rounded-lg" />
<Skeleton className="h-[260px] w-full rounded-lg" />
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3].map((item) => (
Expand All @@ -284,13 +289,14 @@ export default function CodingActivityInsightsCard() {
) : error ? (
<div className="rounded-lg border border-[var(--destructive)]/20 bg-[var(--destructive)]/10 p-4 text-sm text-[var(--destructive)]">
<p>{error}</p>
<button
type="button"
<Button
variant="outline"
size="sm"
onClick={fetchInsights}
className="mt-3 rounded-md border border-[var(--destructive)]/30 px-3 py-1.5 text-xs font-medium text-[var(--destructive)] transition-colors hover:bg-[var(--destructive)]/10"
className="mt-3 border-[var(--destructive)]/30 text-[var(--destructive)] hover:bg-[var(--destructive)]/10"
>
Try again
</button>
</Button>
</div>
) : !hasData ? (
<div className="flex min-h-[320px] items-center justify-center rounded-lg border border-dashed border-[var(--border)] bg-[var(--card-muted)] px-4 text-center">
Expand Down Expand Up @@ -399,6 +405,7 @@ export default function CodingActivityInsightsCard() {
)}
</div>
)}
</div>
</CardContent>
</Card>
);
}
119 changes: 75 additions & 44 deletions src/components/ProjectMetrics.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/Skeleton";

interface ProjectData {
Expand Down Expand Up @@ -120,6 +124,27 @@ export default function ProjectMetrics() {

if (loading) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle>Project Tracking</CardTitle>
<Skeleton className="h-4 w-16" />
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
{[1, 2, 3, 4].map((i) => (
<Skeleton key={i} className="h-[88px] w-full rounded-lg" />
))}
</div>
<div>
<Skeleton className="h-4 w-24 mb-3" />
<div className="space-y-2">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-[60px] w-full rounded-lg" />
))}
</div>
</div>
</CardContent>
</Card>
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-[var(--card-foreground)]">
Expand Down Expand Up @@ -157,17 +182,18 @@ export default function ProjectMetrics() {
className="rounded-md bg-[var(--accent)] px-3 py-1.5 text-sm font-medium text-[var(--accent-foreground)] transition-colors hover:opacity-90"
>
Connect Jira
</button>
</div>
<div className="rounded-lg border border-dashed border-[var(--border)] p-8 text-center">
<p className="text-[var(--muted-foreground)] mb-3">
Connect Jira to track issues alongside your code activity
</p>
<p className="text-xs text-[var(--muted-foreground)]">
See your issue status, velocity, and time to close
</p>
</div>
{showForm && (
</Button>
</CardHeader>
<CardContent>
<div className="rounded-lg border border-dashed border-[var(--border)] p-8 text-center mt-4">
<p className="text-[var(--muted-foreground)] mb-3">
Connect Jira to track issues alongside your code activity
</p>
<p className="text-xs text-[var(--muted-foreground)]">
See your issue status, velocity, and time to close
</p>
</div>
{showForm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-[var(--card)] rounded-xl p-6 w-full max-w-md border border-[var(--border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--card-foreground)]">
Expand Down Expand Up @@ -246,20 +272,21 @@ export default function ProjectMetrics() {
className="flex-1 rounded-md bg-[var(--accent)] px-3 py-2 text-sm font-medium text-[var(--accent-foreground)] transition-all hover:opacity-90 disabled:opacity-50 active:scale-95"
>
{connecting ? "Connecting..." : "Connect"}
</button>
<button
</Button>
<Button
type="button"
variant="outline"
onClick={() => setShowForm(false)}
className="rounded-md border border-[var(--border)] px-3 py-2 text-sm font-medium text-[var(--foreground)] transition-colors hover:bg-[var(--control)]"
>
Cancel
</button>
</Button>
</div>
</form>
</div>
</div>
)}
</div>
</CardContent>
</Card>
);
}

Expand All @@ -276,20 +303,21 @@ export default function ProjectMetrics() {
className="rounded-md bg-[var(--accent)] px-3 py-1.5 text-sm font-medium text-[var(--accent-foreground)] transition-colors hover:opacity-90"
>
Connect Jira
</button>
</div>
<div className="rounded-lg border border-[var(--destructive)]/20 bg-[var(--destructive)]/10 p-4 text-sm text-[var(--destructive)]">
<p>{error}</p>
<button
type="button"
onClick={fetchData}

className="mt-3 rounded-md border border-[var(--destructive)]/30 px-3 py-1.5 text-xs font-medium text-[var(--destructive)] transition-colors hover:bg-[var(--destructive)]/10"
>
Try again
</button>
</div>
{showForm && (
</Button>
</CardHeader>
<CardContent>
<div className="rounded-lg border border-[var(--destructive)]/20 bg-[var(--destructive)]/10 p-4 text-sm text-[var(--destructive)] mt-4">
<p>{error}</p>
<Button
variant="outline"
size="sm"
onClick={fetchData}
className="mt-3 border-[var(--destructive)]/30 text-[var(--destructive)] hover:bg-[var(--destructive)]/10"
>
Try again
</Button>
</div>
{showForm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-[var(--card)] rounded-xl p-6 w-full max-w-md border border-[var(--border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--card-foreground)]">
Expand Down Expand Up @@ -368,20 +396,21 @@ export default function ProjectMetrics() {
className="flex-1 rounded-md bg-[var(--accent)] px-3 py-2 text-sm font-medium text-[var(--accent-foreground)] transition-all hover:opacity-90 disabled:opacity-50 active:scale-95"
>
{connecting ? "Connecting..." : "Connect"}
</button>
<button
</Button>
<Button
type="button"
variant="outline"
onClick={() => setShowForm(false)}
className="rounded-md border border-[var(--border)] px-3 py-2 text-sm font-medium text-[var(--foreground)] transition-colors hover:bg-[var(--control)]"
>
Cancel
</button>
</Button>
</div>
</form>
</div>
</div>
)}
</div>
</CardContent>
</Card>
);
}

Expand All @@ -404,7 +433,7 @@ export default function ProjectMetrics() {
<button
type="button"
onClick={handleDisconnect}
className="text-xs text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
className="text-xs text-[var(--muted-foreground)]"
>
Disconnect
</button>
Expand Down Expand Up @@ -443,22 +472,24 @@ export default function ProjectMetrics() {
{issue.summary}
</p>
</div>
<span
className={`ml-3 text-xs font-medium ${getStatusColor(
<Badge
variant={
issue.statusCategory === "done"
? "Done"
? "success"
: issue.statusCategory === "indeterminate"
? "In Progress"
: "To Do"
)}`}
? "secondary"
: "outline"
}
className="ml-3 font-medium"
>
{issue.status}
</span>
</Badge>
</div>
))}
</div>
</div>
)}
</div>
</CardContent>
</Card>
);
}
28 changes: 18 additions & 10 deletions src/components/WeeklySummaryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import { ChevronDown } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { useAccount } from "@/components/AccountContext";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

interface WeeklySummaryData {
commits: {
Expand Down Expand Up @@ -75,16 +78,16 @@ const maxActiveDays = summary?.activeDays
<button
type="button"
onClick={() => setIsCollapsed((value) => !value)}
className="text-sm text-[var(--muted-foreground)] transition-colors hover:text-[var(--card-foreground)]"
className="text-sm text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
aria-expanded={!isCollapsed}
aria-label={
isCollapsed ? "Expand weekly summary" : "Collapse weekly summary"
}
suppressHydrationWarning
>
<ChevronDown className="h-4 w-4" />
</button>
</div>
{isCollapsed ? ">" : "v"}
</Button>
</CardHeader>

{!isCollapsed &&
(loading ? (
Expand All @@ -104,10 +107,14 @@ const maxActiveDays = summary?.activeDays
))}
</div>
) : error ? (
<div className="mt-4 rounded-lg border border-[var(--destructive)]/20 bg-[var(--destructive)]/10 p-4 text-sm text-[var(--destructive)]">
{error}
</div>
<div className="mt-4 space-y-4">
<CardContent>
<div className="mt-4 rounded-lg border border-[var(--destructive)]/20 bg-[var(--destructive)]/10 p-4 text-sm text-[var(--destructive)]">
{error}
</div>
</CardContent>
) : summary && summary.commits && summary.prs && summary.activeDays ? (
<CardContent>
<div className="space-y-4 pt-2">
{/* Commits Comparison */}
<div className="rounded-lg bg-[var(--control)] p-4 stat-cell">
<div className="mb-3 flex items-center justify-between">
Expand Down Expand Up @@ -250,8 +257,9 @@ const maxActiveDays = summary?.activeDays
</span>
</div>
</div>
</div>
</div>
</CardContent>
) : null)}
</div>
</Card>
);
}
37 changes: 37 additions & 0 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--ring)] focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-[var(--accent)] text-[var(--accent-foreground)] shadow hover:opacity-80",
secondary:
"border-transparent bg-[var(--card-muted)] text-[var(--foreground)] hover:opacity-80",
destructive:
"border-transparent bg-[var(--destructive)] text-white shadow hover:opacity-80",
outline: "text-[var(--foreground)]",
success:
"border-transparent bg-[var(--success)]/10 text-[var(--success)]",
},
},
defaultVariants: {
variant: "default",
},
}
);

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}

export { Badge, badgeVariants };
Loading
Loading