From 18d2186101043c251687235bddf13b7f00b16846 Mon Sep 17 00:00:00 2001 From: Dominik Koch Date: Wed, 16 Jul 2025 15:19:06 +0200 Subject: feat(hero): add sponsored by vercel to hero --- apps/web/src/components/icons.tsx | 8 +++++ apps/web/src/components/landing/hero.tsx | 14 +++++++++ apps/web/src/components/ui/sponsor-button.tsx | 42 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 apps/web/src/components/ui/sponsor-button.tsx (limited to 'apps/web') diff --git a/apps/web/src/components/icons.tsx b/apps/web/src/components/icons.tsx index 4f3986e3..68e8f0ff 100644 --- a/apps/web/src/components/icons.tsx +++ b/apps/web/src/components/icons.tsx @@ -38,6 +38,14 @@ export function GithubIcon({ className }: { className?: string }) { ); } +export function VercelIcon({ className }: { className?: string }) { + return ( + + + + ); +} + export function BackgroundIcon({ className }: { className?: string }) { return ( + + + ; + companyName: string; + className?: string; +} + +export function SponsorButton({ + href, + logo: Logo, + companyName, + className = "", +}: SponsorButtonProps) { + + return ( + + + Sponsored by + + +
+
+ +
+ + + {companyName} + +
+ + ); +} -- cgit v1.3.1 From 5483db83de2c8d3a051148dfc56d6ab52335ee73 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 15:52:25 +0200 Subject: fix keyboard shortcuts --- .../web/src/components/keyboard-shortcuts-help.tsx | 24 ++++++---------------- apps/web/src/components/ui/dialog.tsx | 12 +++++++++-- apps/web/src/hooks/use-keyboard-shortcuts.ts | 16 +-------------- 3 files changed, 17 insertions(+), 35 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx index 9f0d1e57..95fb6031 100644 --- a/apps/web/src/components/keyboard-shortcuts-help.tsx +++ b/apps/web/src/components/keyboard-shortcuts-help.tsx @@ -21,7 +21,7 @@ const KeyBadge = ({ keyName }: { keyName: string }) => { .replace("Shift", "⇧") .replace("←", "◀") .replace("→", "▶") - .replace("Space", "⎵"); + .replace("Space", "Space"); return ( @@ -31,7 +31,7 @@ const KeyBadge = ({ keyName }: { keyName: string }) => { }; const ShortcutItem = ({ shortcut }: { shortcut: any }) => ( -
+
{shortcut.icon && (
{shortcut.icon}
@@ -67,7 +67,7 @@ export const KeyboardShortcutsHelp = () => { Shortcuts - + @@ -81,11 +81,11 @@ export const KeyboardShortcutsHelp = () => {
{categories.map((category) => ( -
-

+
+

{category}

-
+
{shortcuts .filter((shortcut) => shortcut.category === category) .map((shortcut, index) => ( @@ -95,18 +95,6 @@ export const KeyboardShortcutsHelp = () => {
))}
- -
-

Tips:

-
    -
  • - • Shortcuts work when the editor is focused (not typing in inputs) -
  • -
  • • J/K/L are industry-standard video editing shortcuts
  • -
  • • Use arrow keys for frame-perfect positioning
  • -
  • • Hold Shift with arrow keys for larger jumps
  • -
-
); diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx index 0a487d0a..ebfd8907 100644 --- a/apps/web/src/components/ui/dialog.tsx +++ b/apps/web/src/components/ui/dialog.tsx @@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef< { + e.stopPropagation(); + e.preventDefault(); + }} + onOpenAutoFocus={(e) => { + e.stopPropagation(); + e.preventDefault(); + }} {...props} > diff --git a/apps/web/src/hooks/use-keyboard-shortcuts.ts b/apps/web/src/hooks/use-keyboard-shortcuts.ts index 86190c94..d8fc032e 100644 --- a/apps/web/src/hooks/use-keyboard-shortcuts.ts +++ b/apps/web/src/hooks/use-keyboard-shortcuts.ts @@ -15,6 +15,7 @@ export interface KeyboardShortcut { enabled?: boolean; requiresSelection?: boolean; icon?: React.ReactNode; + displayKeys?: string[]; // Keys to show in help (defaults to keys if not provided) } interface UseKeyboardShortcutsOptions { @@ -66,7 +67,6 @@ export const useKeyboardShortcuts = ( category: "Playback", action: () => { toggle(); - toast.info(isPlaying ? "Paused" : "Playing", { duration: 1000 }); }, }, { @@ -76,7 +76,6 @@ export const useKeyboardShortcuts = ( category: "Playback", action: () => { seek(Math.max(0, currentTime - 1)); - toast.info("Rewind 1s", { duration: 1000 }); }, }, { @@ -86,7 +85,6 @@ export const useKeyboardShortcuts = ( category: "Playback", action: () => { toggle(); - toast.info(isPlaying ? "Paused" : "Playing", { duration: 1000 }); }, }, { @@ -96,7 +94,6 @@ export const useKeyboardShortcuts = ( category: "Playback", action: () => { seek(Math.min(duration, currentTime + 1)); - toast.info("Forward 1s", { duration: 1000 }); }, }, @@ -146,7 +143,6 @@ export const useKeyboardShortcuts = ( category: "Navigation", action: () => { seek(0); - toast.info("Start of timeline", { duration: 1000 }); }, }, { @@ -156,7 +152,6 @@ export const useKeyboardShortcuts = ( category: "Navigation", action: () => { seek(duration); - toast.info("End of timeline", { duration: 1000 }); }, }, @@ -185,7 +180,6 @@ export const useKeyboardShortcuts = ( if (currentTime > effectiveStart && currentTime < effectiveEnd) { splitElement(trackId, elementId, currentTime); - toast.success("Element split at playhead"); } else { toast.error("Playhead must be within selected element"); } @@ -218,9 +212,6 @@ export const useKeyboardShortcuts = ( category: "Editing", action: () => { toggleSnapping(); - toast.info(`Snapping ${snappingEnabled ? "disabled" : "enabled"}`, { - duration: 1000, - }); }, }, @@ -238,9 +229,6 @@ export const useKeyboardShortcuts = ( })) ); setSelectedElements(allElements); - toast.info(`Selected ${allElements.length} elements`, { - duration: 1000, - }); }, }, { @@ -270,8 +258,6 @@ export const useKeyboardShortcuts = ( ...elementWithoutId, startTime: newStartTime, }); - - toast.success("Element duplicated"); } }, }, -- cgit v1.3.1 From 0c086e3c240e229b3dd89e6925b34f8c5fc20052 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 16:05:30 +0200 Subject: more shortcut fixes --- .../web/src/components/keyboard-shortcuts-help.tsx | 76 +++++++++++++++------- apps/web/src/hooks/use-keyboard-shortcuts.ts | 1 - 2 files changed, 54 insertions(+), 23 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/keyboard-shortcuts-help.tsx b/apps/web/src/components/keyboard-shortcuts-help.tsx index 95fb6031..936254be 100644 --- a/apps/web/src/components/keyboard-shortcuts-help.tsx +++ b/apps/web/src/components/keyboard-shortcuts-help.tsx @@ -15,41 +15,73 @@ import { Keyboard } from "lucide-react"; import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts"; const KeyBadge = ({ keyName }: { keyName: string }) => { - // Replace common key names with symbols + // Replace common key names with symbols or friendly names const displayKey = keyName .replace("Cmd", "⌘") - .replace("Shift", "⇧") + .replace("Shift", "Shift") + .replace("ArrowLeft", "Arrow Left") + .replace("ArrowRight", "Arrow Right") + .replace("ArrowUp", "Arrow Up") + .replace("ArrowDown", "Arrow Down") .replace("←", "◀") .replace("→", "▶") .replace("Space", "Space"); return ( - + {displayKey} ); }; -const ShortcutItem = ({ shortcut }: { shortcut: any }) => ( -
-
- {shortcut.icon && ( -
{shortcut.icon}
- )} - {shortcut.description} -
-
- {shortcut.keys.map((key: string, index: number) => ( -
- - {index < shortcut.keys.length - 1 && ( - + - )} -
- ))} +const ShortcutItem = ({ shortcut }: { shortcut: any }) => { + // Filter out lowercase duplicates for display - if both "j" and "J" exist, only show "J" + const displayKeys = shortcut.keys.filter((key: string) => { + const lowerKey = key.toLowerCase(); + const upperKey = key.toUpperCase(); + + // If this is a lowercase letter and the uppercase version exists, skip it + if ( + key === lowerKey && + key !== upperKey && + shortcut.keys.includes(upperKey) + ) { + return false; + } + + return true; + }); + + return ( +
+
+ {shortcut.icon && ( +
{shortcut.icon}
+ )} + {shortcut.description} +
+
+ {displayKeys.map((key: string, index: number) => ( +
+
+ {key.split("+").map((keyPart: string, partIndex: number) => ( +
+ + {partIndex < key.split("+").length - 1 && ( + + + )} +
+ ))} +
+ {index < displayKeys.length - 1 && ( + or + )} +
+ ))} +
-
-); + ); +}; export const KeyboardShortcutsHelp = () => { const [open, setOpen] = useState(false); diff --git a/apps/web/src/hooks/use-keyboard-shortcuts.ts b/apps/web/src/hooks/use-keyboard-shortcuts.ts index d8fc032e..84cb78f8 100644 --- a/apps/web/src/hooks/use-keyboard-shortcuts.ts +++ b/apps/web/src/hooks/use-keyboard-shortcuts.ts @@ -15,7 +15,6 @@ export interface KeyboardShortcut { enabled?: boolean; requiresSelection?: boolean; icon?: React.ReactNode; - displayKeys?: string[]; // Keys to show in help (defaults to keys if not provided) } interface UseKeyboardShortcutsOptions { -- cgit v1.3.1 From c3de5327091cb84d8233c25b2e7eeacc2c972a7c Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 16:14:45 +0200 Subject: fix --- apps/web/src/components/landing/hero.tsx | 2 +- apps/web/src/components/ui/sponsor-button.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index 4a66e433..d5fe38d1 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -119,7 +119,7 @@ export function Hero() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.6, duration: 0.8 }} - className="mb-4 flex justify-center" + className="mb-8 flex justify-center" > Sponsored by -- cgit v1.3.1 From fcfe612cd66c6c38c4ea27513dbcb9a70bd89c92 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 17:36:47 +0200 Subject: fix: handlers component on mobile --- apps/web/src/components/landing/handlebars.tsx | 48 +++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/landing/handlebars.tsx b/apps/web/src/components/landing/handlebars.tsx index 36705e90..e2eb2573 100644 --- a/apps/web/src/components/landing/handlebars.tsx +++ b/apps/web/src/components/landing/handlebars.tsx @@ -19,6 +19,7 @@ export function Handlebars({ const [leftHandle, setLeftHandle] = useState(0); const [rightHandle, setRightHandle] = useState(maxWidth); const [contentWidth, setContentWidth] = useState(maxWidth); + const [isDragging, setIsDragging] = useState(false); const leftHandleX = useMotionValue(0); const rightHandleX = useMotionValue(maxWidth); @@ -33,6 +34,27 @@ export function Handlebars({ const containerRef = useRef(null); const measureRef = useRef(null); + // Prevent scroll when dragging on mobile + useEffect(() => { + const preventDefault = (e: TouchEvent) => { + if (isDragging) { + e.preventDefault(); + } + }; + + if (isDragging) { + document.addEventListener("touchmove", preventDefault, { + passive: false, + }); + document.body.style.overflow = "hidden"; + } + + return () => { + document.removeEventListener("touchmove", preventDefault); + document.body.style.overflow = ""; + }; + }, [isDragging]); + useEffect(() => { if (!measureRef.current) return; @@ -80,6 +102,14 @@ export function Handlebars({ setRightHandle(newRight); }; + const handleDragStart = () => { + setIsDragging(true); + }; + + const handleDragEnd = () => { + setIsDragging(false); + }; + return (
+ {/* Left Handle */}
+ {/* Right Handle */}
@@ -161,4 +201,4 @@ export function Handlebars({
); -}; +} -- cgit v1.3.1 From c1c4b114eb583489afdf1e49cf96197fbb10bca2 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 17:38:29 +0200 Subject: fix: snapping for both directions (left/right) --- apps/web/src/components/editor/timeline-track.tsx | 47 ++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 2be143eb..9b94ee6d 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -54,7 +54,7 @@ export function TimelineTrackContent({ const { currentTime } = usePlaybackStore(); // Initialize snapping hook - const { snapElementPosition } = useTimelineSnapping({ + const { snapElementPosition, snapElementEdge } = useTimelineSnapping({ snapThreshold: 10, enableElementSnapping: snappingEnabled, enablePlayheadSnapping: snappingEnabled, @@ -103,15 +103,52 @@ export function TimelineTrackContent({ let finalTime = adjustedTime; let snapPoint = null; if (snappingEnabled) { - const snapResult = snapElementPosition( + // Find the element being dragged to get its duration + let elementDuration = 5; // fallback duration + if (dragState.elementId && dragState.trackId) { + const sourceTrack = tracks.find((t) => t.id === dragState.trackId); + const element = sourceTrack?.elements.find( + (e) => e.id === dragState.elementId + ); + if (element) { + elementDuration = + element.duration - element.trimStart - element.trimEnd; + } + } + + // Try snapping both start and end edges + const startSnapResult = snapElementEdge( adjustedTime, + elementDuration, tracks, currentTime, zoomLevel, - dragState.elementId || undefined + dragState.elementId || undefined, + true // snap to start edge ); - finalTime = snapResult.snappedTime; - snapPoint = snapResult.snapPoint; + + const endSnapResult = snapElementEdge( + adjustedTime, + elementDuration, + tracks, + currentTime, + zoomLevel, + dragState.elementId || undefined, + false // snap to end edge + ); + + // Choose the snap result with the smaller distance (closer snap) + let bestSnapResult = startSnapResult; + if ( + endSnapResult.snapPoint && + (!startSnapResult.snapPoint || + endSnapResult.snapDistance < startSnapResult.snapDistance) + ) { + bestSnapResult = endSnapResult; + } + + finalTime = bestSnapResult.snappedTime; + snapPoint = bestSnapResult.snapPoint; // Notify parent component about snap point change onSnapPointChange?.(snapPoint); -- cgit v1.3.1 From 218625b61a344e371688bbffc152ec3632615e46 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 17:44:44 +0200 Subject: feat: advance snapping --- apps/web/src/components/editor/timeline-track.tsx | 119 +++++++++++++++++----- 1 file changed, 91 insertions(+), 28 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 9b94ee6d..91b3ffa3 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -60,6 +60,53 @@ export function TimelineTrackContent({ enablePlayheadSnapping: snappingEnabled, }); + // Helper function for drop snapping that tries both edges + const getDropSnappedTime = ( + dropTime: number, + elementDuration: number, + excludeElementId?: string + ) => { + if (!snappingEnabled) { + // Use frame snapping if project has FPS, otherwise use decimal snapping + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + return snapTimeToFrame(dropTime, projectFps); + } + + // Try snapping both start and end edges for drops + const startSnapResult = snapElementEdge( + dropTime, + elementDuration, + tracks, + currentTime, + zoomLevel, + excludeElementId, + true // snap to start edge + ); + + const endSnapResult = snapElementEdge( + dropTime, + elementDuration, + tracks, + currentTime, + zoomLevel, + excludeElementId, + false // snap to end edge + ); + + // Choose the snap result with the smaller distance (closer snap) + let bestSnapResult = startSnapResult; + if ( + endSnapResult.snapPoint && + (!startSnapResult.snapPoint || + endSnapResult.snapDistance < startSnapResult.snapDistance) + ) { + bestSnapResult = endSnapResult; + } + + return bestSnapResult.snappedTime; + }; + const timelineRef = useRef(null); const [isDropping, setIsDropping] = useState(false); const [dropPosition, setDropPosition] = useState(null); @@ -427,9 +474,10 @@ export function TimelineTrackContent({ if (dragData.type === "text") { // Text elements have default duration of 5 seconds const newElementDuration = 5; - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - const snappedTime = snapTimeToFrame(dropTime, projectFps); + const snappedTime = getDropSnappedTime( + dropTime, + newElementDuration + ); const newElementEnd = snappedTime + newElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -448,9 +496,10 @@ export function TimelineTrackContent({ ); if (mediaItem) { const newElementDuration = mediaItem.duration || 5; - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - const snappedTime = snapTimeToFrame(dropTime, projectFps); + const snappedTime = getDropSnappedTime( + dropTime, + newElementDuration + ); const newElementEnd = snappedTime + newElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -490,9 +539,11 @@ export function TimelineTrackContent({ movingElement.duration - movingElement.trimStart - movingElement.trimEnd; - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - const snappedTime = snapTimeToFrame(dropTime, projectFps); + const snappedTime = getDropSnappedTime( + dropTime, + movingElementDuration, + elementId + ); const movingElementEnd = snappedTime + movingElementDuration; wouldOverlap = track.elements.some((existingElement) => { @@ -519,17 +570,15 @@ export function TimelineTrackContent({ if (wouldOverlap) { e.dataTransfer.dropEffect = "none"; setWouldOverlap(true); - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - setDropPosition(snapTimeToFrame(dropTime, projectFps)); + // Use default duration for position indicator + setDropPosition(getDropSnappedTime(dropTime, 5)); return; } e.dataTransfer.dropEffect = hasTimelineElement ? "move" : "copy"; setWouldOverlap(false); - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - setDropPosition(snapTimeToFrame(dropTime, projectFps)); + // Use default duration for position indicator + setDropPosition(getDropSnappedTime(dropTime, 5)); }; const handleTrackDragEnter = (e: React.DragEvent) => { @@ -651,18 +700,20 @@ export function TimelineTrackContent({ return; } - // Adjust position based on where user clicked on the element - const adjustedStartTime = snappedTime - clickOffsetTime; - const finalStartTime = Math.max( - 0, - snapTimeToFrame(adjustedStartTime, projectFps) - ); - // Check for overlaps with existing elements (excluding the moving element itself) const movingElementDuration = movingElement.duration - movingElement.trimStart - movingElement.trimEnd; + + // Adjust position based on where user clicked on the element + const adjustedStartTime = newStartTime - clickOffsetTime; + const snappedStartTime = getDropSnappedTime( + adjustedStartTime, + movingElementDuration, + elementId + ); + const finalStartTime = Math.max(0, snappedStartTime); const movingElementEnd = finalStartTime + movingElementDuration; const hasOverlap = track.elements.some((existingElement) => { @@ -748,7 +799,11 @@ export function TimelineTrackContent({ // Check for overlaps with existing elements in target track const newElementDuration = 5; // Default text duration - const newElementEnd = snappedTime + newElementDuration; + const textSnappedTime = getDropSnappedTime( + newStartTime, + newElementDuration + ); + const newElementEnd = textSnappedTime + newElementDuration; const hasOverlap = targetTrack.elements.some((existingElement) => { const existingStart = existingElement.startTime; @@ -759,7 +814,9 @@ export function TimelineTrackContent({ existingElement.trimEnd); // Check if elements overlap - return snappedTime < existingEnd && newElementEnd > existingStart; + return ( + textSnappedTime < existingEnd && newElementEnd > existingStart + ); }); if (hasOverlap) { @@ -774,7 +831,7 @@ export function TimelineTrackContent({ name: dragData.name || "Text", content: dragData.content || "Default Text", duration: TIMELINE_CONSTANTS.DEFAULT_TEXT_DURATION, - startTime: snappedTime, + startTime: textSnappedTime, trimStart: 0, trimEnd: 0, fontSize: 48, @@ -894,7 +951,11 @@ export function TimelineTrackContent({ // Check for overlaps with existing elements in target track const newElementDuration = mediaItem.duration || 5; - const newElementEnd = snappedTime + newElementDuration; + const mediaSnappedTime = getDropSnappedTime( + newStartTime, + newElementDuration + ); + const newElementEnd = mediaSnappedTime + newElementDuration; const hasOverlap = targetTrack.elements.some((existingElement) => { const existingStart = existingElement.startTime; @@ -905,7 +966,9 @@ export function TimelineTrackContent({ existingElement.trimEnd); // Check if elements overlap - return snappedTime < existingEnd && newElementEnd > existingStart; + return ( + mediaSnappedTime < existingEnd && newElementEnd > existingStart + ); }); if (hasOverlap) { @@ -920,7 +983,7 @@ export function TimelineTrackContent({ mediaId: mediaItem.id, name: mediaItem.name, duration: mediaItem.duration || 5, - startTime: snappedTime, + startTime: mediaSnappedTime, trimStart: 0, trimEnd: 0, }); -- cgit v1.3.1 From caf6fef3adb0425e3a89ca318136adc3e5f5d17c Mon Sep 17 00:00:00 2001 From: Sahil Sobhani Date: Wed, 16 Jul 2025 23:34:20 +0530 Subject: Redesigned the footer for mobile devices: UI Changes --- apps/web/.env.example | 15 --------------- apps/web/src/components/footer.tsx | 8 ++++---- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 apps/web/.env.example (limited to 'apps/web') diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index d63df851..00000000 --- a/apps/web/.env.example +++ /dev/null @@ -1,15 +0,0 @@ -# Environment Variables Example -# Copy this file to .env.local and update the values as needed - -DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut" - -# Better Auth -NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000 -BETTER_AUTH_SECRET=your-secret-key-here - -# Development Environment -NODE_ENV=development - -# Redis -UPSTASH_REDIS_REST_URL=http://localhost:8079 -UPSTASH_REDIS_REST_TOKEN=example_token diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 81505d4a..731aa2f7 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -34,15 +34,15 @@ export function Footer() {
{/* Brand Section */}
-
+
OpenCut OpenCut
-

+

The open source video editor that gets the job done. Simple, powerful, and works on any platform.

-
+
-
+

Resources

    -- cgit v1.3.1 From 04a10367701a804a4421051b1ff39ef5e09479c2 Mon Sep 17 00:00:00 2001 From: Sahil Sobhani Date: Wed, 16 Jul 2025 23:35:25 +0530 Subject: added .env.example --- apps/web/.env.example | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 apps/web/.env.example (limited to 'apps/web') diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 00000000..d63df851 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,15 @@ +# Environment Variables Example +# Copy this file to .env.local and update the values as needed + +DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut" + +# Better Auth +NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000 +BETTER_AUTH_SECRET=your-secret-key-here + +# Development Environment +NODE_ENV=development + +# Redis +UPSTASH_REDIS_REST_URL=http://localhost:8079 +UPSTASH_REDIS_REST_TOKEN=example_token -- cgit v1.3.1 From 87dd275e573958af32412147378b73dcf988ed18 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 21:05:28 +0200 Subject: fix: no more rounded corners in preview --- apps/web/src/components/editor/preview-panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web') diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 894b6257..2ecee391 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -347,7 +347,7 @@ export function PreviewPanel() { {hasAnyElements ? (
    Date: Wed, 16 Jul 2025 22:39:33 +0200 Subject: style(footer): align everything to left --- apps/web/src/components/footer.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 731aa2f7..5b7f2303 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -34,15 +34,15 @@ export function Footer() {
    {/* Brand Section */}
    -
    +
    OpenCut OpenCut
    -

    +

    The open source video editor that gets the job done. Simple, powerful, and works on any platform.

    -
    +
    -
    +

    Resources

      @@ -129,7 +129,7 @@ export function Footer() {
    {/* Bottom Section */} -
    +
    © 2025 OpenCut, All Rights Reserved
    -- cgit v1.3.1 From accad442f48f9d1254d889054dfbce81ea6acda0 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 22:47:17 +0200 Subject: cleanup --- apps/web/src/app/editor/[project_id]/page.tsx | 11 ++++++++-- apps/web/src/app/projects/page.tsx | 12 +++++++++-- apps/web/src/components/onboarding.tsx | 29 +++++++++++++++++++++++++++ apps/web/src/middleware.ts | 4 +++- apps/web/src/stores/project-store.ts | 11 ++++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/components/onboarding.tsx (limited to 'apps/web') diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index 65667106..41bef8bd 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { ResizablePanelGroup, @@ -16,6 +16,7 @@ import { usePanelStore } from "@/stores/panel-store"; import { useProjectStore } from "@/stores/project-store"; import { EditorProvider } from "@/components/editor-provider"; import { usePlaybackControls } from "@/hooks/use-playback-controls"; +import { Onboarding } from "@/components/onboarding"; export default function Editor() { const { @@ -36,12 +37,12 @@ export default function Editor() { const router = useRouter(); const projectId = params.project_id as string; const handledProjectIds = useRef>(new Set()); + const [isOnboardingOpen, setIsOnboardingOpen] = useState(true); usePlaybackControls(); useEffect(() => { const initProject = async () => { - if (!projectId) return; if (activeProject?.id === projectId) { @@ -139,6 +140,12 @@ export default function Editor() {
    + { + setIsOnboardingOpen(false); + }} + /> ); } diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index 499b999a..9fcb025b 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -1,7 +1,8 @@ -"use client"; +"use client" +import { redirect } from "next/navigation"; import Link from "next/link"; -import { useState } from "react"; +import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; @@ -28,8 +29,15 @@ import { useProjectStore } from "@/stores/project-store"; import { useRouter } from "next/navigation"; import { DeleteProjectDialog } from "@/components/delete-project-dialog"; import { RenameProjectDialog } from "@/components/rename-project-dialog"; +import { toast } from "sonner"; export default function ProjectsPage() { + + if (process.env.NODE_ENV !== "development") { + toast.error("You are not allowed to access this page"); + redirect("/"); + } + const { createNewProject, savedProjects, diff --git a/apps/web/src/components/onboarding.tsx b/apps/web/src/components/onboarding.tsx new file mode 100644 index 00000000..a2943677 --- /dev/null +++ b/apps/web/src/components/onboarding.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "./ui/dialog"; + +interface OnboardingProps { + isOpen: boolean; + onClose: () => void; +} + +export function Onboarding({ isOpen, onClose }: OnboardingProps) { + return ( + + + + Welcome to Clipture + + Let's get you started with the basics. + + + + + ); +} diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 1f229e6d..aabc302a 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -3,6 +3,8 @@ import type { NextRequest } from "next/server"; import { env } from "./env"; export async function middleware(request: NextRequest) { + const protectedPaths = ["/editor", "/projects"]; + // Handle fuckcapcut.com domain redirect if (request.headers.get("host") === "fuckcapcut.com") { return NextResponse.redirect("https://opencut.app/why-not-capcut", 301); @@ -10,7 +12,7 @@ export async function middleware(request: NextRequest) { const path = request.nextUrl.pathname; - if (path.startsWith("/editor") && env.NODE_ENV === "production") { + if (protectedPaths.includes(path) && env.NODE_ENV === "production") { const homeUrl = new URL("/", request.url); homeUrl.searchParams.set("redirect", request.url); return NextResponse.redirect(homeUrl); diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts index efb9cc71..5017be2d 100644 --- a/apps/web/src/stores/project-store.ts +++ b/apps/web/src/stores/project-store.ts @@ -5,6 +5,7 @@ import { toast } from "sonner"; import { useMediaStore } from "./media-store"; import { useTimelineStore } from "./timeline-store"; import { generateUUID } from "@/lib/utils"; +import { env } from "@/env"; interface ProjectStore { activeProject: TProject | null; @@ -36,6 +37,16 @@ export const useProjectStore = create((set, get) => ({ isInitialized: false, createNewProject: async (name: string) => { + try { + if (process.env.NODE_ENV !== "development") { + toast.error("Project creation is disabled outside development environment"); + throw new Error("Not allowed in production"); + } + } catch (error) { + toast.error("Failed to create new project"); + throw error; + } + const newProject: TProject = { id: generateUUID(), name, -- cgit v1.3.1 From 7d1f99c20ca60933984fe08ef83601248c6e0765 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 22:49:10 +0200 Subject: fix --- apps/web/src/app/projects/page.tsx | 12 ++---------- apps/web/src/stores/project-store.ts | 11 ----------- 2 files changed, 2 insertions(+), 21 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index 9fcb025b..499b999a 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -1,8 +1,7 @@ -"use client" -import { redirect } from "next/navigation"; +"use client"; import Link from "next/link"; -import React, { useState } from "react"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; @@ -29,15 +28,8 @@ import { useProjectStore } from "@/stores/project-store"; import { useRouter } from "next/navigation"; import { DeleteProjectDialog } from "@/components/delete-project-dialog"; import { RenameProjectDialog } from "@/components/rename-project-dialog"; -import { toast } from "sonner"; export default function ProjectsPage() { - - if (process.env.NODE_ENV !== "development") { - toast.error("You are not allowed to access this page"); - redirect("/"); - } - const { createNewProject, savedProjects, diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts index 5017be2d..efb9cc71 100644 --- a/apps/web/src/stores/project-store.ts +++ b/apps/web/src/stores/project-store.ts @@ -5,7 +5,6 @@ import { toast } from "sonner"; import { useMediaStore } from "./media-store"; import { useTimelineStore } from "./timeline-store"; import { generateUUID } from "@/lib/utils"; -import { env } from "@/env"; interface ProjectStore { activeProject: TProject | null; @@ -37,16 +36,6 @@ export const useProjectStore = create((set, get) => ({ isInitialized: false, createNewProject: async (name: string) => { - try { - if (process.env.NODE_ENV !== "development") { - toast.error("Project creation is disabled outside development environment"); - throw new Error("Not allowed in production"); - } - } catch (error) { - toast.error("Failed to create new project"); - throw error; - } - const newProject: TProject = { id: generateUUID(), name, -- cgit v1.3.1 From 36516b874f86daee8dea5b559de4fda2a4c602fa Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 22:50:40 +0200 Subject: remove onboarding --- apps/web/src/app/editor/[project_id]/page.tsx | 6 ------ 1 file changed, 6 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/app/editor/[project_id]/page.tsx b/apps/web/src/app/editor/[project_id]/page.tsx index 41bef8bd..aa5b7315 100644 --- a/apps/web/src/app/editor/[project_id]/page.tsx +++ b/apps/web/src/app/editor/[project_id]/page.tsx @@ -140,12 +140,6 @@ export default function Editor() {
    - { - setIsOnboardingOpen(false); - }} - /> ); } -- cgit v1.3.1 From a3c84e4a199add132340fb4f0a5e74e9062d5d5f Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 16 Jul 2025 23:28:26 +0200 Subject: fix handlebars --- apps/web/src/components/landing/handlebars.tsx | 48 +++----------------------- 1 file changed, 4 insertions(+), 44 deletions(-) (limited to 'apps/web') diff --git a/apps/web/src/components/landing/handlebars.tsx b/apps/web/src/components/landing/handlebars.tsx index e2eb2573..36705e90 100644 --- a/apps/web/src/components/landing/handlebars.tsx +++ b/apps/web/src/components/landing/handlebars.tsx @@ -19,7 +19,6 @@ export function Handlebars({ const [leftHandle, setLeftHandle] = useState(0); const [rightHandle, setRightHandle] = useState(maxWidth); const [contentWidth, setContentWidth] = useState(maxWidth); - const [isDragging, setIsDragging] = useState(false); const leftHandleX = useMotionValue(0); const rightHandleX = useMotionValue(maxWidth); @@ -34,27 +33,6 @@ export function Handlebars({ const containerRef = useRef(null); const measureRef = useRef(null); - // Prevent scroll when dragging on mobile - useEffect(() => { - const preventDefault = (e: TouchEvent) => { - if (isDragging) { - e.preventDefault(); - } - }; - - if (isDragging) { - document.addEventListener("touchmove", preventDefault, { - passive: false, - }); - document.body.style.overflow = "hidden"; - } - - return () => { - document.removeEventListener("touchmove", preventDefault); - document.body.style.overflow = ""; - }; - }, [isDragging]); - useEffect(() => { if (!measureRef.current) return; @@ -102,14 +80,6 @@ export function Handlebars({ setRightHandle(newRight); }; - const handleDragStart = () => { - setIsDragging(true); - }; - - const handleDragEnd = () => { - setIsDragging(false); - }; - return (
    - {/* Left Handle */}
    - {/* Right Handle */}
    @@ -201,4 +161,4 @@ export function Handlebars({
    ); -} +}; -- cgit v1.3.1