From ee30f17129dde2f0fc65ecc01272ef0306312773 Mon Sep 17 00:00:00 2001 From: Anton Pascal Date: Tue, 17 Feb 2026 20:40:52 +0000 Subject: [PATCH] feat: rename properties to projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename all DB tables: properties → projects, properties_addresses → projects_addresses, properties_models → projects_models, property_likes → projects_likes - Add projects_likes as proper Drizzle schema table (was SQL-only before) - Rename ID prefixes: property_xxx → project_xxx - Rename auth_sessions column: active_property_id → active_project_id - Simplify project creation: address is now optional (no longer required to get started) - Update all actions, stores, types, components, and routes - Rename route: /editor/[propertyId] → /editor/[projectId] - Add SQL migration for table/column/index/policy/function renames - Update RPC functions: increment_project_views, get_project_like_count - Update storage bucket reference: project-thumbnails - All types/exports follow existing Drizzle patterns (pgTable, relations, zod schemas) --- apps/editor/app/editor/[projectId]/page.tsx | 30 ++ apps/editor/app/editor/[propertyId]/page.tsx | 30 -- apps/editor/app/viewer/[id]/page.tsx | 18 +- .../app/viewer/[id]/thumbnail-generator.tsx | 29 +- apps/editor/components/editor/index.tsx | 26 +- .../sidebar/panels/settings-panel/index.tsx | 22 +- .../components/cloud-save-button.tsx | 24 +- .../community/components/community-hub.tsx | 166 +++---- ...y-button.tsx => create-project-button.tsx} | 10 +- .../components/google-address-search.tsx | 2 +- ...tsx => local-project-migration-dialog.tsx} | 24 +- ...erty-dialog.tsx => new-project-dialog.tsx} | 117 +++-- .../community/components/project-dropdown.tsx | 109 +++++ .../{property-grid.tsx => project-grid.tsx} | 132 ++--- ...dialog.tsx => project-settings-dialog.tsx} | 80 +-- .../components/property-dropdown.tsx | 109 ----- .../community/lib/local-storage/hooks.ts | 40 +- .../lib/local-storage/project-store.ts | 114 +++++ .../lib/local-storage/property-store.ts | 89 ---- .../features/community/lib/models/actions.ts | 92 ++-- .../features/community/lib/models/hooks.ts | 60 +-- .../lib/{properties => projects}/actions.ts | 457 +++++++++--------- .../features/community/lib/projects/store.ts | 89 ++++ .../lib/{properties => projects}/types.ts | 66 +-- .../community/lib/properties/store.ts | 89 ---- apps/editor/lib/navigation.ts | 10 +- packages/core/src/events/bus.ts | 2 +- packages/db/src/schema/auth/sessions.ts | 4 +- packages/db/src/schema/index.ts | 9 +- .../{properties => projects}/addresses.ts | 2 +- packages/db/src/schema/projects/likes.ts | 36 ++ .../schema/{properties => projects}/models.ts | 16 +- .../properties.ts => projects/projects.ts} | 33 +- packages/db/src/types.ts | 18 +- ...17000001_rename_properties_to_projects.sql | 247 ++++++++++ 35 files changed, 1376 insertions(+), 1025 deletions(-) create mode 100644 apps/editor/app/editor/[projectId]/page.tsx delete mode 100644 apps/editor/app/editor/[propertyId]/page.tsx rename apps/editor/features/community/components/{create-property-button.tsx => create-project-button.tsx} (54%) rename apps/editor/features/community/components/{local-property-migration-dialog.tsx => local-project-migration-dialog.tsx} (67%) rename apps/editor/features/community/components/{new-property-dialog.tsx => new-project-dialog.tsx} (53%) create mode 100644 apps/editor/features/community/components/project-dropdown.tsx rename apps/editor/features/community/components/{property-grid.tsx => project-grid.tsx} (58%) rename apps/editor/features/community/components/{property-settings-dialog.tsx => project-settings-dialog.tsx} (74%) delete mode 100644 apps/editor/features/community/components/property-dropdown.tsx create mode 100644 apps/editor/features/community/lib/local-storage/project-store.ts delete mode 100644 apps/editor/features/community/lib/local-storage/property-store.ts rename apps/editor/features/community/lib/{properties => projects}/actions.ts (60%) create mode 100644 apps/editor/features/community/lib/projects/store.ts rename apps/editor/features/community/lib/{properties => projects}/types.ts (56%) delete mode 100644 apps/editor/features/community/lib/properties/store.ts rename packages/db/src/schema/{properties => projects}/addresses.ts (98%) create mode 100644 packages/db/src/schema/projects/likes.ts rename packages/db/src/schema/{properties => projects}/models.ts (72%) rename packages/db/src/schema/{properties/properties.ts => projects/projects.ts} (55%) create mode 100644 packages/db/supabase/migrations/20240217000001_rename_properties_to_projects.sql diff --git a/apps/editor/app/editor/[projectId]/page.tsx b/apps/editor/app/editor/[projectId]/page.tsx new file mode 100644 index 00000000..09102acc --- /dev/null +++ b/apps/editor/app/editor/[projectId]/page.tsx @@ -0,0 +1,30 @@ +'use client' + +import Editor from '@/components/editor' +import { useParams } from 'next/navigation' +import { useEffect, useLayoutEffect } from 'react' +import { useProjectStore } from '@/features/community/lib/projects/store' +import { useAuth } from '@/features/community/lib/auth/hooks' + +export default function EditorPage() { + const params = useParams() + const projectId = params.projectId as string + const { isAuthenticated } = useAuth() + const setActiveProject = useProjectStore((state) => state.setActiveProject) + + // Use layoutEffect to set active project BEFORE the editor renders and hooks run + useLayoutEffect(() => { + // For authenticated users with cloud projects, set the active project from URL + if (isAuthenticated && projectId && !projectId.startsWith('local_')) { + setActiveProject(projectId) + } + }, [projectId, isAuthenticated, setActiveProject]) + + return ( +
+
+ +
+
+ ) +} diff --git a/apps/editor/app/editor/[propertyId]/page.tsx b/apps/editor/app/editor/[propertyId]/page.tsx deleted file mode 100644 index 47954a0a..00000000 --- a/apps/editor/app/editor/[propertyId]/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client' - -import Editor from '@/components/editor' -import { useParams } from 'next/navigation' -import { useEffect, useLayoutEffect } from 'react' -import { usePropertyStore } from '@/features/community/lib/properties/store' -import { useAuth } from '@/features/community/lib/auth/hooks' - -export default function EditorPage() { - const params = useParams() - const propertyId = params.propertyId as string - const { isAuthenticated } = useAuth() - const setActiveProperty = usePropertyStore((state) => state.setActiveProperty) - - // Use layoutEffect to set active property BEFORE the editor renders and hooks run - useLayoutEffect(() => { - // For authenticated users with cloud properties, set the active property from URL - if (isAuthenticated && propertyId && !propertyId.startsWith('local_')) { - setActiveProperty(propertyId) - } - }, [propertyId, isAuthenticated, setActiveProperty]) - - return ( -
-
- -
-
- ) -} diff --git a/apps/editor/app/viewer/[id]/page.tsx b/apps/editor/app/viewer/[id]/page.tsx index ed4266ea..ea0d89aa 100644 --- a/apps/editor/app/viewer/[id]/page.tsx +++ b/apps/editor/app/viewer/[id]/page.tsx @@ -8,14 +8,14 @@ import { ViewerCameraControls } from './viewer-camera-controls' import { ViewerOverlay } from './viewer-overlay' import { ViewerZoneSystem } from './viewer-zone-system' import { ThumbnailGenerator } from './thumbnail-generator' -import { getPropertyModelPublic, incrementPropertyViews } from '@/features/community/lib/properties/actions' +import { getProjectModelPublic, incrementProjectViews } from '@/features/community/lib/projects/actions' export default function ViewerPage() { const params = useParams() const id = params.id as string const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [propertyId, setPropertyId] = useState(null) + const [projectId, setProjectId] = useState(null) const setScene = useScene((state) => state.setScene) useEffect(() => { @@ -33,12 +33,12 @@ export default function ViewerPage() { initSpatialGridSync() } } else { - // Load from database (public property) - const result = await getPropertyModelPublic(id) + // Load from database (public project) + const result = await getProjectModelPublic(id) if (result.success && result.data) { - const { property, model } = result.data - setPropertyId(property.id) + const { project, model } = result.data + setProjectId(project.id) if (model?.scene_graph) { const { nodes, rootNodeIds } = model.scene_graph @@ -47,9 +47,9 @@ export default function ViewerPage() { } // Increment view count - await incrementPropertyViews(id) + await incrementProjectViews(id) } else { - throw new Error(result.error || 'Property not found') + throw new Error(result.error || 'Project not found') } } @@ -88,7 +88,7 @@ export default function ViewerPage() { {/* Custom Zone System */} {/* Thumbnail Generator */} - + ) diff --git a/apps/editor/app/viewer/[id]/thumbnail-generator.tsx b/apps/editor/app/viewer/[id]/thumbnail-generator.tsx index f9909caf..df885751 100644 --- a/apps/editor/app/viewer/[id]/thumbnail-generator.tsx +++ b/apps/editor/app/viewer/[id]/thumbnail-generator.tsx @@ -4,44 +4,41 @@ import { emitter } from '@pascal-app/core' import { useThree } from '@react-three/fiber' import { useEffect, useRef } from 'react' import * as THREE from 'three' -import { uploadPropertyThumbnail } from '@/features/community/lib/properties/actions' +import { uploadProjectThumbnail } from '@/features/community/lib/projects/actions' const THUMBNAIL_WIDTH = 1920 const THUMBNAIL_HEIGHT = 1080 interface ThumbnailGeneratorProps { - propertyId?: string + projectId?: string } -export const ThumbnailGenerator = ({ propertyId: propPropertyId }: ThumbnailGeneratorProps) => { +export const ThumbnailGenerator = ({ projectId: propProjectId }: ThumbnailGeneratorProps) => { const gl = useThree((state) => state.gl) const scene = useThree((state) => state.scene) const camera = useThree((state) => state.camera) const isGenerating = useRef(false) - // Use prop propertyId (from URL) - const fallbackPropertyId = propPropertyId + // Use prop projectId (from URL) + const fallbackProjectId = propProjectId useEffect(() => { - const handleGenerateThumbnail = async (event: { propertyId: string }) => { + const handleGenerateThumbnail = async (event: { projectId: string }) => { if (isGenerating.current) { console.log('⏸️ Thumbnail generation already in progress') return } - // Prioritize prop propertyId over event propertyId (URL has priority over session) - const propertyId = fallbackPropertyId || event.propertyId + // Prioritize prop projectId over event projectId (URL has priority over session) + const projectId = fallbackProjectId || event.projectId - if (!propertyId) { - console.error('❌ No property ID provided') + if (!projectId) { + console.error('❌ No project ID provided') return } isGenerating.current = true - console.log('📸 Generating thumbnail for property:', propertyId) - console.log('📝 Property ID from URL/prop:', fallbackPropertyId) - console.log('📝 Property ID from event:', event.propertyId) - console.log('✅ Using property ID:', propertyId, fallbackPropertyId ? '(from URL)' : '(from event)') + console.log('📸 Generating thumbnail for project:', projectId) try { // Save current renderer state @@ -70,7 +67,7 @@ export const ThumbnailGenerator = ({ propertyId: propPropertyId }: ThumbnailGene if (blob) { // Upload to Supabase Storage console.log('☁️ Uploading thumbnail to storage...') - const result = await uploadPropertyThumbnail(propertyId, blob) + const result = await uploadProjectThumbnail(projectId, blob) if (result.success) { console.log('✅ Thumbnail uploaded successfully!') @@ -116,7 +113,7 @@ export const ThumbnailGenerator = ({ propertyId: propPropertyId }: ThumbnailGene return () => { emitter.off('camera-controls:generate-thumbnail', handleGenerateThumbnail) } - }, [gl, scene, camera, fallbackPropertyId]) + }, [gl, scene, camera, fallbackProjectId]) return null } diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 62202521..2416d0e1 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -4,8 +4,8 @@ import { initSpaceDetectionSync, initSpatialGridSync, useScene } from '@pascal-a import { Viewer } from '@pascal-app/viewer' import { useKeyboard } from '@/hooks/use-keyboard' import useEditor from '@/store/use-editor' -import { usePropertyScene } from '@/features/community/lib/models/hooks' -import { useLocalPropertyScene } from '@/features/community/lib/local-storage/hooks' +import { useProjectScene } from '@/features/community/lib/models/hooks' +import { useLocalProjectScene } from '@/features/community/lib/local-storage/hooks' import { useAuth } from '@/features/community/lib/auth/hooks' import { ZoneSystem } from '../systems/zone/zone-system' import { ToolManager } from '../tools/tool-manager' @@ -22,7 +22,7 @@ import { SelectionManager } from './selection-manager' import { initSFXBus } from '@/lib/sfx-bus' import { ThumbnailGenerator } from '@/app/viewer/[id]/thumbnail-generator' -// Load default scene initially (will be replaced when property loads) +// Load default scene initially (will be replaced when project loads) useScene.getState().loadScene() initSpatialGridSync() initSpaceDetectionSync(useScene, useEditor) @@ -31,23 +31,23 @@ initSpaceDetectionSync(useScene, useEditor) initSFXBus() interface EditorProps { - propertyId?: string + projectId?: string } -export default function Editor({ propertyId }: EditorProps) { +export default function Editor({ projectId }: EditorProps) { useKeyboard() const { isAuthenticated } = useAuth() // Determine which mode to use - const isLocalProperty = propertyId?.startsWith('local_') - const shouldUseCloud = isAuthenticated && !isLocalProperty - const shouldUseLocal = !shouldUseCloud && !!propertyId + const isLocalProject = projectId?.startsWith('local_') + const shouldUseCloud = isAuthenticated && !isLocalProject + const shouldUseLocal = !shouldUseCloud && !!projectId // Call hooks unconditionally (hooks internally check if they should activate) - // Cloud hook activates when there's an activeProperty in the store - usePropertyScene() - // Local hook activates when propertyId is provided and starts with 'local_' - useLocalPropertyScene(shouldUseLocal ? propertyId : undefined) + // Cloud hook activates when there's an activeProject in the store + useProjectScene() + // Local hook activates when projectId is provided and starts with 'local_' + useLocalProjectScene(shouldUseLocal ? projectId : undefined) return (
@@ -74,7 +74,7 @@ export default function Editor({ propertyId }: EditorProps) { - +
) diff --git a/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx b/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx index f0e80906..b71e997e 100644 --- a/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx +++ b/apps/editor/components/ui/sidebar/panels/settings-panel/index.tsx @@ -5,7 +5,7 @@ import { useRef, useState } from "react"; import { Button } from "@/components/ui/primitives/button"; import useEditor from "@/store/use-editor"; import { AudioSettingsDialog } from "./audio-settings-dialog"; -import { usePropertyStore } from "@/features/community/lib/properties/store"; +import { useProjectStore } from "@/features/community/lib/projects/store"; export function SettingsPanel() { const fileInputRef = useRef(null); @@ -16,11 +16,11 @@ export function SettingsPanel() { const resetSelection = useViewer((state) => state.resetSelection); const exportScene = useViewer((state) => state.exportScene); const setPhase = useEditor((state) => state.setPhase); - const activeProperty = usePropertyStore((state) => state.activeProperty); + const activeProject = useProjectStore((state) => state.activeProject); const [isGeneratingThumbnail, setIsGeneratingThumbnail] = useState(false); - const propertyId = activeProperty?.id; - const isLocalProperty = false; // Store only contains cloud properties + const projectId = activeProject?.id; + const isLocalProject = false; // Store only contains cloud projects const handleExport = async () => { if (exportScene) { @@ -71,14 +71,14 @@ export function SettingsPanel() { }; const handleGenerateThumbnail = () => { - if (!propertyId) { - console.error('❌ No property ID found'); + if (!projectId) { + console.error('❌ No project ID found'); return; } - console.log('🎯 Generate thumbnail clicked for property:', propertyId); + console.log('🎯 Generate thumbnail clicked for project:', projectId); setIsGeneratingThumbnail(true); - emitter.emit('camera-controls:generate-thumbnail', { propertyId }); - console.log('📤 Event emitted with property ID:', propertyId); + emitter.emit('camera-controls:generate-thumbnail', { projectId }); + console.log('📤 Event emitted with project ID:', projectId); // Reset loading state after a delay (thumbnail generation is async) setTimeout(() => setIsGeneratingThumbnail(false), 3000); }; @@ -100,8 +100,8 @@ export function SettingsPanel() { - {/* Thumbnail Section (only for cloud properties) */} - {propertyId && !isLocalProperty && ( + {/* Thumbnail Section (only for cloud projects) */} + {projectId && !isLocalProject && (