201 lines
6.5 KiB
TypeScript
201 lines
6.5 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { SettingsNav, SettingsSection, SettingToggle, SettingInput } from '@/components/settings'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Download, Upload, Trash2, Loader2, Check } from 'lucide-react'
|
||
import { toast } from 'sonner'
|
||
|
||
export default function DataSettingsPage() {
|
||
const [isExporting, setIsExporting] = useState(false)
|
||
const [isImporting, setIsImporting] = useState(false)
|
||
const [isDeleting, setIsDeleting] = useState(false)
|
||
const [exportUrl, setExportUrl] = useState('')
|
||
|
||
const handleExport = async () => {
|
||
setIsExporting(true)
|
||
try {
|
||
const response = await fetch('/api/notes/export')
|
||
if (response.ok) {
|
||
const blob = await response.blob()
|
||
const url = window.URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `keep-notes-export-${new Date().toISOString().split('T')[0]}.json`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
window.URL.revokeObjectURL(url)
|
||
toast.success('Notes exported successfully')
|
||
}
|
||
} catch (error) {
|
||
console.error('Export error:', error)
|
||
toast.error('Failed to export notes')
|
||
} finally {
|
||
setIsExporting(false)
|
||
}
|
||
}
|
||
|
||
const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = event.target.files?.[0]
|
||
if (!file) return
|
||
|
||
setIsImporting(true)
|
||
try {
|
||
const formData = new FormData()
|
||
formData.append('file', file)
|
||
|
||
const response = await fetch('/api/notes/import', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
|
||
if (response.ok) {
|
||
const result = await response.json()
|
||
toast.success(`Imported ${result.count} notes`)
|
||
// Refresh the page to show imported notes
|
||
window.location.reload()
|
||
} else {
|
||
throw new Error('Import failed')
|
||
}
|
||
} catch (error) {
|
||
console.error('Import error:', error)
|
||
toast.error('Failed to import notes')
|
||
} finally {
|
||
setIsImporting(false)
|
||
// Reset input
|
||
event.target.value = ''
|
||
}
|
||
}
|
||
|
||
const handleDeleteAll = async () => {
|
||
if (!confirm('Are you sure you want to delete all notes? This action cannot be undone.')) {
|
||
return
|
||
}
|
||
|
||
setIsDeleting(true)
|
||
try {
|
||
const response = await fetch('/api/notes/delete-all', { method: 'POST' })
|
||
if (response.ok) {
|
||
toast.success('All notes deleted')
|
||
window.location.reload()
|
||
}
|
||
} catch (error) {
|
||
console.error('Delete error:', error)
|
||
toast.error('Failed to delete notes')
|
||
} finally {
|
||
setIsDeleting(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="container mx-auto py-10 px-4 max-w-6xl">
|
||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||
{/* Sidebar Navigation */}
|
||
<aside className="lg:col-span-1">
|
||
<SettingsNav />
|
||
</aside>
|
||
|
||
{/* Main Content */}
|
||
<main className="lg:col-span-3 space-y-6">
|
||
<div>
|
||
<h1 className="text-3xl font-bold mb-2">Data Management</h1>
|
||
<p className="text-gray-600 dark:text-gray-400">
|
||
Export, import, or manage your data
|
||
</p>
|
||
</div>
|
||
|
||
<SettingsSection
|
||
title="Export Data"
|
||
icon={<span className="text-2xl">💾</span>}
|
||
description="Download your notes as a JSON file"
|
||
>
|
||
<div className="flex items-center justify-between py-4">
|
||
<div>
|
||
<p className="font-medium">Export All Notes</p>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||
Download all your notes in JSON format
|
||
</p>
|
||
</div>
|
||
<Button
|
||
onClick={handleExport}
|
||
disabled={isExporting}
|
||
>
|
||
{isExporting ? (
|
||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||
) : (
|
||
<Download className="h-4 w-4 mr-2" />
|
||
)}
|
||
{isExporting ? 'Exporting...' : 'Export'}
|
||
</Button>
|
||
</div>
|
||
</SettingsSection>
|
||
|
||
<SettingsSection
|
||
title="Import Data"
|
||
icon={<span className="text-2xl">📥</span>}
|
||
description="Import notes from a JSON file"
|
||
>
|
||
<div className="flex items-center justify-between py-4">
|
||
<div>
|
||
<p className="font-medium">Import Notes</p>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||
Upload a JSON file to import notes
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<input
|
||
type="file"
|
||
accept=".json"
|
||
onChange={handleImport}
|
||
disabled={isImporting}
|
||
className="hidden"
|
||
id="import-file"
|
||
/>
|
||
<Button
|
||
onClick={() => document.getElementById('import-file')?.click()}
|
||
disabled={isImporting}
|
||
>
|
||
{isImporting ? (
|
||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||
) : (
|
||
<Upload className="h-4 w-4 mr-2" />
|
||
)}
|
||
{isImporting ? 'Importing...' : 'Import'}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</SettingsSection>
|
||
|
||
<SettingsSection
|
||
title="Danger Zone"
|
||
icon={<span className="text-2xl">⚠️</span>}
|
||
description="Permanently delete your data"
|
||
>
|
||
<div className="flex items-center justify-between py-4 border-t border-red-200 dark:border-red-900">
|
||
<div>
|
||
<p className="font-medium text-red-600 dark:text-red-400">Delete All Notes</p>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||
This action cannot be undone
|
||
</p>
|
||
</div>
|
||
<Button
|
||
variant="destructive"
|
||
onClick={handleDeleteAll}
|
||
disabled={isDeleting}
|
||
>
|
||
{isDeleting ? (
|
||
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
||
) : (
|
||
<Trash2 className="h-4 w-4 mr-2" />
|
||
)}
|
||
{isDeleting ? 'Deleting...' : 'Delete All'}
|
||
</Button>
|
||
</div>
|
||
</SettingsSection>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|