summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorPulkit Garg <pulkitgargbnl@gmail.com>2025-06-25 10:26:29 +0530
committerPulkit Garg <pulkitgargbnl@gmail.com>2025-06-25 10:26:29 +0530
commit411a9a5f1cdb4b52c65f4a1344c9be7694d130d7 (patch)
tree217923d57528feaacdfe359ff3501b317f0d5483 /apps/web
parent7461b763b1e434a763c1d02e9fa750dce1c153a8 (diff)
downloadopencut-411a9a5f1cdb4b52c65f4a1344c9be7694d130d7.tar.gz
feature: add project name editing functionality
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/src/components/editor-header.tsx17
-rw-r--r--apps/web/src/components/editor/project-name-editor.tsx110
-rw-r--r--apps/web/src/stores/project-store.ts13
3 files changed, 133 insertions, 7 deletions
diff --git a/apps/web/src/components/editor-header.tsx b/apps/web/src/components/editor-header.tsx
index c6cecbe8..6f9f0b2f 100644
--- a/apps/web/src/components/editor-header.tsx
+++ b/apps/web/src/components/editor-header.tsx
@@ -6,6 +6,7 @@ import { ChevronLeft, Download } from "lucide-react";
import { useProjectStore } from "@/stores/project-store";
import { useTimelineStore } from "@/stores/timeline-store";
import { HeaderBase } from "./header-base";
+import { ProjectNameEditor } from "./editor/project-name-editor";
export function EditorHeader() {
const { activeProject } = useProjectStore();
@@ -24,13 +25,15 @@ export function EditorHeader() {
};
const leftContent = (
- <Link
- href="/"
- className="font-medium tracking-tight flex items-center gap-2 hover:opacity-80 transition-opacity"
- >
- <ChevronLeft className="h-4 w-4" />
- <span className="text-sm">{activeProject?.name || "Loading..."}</span>
- </Link>
+ <div className="flex items-center gap-2">
+ <Link
+ href="/"
+ className="font-medium tracking-tight flex items-center gap-2 hover:opacity-80 transition-opacity"
+ >
+ <ChevronLeft className="h-4 w-4" />
+ </Link>
+ <ProjectNameEditor />
+ </div>
);
const centerContent = (
diff --git a/apps/web/src/components/editor/project-name-editor.tsx b/apps/web/src/components/editor/project-name-editor.tsx
new file mode 100644
index 00000000..aa0141ae
--- /dev/null
+++ b/apps/web/src/components/editor/project-name-editor.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import { useState, useRef, useEffect } from "react";
+import { Input } from "../ui/input";
+import { useProjectStore } from "@/stores/project-store";
+import { Edit2, Check, X } from "lucide-react";
+import { Button } from "../ui/button";
+
+interface ProjectNameEditorProps {
+ className?: string;
+}
+
+export function ProjectNameEditor({ className }: ProjectNameEditorProps) {
+ const { activeProject, updateProjectName } = useProjectStore();
+ const [isEditing, setIsEditing] = useState(false);
+ const [editValue, setEditValue] = useState("");
+ const inputRef = useRef<HTMLInputElement>(null);
+
+ useEffect(() => {
+ if (activeProject) {
+ setEditValue(activeProject.name);
+ }
+ }, [activeProject]);
+
+ useEffect(() => {
+ if (isEditing && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [isEditing]);
+
+ const handleStartEdit = () => {
+ if (activeProject) {
+ setEditValue(activeProject.name);
+ setIsEditing(true);
+ }
+ };
+
+ const handleSave = () => {
+ if (editValue.trim()) {
+ updateProjectName(editValue.trim());
+ setIsEditing(false);
+ }
+ };
+
+ const handleCancel = () => {
+ if (activeProject) {
+ setEditValue(activeProject.name);
+ }
+ setIsEditing(false);
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ handleSave();
+ } else if (e.key === "Escape") {
+ handleCancel();
+ }
+ };
+
+ if (!activeProject) {
+ return <span className="text-sm text-muted-foreground">Loading...</span>;
+ }
+
+ if (isEditing) {
+ return (
+ <div className="flex items-center gap-1">
+ <Input
+ ref={inputRef}
+ value={editValue}
+ onChange={(e) => setEditValue(e.target.value)}
+ onKeyDown={handleKeyDown}
+ className="h-7 text-sm px-3 py-1 min-w-[200px]"
+ size={1}
+ />
+ <Button
+ size="sm"
+ variant="text"
+ onClick={handleSave}
+ className="h-7 w-7 p-0"
+ disabled={!editValue.trim()}
+ >
+ <Check className="h-3 w-3" />
+ </Button>
+ <Button
+ size="sm"
+ variant="text"
+ onClick={handleCancel}
+ className="h-7 w-7 p-0"
+ >
+ <X className="h-3 w-3" />
+ </Button>
+ </div>
+ );
+ }
+
+ return (
+ <div className="flex items-center gap-1 group">
+ <span className="text-sm font-medium">{activeProject.name}</span>
+ <Button
+ size="sm"
+ variant="text"
+ onClick={handleStartEdit}
+ className="h-6 w-6 p-0 opacity-0 group-hover:opacity-100 transition-opacity"
+ >
+ <Edit2 className="h-3 w-3" />
+ </Button>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
index 734c07e2..0d0812cd 100644
--- a/apps/web/src/stores/project-store.ts
+++ b/apps/web/src/stores/project-store.ts
@@ -7,6 +7,7 @@ interface ProjectStore {
// Actions
createNewProject: (name: string) => void;
closeProject: () => void;
+ updateProjectName: (name: string) => void;
}
export const useProjectStore = create<ProjectStore>((set) => ({
@@ -25,4 +26,16 @@ export const useProjectStore = create<ProjectStore>((set) => ({
closeProject: () => {
set({ activeProject: null });
},
+
+ updateProjectName: (name: string) => {
+ set((state) => ({
+ activeProject: state.activeProject
+ ? {
+ ...state.activeProject,
+ name,
+ updatedAt: new Date(),
+ }
+ : null,
+ }));
+ },
}));