fix(web): minor UI refinements across header, pills, swipe, and settings

- Remove ThemeSwitcher from header (already accessible via settings)
- Increase pill padding and font size for better tap targets
- Guard non-cancelable touchmove preventDefault in SwipeAction
- Restyle settings page with grid-area layout and inline sign-out button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 14:59:58 +01:00
parent 3bb2ef2759
commit b3c30738bd
4 changed files with 147 additions and 117 deletions
+136 -104
View File
@@ -6,11 +6,11 @@
import Input from '$lib/components/ui/Input.svelte';
import { auth } from '$lib/api/endpoints.js';
import { goto } from '$app/navigation';
let apiKey = '';
let saving = false;
let error = '';
/**
* Save API key as manual auth
*/
@@ -19,10 +19,10 @@
error = 'API key is required';
return;
}
saving = true;
error = '';
try {
// Store API key as access token (for manual auth mode)
authStore.setAuth({
@@ -36,7 +36,7 @@
email: null
}
});
goto('/');
} catch (err) {
error = err instanceof Error ? err.message : 'Failed to save API key';
@@ -44,7 +44,7 @@
saving = false;
}
}
/**
* Logout
*/
@@ -56,11 +56,11 @@
console.error('Logout error:', err instanceof Error ? err.message : err);
}
}
authStore.clear();
goto('/auth/login');
}
/**
* Trigger manual sync
*/
@@ -74,107 +74,112 @@
}
</script>
<div class="page">
<div class="container">
<div class="page-header">
<a href="/" class="back-link" aria-label="Back to tasks">
<svg class="back-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</a>
<h1>Settings</h1>
</div>
<header class="settings-header">
<a href="/" class="back-link" aria-label="Back to tasks">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</a>
<h1>Settings</h1>
{#if $authStore.isAuthenticated}
<button class="signout-btn" on:click={logout} aria-label="Sign out">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</button>
{/if}
</header>
<div class="settings-content">
<section class="section">
<h2>Theme</h2>
<ThemeSwitcher mode="full" />
</section>
{#if $authStore.isAuthenticated}
<section class="section">
<h2>Theme</h2>
<ThemeSwitcher mode="full" />
<h2>Account</h2>
<div class="info-row">
<span class="label">Username:</span>
<span class="value">{$authStore.user?.username || 'Unknown'}</span>
</div>
{#if $authStore.user?.email}
<div class="info-row">
<span class="label">Email:</span>
<span class="value">{$authStore.user.email}</span>
</div>
{/if}
</section>
{#if $authStore.isAuthenticated}
<section class="section">
<h2>Account</h2>
<section class="section">
<h2>Sync</h2>
<div class="info-row">
<span class="label">Status:</span>
<span class="value">{$syncStore.status}</span>
</div>
<div class="info-row">
<span class="label">Queue:</span>
<span class="value">{$syncStore.queueSize} changes</span>
</div>
{#if $syncStore.lastSync}
<div class="info-row">
<span class="label">Username:</span>
<span class="value">{$authStore.user?.username || 'Unknown'}</span>
<span class="label">Last Sync:</span>
<span class="value">{new Date($syncStore.lastSync * 1000).toLocaleString()}</span>
</div>
{#if $authStore.user?.email}
<div class="info-row">
<span class="label">Email:</span>
<span class="value">{$authStore.user.email}</span>
</div>
{/if}
</section>
<section class="section">
<h2>Sync</h2>
<div class="info-row">
<span class="label">Status:</span>
<span class="value">{$syncStore.status}</span>
</div>
<div class="info-row">
<span class="label">Queue:</span>
<span class="value">{$syncStore.queueSize} changes</span>
</div>
{#if $syncStore.lastSync}
<div class="info-row">
<span class="label">Last Sync:</span>
<span class="value">{new Date($syncStore.lastSync * 1000).toLocaleString()}</span>
</div>
{/if}
<Button on:click={triggerSync} disabled={$syncStore.status === 'syncing'}>
{$syncStore.status === 'syncing' ? 'Syncing...' : 'Sync Now'}
</Button>
</section>
<section class="section">
<h2>Actions</h2>
<Button variant="danger" on:click={logout}>Logout</Button>
</section>
{:else}
<section class="section">
<h2>API Key Authentication</h2>
<p class="text-secondary mb-md">
For testing, you can authenticate with an API key. Generate a key using:
<code>opal server keygen --name "Web"</code>
{/if}
<Button on:click={triggerSync} disabled={$syncStore.status === 'syncing'}>
{$syncStore.status === 'syncing' ? 'Syncing...' : 'Sync Now'}
</Button>
</section>
{:else}
<section class="section">
<h2>API Key Authentication</h2>
<p class="text-secondary mb-md">
For testing, you can authenticate with an API key. Generate a key using:
<code>opal server keygen --name "Web"</code>
</p>
<Input
label="API Key"
type="password"
placeholder="oak_..."
bind:value={apiKey}
{error}
/>
<Button
on:click={saveApiKey}
loading={saving}
fullWidth
>
Save API Key
</Button>
<div class="mt-lg text-center">
<p class="text-sm text-secondary">
Or <a href="/auth/login">login with OAuth</a>
</p>
<Input
label="API Key"
type="password"
placeholder="oak_..."
bind:value={apiKey}
{error}
/>
<Button
on:click={saveApiKey}
loading={saving}
fullWidth
>
Save API Key
</Button>
<div class="mt-lg text-center">
<p class="text-sm text-secondary">
Or <a href="/auth/login">login with OAuth</a>
</p>
</div>
</section>
{/if}
</div>
</div>
</section>
{/if}
</div>
<style>
.page-header {
.settings-header {
grid-area: header;
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding-top: var(--spacing-lg);
margin-bottom: var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
.page-header h1 {
.settings-header h1 {
flex: 1;
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: 0;
}
@@ -190,23 +195,50 @@
}
.back-link:hover {
background-color: var(--bg-tertiary);
background-color: var(--bg-secondary);
text-decoration: none;
}
.back-icon {
.signout-btn {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
background: none;
border: none;
border-radius: var(--border-radius);
color: var(--text-secondary);
cursor: pointer;
transition: background-color 0.2s;
}
.signout-btn:hover {
background-color: var(--bg-secondary);
color: var(--color-danger);
}
.icon {
width: 1.25rem;
height: 1.25rem;
}
.section {
.settings-content {
grid-area: content;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: var(--spacing-md);
min-height: 0;
background-color: var(--bg-primary);
}
.section {
background-color: var(--bg-secondary);
border-radius: var(--border-radius);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-sm);
}
.info-row {
display: flex;
justify-content: space-between;
@@ -214,20 +246,20 @@
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-color);
}
.info-row:last-of-type {
border-bottom: none;
}
.label {
font-weight: 500;
color: var(--text-secondary);
}
.value {
color: var(--text-primary);
}
code {
background-color: var(--bg-tertiary);
padding: 0.125rem 0.375rem;