Skip to content

Commit ea617ff

Browse files
author
Miriad
committed
Merge branch 'polish/dashboard-realtime' into dev
2 parents c7941bf + db7cc56 commit ea617ff

File tree

10 files changed

+442
-53
lines changed

10 files changed

+442
-53
lines changed

app/(dashboard)/dashboard/content/page.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const dynamic = "force-dynamic";
22

33
import { dashboardQuery } from "@/lib/sanity/dashboard";
44
import { ContentIdeasTable } from "./content-ideas-table";
5+
import { PageRefreshButton } from "@/components/page-refresh-button";
56

67
interface ContentIdea {
78
_id: string;
@@ -36,13 +37,17 @@ export default async function ContentPage() {
3637

3738
return (
3839
<div className="flex flex-col gap-6">
39-
<div>
40-
<h1 className="text-3xl font-bold tracking-tight">
41-
Content Ideas
42-
</h1>
43-
<p className="text-muted-foreground">
44-
Manage content ideas — approve, reject, or review incoming topics.
45-
</p>
40+
<div className="flex items-center justify-between">
41+
<div>
42+
<h1 className="text-3xl font-bold tracking-tight">
43+
Content Ideas
44+
</h1>
45+
<p className="text-muted-foreground">
46+
Manage content ideas — approve, reject, or review incoming
47+
topics.
48+
</p>
49+
</div>
50+
<PageRefreshButton />
4651
</div>
4752

4853
<ContentIdeasTable ideas={ideas} />

app/(dashboard)/dashboard/page.tsx

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
11
export const dynamic = "force-dynamic";
22

3-
import { SectionCards } from "@/components/section-cards"
3+
import { SectionCardsLive } from "@/components/section-cards-live";
4+
import { RecentActivity } from "@/components/recent-activity";
45

56
export default function DashboardPage() {
6-
return (
7-
<div className="flex flex-col gap-6">
8-
<div>
9-
<h1 className="text-3xl font-bold tracking-tight">
10-
Content Ops Dashboard
11-
</h1>
12-
<p className="text-muted-foreground">
13-
Overview of your automated content engine videos, sponsors, and
14-
pipeline health.
15-
</p>
16-
</div>
7+
return (
8+
<div className="flex flex-col gap-6">
9+
<div>
10+
<h1 className="text-3xl font-bold tracking-tight">
11+
Content Ops Dashboard
12+
</h1>
13+
<p className="text-muted-foreground">
14+
Overview of your automated content engine \u2014 videos, sponsors,
15+
and pipeline health.
16+
</p>
17+
</div>
1718

18-
<SectionCards />
19+
<SectionCardsLive />
1920

20-
<div className="grid gap-4 md:grid-cols-2">
21-
<div className="rounded-lg border p-6">
22-
<h2 className="text-lg font-semibold">Recent Activity</h2>
23-
<p className="mt-2 text-sm text-muted-foreground">
24-
Activity feed will show recent content publications, sponsor
25-
updates, and pipeline events.
26-
</p>
27-
</div>
28-
<div className="rounded-lg border p-6">
29-
<h2 className="text-lg font-semibold">Upcoming Schedule</h2>
30-
<p className="mt-2 text-sm text-muted-foreground">
31-
Cadence calendar showing scheduled content drops and sponsor
32-
deliverables.
33-
</p>
34-
</div>
35-
</div>
36-
</div>
37-
)
21+
<div className="grid gap-4 md:grid-cols-2">
22+
<RecentActivity />
23+
<div className="rounded-lg border p-6">
24+
<h2 className="text-lg font-semibold">Pipeline Status</h2>
25+
<p className="mt-2 text-sm text-muted-foreground">
26+
Real-time view of content moving through the pipeline.
27+
</p>
28+
{/* Pipeline status will be added here */}
29+
</div>
30+
</div>
31+
</div>
32+
);
3833
}

app/(dashboard)/dashboard/sponsors/page.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { dashboardQuery } from "@/lib/sanity/dashboard";
44
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
55
import { SponsorLeadsTable } from "./sponsor-leads-table";
66
import { SponsorPoolTable } from "./sponsor-pool-table";
7+
import { PageRefreshButton } from "@/components/page-refresh-button";
78

89
interface SponsorLead {
910
_id: string;
@@ -74,14 +75,17 @@ export default async function SponsorsPage() {
7475

7576
return (
7677
<div className="flex flex-col gap-6">
77-
<div>
78-
<h1 className="text-3xl font-bold tracking-tight">
79-
Sponsor Pipeline
80-
</h1>
81-
<p className="text-muted-foreground">
82-
Manage sponsor leads, track deals through the pipeline, and browse the
83-
sponsor pool.
84-
</p>
78+
<div className="flex items-center justify-between">
79+
<div>
80+
<h1 className="text-3xl font-bold tracking-tight">
81+
Sponsor Pipeline
82+
</h1>
83+
<p className="text-muted-foreground">
84+
Manage sponsor leads, track deals through the pipeline, and
85+
browse the sponsor pool.
86+
</p>
87+
</div>
88+
<PageRefreshButton />
8589
</div>
8690

8791
<Tabs defaultValue="pipeline">

app/(dashboard)/dashboard/videos/page.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export const dynamic = "force-dynamic";
22

33
import { dashboardQuery } from "@/lib/sanity/dashboard";
44
import { VideosTable } from "./videos-table";
5+
import { PageRefreshButton } from "@/components/page-refresh-button";
56

67
interface AutomatedVideo {
78
_id: string;
@@ -43,13 +44,17 @@ export default async function VideosPage() {
4344

4445
return (
4546
<div className="flex flex-col gap-6">
46-
<div>
47-
<h1 className="text-3xl font-bold tracking-tight">
48-
Automated Videos
49-
</h1>
50-
<p className="text-muted-foreground">
51-
Monitor the video pipeline — from script generation to publishing.
52-
</p>
47+
<div className="flex items-center justify-between">
48+
<div>
49+
<h1 className="text-3xl font-bold tracking-tight">
50+
Automated Videos
51+
</h1>
52+
<p className="text-muted-foreground">
53+
Monitor the video pipeline — from script generation to
54+
publishing.
55+
</p>
56+
</div>
57+
<PageRefreshButton />
5358
</div>
5459

5560
<VideosTable videos={videos} />
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { NextResponse } from "next/server";
2+
import { createClient } from "@/lib/supabase/server";
3+
import { dashboardQuery } from "@/lib/sanity/dashboard";
4+
import type { ActivityItem } from "@/lib/types/dashboard";
5+
6+
export const dynamic = "force-dynamic";
7+
8+
export async function GET() {
9+
const hasSupabase =
10+
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
12+
13+
if (hasSupabase) {
14+
const supabase = await createClient();
15+
const {
16+
data: { user },
17+
} = await supabase.auth.getUser();
18+
if (!user) {
19+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20+
}
21+
}
22+
23+
try {
24+
const items = await dashboardQuery<ActivityItem[]>(`
25+
*[_type in ["contentIdea", "automatedVideo", "sponsorLead"]] | order(_updatedAt desc) [0..9] {
26+
_id,
27+
_type,
28+
_updatedAt,
29+
title,
30+
companyName,
31+
status
32+
}
33+
`);
34+
35+
return NextResponse.json(items ?? []);
36+
} catch (error) {
37+
console.error("Failed to fetch activity:", error);
38+
return NextResponse.json([], { status: 500 });
39+
}
40+
}

app/api/dashboard/metrics/route.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { NextResponse } from "next/server";
2+
import { createClient } from "@/lib/supabase/server";
3+
import { dashboardQuery } from "@/lib/sanity/dashboard";
4+
import type { DashboardMetrics } from "@/lib/types/dashboard";
5+
6+
export const dynamic = "force-dynamic";
7+
8+
export async function GET() {
9+
const hasSupabase =
10+
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
12+
13+
if (hasSupabase) {
14+
const supabase = await createClient();
15+
const {
16+
data: { user },
17+
} = await supabase.auth.getUser();
18+
if (!user) {
19+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
20+
}
21+
}
22+
23+
try {
24+
const [videosPublished, flaggedVideos, newIdeas, sponsorPipeline] =
25+
await Promise.all([
26+
dashboardQuery<number>(
27+
`count(*[_type == "automatedVideo" && status == "published"])`,
28+
),
29+
dashboardQuery<number>(
30+
`count(*[_type == "automatedVideo" && status == "flagged"])`,
31+
),
32+
dashboardQuery<number>(
33+
`count(*[_type == "contentIdea" && status == "new"])`,
34+
),
35+
dashboardQuery<number>(
36+
`count(*[_type == "sponsorLead" && status != "paid"])`,
37+
),
38+
]);
39+
40+
const metrics: DashboardMetrics = {
41+
videosPublished: videosPublished ?? 0,
42+
flaggedForReview: (flaggedVideos ?? 0) + (newIdeas ?? 0),
43+
sponsorPipeline: sponsorPipeline ?? 0,
44+
revenue: null,
45+
};
46+
47+
return NextResponse.json(metrics);
48+
} catch (error) {
49+
console.error("Failed to fetch dashboard metrics:", error);
50+
return NextResponse.json(
51+
{ videosPublished: 0, flaggedForReview: 0, sponsorPipeline: 0, revenue: null },
52+
{ status: 500 },
53+
);
54+
}
55+
}

components/page-refresh-button.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useState } from "react";
5+
import { Button } from "@/components/ui/button";
6+
import { RefreshCw } from "lucide-react";
7+
8+
export function PageRefreshButton() {
9+
const router = useRouter();
10+
const [isRefreshing, setIsRefreshing] = useState(false);
11+
12+
const handleRefresh = () => {
13+
setIsRefreshing(true);
14+
router.refresh();
15+
setTimeout(() => setIsRefreshing(false), 1000);
16+
};
17+
18+
return (
19+
<Button
20+
variant="outline"
21+
size="sm"
22+
className="gap-2"
23+
onClick={handleRefresh}
24+
disabled={isRefreshing}
25+
>
26+
<RefreshCw
27+
className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
28+
/>
29+
Refresh
30+
</Button>
31+
);
32+
}

0 commit comments

Comments
 (0)