[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:
63
src/app/clips/[id]/page.tsx
Normal file
63
src/app/clips/[id]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
38
src/app/login/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user