document deployment
This commit is contained in:
@ -2,6 +2,7 @@ import { useEffect } from 'react'
|
||||
import { loadSpace, getSpaceFromUrl } from './store'
|
||||
import { Space } from './components/Space'
|
||||
import { Navigation } from './components/Navigation'
|
||||
import { StorageConfig } from './components/StorageConfig'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
@ -22,6 +23,7 @@ function App() {
|
||||
<>
|
||||
<Space />
|
||||
<Navigation />
|
||||
<StorageConfig />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
66
src/components/StorageConfig.tsx
Normal file
66
src/components/StorageConfig.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { useState } from 'react'
|
||||
import { getStorageMode, setStorageMode, type StorageMode } from '../db'
|
||||
|
||||
export function StorageConfig() {
|
||||
const [currentMode, setCurrentMode] = useState<StorageMode>(getStorageMode())
|
||||
const [showConfig, setShowConfig] = useState(false)
|
||||
|
||||
const handleModeChange = (mode: StorageMode) => {
|
||||
setStorageMode(mode)
|
||||
setCurrentMode(mode)
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
if (!showConfig) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setShowConfig(true)}
|
||||
className="fixed bottom-4 right-4 bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded text-sm"
|
||||
>
|
||||
⚙️ Storage
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 bg-white border rounded-lg shadow-lg p-4 min-w-64">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="font-medium">Storage Mode</h3>
|
||||
<button
|
||||
onClick={() => setShowConfig(false)}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="storage-mode"
|
||||
value="indexeddb"
|
||||
checked={currentMode === 'indexeddb'}
|
||||
onChange={() => handleModeChange('indexeddb')}
|
||||
/>
|
||||
<span>IndexedDB (Local)</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="storage-mode"
|
||||
value="database"
|
||||
checked={currentMode === 'database'}
|
||||
onChange={() => handleModeChange('database')}
|
||||
/>
|
||||
<span>Database (Collaborative)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-xs text-gray-600">
|
||||
Current: {currentMode === 'indexeddb' ? 'Local storage' : 'Remote database'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
118
src/db.ts
118
src/db.ts
@ -10,7 +10,21 @@ type Space = {
|
||||
notes: Note[]
|
||||
}
|
||||
|
||||
class Database {
|
||||
type StorageMode = 'indexeddb' | 'database'
|
||||
|
||||
interface StorageAdapter {
|
||||
init(): Promise<void>
|
||||
getSpace(id: string): Promise<Space | undefined>
|
||||
saveSpace(space: Space): Promise<void>
|
||||
createSpace(id: string): Promise<Space>
|
||||
addNoteToSpace(spaceId: string, note: Note): Promise<void>
|
||||
updateNoteInSpace(spaceId: string, noteId: string, updates: Partial<Note>): Promise<void>
|
||||
deleteNoteFromSpace(spaceId: string, noteId: string): Promise<void>
|
||||
getAllSpaceIds(): Promise<string[]>
|
||||
deleteSpace(id: string): Promise<void>
|
||||
}
|
||||
|
||||
class IndexedDBAdapter implements StorageAdapter {
|
||||
private db: IDBDatabase | null = null
|
||||
|
||||
async init() {
|
||||
@ -121,4 +135,104 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new Database()
|
||||
class DatabaseAdapter implements StorageAdapter {
|
||||
private apiUrl: string
|
||||
|
||||
constructor(apiUrl?: string) {
|
||||
this.apiUrl = apiUrl || this.getApiUrl()
|
||||
}
|
||||
|
||||
private getApiUrl(): string {
|
||||
if (typeof window !== 'undefined' && window.location.hostname === 'palace.raphaelforment.fr') {
|
||||
return 'https://palace.raphaelforment.fr:3001/api'
|
||||
}
|
||||
return '/api'
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
// Database connection is handled per request
|
||||
}
|
||||
|
||||
async getSpace(id: string): Promise<Space | undefined> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${id}`)
|
||||
if (response.status === 404) return undefined
|
||||
if (!response.ok) throw new Error(`Failed to get space: ${response.statusText}`)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async saveSpace(space: Space): Promise<void> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${space.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(space)
|
||||
})
|
||||
if (!response.ok) throw new Error(`Failed to save space: ${response.statusText}`)
|
||||
}
|
||||
|
||||
async createSpace(id: string): Promise<Space> {
|
||||
const space: Space = { id, notes: [] }
|
||||
await this.saveSpace(space)
|
||||
return space
|
||||
}
|
||||
|
||||
async addNoteToSpace(spaceId: string, note: Note): Promise<void> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(note)
|
||||
})
|
||||
if (!response.ok) throw new Error(`Failed to add note: ${response.statusText}`)
|
||||
}
|
||||
|
||||
async updateNoteInSpace(spaceId: string, noteId: string, updates: Partial<Note>): Promise<void> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates)
|
||||
})
|
||||
if (!response.ok) throw new Error(`Failed to update note: ${response.statusText}`)
|
||||
}
|
||||
|
||||
async deleteNoteFromSpace(spaceId: string, noteId: string): Promise<void> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${spaceId}/notes/${noteId}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
if (!response.ok) throw new Error(`Failed to delete note: ${response.statusText}`)
|
||||
}
|
||||
|
||||
async getAllSpaceIds(): Promise<string[]> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces`)
|
||||
if (!response.ok) throw new Error(`Failed to get spaces: ${response.statusText}`)
|
||||
const spaces = await response.json()
|
||||
return spaces.map((space: Space) => space.id)
|
||||
}
|
||||
|
||||
async deleteSpace(id: string): Promise<void> {
|
||||
const response = await fetch(`${this.apiUrl}/spaces/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
if (!response.ok) throw new Error(`Failed to delete space: ${response.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getStorageMode(): StorageMode {
|
||||
return (localStorage.getItem('palace-storage-mode') as StorageMode) || 'indexeddb'
|
||||
}
|
||||
|
||||
function setStorageMode(mode: StorageMode): void {
|
||||
localStorage.setItem('palace-storage-mode', mode)
|
||||
}
|
||||
|
||||
function createStorageAdapter(): StorageAdapter {
|
||||
const mode = getStorageMode()
|
||||
switch (mode) {
|
||||
case 'database':
|
||||
return new DatabaseAdapter()
|
||||
case 'indexeddb':
|
||||
default:
|
||||
return new IndexedDBAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
export const db = createStorageAdapter()
|
||||
export { type Note, type Space, type StorageMode, getStorageMode, setStorageMode }
|
||||
Reference in New Issue
Block a user