isolate cloud-sync feature
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Auth client for the editor using better-auth
|
||||
* Connects to the Pascal monorepo backend
|
||||
*/
|
||||
|
||||
import { createAuthClient } from 'better-auth/react'
|
||||
import {
|
||||
customSessionClient,
|
||||
magicLinkClient,
|
||||
organizationClient,
|
||||
} from 'better-auth/client/plugins'
|
||||
|
||||
/**
|
||||
* Get the backend API URL
|
||||
* Default: http://localhost:3000 (monorepo backend)
|
||||
*/
|
||||
function getBackendURL(): string {
|
||||
// Check if we have an env variable for the backend URL
|
||||
if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_API_URL) {
|
||||
return process.env.NEXT_PUBLIC_API_URL
|
||||
}
|
||||
|
||||
// In browser, try to use the current origin if it's the same host
|
||||
if (typeof window !== 'undefined') {
|
||||
// For local development, always use localhost:3000 (monorepo backend)
|
||||
if (window.location.hostname === 'localhost') {
|
||||
return 'http://localhost:3000'
|
||||
}
|
||||
// For production, use the same origin
|
||||
return window.location.origin
|
||||
}
|
||||
|
||||
// SSR fallback
|
||||
return 'http://localhost:3000'
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth client instance with better-auth
|
||||
* Configured to work with the Pascal monorepo backend
|
||||
*/
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: getBackendURL(),
|
||||
plugins: [
|
||||
magicLinkClient(),
|
||||
organizationClient(),
|
||||
customSessionClient<{
|
||||
session: {
|
||||
activePropertyId: string | null
|
||||
activeOrganizationId: string | null
|
||||
}
|
||||
}>(),
|
||||
],
|
||||
})
|
||||
|
||||
/**
|
||||
* Export types for use in components
|
||||
*/
|
||||
export type AuthState = {
|
||||
user: (typeof authClient)['$Infer']['Session']['user'] | null
|
||||
session: (typeof authClient)['$Infer']['Session']['session'] | null
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export type User = NonNullable<AuthState['user']>
|
||||
export type Session = NonNullable<AuthState['session']>
|
||||
@@ -1,20 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { authClient } from './auth-client'
|
||||
|
||||
/**
|
||||
* Hook to access authentication state using better-auth
|
||||
* @returns Current auth state including user, session, and loading status
|
||||
*/
|
||||
export function useAuth() {
|
||||
const session = authClient.useSession()
|
||||
|
||||
return {
|
||||
user: session.data?.user ?? null,
|
||||
session: session.data?.session ?? null,
|
||||
isAuthenticated: !!session.data?.user && !!session.data?.session,
|
||||
isLoading: session.isPending,
|
||||
signOut: () => authClient.signOut(),
|
||||
signIn: authClient.signIn,
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
/**
|
||||
* Property actions - API client for property management
|
||||
* Makes HTTP requests to the Pascal monorepo backend
|
||||
*/
|
||||
|
||||
import type { CreatePropertyParams, Property } from './types'
|
||||
|
||||
export type ActionResult<T = unknown> = {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backend API URL
|
||||
*/
|
||||
function getBackendURL(): string {
|
||||
if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_API_URL) {
|
||||
return process.env.NEXT_PUBLIC_API_URL
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
||||
return 'http://localhost:3000'
|
||||
}
|
||||
return typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all properties for the current user
|
||||
*/
|
||||
export async function getUserProperties(): Promise<ActionResult<Property[]>> {
|
||||
try {
|
||||
const response = await fetch(`${getBackendURL()}/api/properties`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: error || 'Failed to fetch properties',
|
||||
data: [],
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
data: result.data || [],
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch properties',
|
||||
data: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active property for the current session
|
||||
*/
|
||||
export async function getActiveProperty(): Promise<ActionResult<Property | null>> {
|
||||
try {
|
||||
const response = await fetch(`${getBackendURL()}/api/properties/active`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: error || 'Failed to fetch active property',
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
data: result.data || null,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch active property',
|
||||
data: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active property for the current session
|
||||
*/
|
||||
export async function setActiveProperty(propertyId: string | null): Promise<ActionResult> {
|
||||
try {
|
||||
const response = await fetch(`${getBackendURL()}/api/properties/active`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ propertyId }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: error || 'Failed to set active property',
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
message: result.message || (propertyId ? 'Active property updated' : 'Active property cleared'),
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to set active property',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a property with the given address already exists
|
||||
*/
|
||||
export async function checkPropertyDuplicate(params: {
|
||||
streetNumber?: string
|
||||
route?: string
|
||||
city?: string
|
||||
state?: string
|
||||
postalCode?: string
|
||||
}): Promise<
|
||||
ActionResult<{
|
||||
isDuplicate: boolean
|
||||
isUserProperty?: boolean
|
||||
existingProperty?: Property
|
||||
}>
|
||||
> {
|
||||
try {
|
||||
const response = await fetch(`${getBackendURL()}/api/properties/check-duplicate`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: error || 'Failed to check for duplicates',
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
data: result.data || { isDuplicate: false },
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check for duplicates',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new property
|
||||
*/
|
||||
export async function createProperty(params: CreatePropertyParams): Promise<ActionResult<Property>> {
|
||||
try {
|
||||
const response = await fetch(`${getBackendURL()}/api/properties`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: error || 'Failed to create property',
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
data: result.data,
|
||||
message: result.message || 'Property created successfully',
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create property',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Property-related type definitions
|
||||
* Isolated from monorepo database schema
|
||||
*/
|
||||
|
||||
export type Property = {
|
||||
id: string
|
||||
name: string
|
||||
ownerId: string
|
||||
organizationId: string | null
|
||||
addressId: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
address: {
|
||||
id: string
|
||||
streetNumber?: string
|
||||
route?: string
|
||||
city?: string
|
||||
state?: string
|
||||
postalCode?: string
|
||||
country?: string
|
||||
latitude?: string
|
||||
longitude?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type CreatePropertyParams = {
|
||||
name: string
|
||||
center: [number, number]
|
||||
streetNumber?: string
|
||||
route?: string
|
||||
routeShort?: string
|
||||
neighborhood?: string
|
||||
city?: string
|
||||
county?: string
|
||||
state?: string
|
||||
stateLong?: string
|
||||
postalCode?: string
|
||||
postalCodeSuffix?: string
|
||||
country?: string
|
||||
countryLong?: string
|
||||
rawJson?: Record<string, unknown>
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
getActiveProperty,
|
||||
getUserProperties,
|
||||
setActiveProperty as setActivePropertyAction,
|
||||
} from './actions'
|
||||
import type { Property } from './types'
|
||||
|
||||
interface UsePropertiesReturn {
|
||||
properties: Property[]
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refetch: () => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch and manage user properties
|
||||
*/
|
||||
export function useProperties(): UsePropertiesReturn {
|
||||
const [properties, setProperties] = useState<Property[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetchProperties = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
const result = await getUserProperties()
|
||||
|
||||
if (result.success) {
|
||||
setProperties(result.data || [])
|
||||
} else {
|
||||
setError(result.error || 'Failed to fetch properties')
|
||||
setProperties([])
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An unexpected error occurred')
|
||||
setProperties([])
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchProperties()
|
||||
}, [fetchProperties])
|
||||
|
||||
return {
|
||||
properties,
|
||||
isLoading,
|
||||
error,
|
||||
refetch: fetchProperties,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch a single property by ID
|
||||
*/
|
||||
export function useProperty(propertyId: string | undefined) {
|
||||
const [property, setProperty] = useState<Property | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!propertyId) {
|
||||
setProperty(null)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const fetchProperty = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
const result = await getUserProperties()
|
||||
|
||||
if (result.success) {
|
||||
const found = result.data?.find((p) => p.id === propertyId)
|
||||
if (found) {
|
||||
setProperty(found)
|
||||
} else {
|
||||
setError('Property not found')
|
||||
setProperty(null)
|
||||
}
|
||||
} else {
|
||||
setError(result.error || 'Failed to fetch property')
|
||||
setProperty(null)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An unexpected error occurred')
|
||||
setProperty(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchProperty()
|
||||
}, [propertyId])
|
||||
|
||||
return {
|
||||
property,
|
||||
isLoading,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manage the active property for the current session
|
||||
*/
|
||||
export function useActiveProperty() {
|
||||
const [activeProperty, setActivePropertyState] = useState<Property | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
|
||||
const fetchActiveProperty = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
const result = await getActiveProperty()
|
||||
|
||||
if (result.success) {
|
||||
setActivePropertyState(result.data || null)
|
||||
|
||||
// If no active property is set, automatically set the first property as active
|
||||
if (!result.data) {
|
||||
const propertiesResult = await getUserProperties()
|
||||
|
||||
if (
|
||||
propertiesResult.success &&
|
||||
propertiesResult.data &&
|
||||
propertiesResult.data.length > 0
|
||||
) {
|
||||
const firstProperty = propertiesResult.data[0]
|
||||
if (firstProperty) {
|
||||
const setActiveResult = await setActivePropertyAction(firstProperty.id)
|
||||
|
||||
if (setActiveResult.success) {
|
||||
setActivePropertyState(firstProperty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setError(result.error || 'Failed to fetch active property')
|
||||
setActivePropertyState(null)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An unexpected error occurred')
|
||||
setActivePropertyState(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const changeActiveProperty = useCallback(
|
||||
async (propertyId: string | null) => {
|
||||
try {
|
||||
setIsPending(true)
|
||||
const result = await setActivePropertyAction(propertyId)
|
||||
|
||||
if (result.success) {
|
||||
// Fetch the updated active property
|
||||
if (propertyId) {
|
||||
await fetchActiveProperty()
|
||||
} else {
|
||||
setActivePropertyState(null)
|
||||
}
|
||||
} else {
|
||||
console.error(result.error || 'Failed to update active property')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err instanceof Error ? err.message : 'An unexpected error occurred')
|
||||
} finally {
|
||||
setIsPending(false)
|
||||
}
|
||||
},
|
||||
[fetchActiveProperty],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
fetchActiveProperty()
|
||||
}, [fetchActiveProperty])
|
||||
|
||||
return {
|
||||
activeProperty,
|
||||
isLoading,
|
||||
error,
|
||||
setActiveProperty: changeActiveProperty,
|
||||
isPending,
|
||||
refetch: fetchActiveProperty,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user