From c16f2f377fa16e78a9510102dec0cfbc64638f48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 15:53:08 +0000 Subject: [PATCH 1/2] chore: start builder walkthrough overhaul plan Agent-Logs-Url: https://github.com/SpicyDevs/posterium-frontend/sessions/a76209d8-7169-468c-a429-6bfa7055b372 Co-authored-by: ikrdikhit <155148519+ikrdikhit@users.noreply.github.com> --- package-lock.json | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f62847a..7c98a976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -207,7 +207,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -4297,7 +4296,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4308,7 +4306,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4531,7 +4528,6 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4741,7 +4737,6 @@ "integrity": "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", @@ -4865,7 +4860,6 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -5135,7 +5129,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -9782,7 +9775,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9931,7 +9923,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9941,7 +9932,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10064,8 +10054,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -10481,7 +10470,6 @@ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -11624,7 +11612,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11696,7 +11683,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -12176,7 +12162,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -13230,7 +13215,6 @@ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -13399,7 +13383,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, @@ -14070,7 +14053,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From cd2c08501a5d5e654c980b7b1dcf399e163b0f55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 15:55:07 +0000 Subject: [PATCH 2/2] feat: add walkthrough mode, dropdown selector, and legacy builder route Agent-Logs-Url: https://github.com/SpicyDevs/posterium-frontend/sessions/a76209d8-7169-468c-a429-6bfa7055b372 Co-authored-by: ikrdikhit <155148519+ikrdikhit@users.noreply.github.com> --- .../components/navigation/ModeToggle.tsx | 89 +++++-- .../walkthrough/RevealAndExport.tsx | 110 ++++++++ .../walkthrough/SkeletonLayoutSelector.tsx | 91 +++++++ .../components/walkthrough/StepIndicator.tsx | 26 ++ .../walkthrough/WalkthroughLayout.tsx | 179 +++++++++++++ src/components/builder/index.tsx | 247 ++++++++++-------- src/pages/build.astro | 2 +- src/pages/builder/legacy.astro | 50 ++++ 8 files changed, 666 insertions(+), 128 deletions(-) create mode 100644 src/components/builder/components/walkthrough/RevealAndExport.tsx create mode 100644 src/components/builder/components/walkthrough/SkeletonLayoutSelector.tsx create mode 100644 src/components/builder/components/walkthrough/StepIndicator.tsx create mode 100644 src/components/builder/components/walkthrough/WalkthroughLayout.tsx create mode 100644 src/pages/builder/legacy.astro diff --git a/src/components/builder/components/navigation/ModeToggle.tsx b/src/components/builder/components/navigation/ModeToggle.tsx index 2deeba8a..dc01ba71 100644 --- a/src/components/builder/components/navigation/ModeToggle.tsx +++ b/src/components/builder/components/navigation/ModeToggle.tsx @@ -1,38 +1,77 @@ -import React, { memo } from 'react'; +import React, { memo, Fragment } from 'react'; +import { Menu, Transition } from '@headlessui/react'; +import { Check, ChevronDown } from 'lucide-react'; -export type BuilderMode = 'simple' | 'advanced'; +export type BuilderMode = 'walkthrough' | 'simple' | 'advanced'; interface Props { mode: BuilderMode; onChange: (mode: BuilderMode) => void; } -const ModeToggle: React.FC = memo(({ mode, onChange }) => ( -
- {(['simple', 'advanced'] as const).map((item) => { - const active = mode === item; - return ( - - ); - })} -
-)); + {MODE_OPTIONS.map((item) => ( + + {({ active }) => ( + + )} + + ))} + + + + ); +}); ModeToggle.displayName = 'ModeToggle'; export default ModeToggle; diff --git a/src/components/builder/components/walkthrough/RevealAndExport.tsx b/src/components/builder/components/walkthrough/RevealAndExport.tsx new file mode 100644 index 00000000..71c8bb1e --- /dev/null +++ b/src/components/builder/components/walkthrough/RevealAndExport.tsx @@ -0,0 +1,110 @@ +import React, { memo, useRef, useState } from 'react'; +import { Check, Copy, Download } from 'lucide-react'; +import PreviewCanvas from '../PreviewCanvas'; +import ExportPopover from '../ExportPopover'; +import type { ExtensionType, PosterConfig, RatingType } from '../../types'; +import { generateApiUrl } from '../../utils'; + +interface Props { + config: PosterConfig; + setConfig: React.Dispatch>; + selectedIds: Set; + onSelect: (id: RatingType, multi: boolean) => void; + baseUrl: string; + handleLoadConfig: (url: string) => void; +} + +const RevealAndExport: React.FC = memo( + ({ config, setConfig, selectedIds, onSelect, baseUrl, handleLoadConfig }) => { + const [exportOpen, setExportOpen] = useState(false); + const [copied, setCopied] = useState(false); + const exportBtnRef = useRef(null); + + const setExtension = (ext: ExtensionType) => setConfig((prev) => ({ ...prev, extension: ext })); + + const copyApiUrl = async () => { + const url = generateApiUrl(config, baseUrl); + await navigator.clipboard.writeText(url); + setCopied(true); + window.setTimeout(() => setCopied(false), 1200); + }; + + return ( +
+
+ +
+ +
+
+ {(['png', 'jpg', 'webp', 'svg'] as ExtensionType[]).map((ext) => ( + + ))} + + + + +
+ + setExportOpen(false)} + anchorRef={exportBtnRef} + /> +
+
+ ); + } +); + +RevealAndExport.displayName = 'RevealAndExport'; +export default RevealAndExport; diff --git a/src/components/builder/components/walkthrough/SkeletonLayoutSelector.tsx b/src/components/builder/components/walkthrough/SkeletonLayoutSelector.tsx new file mode 100644 index 00000000..360b9bed --- /dev/null +++ b/src/components/builder/components/walkthrough/SkeletonLayoutSelector.tsx @@ -0,0 +1,91 @@ +import React, { memo } from 'react'; +import clsx from 'clsx'; +import type { LayoutType, PosterConfig, PresetType } from '../../types'; + +interface LayoutOption { + id: string; + name: string; + preset: PresetType; + layout: LayoutType; +} + +interface Props { + config: PosterConfig; + setConfig: React.Dispatch>; +} + +const OPTIONS: LayoutOption[] = [ + { id: 'top-corners', name: 'Top Corners', preset: 'tl', layout: 'row' }, + { id: 'bottom-bar', name: 'Bottom Bar', preset: 'bc', layout: 'row' }, + { id: 'minimalist', name: 'Minimalist', preset: 'br', layout: 'col' }, +]; + +const SkeletonLayoutSelector: React.FC = memo(({ config, setConfig }) => { + const applyLayout = (preset: PresetType, layout: LayoutType) => { + setConfig((prev) => { + const newItems = { ...prev.items }; + (Object.keys(newItems) as (keyof typeof newItems)[]).forEach((k) => { + const current = newItems[k]; + if (!current) return; + const { x: _x, y: _y, ...rest } = current; + newItems[k] = rest; + }); + + return { ...prev, preset, layout, items: newItems }; + }); + }; + + return ( +
+ {OPTIONS.map((item) => { + const active = config.preset === item.preset && config.layout === item.layout; + return ( + + ); + })} +
+ ); +}); + +SkeletonLayoutSelector.displayName = 'SkeletonLayoutSelector'; +export default SkeletonLayoutSelector; diff --git a/src/components/builder/components/walkthrough/StepIndicator.tsx b/src/components/builder/components/walkthrough/StepIndicator.tsx new file mode 100644 index 00000000..f1934de6 --- /dev/null +++ b/src/components/builder/components/walkthrough/StepIndicator.tsx @@ -0,0 +1,26 @@ +import React, { memo } from 'react'; + +interface Props { + currentStep: number; + steps: readonly string[]; +} + +const StepIndicator: React.FC = memo(({ currentStep, steps }) => ( +
+
+
+
+
+ Step {currentStep + 1} · {steps[currentStep]} +
+
+)); + +StepIndicator.displayName = 'StepIndicator'; +export default StepIndicator; diff --git a/src/components/builder/components/walkthrough/WalkthroughLayout.tsx b/src/components/builder/components/walkthrough/WalkthroughLayout.tsx new file mode 100644 index 00000000..ad39d900 --- /dev/null +++ b/src/components/builder/components/walkthrough/WalkthroughLayout.tsx @@ -0,0 +1,179 @@ +import React, { memo, useMemo, useState } from 'react'; +import clsx from 'clsx'; +import { ArrowLeft, ArrowRight, X } from 'lucide-react'; +import type { PosterConfig, RatingType } from '../../types'; +import { ALL_BADGES } from '../../types'; +import { useEditor } from '../../context/EditorContext'; +import ModeToggle, { type BuilderMode } from '../navigation/ModeToggle'; +import SourcePanel from '../panels/SourcePanel'; +import StepIndicator from './StepIndicator'; +import SkeletonLayoutSelector from './SkeletonLayoutSelector'; +import RevealAndExport from './RevealAndExport'; + +const STEPS = ['Choose Media', 'Choose Layout', 'Select Ratings', 'Reveal & Export'] as const; + +interface Props { + config: PosterConfig; + setConfig: React.Dispatch>; + baseUrl: string; + handleLoadConfig: (url: string) => void; + mode: BuilderMode; + onModeChange: (mode: BuilderMode) => void; +} + +const WalkthroughLayout: React.FC = memo( + ({ config, setConfig, baseUrl, handleLoadConfig, mode, onModeChange }) => { + const { selectedIds, handleSelection } = useEditor(); + const [step, setStep] = useState(0); + + const canContinue = useMemo(() => { + if (step === 0) return Boolean(config.tmdbId || config.imdbId); + if (step === 2) return config.ratings.length > 0; + return true; + }, [step, config.tmdbId, config.imdbId, config.ratings.length]); + + const toggleBadge = (id: RatingType) => { + setConfig((prev) => + prev.ratings.includes(id) + ? { ...prev, ratings: prev.ratings.filter((r) => r !== id) } + : { ...prev, ratings: [id, ...prev.ratings] } + ); + }; + + return ( +
+
+ + POSTERIUM + + + +
+ + + +
+
+ {step === 0 && ( +
+ +
+ )} + + {step === 1 && } + + {step === 2 && ( +
+ {ALL_BADGES.map((badge) => { + const active = config.ratings.includes(badge.id); + return ( + + ); + })} +
+ )} + + {step === 3 && ( + + )} +
+
+ +
+ + + +
+
+ ); + } +); + +WalkthroughLayout.displayName = 'WalkthroughLayout'; +export default WalkthroughLayout; diff --git a/src/components/builder/index.tsx b/src/components/builder/index.tsx index 6dc99d42..c48b923d 100644 --- a/src/components/builder/index.tsx +++ b/src/components/builder/index.tsx @@ -33,6 +33,7 @@ import MobileDock from './components/layout/MobileDock'; import ToolbarBtn from './components/toolbar/ToolbarButton'; import ZoomOverlay from './components/canvas/ZoomOverlay'; import { EditorProvider, useEditor } from './context/EditorContext'; +import WalkthroughLayout from './components/walkthrough/WalkthroughLayout'; import { RotateCcw, Undo2, @@ -82,7 +83,9 @@ const StudioLayout: React.FC<{ redo: () => void; canUndo: boolean; canRedo: boolean; - initialMode?: BuilderMode; + builderMode: Exclude; + onBuilderModeChange: (mode: BuilderMode) => void; + legacySimple?: boolean; }> = ({ config, setConfig, @@ -93,7 +96,9 @@ const StudioLayout: React.FC<{ redo, canUndo, canRedo, - initialMode = 'simple', + builderMode, + onBuilderModeChange, + legacySimple = false, }) => { const { activeTab, @@ -111,7 +116,6 @@ const StudioLayout: React.FC<{ toggleViewOption, } = useEditor(); - const [builderMode, setBuilderMode] = useState(initialMode); const [advancedPanel, setAdvancedPanel] = useState('source'); const [isResetOpen, setIsResetOpen] = useState(false); const [isImportOpen, setIsImportOpen] = useState(false); @@ -160,6 +164,7 @@ const StudioLayout: React.FC<{ }, []); const closeCtxMenu = useCallback(() => setCtxMenu((s) => ({ ...s, visible: false })), []); const [paletteOpen, setPaletteOpen] = useState(false); + const isStrippedSimple = builderMode === 'simple' && !legacySimple; const selectedIdsRef = useRef(selectedIds); const selectedLogoRef = useRef(selectedLogo); @@ -1068,38 +1073,43 @@ const StudioLayout: React.FC<{ P - - - setShortcutsOpen((v) => !v)} - label="Keyboard Shortcuts (⌘/)" - active={shortcutsOpen} - hideOnMobile - > - - + + {!isStrippedSimple && ( + <> + + setShortcutsOpen((v) => !v)} + label="Keyboard Shortcuts (⌘/)" + active={shortcutsOpen} + hideOnMobile + > + + + + )}
{/* Central area: sidebar toggles flank the command palette search */} -
+ {!isStrippedSimple && ( +
{/* Left sidebar toggle - desktop only */} -
+
+ )} {/* Right Header Area */}
-