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
- [ ] 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 made corresponding changes to the documentation
- [ ] My changes generate no new warnings
+2 -2
View File
@@ -60,8 +60,8 @@
"zustand": "^5.0.2"
},
"devDependencies": {
"@types/pg": "^8.15.4",
"@types/bun": "latest",
"@types/pg": "^8.15.4",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"cross-env": "^7.0.3",
@@ -70,6 +70,6 @@
"prettier": "^3.4.2",
"tailwindcss": "^3.4.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}
isSelected={selectedProjects.has(project.id)}
onSelect={handleSelectProject}
searchQuery={searchQuery}
/>
))}
</div>
@@ -266,7 +265,6 @@ interface ProjectCardProps {
isSelectionMode?: boolean;
isSelected?: boolean;
onSelect?: (projectId: string, checked: boolean) => void;
searchQuery?: string;
}
function ProjectCard({
@@ -274,7 +272,6 @@ function ProjectCard({
isSelectionMode = false,
isSelected = false,
onSelect,
searchQuery,
}: ProjectCardProps) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
@@ -363,7 +360,7 @@ function ProjectCard({
<CardContent className="px-0 pt-5 flex flex-col gap-1">
<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">
<Highlight text={project.name} query={searchQuery} />
{project.name}
</h3>
{!isSelectionMode && (
<DropdownMenu
@@ -470,7 +467,7 @@ function ProjectCard({
<CardContent className="px-0 pt-5 flex flex-col gap-1">
<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">
<Highlight text={project.name} query={searchQuery} />
{project.name}
</h3>
<DropdownMenu
open={isDropdownOpen}
@@ -546,6 +543,7 @@ function ProjectCard({
isOpen={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteProject}
projectName={project.name}
/>
<RenameProjectDialog
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 }) {
return (
<Button className="flex" onClick={onClick}>
@@ -7,15 +7,18 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
export function DeleteProjectDialog({
isOpen,
onOpenChange,
onConfirm,
projectName,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
projectName?: string;
}) {
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
@@ -26,7 +29,19 @@ export function DeleteProjectDialog({
}}
>
<DialogHeader>
<DialogTitle>Delete Project</DialogTitle>
<DialogTitle>
{projectName ? (
<>
{"Delete '"}
<span className="inline-block max-w-[300px] truncate align-bottom">
{projectName}
</span>
{"'?"}
</>
) : (
"Delete Project?"
)}
</DialogTitle>
<DialogDescription>
Are you sure you want to delete this project? This action cannot be
undone.
@@ -69,7 +69,7 @@ export function TabBar() {
/>
<div
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) => {
const tab = tabs[tabKey];
+14 -1
View File
@@ -1,9 +1,22 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { env } from "./env";
import { getSessionCookie } from "better-auth/cookies";
export async function middleware(request: NextRequest) {
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
if (request.headers.get("host") === "fuckcapcut.com") {
@@ -12,7 +25,7 @@ export async function middleware(request: NextRequest) {
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);
homeUrl.searchParams.set("redirect", request.url);
return NextResponse.redirect(homeUrl);
+23 -1
View File
@@ -213,7 +213,29 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
try {
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) {
console.error("Failed to load media items:", error);
} finally {
+9 -8
View File
@@ -9,6 +9,7 @@
},
"devDependencies": {
"turbo": "^2.5.4",
"typescript": "5.8.3",
},
},
"apps/web": {
@@ -71,7 +72,7 @@
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.1",
"typescript": "^5",
"typescript": "^5.8.3",
},
},
"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=="],
"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=="],
+2 -1
View File
@@ -2,7 +2,8 @@
"name": "opencut",
"packageManager": "bun@1.2.18",
"devDependencies": {
"turbo": "^2.5.4"
"turbo": "^2.5.4",
"typescript": "5.8.3"
},
"workspaces": [
"apps/*",