feat: rename properties to projects
- 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)
This commit is contained in:
@@ -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 (
|
||||
<div className="flex h-screen w-full max-w-screen">
|
||||
<div className="relative h-full w-full">
|
||||
<Editor projectId={projectId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="flex h-screen w-full max-w-screen">
|
||||
<div className="relative h-full w-full">
|
||||
<Editor propertyId={propertyId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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<string | null>(null)
|
||||
const [propertyId, setPropertyId] = useState<string | null>(null)
|
||||
const [projectId, setProjectId] = useState<string | null>(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 */}
|
||||
<ViewerZoneSystem />
|
||||
{/* Thumbnail Generator */}
|
||||
<ThumbnailGenerator propertyId={propertyId || undefined} />
|
||||
<ThumbnailGenerator projectId={projectId || undefined} />
|
||||
</Viewer>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user