Skip to content

Commit 4b6d546

Browse files
committed
Merge branch 'dev'
2 parents 527ff14 + 11448a9 commit 4b6d546

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5473
-2300
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,11 @@ next-env.d.ts
5151
# Firebase debug files
5252
firebase-debug.log
5353
firebase-debug.*.logpackage-lock.json
54+
55+
# Migration tool generated files
56+
scripts/migration/discovered-references.json
57+
scripts/migration/unique-cloudinary-urls.json
58+
scripts/migration/asset-mapping.json
59+
scripts/migration/migration-report.json
60+
scripts/migration/node_modules/
61+
scripts/migration/.env
Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,92 @@
1-
import { redirect } from "next/navigation";
2-
import { createClient } from "@/lib/supabase/server";
1+
"use client";
2+
33
import {
44
Card,
55
CardContent,
66
CardDescription,
77
CardHeader,
88
CardTitle,
99
} from "@/components/ui/card";
10-
import { Input } from "@/components/ui/input";
11-
import { Label } from "@/components/ui/label";
1210
import { Button } from "@/components/ui/button";
11+
import { createClient } from "@/lib/supabase/client";
12+
import { useSearchParams } from "next/navigation";
13+
import { Suspense } from "react";
1314

14-
export default async function LoginPage(props: {
15-
searchParams: Promise<{ error?: string }>;
16-
}) {
17-
const { error } = await props.searchParams;
18-
19-
async function signIn(formData: FormData) {
20-
"use server";
15+
function GoogleIcon() {
16+
return (
17+
<svg className="h-5 w-5" viewBox="0 0 24 24">
18+
<path
19+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
20+
fill="#4285F4"
21+
/>
22+
<path
23+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
24+
fill="#34A853"
25+
/>
26+
<path
27+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
28+
fill="#FBBC05"
29+
/>
30+
<path
31+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
32+
fill="#EA4335"
33+
/>
34+
</svg>
35+
);
36+
}
2137

22-
const email = formData.get("email") as string;
23-
const password = formData.get("password") as string;
38+
function LoginForm() {
39+
const searchParams = useSearchParams();
40+
const error = searchParams.get("error");
2441

25-
const supabase = await createClient();
26-
const { error } = await supabase.auth.signInWithPassword({
27-
email,
28-
password,
42+
const handleGoogleSignIn = async () => {
43+
const supabase = createClient();
44+
await supabase.auth.signInWithOAuth({
45+
provider: "google",
46+
options: {
47+
redirectTo: `${window.location.origin}/dashboard/auth/callback`,
48+
},
2949
});
30-
31-
if (error) {
32-
redirect(`/dashboard/login?error=${encodeURIComponent(error.message)}`);
33-
}
34-
35-
redirect("/dashboard");
36-
}
50+
};
3751

3852
return (
3953
<div className="flex min-h-screen items-center justify-center bg-background">
4054
<Card className="w-full max-w-sm">
41-
<CardHeader>
55+
<CardHeader className="text-center">
4256
<CardTitle className="text-2xl">CodingCat.dev</CardTitle>
4357
<CardDescription>
4458
Sign in to the Content Ops Dashboard
4559
</CardDescription>
4660
</CardHeader>
4761
<CardContent>
48-
<form action={signIn} className="flex flex-col gap-4">
62+
<div className="flex flex-col gap-4">
4963
{error && (
5064
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
5165
{error}
5266
</div>
5367
)}
54-
<div className="flex flex-col gap-2">
55-
<Label htmlFor="email">Email</Label>
56-
<Input
57-
id="email"
58-
name="email"
59-
type="email"
60-
placeholder="admin@codingcat.dev"
61-
required
62-
/>
63-
</div>
64-
<div className="flex flex-col gap-2">
65-
<Label htmlFor="password">Password</Label>
66-
<Input
67-
id="password"
68-
name="password"
69-
type="password"
70-
required
71-
/>
72-
</div>
73-
<Button type="submit" className="w-full">
74-
Sign In
68+
<Button
69+
variant="outline"
70+
className="w-full gap-2"
71+
onClick={handleGoogleSignIn}
72+
>
73+
<GoogleIcon />
74+
Sign in with Google
7575
</Button>
76-
</form>
76+
<p className="text-center text-xs text-muted-foreground">
77+
Access restricted to authorized accounts only.
78+
</p>
79+
</div>
7780
</CardContent>
7881
</Card>
7982
</div>
8083
);
8184
}
85+
86+
export default function LoginPage() {
87+
return (
88+
<Suspense>
89+
<LoginForm />
90+
</Suspense>
91+
);
92+
}

app/(dashboard)/layout.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ export default async function DashboardLayout({
3333
}: {
3434
children: React.ReactNode;
3535
}) {
36+
// Try to get user — if Supabase isn't configured or user isn't logged in,
37+
// the proxy will have already redirected to login for protected routes.
38+
// The login page itself renders without the sidebar chrome.
39+
let user = null;
40+
try {
41+
const supabaseUrl =
42+
process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL;
43+
const supabaseAnonKey =
44+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY;
45+
46+
if (supabaseUrl && supabaseAnonKey) {
47+
const { createClient } = await import("@/lib/supabase/server");
48+
const supabase = await createClient();
49+
const { data } = await supabase.auth.getUser();
50+
user = data?.user ?? null;
51+
}
52+
} catch {
53+
// Supabase not available — continue without user
54+
}
55+
3656
return (
3757
<html lang="en" suppressHydrationWarning>
3858
<body
@@ -48,15 +68,19 @@ export default async function DashboardLayout({
4868
enableSystem
4969
disableTransitionOnChange
5070
>
51-
<SidebarProvider>
52-
<AppSidebar />
53-
<SidebarInset>
54-
<SiteHeader />
55-
<main className="flex flex-1 flex-col gap-4 p-4 md:p-6">
56-
{children}
57-
</main>
58-
</SidebarInset>
59-
</SidebarProvider>
71+
{user ? (
72+
<SidebarProvider>
73+
<AppSidebar user={user} />
74+
<SidebarInset>
75+
<SiteHeader />
76+
<main className="flex flex-1 flex-col gap-4 p-4 md:p-6">
77+
{children}
78+
</main>
79+
</SidebarInset>
80+
</SidebarProvider>
81+
) : (
82+
<>{children}</>
83+
)}
6084
<Toaster />
6185
</ThemeProvider>
6286
</body>

app/(main)/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as demo from "@/sanity/lib/demo";
1313
import { sanityFetch } from "@/sanity/lib/live";
1414
import { settingsQuery } from "@/sanity/lib/queries";
1515
import { cn } from "@/lib/utils";
16+
import { resolveOpenGraphImage } from "@/sanity/lib/utils";
1617
import { ThemeProvider } from "@/components/theme-provider";
1718
import Link from "next/link";
1819
import { Button } from "@/components/ui/button";
@@ -56,8 +57,7 @@ export async function generateMetadata(): Promise<Metadata> {
5657
const title = settings?.title || demo.title;
5758
const description = settings?.description || demo.description;
5859

59-
// const ogImage = resolveOpenGraphImage(settings?.ogImage);
60-
const ogImage = settings?.ogImage?.secure_url;
60+
const ogImage = resolveOpenGraphImage(settings?.ogImage);
6161
return {
6262
title: {
6363
template: `%s | ${title}`,

app/api/cron/ingest/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function buildPrompt(trends: TrendResult[], research?: ResearchPayload): string
152152
}
153153
}
154154

155-
if (research.infographicPath) {
155+
if (research.infographicUrl) {
156156
researchContext += `\n### Infographic Available\nAn infographic has been generated for this topic. Use sceneType "narration" with bRollUrl pointing to the infographic for at least one scene.\n`;
157157
}
158158
}

app/api/dashboard/activity/route.ts

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

88
export async function GET() {
99
const hasSupabase =
10-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
10+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
11+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1212

1313
if (!hasSupabase) {
1414
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/metrics/route.ts

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

88
export async function GET() {
99
const hasSupabase =
10-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
11-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
10+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
11+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1212

1313
if (!hasSupabase) {
1414
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/pipeline/route.ts

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

77
export async function GET() {
88
const hasSupabase =
9-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
10-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
9+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
10+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
1111

1212
if (!hasSupabase) {
1313
return NextResponse.json({ error: "Auth not configured" }, { status: 503 });

app/api/dashboard/settings/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ const DEFAULT_SETTINGS = {
2323

2424
async function requireAuth() {
2525
const hasSupabase =
26-
process.env.NEXT_PUBLIC_SUPABASE_URL &&
27-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
26+
(process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL) &&
27+
(process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY);
2828

2929
if (!hasSupabase) {
3030
return { error: NextResponse.json({ error: "Auth not configured" }, { status: 503 }) };

app/api/devto/route.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { podcastQuery, postQuery } from "@/sanity/lib/queries";
33
import { isValidSignature, SIGNATURE_HEADER_NAME } from "@sanity/webhook";
44
import toMarkdown from "@sanity/block-content-to-markdown";
55
import { createClient } from "next-sanity";
6+
import { urlForImage } from "@/sanity/lib/utils";
67

78
const secret = process.env.PRIVATE_SYNDICATE_WEBOOK_SECRET;
89
import { apiVersion, dataset, projectId, studioUrl } from "@/sanity/lib/api";
@@ -81,10 +82,7 @@ const formatPodcast = async (_type: string, slug: string) => {
8182
title: podcast.title,
8283
published: true,
8384
tags: ["webdev", "javascript", "beginners"],
84-
main_image: podcast?.coverImage?.secure_url?.replace(
85-
"upload/",
86-
"upload/b_rgb:5e1186,c_pad,w_1000,h_420/",
87-
),
85+
main_image: urlForImage(podcast?.coverImage)?.width(1000).height(420).url() || "",
8886
canonical_url: `https://codingcat.dev/${podcast._type}/${podcast.slug}`,
8987
description: podcast?.excerpt || "",
9088
organization_id: "1009",
@@ -239,7 +237,12 @@ const serializers = {
239237
types: {
240238
code: (props: any) =>
241239
"```" + props?.node?.language + "\n" + props?.node?.code + "\n```",
242-
"cloudinary.asset": (props: any) => `![](${props?.node?.secure_url})`,
240+
image: (props: any) => {
241+
const url = props?.node?.asset?._ref
242+
? urlForImage(props.node)?.url()
243+
: "";
244+
return `![](${url})`;
245+
},
243246
codepen: (props: any) => `{% codepen ${props?.node?.url} %}`,
244247
codesandbox: (props: any) =>
245248
`{% codesandbox ${props?.node?.url?.split("https://codesandbox.io/p/sandbox/")?.at(-1)} %}`,

0 commit comments

Comments
 (0)