From ba2dfa8caa09813934e88548c87863c7144365c5 Mon Sep 17 00:00:00 2001 From: wass08 Date: Wed, 4 Mar 2026 13:38:51 +0100 Subject: [PATCH] presets community --- apps/editor/app/api/presets/[id]/route.ts | 22 ++- apps/editor/app/api/presets/route.ts | 1 - .../components/ui/panels/door-panel.tsx | 25 ++- .../ui/panels/presets/presets-popover.tsx | 157 +++++++++++++----- .../components/ui/panels/window-panel.tsx | 25 ++- 5 files changed, 174 insertions(+), 56 deletions(-) diff --git a/apps/editor/app/api/presets/[id]/route.ts b/apps/editor/app/api/presets/[id]/route.ts index 3fede668..828f38c6 100644 --- a/apps/editor/app/api/presets/[id]/route.ts +++ b/apps/editor/app/api/presets/[id]/route.ts @@ -4,6 +4,7 @@ import { auth } from '@/lib/auth' import { supabaseAdmin } from '@/lib/supabase/server' // PUT /api/presets/[id] +// Accepts any subset of: name, data, is_community export async function PUT( req: NextRequest, { params }: { params: Promise<{ id: string }> }, @@ -15,25 +16,30 @@ export async function PUT( const { id } = await params const body = await req.json() - const { name } = body + const { name, data, is_community } = body - if (!name) { - return NextResponse.json({ error: 'Missing name' }, { status: 400 }) + if (name === undefined && data === undefined && is_community === undefined) { + return NextResponse.json({ error: 'Nothing to update' }, { status: 400 }) } const { data: existing } = await supabaseAdmin .from('presets') - .select('user_id, is_community') + .select('user_id') .eq('id', id) .single() - if (!existing || existing.user_id !== session.user.id || existing.is_community) { + if (!existing || existing.user_id !== session.user.id) { return NextResponse.json({ error: 'Not found or forbidden' }, { status: 403 }) } + const updates: Record = {} + if (name !== undefined) updates.name = name + if (data !== undefined) updates.data = data + if (is_community !== undefined) updates.is_community = is_community + const { data: preset, error } = await supabaseAdmin .from('presets') - .update({ name }) + .update(updates) .eq('id', id) .select() .single() @@ -56,11 +62,11 @@ export async function DELETE( const { data: existing } = await supabaseAdmin .from('presets') - .select('user_id, is_community, thumbnail_url') + .select('user_id, thumbnail_url') .eq('id', id) .single() - if (!existing || existing.user_id !== session.user.id || existing.is_community) { + if (!existing || existing.user_id !== session.user.id) { return NextResponse.json({ error: 'Not found or forbidden' }, { status: 403 }) } diff --git a/apps/editor/app/api/presets/route.ts b/apps/editor/app/api/presets/route.ts index eb5d9367..10c41dfb 100644 --- a/apps/editor/app/api/presets/route.ts +++ b/apps/editor/app/api/presets/route.ts @@ -25,7 +25,6 @@ export async function GET(req: NextRequest) { .select('*') .eq('type', type) .eq('user_id', session.user.id) - .eq('is_community', false) .order('created_at', { ascending: false }) if (error) return NextResponse.json({ error: error.message }, { status: 500 }) diff --git a/apps/editor/components/ui/panels/door-panel.tsx b/apps/editor/components/ui/panels/door-panel.tsx index 386b0e2a..ed9f1f40 100644 --- a/apps/editor/components/ui/panels/door-panel.tsx +++ b/apps/editor/components/ui/panels/door-panel.tsx @@ -117,9 +117,9 @@ export function DoorPanel() { handleUpdate({ segments: updated }) } - const handleSavePreset = useCallback(async (name: string) => { - if (!node) return - const data = { + const getDoorPresetData = useCallback(() => { + if (!node) return null + return { width: node.width, height: node.height, frameThickness: node.frameThickness, @@ -137,12 +137,27 @@ export function DoorPanel() { panicBarHeight: node.panicBarHeight, segments: node.segments, } + }, [node]) + + const handleSavePreset = useCallback(async (name: string) => { + const data = getDoorPresetData() + if (!data) return await fetch('/api/presets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'door', name, data }), }) - }, [node]) + }, [getDoorPresetData]) + + const handleOverwritePreset = useCallback(async (id: string) => { + const data = getDoorPresetData() + if (!data) return + await fetch(`/api/presets/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ data }), + }) + }, [getDoorPresetData]) const handleApplyPreset = useCallback((data: Record) => { handleUpdate(data as Partial) @@ -162,7 +177,7 @@ export function DoorPanel() { > {/* Presets strip */}
- +
{isAuthenticated && ( )} - {/* Save input */} + {/* New preset name input */} {showSaveInput && (
setSaveName(e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') handleSave() + if (e.key === 'Enter') handleSaveNew() if (e.key === 'Escape') { setShowSaveInput(false); setSaveName('') } }} placeholder="Preset name…" @@ -151,7 +172,7 @@ export function PresetsPopover({ type, onApply, onSave, children }: PresetsPopov />
@@ -269,7 +290,10 @@ interface PresetRowProps { renamingId: string | null renameValue: string deletingId: string | null + overwrittenId: string | null onApply: () => void + onOverwrite: () => void + onToggleCommunity: () => void onStartRename: () => void onRenameChange: (v: string) => void onRenameConfirm: () => void @@ -285,7 +309,10 @@ function PresetRow({ renamingId, renameValue, deletingId, + overwrittenId, onApply, + onOverwrite, + onToggleCommunity, onStartRename, onRenameChange, onRenameConfirm, @@ -296,6 +323,7 @@ function PresetRow({ }: PresetRowProps) { const isRenaming = renamingId === preset.id const isDeleting = deletingId === preset.id + const justOverwritten = overwrittenId === preset.id if (isDeleting) { return ( @@ -350,7 +378,7 @@ function PresetRow({ return (
  • - {/* Thumbnail placeholder */} + {/* Thumbnail */}
    {preset.thumbnail_url ? ( // eslint-disable-next-line @next/next/no-img-element @@ -362,32 +390,87 @@ function PresetRow({ )}
    - + {/* Actions — only shown on hover for "My presets" */} {isMine && (
    - - + {/* Save into (overwrite) */} + + + + + Save current config here + + + {/* Community toggle */} + + + + + + {preset.is_community ? 'Remove from community' : 'Share with community'} + + + + {/* Rename */} + + + + + Rename + + + {/* Delete */} + + + + + Delete +
    )}
  • diff --git a/apps/editor/components/ui/panels/window-panel.tsx b/apps/editor/components/ui/panels/window-panel.tsx index 80f64679..e4238aac 100644 --- a/apps/editor/components/ui/panels/window-panel.tsx +++ b/apps/editor/components/ui/panels/window-panel.tsx @@ -92,9 +92,9 @@ export function WindowPanel() { setSelection({ selectedIds: [] }) }, [node, setMovingNode, setSelection]) - const handleSavePreset = useCallback(async (name: string) => { - if (!node) return - const data = { + const getWindowPresetData = useCallback(() => { + if (!node) return null + return { width: node.width, height: node.height, frameThickness: node.frameThickness, @@ -107,12 +107,27 @@ export function WindowPanel() { sillDepth: node.sillDepth, sillThickness: node.sillThickness, } + }, [node]) + + const handleSavePreset = useCallback(async (name: string) => { + const data = getWindowPresetData() + if (!data) return await fetch('/api/presets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'window', name, data }), }) - }, [node]) + }, [getWindowPresetData]) + + const handleOverwritePreset = useCallback(async (id: string) => { + const data = getWindowPresetData() + if (!data) return + await fetch(`/api/presets/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ data }), + }) + }, [getWindowPresetData]) const handleApplyPreset = useCallback((data: Record) => { handleUpdate(data as Partial) @@ -163,7 +178,7 @@ export function WindowPanel() { > {/* Presets strip */}
    - +