Merge branch 'staging'

This commit is contained in:
Maze Winther
2025-07-17 19:51:31 +02:00
13 changed files with 2639 additions and 2610 deletions
+1
View File
@@ -36,6 +36,7 @@ Add screenshots to help explain your changes.
- [ ] My code follows the style guidelines of this project - [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code - [ ] I have performed a self-review of my code
- [ ] I have added screenshots if ui has been changed
- [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation - [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings - [ ] My changes generate no new warnings
+2 -2
View File
@@ -60,8 +60,8 @@
"zustand": "^5.0.2" "zustand": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/pg": "^8.15.4",
"@types/bun": "latest", "@types/bun": "latest",
"@types/pg": "^8.15.4",
"@types/react": "^18.2.48", "@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@@ -70,6 +70,6 @@
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"tsx": "^4.7.1", "tsx": "^4.7.1",
"typescript": "^5" "typescript": "^5.8.3"
} }
} }
+3 -27
View File
@@ -245,7 +245,6 @@ export default function ProjectsPage() {
isSelectionMode={isSelectionMode} isSelectionMode={isSelectionMode}
isSelected={selectedProjects.has(project.id)} isSelected={selectedProjects.has(project.id)}
onSelect={handleSelectProject} onSelect={handleSelectProject}
searchQuery={searchQuery}
/> />
))} ))}
</div> </div>
@@ -266,7 +265,6 @@ interface ProjectCardProps {
isSelectionMode?: boolean; isSelectionMode?: boolean;
isSelected?: boolean; isSelected?: boolean;
onSelect?: (projectId: string, checked: boolean) => void; onSelect?: (projectId: string, checked: boolean) => void;
searchQuery?: string;
} }
function ProjectCard({ function ProjectCard({
@@ -274,7 +272,6 @@ function ProjectCard({
isSelectionMode = false, isSelectionMode = false,
isSelected = false, isSelected = false,
onSelect, onSelect,
searchQuery,
}: ProjectCardProps) { }: ProjectCardProps) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
@@ -363,7 +360,7 @@ function ProjectCard({
<CardContent className="px-0 pt-5 flex flex-col gap-1"> <CardContent className="px-0 pt-5 flex flex-col gap-1">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<h3 className="font-medium text-sm leading-snug group-hover:text-foreground/90 transition-colors line-clamp-2"> <h3 className="font-medium text-sm leading-snug group-hover:text-foreground/90 transition-colors line-clamp-2">
<Highlight text={project.name} query={searchQuery} /> {project.name}
</h3> </h3>
{!isSelectionMode && ( {!isSelectionMode && (
<DropdownMenu <DropdownMenu
@@ -470,7 +467,7 @@ function ProjectCard({
<CardContent className="px-0 pt-5 flex flex-col gap-1"> <CardContent className="px-0 pt-5 flex flex-col gap-1">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<h3 className="font-medium text-sm leading-snug group-hover:text-foreground/90 transition-colors line-clamp-2"> <h3 className="font-medium text-sm leading-snug group-hover:text-foreground/90 transition-colors line-clamp-2">
<Highlight text={project.name} query={searchQuery} /> {project.name}
</h3> </h3>
<DropdownMenu <DropdownMenu
open={isDropdownOpen} open={isDropdownOpen}
@@ -546,6 +543,7 @@ function ProjectCard({
isOpen={isDeleteDialogOpen} isOpen={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteProject} onConfirm={handleDeleteProject}
projectName={project.name}
/> />
<RenameProjectDialog <RenameProjectDialog
isOpen={isRenameDialogOpen} isOpen={isRenameDialogOpen}
@@ -557,28 +555,6 @@ function ProjectCard({
); );
} }
function Highlight({ text, query }: { text: string; query?: string }) {
if (!query || query.trim() === "") {
return <span>{text}</span>;
}
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const parts = text.split(new RegExp(`(${escapedQuery})`, "gi"));
return (
<span>
{parts.map((part, i) =>
part.toLowerCase() === query.toLowerCase() ? (
<mark key={i} className="bg-yellow-200 text-black">
{part}
</mark>
) : (
part
)
)}
</span>
);
}
function CreateButton({ onClick }: { onClick?: () => void }) { function CreateButton({ onClick }: { onClick?: () => void }) {
return ( return (
<Button className="flex" onClick={onClick}> <Button className="flex" onClick={onClick}>
@@ -7,15 +7,18 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
export function DeleteProjectDialog({ export function DeleteProjectDialog({
isOpen, isOpen,
onOpenChange, onOpenChange,
onConfirm, onConfirm,
projectName,
}: { }: {
isOpen: boolean; isOpen: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
onConfirm: () => void; onConfirm: () => void;
projectName?: string;
}) { }) {
return ( return (
<Dialog open={isOpen} onOpenChange={onOpenChange}> <Dialog open={isOpen} onOpenChange={onOpenChange}>
@@ -26,7 +29,19 @@ export function DeleteProjectDialog({
}} }}
> >
<DialogHeader> <DialogHeader>
<DialogTitle>Delete Project</DialogTitle> <DialogTitle>
{projectName ? (
<>
{"Delete '"}
<span className="inline-block max-w-[300px] truncate align-bottom">
{projectName}
</span>
{"'?"}
</>
) : (
"Delete Project?"
)}
</DialogTitle>
<DialogDescription> <DialogDescription>
Are you sure you want to delete this project? This action cannot be Are you sure you want to delete this project? This action cannot be
undone. undone.
@@ -69,7 +69,7 @@ export function TabBar() {
/> />
<div <div
ref={scrollContainerRef} ref={scrollContainerRef}
className="h-12 bg-panel-accent px-3 flex justify-start items-center gap-5 overflow-x-auto scrollbar-x-hidden relative" className="h-12 bg-panel-accent px-3 flex justify-start items-center gap-5 overflow-x-auto scrollbar-x-hidden relative w-full"
> >
{(Object.keys(tabs) as Tab[]).map((tabKey) => { {(Object.keys(tabs) as Tab[]).map((tabKey) => {
const tab = tabs[tabKey]; const tab = tabs[tabKey];
+14 -1
View File
@@ -1,9 +1,22 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { env } from "./env"; import { env } from "./env";
import { getSessionCookie } from "better-auth/cookies";
export async function middleware(request: NextRequest) { export async function middleware(request: NextRequest) {
const protectedPaths = ["/editor", "/projects"]; const protectedPaths = ["/editor", "/projects"];
const sessionCookie = getSessionCookie(request);
const canAccessProtectedPaths = (request: NextRequest) => {
if (env.NODE_ENV === "development") {
return true;
}
if (sessionCookie) {
return true;
}
return false;
};
// Handle fuckcapcut.com domain redirect // Handle fuckcapcut.com domain redirect
if (request.headers.get("host") === "fuckcapcut.com") { if (request.headers.get("host") === "fuckcapcut.com") {
@@ -12,7 +25,7 @@ export async function middleware(request: NextRequest) {
const path = request.nextUrl.pathname; const path = request.nextUrl.pathname;
if (protectedPaths.includes(path) && env.NODE_ENV === "production") { if (protectedPaths.includes(path) && !canAccessProtectedPaths(request)) {
const homeUrl = new URL("/", request.url); const homeUrl = new URL("/", request.url);
homeUrl.searchParams.set("redirect", request.url); homeUrl.searchParams.set("redirect", request.url);
return NextResponse.redirect(homeUrl); return NextResponse.redirect(homeUrl);
+23 -1
View File
@@ -213,7 +213,29 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
try { try {
const mediaItems = await storageService.loadAllMediaItems(projectId); const mediaItems = await storageService.loadAllMediaItems(projectId);
set({ mediaItems });
// Regenerate thumbnails for video items
const updatedMediaItems = await Promise.all(
mediaItems.map(async (item) => {
if (item.type === "video" && item.file) {
try {
const { thumbnailUrl, width, height } = await generateVideoThumbnail(item.file);
return {
...item,
thumbnailUrl,
width: width || item.width,
height: height || item.height
};
} catch (error) {
console.error(`Failed to regenerate thumbnail for video ${item.id}:`, error);
return item;
}
}
return item;
})
);
set({ mediaItems: updatedMediaItems });
} catch (error) { } catch (error) {
console.error("Failed to load media items:", error); console.error("Failed to load media items:", error);
} finally { } finally {
+9 -8
View File
@@ -9,6 +9,7 @@
}, },
"devDependencies": { "devDependencies": {
"turbo": "^2.5.4", "turbo": "^2.5.4",
"typescript": "5.8.3",
}, },
}, },
"apps/web": { "apps/web": {
@@ -71,7 +72,7 @@
"prettier": "^3.4.2", "prettier": "^3.4.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"tsx": "^4.7.1", "tsx": "^4.7.1",
"typescript": "^5", "typescript": "^5.8.3",
}, },
}, },
"packages/auth": { "packages/auth": {
@@ -1022,19 +1023,19 @@
"tsx": ["tsx@4.20.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ=="], "tsx": ["tsx@4.20.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ=="],
"turbo": ["turbo@2.5.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.4", "turbo-darwin-arm64": "2.5.4", "turbo-linux-64": "2.5.4", "turbo-linux-arm64": "2.5.4", "turbo-windows-64": "2.5.4", "turbo-windows-arm64": "2.5.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-kc8ZibdRcuWUG1pbYSBFWqmIjynlD8Lp7IB6U3vIzvOv9VG+6Sp8bzyeBWE3Oi8XV5KsQrznyRTBPvrf99E4mA=="], "turbo": ["turbo@2.5.5", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.5", "turbo-darwin-arm64": "2.5.5", "turbo-linux-64": "2.5.5", "turbo-linux-arm64": "2.5.5", "turbo-windows-64": "2.5.5", "turbo-windows-arm64": "2.5.5" }, "bin": { "turbo": "bin/turbo" } }, "sha512-eZ7wI6KjtT1eBqCnh2JPXWNUAxtoxxfi6VdBdZFvil0ychCOTxbm7YLRBi1JSt7U3c+u3CLxpoPxLdvr/Npr3A=="],
"turbo-darwin-64": ["turbo-darwin-64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ah6YnH2dErojhFooxEzmvsoZQTMImaruZhFPfMKPBq8sb+hALRdvBNLqfc8NWlZq576FkfRZ/MSi4SHvVFT9PQ=="], "turbo-darwin-64": ["turbo-darwin-64@2.5.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-RYnTz49u4F5tDD2SUwwtlynABNBAfbyT2uU/brJcyh5k6lDLyNfYKdKmqd3K2ls4AaiALWrFKVSBsiVwhdFNzQ=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2+Nx6LAyuXw2MdXb7pxqle3MYignLvS7OwtsP9SgtSBaMlnNlxl9BovzqdYAgkUW3AsYiQMJ/wBRb7d+xemM5A=="], "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Tk+ZeSNdBobZiMw9aFypQt0DlLsWSFWu1ymqsAdJLuPoAH05qCfYtRxE1pJuYHcJB5pqI+/HOxtJoQ40726Btw=="],
"turbo-linux-64": ["turbo-linux-64@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-5May2kjWbc8w4XxswGAl74GZ5eM4Gr6IiroqdLhXeXyfvWEdm2mFYCSWOzz0/z5cAgqyGidF1jt1qzUR8hTmOA=="], "turbo-linux-64": ["turbo-linux-64@2.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-2/XvMGykD7VgsvWesZZYIIVXMlgBcQy+ZAryjugoTcvJv8TZzSU/B1nShcA7IAjZ0q7OsZ45uP2cOb8EgKT30w=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-/2yqFaS3TbfxV3P5yG2JUI79P7OUQKOUvAnx4MV9Bdz6jqHsHwc9WZPpO4QseQm+NvmgY6ICORnoVPODxGUiJg=="], "turbo-linux-arm64": ["turbo-linux-arm64@2.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-DW+8CjCjybu0d7TFm9dovTTVg1VRnlkZ1rceO4zqsaLrit3DgHnN4to4uwyuf9s2V/BwS3IYcRy+HG9BL596Iw=="],
"turbo-windows-64": ["turbo-windows-64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EQUO4SmaCDhO6zYohxIjJpOKRN3wlfU7jMAj3CgcyTPvQR/UFLEKAYHqJOnJtymbQmiiM/ihX6c6W6Uq0yC7mA=="], "turbo-windows-64": ["turbo-windows-64@2.5.5", "", { "os": "win32", "cpu": "x64" }, "sha512-q5p1BOy8ChtSZfULuF1BhFMYIx6bevXu4fJ+TE/hyNfyHJIfjl90Z6jWdqAlyaFLmn99X/uw+7d6T/Y/dr5JwQ=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-oQ8RrK1VS8lrxkLriotFq+PiF7iiGgkZtfLKF4DDKsmdbPo0O9R2mQxm7jHLuXraRCuIQDWMIw6dpcr7Iykf4A=="], "turbo-windows-arm64": ["turbo-windows-arm64@2.5.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-AXbF1KmpHUq3PKQwddMGoKMYhHsy5t1YBQO8HZ04HLMR0rWv9adYlQ8kaeQJTko1Ay1anOBFTqaxfVOOsu7+1Q=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+2 -1
View File
@@ -2,7 +2,8 @@
"name": "opencut", "name": "opencut",
"packageManager": "bun@1.2.18", "packageManager": "bun@1.2.18",
"devDependencies": { "devDependencies": {
"turbo": "^2.5.4" "turbo": "^2.5.4",
"typescript": "5.8.3"
}, },
"workspaces": [ "workspaces": [
"apps/*", "apps/*",