vue · 18062 bytes Raw Blame History
1 <template>
2 <AppLayout>
3 <div class="settings-view">
4 <div class="max-w-4xl mx-auto py-8 px-4">
5 <h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-8">Settings</h1>
6
7 <!-- Settings sections -->
8 <div class="space-y-8">
9 <!-- Appearance -->
10 <div class="card">
11 <div class="card-header">
12 <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Appearance</h2>
13 <p class="text-sm text-gray-600 dark:text-gray-400">Customize the look and feel</p>
14 </div>
15 <div class="card-body space-y-4">
16 <!-- Theme -->
17 <div class="flex items-center justify-between">
18 <div>
19 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Theme</label>
20 <p class="text-xs text-gray-500 dark:text-gray-400">Choose your preferred color scheme</p>
21 </div>
22 <select
23 v-model="settings.theme"
24 @change="updateSetting('theme', $event.target.value)"
25 class="input w-32"
26 >
27 <option value="system">System</option>
28 <option value="light">Light</option>
29 <option value="dark">Dark</option>
30 </select>
31 </div>
32
33 <!-- Language -->
34 <div class="flex items-center justify-between">
35 <div>
36 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Language</label>
37 <p class="text-xs text-gray-500 dark:text-gray-400">Select your preferred language</p>
38 </div>
39 <select
40 v-model="settings.language"
41 @change="updateSetting('language', $event.target.value)"
42 class="input w-32"
43 >
44 <option value="en">English</option>
45 <option value="es">Español</option>
46 <option value="fr">Français</option>
47 <option value="de">Deutsch</option>
48 </select>
49 </div>
50
51 <!-- File view -->
52 <div class="flex items-center justify-between">
53 <div>
54 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Default file view</label>
55 <p class="text-xs text-gray-500 dark:text-gray-400">How files are displayed by default</p>
56 </div>
57 <select
58 v-model="settings.defaultFileView"
59 @change="updateSetting('defaultFileView', $event.target.value)"
60 class="input w-32"
61 >
62 <option value="grid">Grid</option>
63 <option value="list">List</option>
64 </select>
65 </div>
66 </div>
67 </div>
68
69 <!-- Files & Storage -->
70 <div class="card">
71 <div class="card-header">
72 <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Files & Storage</h2>
73 <p class="text-sm text-gray-600 dark:text-gray-400">Configure file handling preferences</p>
74 </div>
75 <div class="card-body space-y-4">
76 <!-- Auto-encrypt -->
77 <div class="flex items-center justify-between">
78 <div>
79 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Auto-encrypt uploads</label>
80 <p class="text-xs text-gray-500 dark:text-gray-400">Automatically encrypt all uploaded files</p>
81 </div>
82 <label class="relative inline-flex items-center cursor-pointer">
83 <input
84 v-model="settings.autoEncrypt"
85 @change="updateSetting('autoEncrypt', $event.target.checked)"
86 type="checkbox"
87 class="sr-only peer"
88 />
89 <div class="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
90 </label>
91 </div>
92
93 <!-- Default upload folder -->
94 <div class="flex items-center justify-between">
95 <div>
96 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Default upload folder</label>
97 <p class="text-xs text-gray-500 dark:text-gray-400">Where new files are uploaded by default</p>
98 </div>
99 <input
100 v-model="settings.defaultUploadFolder"
101 @blur="updateSetting('defaultUploadFolder', $event.target.value)"
102 type="text"
103 placeholder="/uploads"
104 class="input w-48"
105 />
106 </div>
107
108 <!-- Max file size -->
109 <div class="flex items-center justify-between">
110 <div>
111 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Max upload size</label>
112 <p class="text-xs text-gray-500 dark:text-gray-400">Maximum file size for uploads</p>
113 </div>
114 <select
115 v-model="settings.maxUploadSize"
116 @change="updateSetting('maxUploadSize', parseInt($event.target.value))"
117 class="input w-32"
118 >
119 <option :value="100 * 1024 * 1024">100 MB</option>
120 <option :value="500 * 1024 * 1024">500 MB</option>
121 <option :value="1024 * 1024 * 1024">1 GB</option>
122 <option :value="5 * 1024 * 1024 * 1024">5 GB</option>
123 </select>
124 </div>
125 </div>
126 </div>
127
128 <!-- Privacy & Security -->
129 <div class="card">
130 <div class="card-header">
131 <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Privacy & Security</h2>
132 <p class="text-sm text-gray-600 dark:text-gray-400">Control your privacy and security settings</p>
133 </div>
134 <div class="card-body space-y-4">
135 <!-- Session timeout -->
136 <div class="flex items-center justify-between">
137 <div>
138 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Session timeout</label>
139 <p class="text-xs text-gray-500 dark:text-gray-400">Automatically log out after inactivity</p>
140 </div>
141 <select
142 v-model="settings.sessionTimeout"
143 @change="updateSetting('sessionTimeout', parseInt($event.target.value))"
144 class="input w-32"
145 >
146 <option :value="30">30 minutes</option>
147 <option :value="60">1 hour</option>
148 <option :value="240">4 hours</option>
149 <option :value="480">8 hours</option>
150 <option :value="0">Never</option>
151 </select>
152 </div>
153
154 <!-- Clear data on logout -->
155 <div class="flex items-center justify-between">
156 <div>
157 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Clear data on logout</label>
158 <p class="text-xs text-gray-500 dark:text-gray-400">Remove cached data when you log out</p>
159 </div>
160 <label class="relative inline-flex items-center cursor-pointer">
161 <input
162 v-model="settings.clearDataOnLogout"
163 @change="updateSetting('clearDataOnLogout', $event.target.checked)"
164 type="checkbox"
165 class="sr-only peer"
166 />
167 <div class="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
168 </label>
169 </div>
170 </div>
171 </div>
172
173 <!-- Notifications -->
174 <div class="card">
175 <div class="card-header">
176 <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Notifications</h2>
177 <p class="text-sm text-gray-600 dark:text-gray-400">Choose what notifications you receive</p>
178 </div>
179 <div class="card-body space-y-4">
180 <!-- Upload notifications -->
181 <div class="flex items-center justify-between">
182 <div>
183 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Upload notifications</label>
184 <p class="text-xs text-gray-500 dark:text-gray-400">Show notifications when uploads complete</p>
185 </div>
186 <label class="relative inline-flex items-center cursor-pointer">
187 <input
188 v-model="settings.notifyUploads"
189 @change="updateSetting('notifyUploads', $event.target.checked)"
190 type="checkbox"
191 class="sr-only peer"
192 />
193 <div class="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
194 </label>
195 </div>
196
197 <!-- Error notifications -->
198 <div class="flex items-center justify-between">
199 <div>
200 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Error notifications</label>
201 <p class="text-xs text-gray-500 dark:text-gray-400">Show notifications when errors occur</p>
202 </div>
203 <label class="relative inline-flex items-center cursor-pointer">
204 <input
205 v-model="settings.notifyErrors"
206 @change="updateSetting('notifyErrors', $event.target.checked)"
207 type="checkbox"
208 class="sr-only peer"
209 />
210 <div class="w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
211 </label>
212 </div>
213 </div>
214 </div>
215
216 <!-- Actions -->
217 <div class="card">
218 <div class="card-header">
219 <h2 class="text-lg font-semibold text-gray-900 dark:text-white">Actions</h2>
220 <p class="text-sm text-gray-600 dark:text-gray-400">Manage your account and data</p>
221 </div>
222 <div class="card-body space-y-4">
223 <!-- Export settings -->
224 <div class="flex items-center justify-between">
225 <div>
226 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Export settings</label>
227 <p class="text-xs text-gray-500 dark:text-gray-400">Download your settings as a file</p>
228 </div>
229 <button @click="exportSettings" class="btn btn-outline">
230 Export
231 </button>
232 </div>
233
234 <!-- Import settings -->
235 <div class="flex items-center justify-between">
236 <div>
237 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Import settings</label>
238 <p class="text-xs text-gray-500 dark:text-gray-400">Restore settings from a file</p>
239 </div>
240 <div class="flex items-center space-x-2">
241 <input
242 ref="importInput"
243 type="file"
244 accept=".json"
245 @change="importSettings"
246 class="hidden"
247 />
248 <button @click="$refs.importInput.click()" class="btn btn-outline">
249 Import
250 </button>
251 </div>
252 </div>
253
254 <!-- Reset settings -->
255 <div class="flex items-center justify-between">
256 <div>
257 <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Reset to defaults</label>
258 <p class="text-xs text-gray-500 dark:text-gray-400">Restore all settings to their default values</p>
259 </div>
260 <button @click="resetSettings" class="btn btn-danger">
261 Reset
262 </button>
263 </div>
264 </div>
265 </div>
266 </div>
267 </div>
268 </div>
269 </AppLayout>
270 </template>
271
272 <script setup lang="ts">
273 import { ref, reactive, onMounted } from 'vue'
274 import AppLayout from '@/components/AppLayout.vue'
275
276 interface Settings {
277 theme: 'system' | 'light' | 'dark'
278 language: string
279 defaultFileView: 'grid' | 'list'
280 autoEncrypt: boolean
281 defaultUploadFolder: string
282 maxUploadSize: number
283 sessionTimeout: number
284 clearDataOnLogout: boolean
285 notifyUploads: boolean
286 notifyErrors: boolean
287 }
288
289 const defaultSettings: Settings = {
290 theme: 'system',
291 language: 'en',
292 defaultFileView: 'grid',
293 autoEncrypt: false,
294 defaultUploadFolder: '/uploads',
295 maxUploadSize: 1024 * 1024 * 1024, // 1GB
296 sessionTimeout: 60, // 1 hour
297 clearDataOnLogout: true,
298 notifyUploads: true,
299 notifyErrors: true,
300 }
301
302 const settings = reactive<Settings>({ ...defaultSettings })
303 const importInput = ref<HTMLInputElement>()
304
305 function loadSettings() {
306 try {
307 const saved = localStorage.getItem('zephyrfs_settings')
308 if (saved) {
309 const parsed = JSON.parse(saved)
310 Object.assign(settings, { ...defaultSettings, ...parsed })
311 }
312 } catch (error) {
313 console.warn('Failed to load settings:', error)
314 }
315 }
316
317 function saveSettings() {
318 try {
319 localStorage.setItem('zephyrfs_settings', JSON.stringify(settings))
320 } catch (error) {
321 console.error('Failed to save settings:', error)
322 if (window.$notify) {
323 window.$notify.error('Failed to save settings')
324 }
325 }
326 }
327
328 function updateSetting(key: keyof Settings, value: any) {
329 (settings as any)[key] = value
330 saveSettings()
331
332 // Apply certain settings immediately
333 if (key === 'theme') {
334 applyTheme(value)
335 }
336
337 if (window.$notify) {
338 window.$notify.success('Setting updated')
339 }
340 }
341
342 function applyTheme(theme: string) {
343 const html = document.documentElement
344
345 if (theme === 'dark') {
346 html.classList.add('dark')
347 } else if (theme === 'light') {
348 html.classList.remove('dark')
349 } else {
350 // System theme
351 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
352 html.classList.toggle('dark', prefersDark)
353 }
354 }
355
356 function exportSettings() {
357 try {
358 const data = JSON.stringify(settings, null, 2)
359 const blob = new Blob([data], { type: 'application/json' })
360 const url = URL.createObjectURL(blob)
361
362 const link = document.createElement('a')
363 link.href = url
364 link.download = `zephyrfs-settings-${new Date().toISOString().split('T')[0]}.json`
365 document.body.appendChild(link)
366 link.click()
367 document.body.removeChild(link)
368
369 URL.revokeObjectURL(url)
370
371 if (window.$notify) {
372 window.$notify.success('Settings exported successfully')
373 }
374 } catch (error) {
375 console.error('Failed to export settings:', error)
376 if (window.$notify) {
377 window.$notify.error('Failed to export settings')
378 }
379 }
380 }
381
382 function importSettings(event: Event) {
383 const file = (event.target as HTMLInputElement).files?.[0]
384 if (!file) return
385
386 const reader = new FileReader()
387 reader.onload = (e) => {
388 try {
389 const imported = JSON.parse(e.target?.result as string)
390 Object.assign(settings, { ...defaultSettings, ...imported })
391 saveSettings()
392
393 // Apply theme immediately
394 applyTheme(settings.theme)
395
396 if (window.$notify) {
397 window.$notify.success('Settings imported successfully')
398 }
399 } catch (error) {
400 console.error('Failed to import settings:', error)
401 if (window.$notify) {
402 window.$notify.error('Failed to import settings. Please check the file format.')
403 }
404 }
405 }
406
407 reader.readAsText(file)
408
409 // Reset input
410 if (importInput.value) {
411 importInput.value.value = ''
412 }
413 }
414
415 function resetSettings() {
416 if (confirm('Are you sure you want to reset all settings to their defaults? This action cannot be undone.')) {
417 Object.assign(settings, defaultSettings)
418 saveSettings()
419 applyTheme(settings.theme)
420
421 if (window.$notify) {
422 window.$notify.success('Settings reset to defaults')
423 }
424 }
425 }
426
427 onMounted(() => {
428 loadSettings()
429 applyTheme(settings.theme)
430 })
431 </script>