Skip to content

Commit 3695f1a

Browse files
committed
Merge remote-tracking branch 'origin/merge/dev-to-main'
2 parents 9f0d069 + 52896e1 commit 3695f1a

File tree

22 files changed

+1120
-170
lines changed

22 files changed

+1120
-170
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ name: CI
22

33
on:
44
push:
5-
branches: [dev]
5+
branches: [dev, main]
66
pull_request:
7-
branches: [dev]
7+
branches: [dev, main]
88

99
concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref }}
1111
cancel-in-progress: true
1212

1313
env:
14-
NODE_VERSION: "20"
14+
NODE_VERSION: "22"
1515

1616
jobs:
1717
ci:
@@ -71,12 +71,7 @@ jobs:
7171
run: pnpm exec playwright test
7272
env:
7373
CI: true
74-
PLAYWRIGHT_BASE_URL: http://localhost:3000
75-
NEXT_PUBLIC_SANITY_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_SANITY_PROJECT_ID }}
76-
NEXT_PUBLIC_SANITY_DATASET: ${{ secrets.NEXT_PUBLIC_SANITY_DATASET }}
77-
NEXT_PUBLIC_SANITY_API_VERSION: ${{ secrets.NEXT_PUBLIC_SANITY_API_VERSION }}
78-
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
79-
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
74+
PLAYWRIGHT_BASE_URL: https://codingcat.dev
8075

8176
- name: Upload Playwright report
8277
uses: actions/upload-artifact@v4

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/settings/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ const INTEGRATIONS = [
6868
description: "AI voice generation for videos",
6969
},
7070
{
71-
name: "OpenAI",
72-
envVar: "OPENAI_API_KEY",
71+
name: "Gemini",
72+
envVar: "GEMINI_API_KEY",
7373
description: "Script generation and content AI",
7474
},
7575
{

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+
}

app/api/sponsorship/route.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { Resend } from "resend";
66
import { EmailTemplate } from "./sponsorship-template";
77
import { formSchema } from "@/lib/sponsorship-schema";
88
import { render } from "@react-email/render";
9+
import { extractSponsorIntent } from "@/lib/sponsor/gemini-intent";
10+
import { sanityWriteClient as pipelineClient } from "@/lib/sanity-write-client";
911

1012
const sanityWriteClient = createClient({
1113
projectId,
@@ -112,6 +114,30 @@ export async function POST(request: Request) {
112114
);
113115
}
114116

117+
// Also create a sponsorLead for the automated pipeline
118+
try {
119+
const intent = await extractSponsorIntent(
120+
`Company: ${companyName || "Unknown"}\nFrom: ${fullName} (${email})\nTiers: ${sponsorshipTier.join(", ")}\n${message || ""}`,
121+
);
122+
123+
await pipelineClient.create({
124+
_type: "sponsorLead",
125+
companyName: intent.companyName || companyName || "Unknown",
126+
contactName: intent.contactName || fullName,
127+
contactEmail: email,
128+
source: "inbound",
129+
status: "new",
130+
intent: intent.intent,
131+
rateCard: sponsorshipTier.join(", "),
132+
threadId: crypto.randomUUID(),
133+
lastEmailAt: new Date().toISOString(),
134+
});
135+
console.log("[SPONSOR] Created sponsorLead from form submission");
136+
} catch (error) {
137+
// Don't fail the form submission if pipeline creation fails
138+
console.error("[SPONSOR] Failed to create sponsorLead from form:", error);
139+
}
140+
115141
try {
116142
const resendApiKey = process.env.RESEND_SPONSORSHIP_API_KEY;
117143
if (resendApiKey) {

0 commit comments

Comments
 (0)