From a60a7a3d1f9166110b07342f2b22b7b73fc327f3 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Wed, 1 Apr 2026 04:02:41 -0500 Subject: [PATCH 1/2] Fix marketing Vercel output config --- apps/marketing/next-env.d.ts | 2 +- apps/marketing/next.config.mjs | 3 +++ turbo.json | 4 ++++ vercel.json | 6 ++++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 vercel.json diff --git a/apps/marketing/next-env.d.ts b/apps/marketing/next-env.d.ts index c4b7818fb..9edff1c7c 100644 --- a/apps/marketing/next-env.d.ts +++ b/apps/marketing/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/marketing/next.config.mjs b/apps/marketing/next.config.mjs index bd3419136..8ee7a7de9 100644 --- a/apps/marketing/next.config.mjs +++ b/apps/marketing/next.config.mjs @@ -1,5 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + turbopack: { + root: new URL("../..", import.meta.url).pathname, + }, typescript: { ignoreBuildErrors: true, }, diff --git a/turbo.json b/turbo.json index c9471e538..040ee2c39 100644 --- a/turbo.json +++ b/turbo.json @@ -18,6 +18,10 @@ "dependsOn": ["^build"], "outputs": ["dist/**", "dist-electron/**"] }, + "@okcode/marketing#build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**"] + }, "dev": { "dependsOn": ["@okcode/contracts#build"], "cache": false, diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..dc2a991dd --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "bun run build:marketing", + "installCommand": "bun install", + "outputDirectory": "apps/marketing/.next" +} From c078320251535cb4aa9e1231c99852019da6a799 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Wed, 1 Apr 2026 04:23:24 -0500 Subject: [PATCH 2/2] Remove YouTube player --- apps/marketing/components/okcode-mockup.tsx | 2 - apps/web/src/components/ChatView.tsx | 3 - apps/web/src/components/Sidebar.tsx | 4 - apps/web/src/components/YouTubePlayer.tsx | 540 -------------------- apps/web/src/youtubePlayerStore.ts | 202 -------- 5 files changed, 751 deletions(-) delete mode 100644 apps/web/src/components/YouTubePlayer.tsx delete mode 100644 apps/web/src/youtubePlayerStore.ts diff --git a/apps/marketing/components/okcode-mockup.tsx b/apps/marketing/components/okcode-mockup.tsx index cdcac6a9b..c5b45ad5d 100644 --- a/apps/marketing/components/okcode-mockup.tsx +++ b/apps/marketing/components/okcode-mockup.tsx @@ -21,7 +21,6 @@ import { Monitor, MessageSquare, Code, - Check, Clock, } from "lucide-react"; import { useState } from "react"; @@ -149,7 +148,6 @@ export function OKCodeMockup() {
Files
- diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index e3c2b3122..8272abe33 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -99,7 +99,6 @@ import { resolveShortcutCommand, shortcutLabelForCommand } from "../keybindings" import { buildChatShortcutGuides } from "~/lib/chatShortcutGuidance"; import PlanSidebar from "./PlanSidebar"; import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; -import { YouTubePlayerDrawer } from "./YouTubePlayer"; import { AtSignIcon, BotIcon, @@ -5538,8 +5537,6 @@ export default function ChatView({ threadId }: ChatViewProps) { ); })()} - - { diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index b26546c61..464ff400d 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -14,7 +14,6 @@ import { TerminalIcon, TriangleAlertIcon, } from "lucide-react"; -import { YouTubeToggleButton } from "./YouTubePlayer"; import { ThemeModeSwitcher } from "./ThemeModeSwitcher"; import { autoAnimate } from "@formkit/auto-animate"; import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; @@ -2065,9 +2064,6 @@ export default function Sidebar() { - - - {isOnSubPage ? ( { - if (selectedIndex === null) return null; - if (selectedIndex < DEFAULT_PLAYLISTS.length) { - return DEFAULT_PLAYLISTS[selectedIndex]?.name ?? null; - } - const slotIdx = selectedIndex - DEFAULT_PLAYLISTS.length; - return customSlots[slotIdx]?.name ?? null; - }, [selectedIndex, customSlots]); - - return ( - - ); -} - -// --------------------------------------------------------------------------- -// Volume slider — controls the actual iframe player volume -// --------------------------------------------------------------------------- -function VolumeControl({ iframeRef }: { iframeRef: React.RefObject }) { - const { volume, setVolume } = useYouTubePlayerStore(); - const [premuteVolume, setPremuteVolume] = useState(80); - - // Sync volume to the YouTube iframe whenever it changes - useEffect(() => { - const iframe = iframeRef.current; - if (!iframe) return; - if (volume === 0) { - ytCommand(iframe, "mute"); - } else { - ytCommand(iframe, "unMute"); - ytCommand(iframe, "setVolume", [volume]); - } - }, [volume, iframeRef]); - - const toggleMute = useCallback(() => { - if (volume > 0) { - setPremuteVolume(volume); - setVolume(0); - } else { - setVolume(premuteVolume || 80); - } - }, [volume, premuteVolume, setVolume]); - - const VolumeIcon = volume === 0 ? VolumeXIcon : volume < 50 ? Volume1Icon : Volume2Icon; - - return ( -
- -
- {/* Filled track behind the slider */} -
- setVolume(Number(e.target.value))} - className="relative h-1 w-16 cursor-pointer appearance-none rounded-full bg-muted-foreground/20 [&::-webkit-slider-thumb]:relative [&::-webkit-slider-thumb]:z-10 [&::-webkit-slider-thumb]:size-2.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-red-400 [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-125 [&::-moz-range-thumb]:size-2.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-red-400 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-muted-foreground/20" - aria-label="Volume" - /> -
-
- ); -} - -// --------------------------------------------------------------------------- -// Custom slot editor inline form -// --------------------------------------------------------------------------- -function CustomSlotEditor({ - slotIndex, - existingSlot, - onDone, -}: { - slotIndex: 0 | 1; - existingSlot: CustomSlot | null; - onDone: () => void; -}) { - const { setCustomSlot } = useYouTubePlayerStore(); - const [name, setName] = useState(existingSlot?.name ?? ""); - const [url, setUrl] = useState(existingSlot?.url ?? ""); - const [error, setError] = useState(null); - const urlRef = useRef(null); - - // Auto-focus the URL field on mount - useEffect(() => { - urlRef.current?.focus(); - }, []); - - const handleSave = useCallback(() => { - const trimmedName = name.trim() || `Custom ${slotIndex + 1}`; - const trimmedUrl = url.trim(); - if (!trimmedUrl) { - setError("Paste a YouTube URL"); - return; - } - const parsed = parseYouTubeUrl(trimmedUrl); - if (!parsed) { - setError("Not a valid YouTube URL or video ID"); - return; - } - setError(null); - setCustomSlot(slotIndex, trimmedName, trimmedUrl); - onDone(); - }, [name, url, slotIndex, setCustomSlot, onDone]); - - return ( -
- setName(e.target.value)} - placeholder={`Name (optional)`} - className="rounded-md border border-border/60 bg-background px-2 py-1 text-[11px] text-foreground placeholder:text-muted-foreground/40 focus:border-red-500/50 focus:outline-none" - /> - { - setUrl(e.target.value); - if (error) setError(null); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - handleSave(); - } - if (e.key === "Escape") { - e.preventDefault(); - onDone(); - } - }} - placeholder="Paste YouTube URL..." - className={cn( - "rounded-md border bg-background px-2 py-1 text-[11px] text-foreground placeholder:text-muted-foreground/40 focus:outline-none", - error - ? "border-red-500/60 focus:border-red-500/80" - : "border-border/60 focus:border-red-500/50", - )} - /> - {error &&

{error}

} -
- - -
-
- ); -} - -// --------------------------------------------------------------------------- -// Main YouTube Player Drawer — rendered at the bottom of ChatView -// --------------------------------------------------------------------------- -export function YouTubePlayerDrawer() { - const { - isOpen, - minimized, - selectedIndex, - volume, - customSlots, - setOpen, - setMinimized, - selectByIndex, - clearCustomSlot, - } = useYouTubePlayerStore(); - const [expanded, setExpanded] = useState(false); - const [editingSlot, setEditingSlot] = useState<0 | 1 | null>(null); - const iframeRef = useRef(null); - - const activeName = useMemo(() => { - if (selectedIndex === null) return null; - if (selectedIndex < DEFAULT_PLAYLISTS.length) { - return DEFAULT_PLAYLISTS[selectedIndex]?.name ?? null; - } - const slotIdx = selectedIndex - DEFAULT_PLAYLISTS.length; - return customSlots[slotIdx]?.name ?? null; - }, [selectedIndex, customSlots]); - - const embedUrl = useMemo(() => { - if (selectedIndex === null) return null; - - // Default playlists - if (selectedIndex < DEFAULT_PLAYLISTS.length) { - const pl = DEFAULT_PLAYLISTS[selectedIndex]; - if (!pl) return null; - return buildYouTubeEmbedUrl(pl.type, pl.id); - } - - // Custom slots - const slotIdx = selectedIndex - DEFAULT_PLAYLISTS.length; - const slot = customSlots[slotIdx]; - if (!slot) return null; - const parsed = parseYouTubeUrl(slot.url); - if (!parsed) return null; - return buildYouTubeEmbedUrl(parsed.type, parsed.id); - }, [selectedIndex, customSlots]); - - // When the iframe loads (new video / playlist), apply the stored volume. - const handleIframeLoad = useCallback(() => { - const iframe = iframeRef.current; - if (!iframe) return; - // Small delay to let the YT player initialise its JS API listener - const timer = setTimeout(() => { - if (volume === 0) { - ytCommand(iframe, "mute"); - } else { - ytCommand(iframe, "unMute"); - ytCommand(iframe, "setVolume", [volume]); - } - }, 500); - return () => clearTimeout(timer); - }, [volume]); - - // Listen for the YouTube player's "onReady" info-delivery message - // so we can set volume as soon as it's truly ready. - useEffect(() => { - function onMessage(e: MessageEvent) { - if (typeof e.data !== "string") return; - try { - const msg = JSON.parse(e.data); - // YouTube fires { event: "onReady" } and also - // { event: "initialDelivery", info: { ... } } - if (msg?.event === "onReady" || msg?.event === "initialDelivery") { - const iframe = iframeRef.current; - if (!iframe) return; - if (volume === 0) { - ytCommand(iframe, "mute"); - } else { - ytCommand(iframe, "unMute"); - ytCommand(iframe, "setVolume", [volume]); - } - } - } catch { - // Not a JSON message — ignore. - } - } - window.addEventListener("message", onMessage); - return () => window.removeEventListener("message", onMessage); - }, [volume]); - - if (!isOpen) return null; - - return ( -
- {/* Header bar — always visible */} -
- - - {/* Now-playing label */} - - {activeName ?? "YouTube"} - - - {/* Spacer */} -
- - {/* Volume — wired to the iframe */} - - - {/* Playlist picker toggle */} - {!minimized && ( - - )} - - {/* Minimize / Restore */} - - - {/* Close */} - -
- - {/* Expanded playlist picker (hidden when minimized) */} - {expanded && !minimized && ( -
-
- {/* Default playlists */} - {DEFAULT_PLAYLISTS.map((pl, idx) => ( - - ))} - - {/* Divider */} -
- - {/* Custom slots */} - {([0, 1] as const).map((slotIdx) => { - const globalIdx = DEFAULT_PLAYLISTS.length + slotIdx; - const slot = customSlots[slotIdx]; - - if (editingSlot === slotIdx) { - return ( - setEditingSlot(null)} - /> - ); - } - - if (slot) { - return ( -
- - - -
- ); - } - - // Empty slot - return ( - - ); - })} -
-
- )} - - {/* YouTube embed iframe — kept in DOM when minimized so audio continues */} - {embedUrl ? ( -
-