From fe020be6cea00691d6ce66e21843915defa5e944 Mon Sep 17 00:00:00 2001 From: Hyteq Date: Sun, 20 Jul 2025 10:50:17 +0300 Subject: Revert "format, cleanup, deps" This reverts commit eab8ec3ba81b86fdd4ade920d999c28fb34785b1. --- apps/web/src/app/(auth)/login/page.tsx | 248 +-- apps/web/src/app/(auth)/signup/page.tsx | 282 +-- apps/web/src/app/api/auth/[...all]/route.ts | 8 +- apps/web/src/app/api/health/route.ts | 2 +- apps/web/src/app/api/waitlist/route.ts | 188 +- apps/web/src/app/api/waitlist/token/route.ts | 87 +- apps/web/src/app/contributors/page.tsx | 554 ++--- apps/web/src/app/editor/[project_id]/page.tsx | 254 +-- apps/web/src/app/globals.css | 158 +- apps/web/src/app/layout.tsx | 66 +- apps/web/src/app/metadata.ts | 158 +- apps/web/src/app/page.tsx | 14 +- apps/web/src/app/privacy/page.tsx | 506 ++--- apps/web/src/app/projects/page.tsx | 1086 +++++----- apps/web/src/app/roadmap/page.tsx | 494 ++--- apps/web/src/app/terms/page.tsx | 634 +++--- apps/web/src/app/why-not-capcut/page.tsx | 382 ++-- apps/web/src/components/background-settings.tsx | 368 ++-- apps/web/src/components/delete-project-dialog.tsx | 118 +- apps/web/src/components/editor-header.tsx | 136 +- apps/web/src/components/editor-provider.tsx | 76 +- apps/web/src/components/editor/audio-waveform.tsx | 196 +- .../components/editor/media-panel/drag-overlay.tsx | 114 +- .../src/components/editor/media-panel/index.tsx | 110 +- .../web/src/components/editor/media-panel/store.ts | 146 +- .../src/components/editor/media-panel/tabbar.tsx | 248 +-- .../components/editor/media-panel/views/media.tsx | 596 +++--- .../components/editor/media-panel/views/text.tsx | 106 +- apps/web/src/components/editor/preview-panel.tsx | 936 ++++----- .../editor/properties-panel/audio-properties.tsx | 10 +- .../components/editor/properties-panel/index.tsx | 174 +- .../editor/properties-panel/media-properties.tsx | 10 +- .../editor/properties-panel/property-item.tsx | 94 +- .../editor/properties-panel/text-properties.tsx | 152 +- apps/web/src/components/editor/selection-box.tsx | 116 +- apps/web/src/components/editor/snap-indicator.tsx | 76 +- apps/web/src/components/editor/speed-control.tsx | 72 +- .../timeline-canvas-ruler-wrapper.tsx | 24 +- .../timeline-canvas/timeline-canvas-ruler.tsx | 112 +- .../web/src/components/editor/timeline-element.tsx | 940 ++++----- .../src/components/editor/timeline-playhead.tsx | 224 +- apps/web/src/components/editor/timeline-track.tsx | 2194 +++++++++---------- apps/web/src/components/editor/timeline.tsx | 1897 +++++++++-------- apps/web/src/components/footer.tsx | 246 +-- apps/web/src/components/header-base.tsx | 82 +- apps/web/src/components/header.tsx | 140 +- apps/web/src/components/icons.tsx | 201 +- .../web/src/components/keyboard-shortcuts-help.tsx | 210 +- apps/web/src/components/landing/handlebars.tsx | 308 +-- apps/web/src/components/landing/hero.tsx | 336 +-- apps/web/src/components/onboarding.tsx | 58 +- apps/web/src/components/rename-project-dialog.tsx | 146 +- apps/web/src/components/storage-provider.tsx | 160 +- apps/web/src/components/ui/accordion.tsx | 62 +- apps/web/src/components/ui/alert-dialog.tsx | 170 +- apps/web/src/components/ui/alert.tsx | 70 +- apps/web/src/components/ui/aspect-ratio.tsx | 8 +- apps/web/src/components/ui/audio-player.tsx | 254 +-- apps/web/src/components/ui/avatar.tsx | 54 +- apps/web/src/components/ui/badge.tsx | 44 +- apps/web/src/components/ui/breadcrumb.tsx | 144 +- apps/web/src/components/ui/button.tsx | 83 +- apps/web/src/components/ui/calendar.tsx | 110 +- apps/web/src/components/ui/card.tsx | 91 +- apps/web/src/components/ui/carousel.tsx | 384 ++-- apps/web/src/components/ui/chart.tsx | 626 +++--- apps/web/src/components/ui/checkbox.tsx | 32 +- apps/web/src/components/ui/collapsible.tsx | 12 +- apps/web/src/components/ui/command.tsx | 186 +- apps/web/src/components/ui/context-menu.tsx | 288 +-- apps/web/src/components/ui/dialog.tsx | 168 +- apps/web/src/components/ui/draggable-item.tsx | 350 ++-- apps/web/src/components/ui/drawer.tsx | 136 +- apps/web/src/components/ui/dropdown-menu.tsx | 318 +-- apps/web/src/components/ui/font-picker.tsx | 80 +- apps/web/src/components/ui/form.tsx | 234 +-- apps/web/src/components/ui/hover-card.tsx | 24 +- .../src/components/ui/image-timeline-treatment.tsx | 204 +- apps/web/src/components/ui/input-otp.tsx | 90 +- apps/web/src/components/ui/input.tsx | 82 +- apps/web/src/components/ui/label.tsx | 18 +- apps/web/src/components/ui/menubar.tsx | 322 +-- apps/web/src/components/ui/navigation-menu.tsx | 164 +- apps/web/src/components/ui/pagination.tsx | 144 +- apps/web/src/components/ui/phone-input.tsx | 334 +-- apps/web/src/components/ui/popover.tsx | 28 +- apps/web/src/components/ui/progress.tsx | 30 +- apps/web/src/components/ui/radio-group.tsx | 50 +- apps/web/src/components/ui/resizable.tsx | 40 +- apps/web/src/components/ui/scroll-area.tsx | 60 +- apps/web/src/components/ui/select.tsx | 246 +-- apps/web/src/components/ui/separator.tsx | 36 +- apps/web/src/components/ui/sheet.tsx | 172 +- apps/web/src/components/ui/sidebar.tsx | 1230 ++++++----- apps/web/src/components/ui/skeleton.tsx | 16 +- apps/web/src/components/ui/slider.tsx | 30 +- apps/web/src/components/ui/sonner.tsx | 50 +- apps/web/src/components/ui/sponsor-button.tsx | 57 +- apps/web/src/components/ui/switch.tsx | 32 +- apps/web/src/components/ui/table.tsx | 148 +- apps/web/src/components/ui/tabs.tsx | 60 +- apps/web/src/components/ui/textarea.tsx | 24 +- apps/web/src/components/ui/toast.tsx | 158 +- apps/web/src/components/ui/toaster.tsx | 52 +- apps/web/src/components/ui/toggle-group.tsx | 70 +- apps/web/src/components/ui/toggle.tsx | 54 +- apps/web/src/components/ui/tooltip.tsx | 26 +- apps/web/src/components/ui/video-player.tsx | 240 +-- apps/web/src/constants/font-constants.ts | 158 +- apps/web/src/constants/timeline-constants.ts | 120 +- apps/web/src/data/colors.ts | 488 ++--- apps/web/src/env.ts | 38 +- apps/web/src/hooks/auth/useLogin.ts | 108 +- apps/web/src/hooks/auth/useSignUp.ts | 102 +- apps/web/src/hooks/use-aspect-ratio.ts | 184 +- apps/web/src/hooks/use-disable-browser-zoom.ts | 88 +- apps/web/src/hooks/use-drag-drop.ts | 170 +- apps/web/src/hooks/use-keyboard-shortcuts.ts | 676 +++--- apps/web/src/hooks/use-mobile.tsx | 28 +- apps/web/src/hooks/use-playback-controls.ts | 204 +- apps/web/src/hooks/use-selection-box.ts | 398 ++-- apps/web/src/hooks/use-timeline-element-resize.ts | 308 +-- apps/web/src/hooks/use-timeline-playhead.ts | 250 +-- apps/web/src/hooks/use-timeline-snapping.ts | 324 +-- apps/web/src/hooks/use-timeline-zoom.ts | 186 +- apps/web/src/hooks/use-toast.ts | 278 +-- apps/web/src/lib/fetch-github-stars.ts | 48 +- apps/web/src/lib/ffmpeg-utils.ts | 455 ++-- apps/web/src/lib/font-config.ts | 78 +- apps/web/src/lib/media-processing.ts | 162 +- apps/web/src/lib/rate-limit.ts | 32 +- apps/web/src/lib/storage/indexeddb-adapter.ts | 178 +- apps/web/src/lib/storage/opfs-adapter.ts | 114 +- apps/web/src/lib/storage/storage-service.ts | 530 ++--- apps/web/src/lib/storage/types.ts | 54 +- apps/web/src/lib/time.ts | 50 +- apps/web/src/lib/utils.ts | 68 +- apps/web/src/lib/waitlist.ts | 26 +- apps/web/src/middleware.ts | 96 +- apps/web/src/stores/editor-store.ts | 210 +- apps/web/src/stores/media-store.ts | 508 +++-- apps/web/src/stores/panel-store.ts | 90 +- apps/web/src/stores/playback-store.ts | 236 +-- apps/web/src/stores/project-store.ts | 722 +++---- apps/web/src/stores/timeline-store.ts | 2216 ++++++++++---------- apps/web/src/types/editor.ts | 24 +- apps/web/src/types/playback.ts | 32 +- apps/web/src/types/project.ts | 20 +- apps/web/src/types/timeline.ts | 200 +- 149 files changed, 17304 insertions(+), 17401 deletions(-) (limited to 'apps/web/src') diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx index 170be83f..f28cd191 100644 --- a/apps/web/src/app/(auth)/login/page.tsx +++ b/apps/web/src/app/(auth)/login/page.tsx @@ -3,11 +3,11 @@ import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { memo, Suspense } from "react"; import { Input } from "@/components/ui/input"; @@ -20,127 +20,127 @@ import { GoogleIcon } from "@/components/icons"; import { useLogin } from "@/hooks/auth/useLogin"; const LoginPage = () => { - const router = useRouter(); - const { - email, - setEmail, - password, - setPassword, - error, - isAnyLoading, - isEmailLoading, - isGoogleLoading, - handleLogin, - handleGoogleLogin, - } = useLogin(); + const router = useRouter(); + const { + email, + setEmail, + password, + setPassword, + error, + isAnyLoading, + isEmailLoading, + isGoogleLoading, + handleLogin, + handleGoogleLogin, + } = useLogin(); - return ( -
- - - - Welcome back - - Sign in to your account to continue - - - - - -
- } - > -
- {error && ( - - Error - {error} - - )} + return ( +
+ + + + Welcome back + + Sign in to your account to continue + + + + + +
+ } + > +
+ {error && ( + + Error + {error} + + )} - -
-
- -
-
- - Or continue with - -
-
-
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
-
- Don't have an account?{" "} - - Sign up - -
- - - -
- ); + +
+
+ +
+
+ + Or continue with + +
+
+
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+ +
+ +
+ Don't have an account?{" "} + + Sign up + +
+ + + + + ); }; export default memo(LoginPage); diff --git a/apps/web/src/app/(auth)/signup/page.tsx b/apps/web/src/app/(auth)/signup/page.tsx index a3cd1faf..c3a0f899 100644 --- a/apps/web/src/app/(auth)/signup/page.tsx +++ b/apps/web/src/app/(auth)/signup/page.tsx @@ -3,11 +3,11 @@ import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { memo, Suspense } from "react"; import { Input } from "@/components/ui/input"; @@ -20,143 +20,143 @@ import { GoogleIcon } from "@/components/icons"; import { useSignUp } from "@/hooks/auth/useSignUp"; const SignUpPage = () => { - const router = useRouter(); - const { - name, - setName, - email, - setEmail, - password, - setPassword, - error, - isAnyLoading, - isEmailLoading, - isGoogleLoading, - handleSignUp, - handleGoogleSignUp, - } = useSignUp(); + const router = useRouter(); + const { + name, + setName, + email, + setEmail, + password, + setPassword, + error, + isAnyLoading, + isEmailLoading, + isGoogleLoading, + handleSignUp, + handleGoogleSignUp, + } = useSignUp(); - return ( -
- - - - - Create your account - - - Get started with your free account today - - - - - -
- } - > -
- {error && ( - - Error - {error} - - )} - -
-
- -
-
- - Or continue with - -
-
-
-
- - setName(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
-
- Already have an account?{" "} - - Sign in - -
- - - - - ); + return ( +
+ + + + + Create your account + + + Get started with your free account today + + + + + +
+ } + > +
+ {error && ( + + Error + {error} + + )} + +
+
+ +
+
+ + Or continue with + +
+
+
+
+ + setName(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+ +
+
+
+ Already have an account?{" "} + + Sign in + +
+ + + + + ); }; export default memo(SignUpPage); diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts index 5deef204..55510dcb 100644 --- a/apps/web/src/app/api/auth/[...all]/route.ts +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@opencut/auth"; -import { toNextJsHandler } from "better-auth/next-js"; - -export const { POST, GET } = toNextJsHandler(auth); +import { auth } from "@opencut/auth"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { POST, GET } = toNextJsHandler(auth); \ No newline at end of file diff --git a/apps/web/src/app/api/health/route.ts b/apps/web/src/app/api/health/route.ts index f8c951fe..ecb9ce04 100644 --- a/apps/web/src/app/api/health/route.ts +++ b/apps/web/src/app/api/health/route.ts @@ -1,5 +1,5 @@ import { NextRequest } from "next/server"; export async function GET(request: NextRequest) { - return new Response("OK", { status: 200 }); + return new Response("OK", { status: 200 }); } diff --git a/apps/web/src/app/api/waitlist/route.ts b/apps/web/src/app/api/waitlist/route.ts index 98232e49..e5c9fd94 100644 --- a/apps/web/src/app/api/waitlist/route.ts +++ b/apps/web/src/app/api/waitlist/route.ts @@ -1,105 +1,83 @@ -import { NextRequest, NextResponse } from "next/server"; -import { db, eq, waitlist } from "@opencut/db"; -import { checkBotId } from "botid/server"; -import { nanoid } from "nanoid"; -import { waitlistRateLimit } from "@/lib/rate-limit"; -import { z } from "zod"; -import { env } from "@/env"; -import { cookies } from "next/headers"; -import crypto from "crypto"; - -const waitlistSchema = z.object({ - email: z.string().email("Invalid email format").min(1, "Email is required"), -}); - -const CSRF_TOKEN_NAME = "waitlist-csrf"; -const TOKEN_EXPIRY = 60 * 60 * 1000; - -async function validateCSRFToken(request: NextRequest): Promise { - const clientToken = request.headers.get("x-csrf-token"); - if (!clientToken) return false; - - const cookieStore = await cookies(); - const cookieValue = cookieStore.get(CSRF_TOKEN_NAME)?.value; - if (!cookieValue) return false; - - const [token, timestamp, signature] = cookieValue.split(":"); - if (!token || !timestamp || !signature) return false; - - if (clientToken !== token) return false; - - const now = Date.now(); - const tokenTime = parseInt(timestamp); - if (now - tokenTime > TOKEN_EXPIRY) return false; - - const expectedSignature = crypto - .createHmac("sha256", env.BETTER_AUTH_SECRET) - .update(`${token}:${timestamp}`) - .digest("hex"); - - return signature === expectedSignature; -} - -export async function POST(request: NextRequest) { - const verification = await checkBotId(); - - if (verification.isBot) { - return NextResponse.json({ error: "Access denied" }, { status: 403 }); - } - - const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; - const { success } = await waitlistRateLimit.limit(identifier); - - if (!success) { - return NextResponse.json( - { error: "Too many requests. Please try again later." }, - { status: 429 }, - ); - } - const isValidToken = await validateCSRFToken(request); - if (!isValidToken) { - return NextResponse.json( - { error: "Invalid security token" }, - { status: 403 }, - ); - } - - try { - const body = await request.json(); - const { email } = waitlistSchema.parse(body); - - const existingEmail = await db - .select() - .from(waitlist) - .where(eq(waitlist.email, email.toLowerCase())) - .limit(1); - - if (existingEmail.length > 0) { - return NextResponse.json( - { error: "Email already registered" }, - { status: 409 }, - ); - } - - await db.insert(waitlist).values({ - id: nanoid(), - email: email.toLowerCase(), - }); - - return NextResponse.json( - { message: "Successfully joined waitlist!" }, - { status: 201 }, - ); - } catch (error) { - if (error instanceof z.ZodError) { - const firstError = error.errors[0]; - return NextResponse.json({ error: firstError.message }, { status: 400 }); - } - - console.error("Waitlist signup error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 }, - ); - } -} +import { NextRequest, NextResponse } from "next/server"; +import { db, eq, waitlist } from "@opencut/db"; +import { checkBotId } from "botid/server"; +import { nanoid } from "nanoid"; +import { waitlistRateLimit } from "@/lib/rate-limit"; +import { z } from "zod"; +import { env } from "@/env"; +import { cookies } from "next/headers"; +import crypto from "crypto"; + +const waitlistSchema = z.object({ + email: z.string().email("Invalid email format").min(1, "Email is required"), +}); + +const CSRF_TOKEN_NAME = "waitlist-csrf"; +const TOKEN_EXPIRY = 60 * 60 * 1000; + +async function validateCSRFToken(request: NextRequest): Promise { + const clientToken = request.headers.get("x-csrf-token"); + if (!clientToken) return false; + + const cookieStore = await cookies(); + const cookieValue = cookieStore.get(CSRF_TOKEN_NAME)?.value; + if (!cookieValue) return false; + + const [token, timestamp, signature] = cookieValue.split(":"); + if (!token || !timestamp || !signature) return false; + + if (clientToken !== token) return false; + + const now = Date.now(); + const tokenTime = parseInt(timestamp); + if (now - tokenTime > TOKEN_EXPIRY) return false; + + const expectedSignature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex"); + + return signature === expectedSignature; +} + +export async function POST(request: NextRequest) { + const verification = await checkBotId(); + + if (verification.isBot) { + return NextResponse.json({ error: "Access denied" }, { status: 403 }); + } + + const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; + const { success } = await waitlistRateLimit.limit(identifier); + + if (!success) { + return NextResponse.json({ error: "Too many requests. Please try again later." }, { status: 429 }); + } + const isValidToken = await validateCSRFToken(request); + if (!isValidToken) { + return NextResponse.json({ error: "Invalid security token" }, { status: 403 }); + } + + try { + const body = await request.json(); + const { email } = waitlistSchema.parse(body); + + const existingEmail = await db.select().from(waitlist).where(eq(waitlist.email, email.toLowerCase())).limit(1); + + if (existingEmail.length > 0) { + return NextResponse.json({ error: "Email already registered" }, { status: 409 }); + } + + await db.insert(waitlist).values({ + id: nanoid(), + email: email.toLowerCase(), + }); + + return NextResponse.json({ message: "Successfully joined waitlist!" }, { status: 201 }); + } catch (error) { + if (error instanceof z.ZodError) { + const firstError = error.errors[0]; + return NextResponse.json({ error: firstError.message }, { status: 400 }); + } + + console.error("Waitlist signup error:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/apps/web/src/app/api/waitlist/token/route.ts b/apps/web/src/app/api/waitlist/token/route.ts index 6f5ee18f..4c7695fa 100644 --- a/apps/web/src/app/api/waitlist/token/route.ts +++ b/apps/web/src/app/api/waitlist/token/route.ts @@ -5,57 +5,42 @@ import { env } from "@/env"; const CSRF_TOKEN_NAME = "waitlist-csrf"; const TOKEN_EXPIRY = 60 * 60 * 1000; -const allowedHosts = - env.NODE_ENV === "development" - ? ["localhost:3000", "127.0.0.1:3000"] - : ["opencut.app", "www.opencut.app"]; +const allowedHosts = env.NODE_ENV === "development" ? ["localhost:3000", "127.0.0.1:3000"] : ["opencut.app", "www.opencut.app"]; export async function GET(request: NextRequest) { - const referer = request.headers.get("referer"); - const host = request.headers.get("host"); - - if (referer) { - const refererUrl = new URL(referer); - - if ( - !allowedHosts.some( - (allowed) => - refererUrl.host === allowed || refererUrl.host.endsWith(allowed), - ) - ) { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - } else if (host) { - if ( - !allowedHosts.some( - (allowed) => host === allowed || host.endsWith(allowed), - ) - ) { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - } else { - return NextResponse.json({ error: "Forbidden" }, { status: 403 }); - } - - if (!env.BETTER_AUTH_SECRET) { - throw new Error("BETTER_AUTH_SECRET must be configured"); - } - - const token = crypto.randomBytes(32).toString("hex"); - const timestamp = Date.now(); - const signature = crypto - .createHmac("sha256", env.BETTER_AUTH_SECRET) - .update(`${token}:${timestamp}`) - .digest("hex"); - - const cookieStore = await cookies(); - cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, { - httpOnly: true, - secure: env.NODE_ENV === "production", - sameSite: "strict", - maxAge: TOKEN_EXPIRY / 1000, - path: "/", - }); - - return NextResponse.json({ token }); + const referer = request.headers.get("referer"); + const host = request.headers.get("host"); + + if (referer) { + const refererUrl = new URL(referer); + + if (!allowedHosts.some((allowed) => refererUrl.host === allowed || refererUrl.host.endsWith(allowed))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + } else if (host) { + if (!allowedHosts.some((allowed) => host === allowed || host.endsWith(allowed))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + } else { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + if (!env.BETTER_AUTH_SECRET) { + throw new Error("BETTER_AUTH_SECRET must be configured"); + } + + const token = crypto.randomBytes(32).toString("hex"); + const timestamp = Date.now(); + const signature = crypto.createHmac("sha256", env.BETTER_AUTH_SECRET).update(`${token}:${timestamp}`).digest("hex"); + + const cookieStore = await cookies(); + cookieStore.set(CSRF_TOKEN_NAME, `${token}:${timestamp}:${signature}`, { + httpOnly: true, + secure: env.NODE_ENV === "production", + sameSite: "strict", + maxAge: TOKEN_EXPIRY / 1000, + path: "/", + }); + + return NextResponse.json({ token }); } diff --git a/apps/web/src/app/contributors/page.tsx b/apps/web/src/app/contributors/page.tsx index 2c602e25..5613f1f9 100644 --- a/apps/web/src/app/contributors/page.tsx +++ b/apps/web/src/app/contributors/page.tsx @@ -1,277 +1,277 @@ -import { Metadata } from "next"; -import { Header } from "@/components/header"; -import { Card, CardContent } from "@/components/ui/card"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Button } from "@/components/ui/button"; -import { ExternalLink } from "lucide-react"; -import Link from "next/link"; -import { GithubIcon } from "@/components/icons"; -import { Badge } from "@/components/ui/badge"; - -export const metadata: Metadata = { - title: "Contributors - OpenCut", - description: - "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", - openGraph: { - title: "Contributors - OpenCut", - description: - "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", - type: "website", - }, -}; - -interface Contributor { - id: number; - login: string; - avatar_url: string; - html_url: string; - contributions: number; - type: string; -} - -async function getContributors(): Promise { - try { - const response = await fetch( - "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100", - { - headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "OpenCut-Web-App", - }, - next: { revalidate: 600 }, // 10 minutes - }, - ); - - if (!response.ok) { - console.error("Failed to fetch contributors"); - return []; - } - - const contributors = (await response.json()) as Contributor[]; - - const filteredContributors = contributors.filter( - (contributor: Contributor) => contributor.type === "User", - ); - - return filteredContributors; - } catch (error) { - console.error("Error fetching contributors:", error); - return []; - } -} - -export default async function ContributorsPage() { - const contributors = await getContributors(); - const topContributors = contributors.slice(0, 2); - const otherContributors = contributors.slice(2); - - return ( -
-
- -
-
-
-
-
- -
-
-
- - - - Open Source - - -

- Contributors -

-

- Meet the amazing developers who are building the future of video - editing -

- -
-
-
- {contributors.length} - contributors -
-
-
- - {contributors.reduce((sum, c) => sum + c.contributions, 0)} - - contributions -
-
-
- - {topContributors.length > 0 && ( -
-
-

- Top Contributors -

-

- Leading the way in contributions -

-
- -
- {topContributors.map((contributor, index) => ( - -
-
- - -
- - - - {contributor.login.charAt(0).toUpperCase()} - - -
-

- {contributor.login} -

-
- - {contributor.contributions} - - contributions -
-
-
-
- - ))} -
-
- )} - - {otherContributors.length > 0 && ( -
-
-

- All Contributors -

-

- Everyone who makes OpenCut better -

-
- -
- {otherContributors.map((contributor, index) => ( - -
- - - - {contributor.login.charAt(0).toUpperCase()} - - -

- {contributor.login} -

-

- {contributor.contributions} -

-
- - ))} -
-
- )} - - {contributors.length === 0 && ( -
-
- -
-

- No contributors found -

-

- Unable to load contributors at the moment. Check back later or - view on GitHub. -

- - - -
- )} - -
-
-

Join the community

-

- OpenCut is built by developers like you. Every contribution, - no matter how small, helps make video editing more accessible - for everyone. -

- -
- - - - - - -
-
-
-
-
-
-
- ); -} +import { Metadata } from "next"; +import { Header } from "@/components/header"; +import { Card, CardContent } from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { ExternalLink } from "lucide-react"; +import Link from "next/link"; +import { GithubIcon } from "@/components/icons"; +import { Badge } from "@/components/ui/badge"; + +export const metadata: Metadata = { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + openGraph: { + title: "Contributors - OpenCut", + description: + "Meet the amazing people who contribute to OpenCut, the free and open-source video editor.", + type: "website", + }, +}; + +interface Contributor { + id: number; + login: string; + avatar_url: string; + html_url: string; + contributions: number; + type: string; +} + +async function getContributors(): Promise { + try { + const response = await fetch( + "https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100", + { + headers: { + Accept: "application/vnd.github.v3+json", + "User-Agent": "OpenCut-Web-App", + }, + next: { revalidate: 600 }, // 10 minutes + } + ); + + if (!response.ok) { + console.error("Failed to fetch contributors"); + return []; + } + + const contributors = (await response.json()) as Contributor[]; + + const filteredContributors = contributors.filter( + (contributor: Contributor) => contributor.type === "User" + ); + + return filteredContributors; + } catch (error) { + console.error("Error fetching contributors:", error); + return []; + } +} + +export default async function ContributorsPage() { + const contributors = await getContributors(); + const topContributors = contributors.slice(0, 2); + const otherContributors = contributors.slice(2); + + return ( +
+
+ +
+
+
+
+
+ +
+
+
+ + + + Open Source + + +

+ Contributors +

+

+ Meet the amazing developers who are building the future of video + editing +

+ +
+
+
+ {contributors.length} + contributors +
+
+
+ + {contributors.reduce((sum, c) => sum + c.contributions, 0)} + + contributions +
+
+
+ + {topContributors.length > 0 && ( +
+
+

+ Top Contributors +

+

+ Leading the way in contributions +

+
+ +
+ {topContributors.map((contributor, index) => ( + +
+
+ + +
+ + + + {contributor.login.charAt(0).toUpperCase()} + + +
+

+ {contributor.login} +

+
+ + {contributor.contributions} + + contributions +
+
+
+
+ + ))} +
+
+ )} + + {otherContributors.length > 0 && ( +
+
+

+ All Contributors +

+

+ Everyone who makes OpenCut better +

+
+ +
+ {otherContributors.map((contributor, index) => ( + +
+ + + + {contributor.login.charAt(0).toUpperCase()} + + +

+ {contributor.login} +

+

+ {contributor.contributions} +

+
+ + ))} +
+
+ )} + + {contributors.length === 0 && ( +
+
+ +
+

+ No contributors found +

+

+ Unable to load contributors at the moment. Check back later or + view on GitHub. +

+ + + +
+ )} + +
+
+

Join the community

+

+ OpenCut is built by developers like you. Every contribution, + no matter how small, helps make video editing more accessible + for everyone. +

+ +
+ + + + + + +
+
+
+
+
+
+
+ ); +} diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index b4afa355..4be4d9cc 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -3,9 +3,9 @@ import { useEffect, useRef, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, + ResizablePanelGroup, + ResizablePanel, + ResizableHandle, } from "../../../components/ui/resizable"; import { MediaPanel } from "../../../components/editor/media-panel"; import { PropertiesPanel } from "../../../components/editor/properties-panel"; @@ -20,128 +20,128 @@ import { useDisableBrowserZoom } from "@/hooks/use-disable-browser-zoom"; import { Onboarding } from "@/components/onboarding"; export default function Editor() { - const { - toolsPanel, - previewPanel, - mainContent, - timeline, - setToolsPanel, - setPreviewPanel, - setMainContent, - setTimeline, - propertiesPanel, - setPropertiesPanel, - } = usePanelStore(); - - const { activeProject, loadProject, createNewProject } = useProjectStore(); - const params = useParams(); - const router = useRouter(); - const projectId = params.project_id as string; - const handledProjectIds = useRef>(new Set()); - const [isOnboardingOpen, setIsOnboardingOpen] = useState(true); - - useDisableBrowserZoom(); - usePlaybackControls(); - - useEffect(() => { - const initProject = async () => { - if (!projectId) return; - - if (activeProject?.id === projectId) { - return; - } - - if (handledProjectIds.current.has(projectId)) { - return; - } - - try { - await loadProject(projectId); - } catch (error) { - handledProjectIds.current.add(projectId); - - const newProjectId = await createNewProject("Untitled Project"); - router.replace(`/editor/${newProjectId}`); - return; - } - }; - - initProject(); - }, [projectId, activeProject?.id, loadProject, createNewProject, router]); - - return ( - -
- -
- - - {/* Main content area */} - - {/* Tools Panel */} - - - - - - - {/* Preview Area */} - - - - - - - - - - - - - - - {/* Timeline */} - - - - -
-
-
- ); + const { + toolsPanel, + previewPanel, + mainContent, + timeline, + setToolsPanel, + setPreviewPanel, + setMainContent, + setTimeline, + propertiesPanel, + setPropertiesPanel, + } = usePanelStore(); + + const { activeProject, loadProject, createNewProject } = useProjectStore(); + const params = useParams(); + const router = useRouter(); + const projectId = params.project_id as string; + const handledProjectIds = useRef>(new Set()); + const [isOnboardingOpen, setIsOnboardingOpen] = useState(true); + + useDisableBrowserZoom(); + usePlaybackControls(); + + useEffect(() => { + const initProject = async () => { + if (!projectId) return; + + if (activeProject?.id === projectId) { + return; + } + + if (handledProjectIds.current.has(projectId)) { + return; + } + + try { + await loadProject(projectId); + } catch (error) { + handledProjectIds.current.add(projectId); + + const newProjectId = await createNewProject("Untitled Project"); + router.replace(`/editor/${newProjectId}`); + return; + } + }; + + initProject(); + }, [projectId, activeProject?.id, loadProject, createNewProject, router]); + + return ( + +
+ +
+ + + {/* Main content area */} + + {/* Tools Panel */} + + + + + + + {/* Preview Area */} + + + + + + + + + + + + + + + {/* Timeline */} + + + + +
+
+
+ ); } diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index e7c575a1..2f7158cc 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -3,86 +3,86 @@ @tailwind utilities; @layer base { - :root { - --background: 0 0% 100%; - --foreground: 0 0% 3.9%; - --card: 0 0% 100%; - --card-foreground: 0 0% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 0 0% 3.9%; - --primary: 0 0% 9%; - --primary-foreground: 0 0% 98%; - --secondary: 0 0% 96.1%; - --secondary-foreground: 0 0% 9%; - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; - --accent-foreground: 0 0% 9%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; - --input: 0 0% 89.8%; - --ring: 0 0% 3.9%; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - --radius: 1rem; - --sidebar-background: 0 0% 100%; - --sidebar-foreground: 0 0% 3.9%; - --sidebar-primary: 0 0% 9%; - --sidebar-primary-foreground: 0 0% 98%; - --sidebar-accent: 0 0% 96.1%; - --sidebar-accent-foreground: 0 0% 9%; - --sidebar-border: 0 0% 89.8%; - --sidebar-ring: 0 0% 3.9%; - } - .dark { - --background: 0 0% 4%; - --foreground: 0 0% 89%; - --card: 0 0% 14.9%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 14.9%; - --popover-foreground: 0 0% 98%; - --primary: 180 95% 40%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 100% 60%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 17%; - --input: 0 0% 14.9%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - --sidebar-background: 0 0% 3.9%; - --sidebar-foreground: 0 0% 98%; - --sidebar-primary: 0 0% 98%; - --sidebar-primary-foreground: 0 0% 9%; - --sidebar-accent: 0 0% 14.9%; - --sidebar-accent-foreground: 0 0% 98%; - --sidebar-border: 0 0% 14.9%; - --sidebar-ring: 0 0% 83.1%; - --panel-background: 0 0% 11%; - --panel-accent: 0 0% 15%; - } + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 1rem; + --sidebar-background: 0 0% 100%; + --sidebar-foreground: 0 0% 3.9%; + --sidebar-primary: 0 0% 9%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 0 0% 96.1%; + --sidebar-accent-foreground: 0 0% 9%; + --sidebar-border: 0 0% 89.8%; + --sidebar-ring: 0 0% 3.9%; + } + .dark { + --background: 0 0% 4%; + --foreground: 0 0% 89%; + --card: 0 0% 14.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 14.9%; + --popover-foreground: 0 0% 98%; + --primary: 180 95% 40%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 100% 60%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 17%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 0 0% 3.9%; + --sidebar-foreground: 0 0% 98%; + --sidebar-primary: 0 0% 98%; + --sidebar-primary-foreground: 0 0% 9%; + --sidebar-accent: 0 0% 14.9%; + --sidebar-accent-foreground: 0 0% 98%; + --sidebar-border: 0 0% 14.9%; + --sidebar-ring: 0 0% 83.1%; + --panel-background: 0 0% 11%; + --panel-accent: 0 0% 15%; + } } @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - /* Prevent back/forward swipe */ - overscroll-behavior-x: contain; - } + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + /* Prevent back/forward swipe */ + overscroll-behavior-x: contain; + } } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 4b65f6f0..1cbc3f2e 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -12,42 +12,42 @@ import { BotIdClient } from "botid/client"; export const metadata = baseMetaData; const protectedRoutes = [ - { - path: "/api/waitlist", - method: "POST", - }, + { + path: "/api/waitlist", + method: "POST", + }, ]; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - - - - - - - {children} - - -