[dyad] Added user accounts and video sharing - wrote 9 file(s), added @supabase/auth-ui-react, @supabase/auth-ui-shared package(s), executed 1 SQL queries

This commit is contained in:
[dyad]
2026-01-30 08:35:15 +01:00
parent f131a681ab
commit b3b98070d7
9 changed files with 478 additions and 155 deletions

View File

@@ -0,0 +1,63 @@
import { supabase } from "@/integrations/supabase/client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "lucide-react";
type ClipPageProps = {
params: {
id: string;
};
};
export default async function ClipPage({ params }: ClipPageProps) {
const { data: clip, error } = await supabase
.from('clips')
.select('storage_path, original_file_name')
.eq('short_id', params.id)
.single();
if (error || !clip) {
return (
<div className="flex flex-col items-center justify-center min-h-screen text-center">
<h1 className="text-4xl font-bold mb-4">Clip Not Found</h1>
<p className="text-muted-foreground mb-8">The link may be broken or the clip may have been removed.</p>
<Button asChild>
<Link href="/">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Editor
</Link>
</Button>
</div>
);
}
const { data: video } = supabase.storage.from('clips').getPublicUrl(clip.storage_path);
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background p-4">
<Card className="w-full max-w-3xl">
<CardHeader>
<CardTitle className="truncate">
{clip.original_file_name || "Shared Clip"}
</CardTitle>
</CardHeader>
<CardContent>
<div className="aspect-video w-full overflow-hidden rounded-lg border bg-black">
<video
src={video.publicUrl}
controls
autoPlay
className="w-full h-full object-contain"
/>
</div>
</CardContent>
</Card>
<Button asChild variant="link" className="mt-8">
<Link href="/">
Create your own clip
</Link>
</Button>
</div>
);
}

View File

@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Header } from "@/components/header";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Video Clip Cutter",
description: "Trim and share video clips",
};
export default function RootLayout({
@@ -27,6 +28,7 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Header />
{children}
</body>
</html>

38
src/app/login/page.tsx Normal file
View File

@@ -0,0 +1,38 @@
"use client";
import { supabase } from "@/integrations/supabase/client";
import { Auth } from "@supabase/auth-ui-react";
import { ThemeSupa } from "@supabase/auth-ui-shared";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const router = useRouter();
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (session) {
router.push('/');
}
});
return () => subscription.unsubscribe();
}, [router]);
return (
<div className="flex items-center justify-center min-h-screen bg-background">
<div className="w-full max-w-md p-8 space-y-8">
<div>
<h1 className="text-3xl font-bold text-center">Welcome Back</h1>
<p className="text-center text-muted-foreground">Sign in to continue to the Video Editor</p>
</div>
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
providers={[]}
theme="dark"
/>
</div>
</div>
);
}

View File

@@ -1,6 +1,44 @@
"use client";
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { supabase } from '@/integrations/supabase/client';
import { VideoEditor } from "@/components/video-editor";
import { Loader2 } from 'lucide-react';
export default function Home() {
const router = useRouter();
const [isAuthenticating, setIsAuthenticating] = useState(true);
useEffect(() => {
const checkSession = async () => {
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
router.push('/login');
} else {
setIsAuthenticating(false);
}
};
checkSession();
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_OUT') {
router.push('/login');
}
});
return () => subscription.unsubscribe();
}, [router]);
if (isAuthenticating) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background">
<Loader2 className="w-12 h-12 animate-spin text-primary" />
</div>
);
}
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background p-4 sm:p-6 md:p-8 font-[family-name:var(--font-geist-sans)]">
<main className="w-full max-w-4xl">