Compare commits
10 Commits
f05bc7d830
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4970d0117f | ||
|
|
1659519f0a | ||
|
|
000a8455b3 | ||
|
|
35685ebe1e | ||
|
|
094b1aafc0 | ||
|
|
af82499397 | ||
|
|
9f97bf6fd0 | ||
|
|
41e368dce5 | ||
|
|
3a459b73a1 | ||
|
|
7d9f7b7fbf |
@@ -1,6 +1,5 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
webpack: (config) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
config.module.rules.push({
|
||||
@@ -14,4 +13,4 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default nextConfig;
|
||||
@@ -40,11 +40,13 @@
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@supabase/auth-ui-react": "^0.4.7",
|
||||
"@supabase/auth-ui-shared": "^0.1.8",
|
||||
"@supabase/supabase-js": "^2.93.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"geist": "^1.5.1",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "14.2.3",
|
||||
|
||||
1639
pnpm-lock.yaml
generated
1639
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { GeistSans } from "geist/font/sans";
|
||||
import "./globals.css";
|
||||
import { Header } from "@/components/header";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://clips.linxweiler.xyz"),
|
||||
title: "Video Clip Cutter",
|
||||
@@ -25,9 +15,9 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" className="dark">
|
||||
<html lang="en" className={`${GeistSans.variable} dark`}>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className="antialiased"
|
||||
>
|
||||
<Header />
|
||||
{children}
|
||||
|
||||
@@ -7,12 +7,13 @@ import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { KeyRound } from "lucide-react";
|
||||
import type { AuthChangeEvent, Session, Provider } from "@supabase/supabase-js";
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event: AuthChangeEvent, session: Session | null) => {
|
||||
if (session) {
|
||||
router.push('/');
|
||||
}
|
||||
@@ -23,7 +24,7 @@ export default function LoginPage() {
|
||||
|
||||
const handleOidcSignIn = async () => {
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: 'pocket_id',
|
||||
provider: 'pocket_id' as Provider,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -40,7 +41,7 @@ export default function LoginPage() {
|
||||
providers={[]}
|
||||
theme="dark"
|
||||
view="sign_in"
|
||||
showLinks={false}
|
||||
showLinks={true}
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { VideoEditor } from "@/components/video-editor";
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import type { AuthChangeEvent, Session } from '@supabase/supabase-js';
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
@@ -22,7 +23,7 @@ export default function Home() {
|
||||
|
||||
checkSession();
|
||||
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, _session: Session | null) => {
|
||||
if (event === 'SIGNED_OUT') {
|
||||
router.push('/login');
|
||||
}
|
||||
@@ -40,7 +41,7 @@ export default function Home() {
|
||||
}
|
||||
|
||||
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)]">
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-background p-4 sm:p-6 md:p-8">
|
||||
<main className="w-full max-w-4xl">
|
||||
<VideoEditor />
|
||||
</main>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { User as SupabaseUser } from '@supabase/supabase-js';
|
||||
import { User as SupabaseUser, AuthChangeEvent, Session } from '@supabase/supabase-js';
|
||||
import { Button } from './ui/button';
|
||||
import { LogOut, User } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -21,7 +21,7 @@ export function Header() {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event: AuthChangeEvent, session: Session | null) => {
|
||||
setUser(session?.user ?? null);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/componen
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogClose } from '@/components/ui/dialog';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from '@/components/ui/dialog';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from 'sonner';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
@@ -25,6 +25,7 @@ type UserClipsProps = {
|
||||
|
||||
export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProps) {
|
||||
const [clipToEdit, setClipToEdit] = useState<Clip | null>(null);
|
||||
const [clipToDelete, setClipToDelete] = useState<Clip | null>(null);
|
||||
const [newTitle, setNewTitle] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
@@ -74,6 +75,7 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp
|
||||
|
||||
onClipDeleted(clip.id);
|
||||
toast.success('Clip deleted successfully!');
|
||||
setClipToDelete(null);
|
||||
} catch (error) {
|
||||
toast.error('Failed to delete clip.');
|
||||
console.error(error);
|
||||
@@ -112,18 +114,17 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DialogTrigger asChild onClick={() => handleEditClick(clip)}>
|
||||
<DropdownMenuItem>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
<span>Edit Title</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<AlertDialogTrigger asChild>
|
||||
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
<DropdownMenuItem onSelect={() => handleEditClick(clip)}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
<span>Edit Title</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onSelect={() => setClipToDelete(clip)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</CardTitle>
|
||||
@@ -154,25 +155,6 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp
|
||||
{hasCopied === clip.short_id ? 'Copied!' : 'Copy Link'}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<AlertDialog>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your clip and its thumbnail from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => handleDeleteClip(clip)} disabled={isDeleting} className="bg-destructive hover:bg-destructive/90">
|
||||
{isDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
@@ -209,6 +191,25 @@ export function UserClips({ clips, onClipDeleted, onClipUpdated }: UserClipsProp
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<AlertDialog open={!!clipToDelete} onOpenChange={(isOpen) => !isOpen && setClipToDelete(null)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your clip and its thumbnail from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => clipToDelete && handleDeleteClip(clipToDelete)} disabled={isDeleting} className="bg-destructive hover:bg-destructive/90">
|
||||
{isDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,9 @@ export default {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ["var(--font-geist-sans)"],
|
||||
},
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
@@ -91,4 +94,4 @@ export default {
|
||||
}
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config;
|
||||
} satisfies Config;
|
||||
Reference in New Issue
Block a user