From 4f9c4655e685dda1d4ffed1ada00a37e85e68a6f Mon Sep 17 00:00:00 2001 From: wass08 Date: Wed, 11 Feb 2026 10:48:24 +0900 Subject: [PATCH] auth flow + property switching --- apps/editor/components/editor/index.tsx | 3 +- apps/editor/features/cloud-sync/README.md | 9 + .../components/cloud-save-button.tsx | 11 +- .../components/new-property-dialog.tsx | 6 +- .../components/property-dropdown.tsx | 21 +- .../features/cloud-sync/lib/models/actions.ts | 240 ++++++++++++++++++ .../features/cloud-sync/lib/models/hooks.ts | 142 +++++++++++ .../cloud-sync/lib/properties/hooks.ts | 48 +++- .../cloud-sync/lib/properties/store.ts | 126 +++++++++ 9 files changed, 589 insertions(+), 17 deletions(-) create mode 100644 apps/editor/features/cloud-sync/lib/models/actions.ts create mode 100644 apps/editor/features/cloud-sync/lib/models/hooks.ts create mode 100644 apps/editor/features/cloud-sync/lib/properties/store.ts diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index d1755205..4a39bc15 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -4,6 +4,7 @@ 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/cloud-sync/lib/models/hooks' import { ZoneSystem } from '../systems/zone/zone-system' import { ToolManager } from '../tools/tool-manager' import { ActionMenu } from '../ui/action-menu' @@ -16,8 +17,8 @@ import { ExportManager } from './export-manager' import { Grid } from './grid' import { SelectionManager } from './selection-manager' +// Load default scene initially (will be replaced when property loads) useScene.getState().loadScene() -console.log('Loaded scene in editor') initSpatialGridSync() initSpaceDetectionSync(useScene, useEditor) diff --git a/apps/editor/features/cloud-sync/README.md b/apps/editor/features/cloud-sync/README.md index ea17757f..f2324c44 100644 --- a/apps/editor/features/cloud-sync/README.md +++ b/apps/editor/features/cloud-sync/README.md @@ -8,6 +8,8 @@ The cloud sync feature provides: - **Authentication** - Sign in with magic link via Better Auth - **Property Management** - Create and manage properties with Google Maps address search +- **Scene Loading** - Automatically load property scenes from the database when a property is selected +- **Auto-Save** - Automatically save scene changes to the database (2-second debounce) - **Database Sync** - Save and load editor state from a PostgreSQL database via Supabase ## Architecture @@ -51,6 +53,13 @@ features/cloud-sync/ 3. Properties are associated with the authenticated user 4. User can switch between properties +### Scene Management +1. When a property is selected, its scene is loaded from `properties_models` table +2. If no scene exists, loads default empty scene +3. Scene changes are auto-saved every 2 seconds (debounced) +4. Updates existing model (highest version) instead of creating new ones +5. Scene graph includes all nodes and hierarchy + ### Database Integration - Uses Supabase (PostgreSQL) for database access - Server actions use service role key to bypass RLS diff --git a/apps/editor/features/cloud-sync/components/cloud-save-button.tsx b/apps/editor/features/cloud-sync/components/cloud-save-button.tsx index 373ae685..b61f17fb 100644 --- a/apps/editor/features/cloud-sync/components/cloud-save-button.tsx +++ b/apps/editor/features/cloud-sync/components/cloud-save-button.tsx @@ -1,8 +1,9 @@ 'use client' import { Cloud } from 'lucide-react' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useAuth } from '../lib/auth/hooks' +import { usePropertyStore } from '../lib/properties/store' import { ProfileDropdown } from './profile-dropdown' import { PropertyDropdown } from './property-dropdown' import { SignInDialog } from './sign-in-dialog' @@ -16,6 +17,14 @@ import { SignInDialog } from './sign-in-dialog' export function CloudSaveButton() { const { isAuthenticated, isLoading } = useAuth() const [isSignInDialogOpen, setIsSignInDialogOpen] = useState(false) + const initialize = usePropertyStore(state => state.initialize) + + // Initialize property store when authenticated + useEffect(() => { + if (isAuthenticated) { + initialize() + } + }, [isAuthenticated, initialize]) if (isLoading) { return ( diff --git a/apps/editor/features/cloud-sync/components/new-property-dialog.tsx b/apps/editor/features/cloud-sync/components/new-property-dialog.tsx index 18df3281..2e73e123 100644 --- a/apps/editor/features/cloud-sync/components/new-property-dialog.tsx +++ b/apps/editor/features/cloud-sync/components/new-property-dialog.tsx @@ -9,7 +9,7 @@ import { GoogleAddressSearch } from './google-address-search' interface NewPropertyDialogProps { open: boolean onOpenChange: (open: boolean) => void - onSuccess?: () => void + onSuccess?: (propertyId: string) => void } interface AddressData { @@ -60,10 +60,10 @@ export function NewPropertyDialog({ open, onOpenChange, onSuccess }: NewProperty country: address.country || 'US', }) - if (result.success) { + if (result.success && result.data) { onOpenChange(false) setAddress(null) - onSuccess?.() + onSuccess?.(result.data.id) } else { setError(result.error || 'Failed to create property') } diff --git a/apps/editor/features/cloud-sync/components/property-dropdown.tsx b/apps/editor/features/cloud-sync/components/property-dropdown.tsx index e30f5bce..095cdee8 100644 --- a/apps/editor/features/cloud-sync/components/property-dropdown.tsx +++ b/apps/editor/features/cloud-sync/components/property-dropdown.tsx @@ -2,7 +2,7 @@ import { Check, ChevronDown, Home, Plus } from 'lucide-react' import { useState } from 'react' -import { useActiveProperty, useProperties } from '../lib/properties/hooks' +import { usePropertyStore } from '../lib/properties/store' import { cn } from '@/lib/utils' import { DropdownMenu, @@ -11,13 +11,21 @@ import { DropdownMenuTrigger, } from '@/components/ui/primitives/dropdown-menu' import { NewPropertyDialog } from './new-property-dialog' +import { usePropertyScene } from '../lib/models/hooks' /** * PropertyDropdown - Shows active property and allows switching between properties */ export function PropertyDropdown() { - const { properties, isLoading: propertiesLoading, refetch } = useProperties() - const { activeProperty, setActiveProperty, isPending } = useActiveProperty() + usePropertyScene() // Load and auto-save property scenes + + // Use property store + const properties = usePropertyStore(state => state.properties) + const activeProperty = usePropertyStore(state => state.activeProperty) + const isLoading = usePropertyStore(state => state.isLoading) + const setActiveProperty = usePropertyStore(state => state.setActiveProperty) + const fetchProperties = usePropertyStore(state => state.fetchProperties) + const [isNewPropertyDialogOpen, setIsNewPropertyDialogOpen] = useState(false) const handlePropertySelect = async (propertyId: string) => { @@ -28,8 +36,9 @@ export function PropertyDropdown() { setIsNewPropertyDialogOpen(true) } - const handlePropertyCreated = () => { - refetch() + const handlePropertyCreated = async (propertyId: string) => { + // Set the newly created property as active (this will also fetch properties) + await setActiveProperty(propertyId) } return ( @@ -38,7 +47,7 @@ export function PropertyDropdown() {