feat: add SettingsView with entry point style toggle
This commit is contained in:
parent
184ad07f80
commit
426b4207eb
1 changed files with 82 additions and 0 deletions
82
web/src/views/SettingsView.vue
Normal file
82
web/src/views/SettingsView.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-6 max-w-2xl mx-auto">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h1 class="text-text-primary text-xl font-semibold mb-1">Settings</h1>
|
||||||
|
<p class="text-text-dim text-sm">
|
||||||
|
Turnstone preferences — stored alongside the log database.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded border border-surface-border bg-surface-raised p-5 space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-text-primary text-sm font-semibold mb-1">Quick Capture Entry Point</h2>
|
||||||
|
<p class="text-text-dim text-xs mb-3">
|
||||||
|
Where the "describe it and search" input appears on every page.
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button
|
||||||
|
v-for="opt in entryPointOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
@click="setEntryPoint(opt.value as 'topbar' | 'fab')"
|
||||||
|
:class="[
|
||||||
|
'flex-1 px-4 py-3 rounded border text-sm transition-colors text-left',
|
||||||
|
prefs.entry_point_style === opt.value
|
||||||
|
? 'border-accent bg-accent/10 text-accent'
|
||||||
|
: 'border-surface-border text-text-muted hover:text-text-primary hover:border-accent'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="font-medium">{{ opt.label }}</div>
|
||||||
|
<div class="text-xs text-text-dim mt-0.5">{{ opt.desc }}</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="saveStatus"
|
||||||
|
class="text-xs mt-2"
|
||||||
|
:class="saveStatus.ok ? 'text-green-400' : 'text-sev-error'"
|
||||||
|
>
|
||||||
|
{{ saveStatus.msg }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const BASE = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||||
|
|
||||||
|
interface Prefs { entry_point_style: 'topbar' | 'fab' }
|
||||||
|
|
||||||
|
const prefs = ref<Prefs>({ entry_point_style: 'topbar' })
|
||||||
|
const saveStatus = ref<{ ok: boolean; msg: string } | null>(null)
|
||||||
|
|
||||||
|
const entryPointOptions = [
|
||||||
|
{ value: 'topbar', label: 'Top bar', desc: 'Persistent input bar below the nav on every page' },
|
||||||
|
{ value: 'fab', label: 'FAB', desc: 'Floating action button in the bottom-right corner' },
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE}/api/settings`)
|
||||||
|
if (res.ok) prefs.value = await res.json()
|
||||||
|
} catch { /* non-critical — default stays topbar */ }
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setEntryPoint(style: 'topbar' | 'fab') {
|
||||||
|
prefs.value = { entry_point_style: style }
|
||||||
|
saveStatus.value = null
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE}/api/settings`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ entry_point_style: style }),
|
||||||
|
})
|
||||||
|
if (!res.ok) throw new Error(await res.text())
|
||||||
|
saveStatus.value = { ok: true, msg: 'Saved' }
|
||||||
|
setTimeout(() => { saveStatus.value = null }, 2000)
|
||||||
|
} catch {
|
||||||
|
saveStatus.value = { ok: false, msg: 'Save failed — check server connection' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Loading…
Reference in a new issue