Settings page: single Save, clean flow

One Save Settings button covers all configuration:
- Welcome message
- Default output formats (video/audio)
- Privacy mode toggle + retention hours

Below the save area, separated by dividers:
- Manual Purge (immediate action, Sure? gate)
- Change Password (immediate action, own button)

Settings fields have subtle bottom borders for visual rhythm.
No section headings — the flow reads naturally top-to-bottom.
Removed redundant privacySaved ref and savePrivacy function.
This commit is contained in:
xpltd 2026-03-19 06:19:46 -05:00
parent fe45fdce50
commit 0df9573caa

View file

@ -23,7 +23,6 @@ const defaultAudioFormat = ref('auto')
const settingsSaved = ref(false)
const privacyMode = ref(false)
const privacyRetentionHours = ref(24)
const privacySaved = ref(false)
const purgeConfirming = ref(false)
let purgeConfirmTimer: ReturnType<typeof setTimeout> | null = null
@ -67,30 +66,19 @@ async function switchTab(tab: typeof activeTab.value) {
}
}
async function saveSettings() {
async function saveAllSettings() {
settingsSaved.value = false
const ok = await store.updateSettings({
welcome_message: welcomeMessage.value,
default_video_format: defaultVideoFormat.value,
default_audio_format: defaultAudioFormat.value,
})
if (ok) {
await configStore.loadConfig()
settingsSaved.value = true
setTimeout(() => { settingsSaved.value = false }, 3000)
}
}
async function savePrivacy() {
privacySaved.value = false
const ok = await store.updateSettings({
privacy_mode: privacyMode.value,
privacy_retention_hours: privacyRetentionHours.value,
})
if (ok) {
await configStore.loadConfig()
privacySaved.value = true
setTimeout(() => { privacySaved.value = false }, 3000)
settingsSaved.value = true
setTimeout(() => { settingsSaved.value = false }, 3000)
}
}
@ -271,10 +259,8 @@ function formatFilesize(bytes: number | null): string {
<!-- Settings tab -->
<div v-if="activeTab === 'settings'" class="tab-content">
<!-- Section: Appearance & Defaults -->
<div class="settings-section">
<h3 class="section-heading">Appearance &amp; Defaults</h3>
<!-- All configurable settings in one form -->
<div class="settings-form">
<div class="settings-field">
<label for="welcome-msg">Welcome Message</label>
<p class="field-hint">Displayed above the URL input on the main page. Leave empty to hide.</p>
@ -287,7 +273,7 @@ function formatFilesize(bytes: number | null): string {
></textarea>
</div>
<div class="settings-field" style="margin-top: var(--space-lg);">
<div class="settings-field">
<label>Default Output Formats</label>
<p class="field-hint">When "Auto" is selected, files are converted to these formats instead of the native container.</p>
<div class="format-defaults">
@ -313,20 +299,6 @@ function formatFilesize(bytes: number | null): string {
</div>
</div>
<div class="settings-actions">
<button @click="saveSettings" :disabled="store.isLoading" class="btn-save">
{{ store.isLoading ? 'Saving…' : 'Save' }}
</button>
<span v-if="settingsSaved" class="save-confirm"> Saved</span>
</div>
</div>
<hr class="settings-divider" />
<!-- Section: Privacy & Data -->
<div class="settings-section">
<h3 class="section-heading">Privacy &amp; Data</h3>
<div class="settings-field">
<label class="toggle-label">
<span>Privacy Mode</span>
@ -357,14 +329,21 @@ function formatFilesize(bytes: number | null): string {
</div>
</div>
<div class="settings-actions">
<button @click="savePrivacy" :disabled="store.isLoading" class="btn-save">
{{ store.isLoading ? 'Saving…' : 'Save' }}
<div class="settings-actions settings-save-row">
<button @click="saveAllSettings" :disabled="store.isLoading" class="btn-save">
{{ store.isLoading ? 'Saving…' : 'Save Settings' }}
</button>
<span v-if="privacySaved" class="save-confirm"> Saved</span>
<span v-if="settingsSaved" class="save-confirm"> Saved</span>
</div>
<p class="field-hint">
Settings are applied immediately but reset on server restart.
</p>
</div>
<div class="settings-field" style="margin-top: var(--space-lg);">
<hr class="settings-divider" />
<!-- Actions (not "settings" these are immediate operations) -->
<div class="settings-field">
<label>Manual Purge</label>
<p class="field-hint">
Immediately clear all completed and failed downloads removes
@ -386,17 +365,12 @@ function formatFilesize(bytes: number | null): string {
<p v-if="store.purgeResult.active_skipped">{{ store.purgeResult.active_skipped }} active jobs skipped</p>
</div>
</div>
</div>
<hr class="settings-divider" />
<!-- Section: Security -->
<div class="settings-section">
<h3 class="section-heading">Security</h3>
<div class="settings-field">
<label>Change Password</label>
<p class="field-hint">Update the admin password. Takes effect immediately but resets on server restart.</p>
<p class="field-hint">Takes effect immediately but resets on server restart.</p>
<div class="password-fields">
<input
v-model="currentPassword"
@ -440,11 +414,6 @@ function formatFilesize(bytes: number | null): string {
</div>
</div>
</div>
<p class="field-hint" style="margin-top: var(--space-lg);">
All settings are applied immediately but reset on server restart.
</p>
</div>
</template>
</div>
</template>
@ -592,6 +561,26 @@ h3 {
margin-bottom: var(--space-sm);
}
.settings-form {
display: flex;
flex-direction: column;
gap: var(--space-lg);
}
.settings-form .settings-field {
padding-bottom: var(--space-md);
border-bottom: 1px solid var(--color-border);
}
.settings-form .settings-field:last-of-type {
border-bottom: none;
padding-bottom: 0;
}
.settings-save-row {
padding-top: var(--space-sm);
}
.section-heading {
font-size: var(--font-size-md);
font-weight: 600;