feat(sys-bar): polish sign-in forms, fix passcode flow, dynamic section headers
Sign In/Out (e_app_sign_in_out.svelte): - Remove redundant internal header (sr-only was broken by :global CSS override) - Full-width form inputs and buttons with 'or' divider between the two methods - Signed-in state shows centered username and full-width action buttons Access/Passcode (e_app_access_type.svelte): - Fix 'Locked' button: was running trigger=true (no-op permission reprocess); now correctly toggles show_passcode_input so the input shows/hides on click System bar (e_app_sys_bar.svelte): - Dynamic section headers: Sign In/Out shows username when signed in; Access/Passcode shows ShieldEllipsis/ShieldMinus/ShieldUser based on state - Fix passcode input not showing on re-open via menu button: onDestroy resets show_element__passcode_input=false; toggle_expand now restores it to true for anonymous/no-access state (matches handle_shield_click) - Broaden anonymous check from === 'anonymous' to !access_type || === 'anonymous' - Remove dead getElementById focus call (DOM not ready at that point; focus_input binding in Element_access_type handles it correctly) - Appearance section: mode/font buttons at top, dark mode gets amber tint in light mode for visual context, theme select uses text-sm Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -424,19 +424,19 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
// handle_check_access_type_passcode();
|
||||
trigger = true;
|
||||
// Toggle the passcode input — show it if hidden, hide it (cancel) if shown.
|
||||
show_passcode_input = !show_passcode_input;
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-success hover:preset-filled-warning-500 access_type_unlock_btn transition-all"
|
||||
title="Anonymous public access is currently set. Access mode is disabled/locked."
|
||||
title={show_passcode_input ? 'Cancel passcode entry' : 'Enter a passcode to unlock access'}
|
||||
>
|
||||
<span class="fas fa-lock mx-1"></span>
|
||||
<span class="lock_icon">Locked</span>
|
||||
|
||||
<span class="fas fa-unlock mx-1 unlock_icon hidden"></span>
|
||||
{#if show_passcode_input}
|
||||
<span class="unlock_text">Cancel</span>
|
||||
<span class="fas fa-lock-open mx-1"></span>
|
||||
<span>Cancel</span>
|
||||
{:else}
|
||||
<span class="fas fa-lock mx-1"></span>
|
||||
<span class="lock_icon">Locked</span>
|
||||
<span class="fas fa-unlock mx-1 unlock_icon hidden"></span>
|
||||
<span class="unlock_text">Access?</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
LockKeyhole,
|
||||
Mail,
|
||||
MailCheck,
|
||||
ShieldUser,
|
||||
User,
|
||||
UserCheck,
|
||||
UserLock
|
||||
UserCheck
|
||||
} from '@lucide/svelte';
|
||||
|
||||
// *** Import Aether specific variables and functions
|
||||
@@ -373,44 +370,11 @@
|
||||
"
|
||||
class:hidden
|
||||
>
|
||||
<header class:hidden={!$ae_sess.show__sign_in_out__fields} class="ae_header w-full">
|
||||
<h2 class="text-sm text-center font-semibold">
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="btn btn-sm variant-outline-surface hover:variant-filled-surface"
|
||||
title="Show/Hide Sign-In/Out Options"
|
||||
onclick={() => {
|
||||
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
||||
}}
|
||||
> -->
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
<!-- <span class="text-sm text-gray-500">
|
||||
{$ae_loc?.user.username ?? '-- not set --'}
|
||||
</span> -->
|
||||
<!-- {#if log_lvl > 1}
|
||||
<span class="text-xs text-gray-500">({$ae_loc.person_id} / {$ae_loc.user_id})</span>
|
||||
{/if} -->
|
||||
<!-- </button> -->
|
||||
{:else}
|
||||
<!-- <LogIn class="mx-1" /> -->
|
||||
User Sign In
|
||||
{/if}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<span
|
||||
class:hidden={!$ae_sess.show__sign_in_out__fields}
|
||||
class="flex flex-col gap-1 transition-all w-full"
|
||||
>
|
||||
<span class="flex flex-col gap-2 w-full">
|
||||
{#if !$ae_loc?.person_id && !$ae_loc?.user_id}
|
||||
<!-- Form for user look up based on email address -->
|
||||
<form
|
||||
class="
|
||||
ae_sign_in_form
|
||||
flex flex-col gap-1 items-end justify-center
|
||||
p-1 my-1
|
||||
"
|
||||
class="ae_sign_in_form flex flex-col gap-2 w-full"
|
||||
onsubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
if ($ae_sess.auth__entered_email) {
|
||||
@@ -423,32 +387,34 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500 font-medium">Email magic link</div>
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-48"
|
||||
class="input w-full text-sm"
|
||||
placeholder="Email Address"
|
||||
value={$ae_sess.auth__entered_email ?? ''}
|
||||
oninput={(e) => ($ae_sess.auth__entered_email = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500 w-full justify-center"
|
||||
title="Look up user by email and send sign in email"
|
||||
>
|
||||
<!-- <User class="mx-1" /> -->
|
||||
<!-- <Mail class="mx-1" /> -->
|
||||
<MailCheck class="mx-1" />
|
||||
Email Sign In
|
||||
<MailCheck size="1em" class="shrink-0" />
|
||||
<span class="ml-1">Send Sign In Email</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Divider between the two sign-in methods -->
|
||||
<div class="flex items-center gap-2 text-xs text-gray-400 dark:text-gray-600">
|
||||
<div class="flex-1 border-t border-gray-200/60 dark:border-gray-700/60"></div>
|
||||
<span>or</span>
|
||||
<div class="flex-1 border-t border-gray-200/60 dark:border-gray-700/60"></div>
|
||||
</div>
|
||||
|
||||
<!-- We need to get the person's linked User ID and auth_key or User username and password to sign in. -->
|
||||
<form
|
||||
class="
|
||||
ae_sign_in_form
|
||||
flex flex-col gap-1 items-end
|
||||
p-1 my-1
|
||||
"
|
||||
class="ae_sign_in_form flex flex-col gap-2 w-full"
|
||||
onsubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -657,17 +623,24 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500 font-medium">
|
||||
{#if $ae_sess.auth__entered_user_id}
|
||||
User ID & Auth Key
|
||||
{:else}
|
||||
Username & password
|
||||
{/if}
|
||||
</div>
|
||||
{#if $ae_sess.auth__entered_user_id}
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-36"
|
||||
class="input w-full text-sm"
|
||||
placeholder="User ID"
|
||||
value={$ae_sess.auth__entered_user_id ?? ''}
|
||||
oninput={(e) => ($ae_sess.auth__entered_user_id = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-36"
|
||||
class="input w-full text-sm"
|
||||
placeholder="Auth Key"
|
||||
value={$ae_sess.auth__entered_user_key ?? ''}
|
||||
oninput={(e) => ($ae_sess.auth__entered_user_key = (e.target as HTMLInputElement).value)}
|
||||
@@ -675,14 +648,14 @@
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="input max-w-48"
|
||||
class="input w-full text-sm"
|
||||
placeholder="Username"
|
||||
value={$ae_sess.auth__entered_username ?? ''}
|
||||
oninput={(e) => ($ae_sess.auth__entered_username = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
class="input max-w-48"
|
||||
class="input w-full text-sm"
|
||||
placeholder="Password"
|
||||
value={$ae_sess.auth__entered_password ?? ''}
|
||||
oninput={(e) => ($ae_sess.auth__entered_password = (e.target as HTMLInputElement).value)}
|
||||
@@ -691,44 +664,42 @@
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500"
|
||||
class="btn btn-sm preset-tonal-secondary border border-secondary-500 hover:preset-filled-secondary-500 w-full justify-center"
|
||||
title="Sign in with username and password"
|
||||
>
|
||||
<!-- <LogIn class="mx-1" /> -->
|
||||
<UserCheck class="mx-1" />
|
||||
{#if $ae_sess.auth__entered_user_id}
|
||||
User ID
|
||||
{:else}
|
||||
Username
|
||||
{/if}
|
||||
Sign In
|
||||
<UserCheck size="1em" class="shrink-0" />
|
||||
<span class="ml-1">
|
||||
{#if $ae_sess.auth__entered_user_id}User ID{:else}Username{/if}
|
||||
Sign In
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-1 items-center justify-center">
|
||||
<span class="text-sm text-gray-500">
|
||||
<!-- Signed in: show username, optional change password, sign out -->
|
||||
<div class="flex flex-col gap-2 items-stretch w-full">
|
||||
<div class="text-sm text-center text-gray-500 dark:text-gray-400 py-1 font-medium">
|
||||
{$ae_loc?.user?.username ?? '-- not set --'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Display change password option if the user is signed in and in Edit Mode -->
|
||||
<!-- Change password — edit mode only -->
|
||||
{#if $ae_loc.edit_mode}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500 w-full justify-center"
|
||||
title="Change Password"
|
||||
onclick={() => {
|
||||
$ae_sess.show__modal_change_password = true;
|
||||
}}
|
||||
>
|
||||
<LockKeyhole class="mx-1" />
|
||||
<span class="hidden sm:inline"> Change Password </span>
|
||||
<LockKeyhole size="1em" class="shrink-0" />
|
||||
<span class="ml-1">Change Password</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Display sign out option if the user is signed in -->
|
||||
<!-- Sign out -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500"
|
||||
class="btn btn-sm preset-tonal-warning border border-warning-500 hover:preset-filled-warning-500 w-full justify-center"
|
||||
title="Sign Out"
|
||||
onclick={async () => {
|
||||
if (confirm('Are you sure you want to sign out?')) {
|
||||
@@ -736,83 +707,13 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LogOut class="mx-1" />
|
||||
<span class="hidden sm:inline"> Sign Out </span>
|
||||
<LogOut size="1em" class="shrink-0" />
|
||||
<span class="ml-1">Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<div class="flex flex-row gap-1 items-center justify-between w-full transition-all">
|
||||
<span>
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
<span class="fas fa-user mx-1 text-gray-500"></span>
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
{:else}
|
||||
<!-- <span class="fas fa-user-x mx-1 text-gray-500"></span> -->
|
||||
<ShieldUser class="inline-block text-gray-500" />
|
||||
User sign in:
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
btn btn-sm text-xs
|
||||
variant-outline-warning hover:preset-tonal-warning
|
||||
border border-warning-500 group
|
||||
"
|
||||
title="Sign In"
|
||||
onclick={() => {
|
||||
$ae_sess.show__sign_in_out__fields = !$ae_sess.show__sign_in_out__fields; // Toggle the visibility of the sign-in form
|
||||
if (!$ae_sess.show__sign_in_out__fields) {
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
{#if $ae_sess.show__sign_in_out__fields}
|
||||
<CircleX class="m-1" />
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
Hide Sign-In
|
||||
</span>
|
||||
{:else}
|
||||
<User class="mx-1 inline-block text-gray-500" />
|
||||
{$ae_loc?.person?.full_name_override ?? $ae_loc?.person?.full_name}
|
||||
{/if}
|
||||
{:else if $ae_sess.show__sign_in_out__fields}
|
||||
<CircleX class="mx-1" />
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
Hide Sign-In
|
||||
</span>
|
||||
{:else}
|
||||
<!-- <LockKeyhole size="1.25em" class="mx-1 inline-block text-gray-500" /> -->
|
||||
<UserLock size="1.25em" class="m-1 inline-block text-gray-500" />
|
||||
<!-- <User class="mx-1 inline-block text-gray-500" /> -->
|
||||
<span
|
||||
class="
|
||||
cfg_text
|
||||
hidden
|
||||
group-hover:inline
|
||||
"
|
||||
>
|
||||
User Sign-In
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Change Password Modal -->
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import {
|
||||
Bug,
|
||||
CircleX,
|
||||
LogOut,
|
||||
Menu,
|
||||
Moon,
|
||||
Sun,
|
||||
@@ -91,7 +92,10 @@
|
||||
$ae_sess.sys_menu.expand = true;
|
||||
$ae_loc.app_cfg.show_element__access_type = true;
|
||||
|
||||
if ($ae_loc?.access_type === 'anonymous') {
|
||||
if (!$ae_loc?.access_type || $ae_loc?.access_type === 'anonymous') {
|
||||
// onDestroy in Element_access_type resets show_element__passcode_input to false
|
||||
// on panel close, so we must restore it here for menu-button opens too.
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
$ae_sess.sys_menu.focus_passcode_input = true;
|
||||
} else {
|
||||
$ae_loc.sys_menu.expand_user = false;
|
||||
@@ -139,8 +143,8 @@
|
||||
$ae_loc.sys_menu.expand_access_type = true;
|
||||
$ae_sess.app_cfg.show_element__passcode_input = true;
|
||||
$ae_sess.sys_menu.focus_passcode_input = true;
|
||||
const to_focus = document.getElementById('access_passcode_input');
|
||||
to_focus?.focus();
|
||||
// Note: focus is handled reactively by the focus_input binding in Element_access_type.
|
||||
// Direct getElementById here would fail — the panel DOM doesn't exist yet at this point.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,9 +269,17 @@
|
||||
class="w-full flex items-center justify-between px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
onclick={() => sec_signin = !sec_signin}
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="fas fa-sign-in-alt opacity-60"></span>
|
||||
Sign In / Out
|
||||
<span class="flex items-center gap-1.5 min-w-0">
|
||||
{#if $ae_loc?.person_id && $ae_loc?.user_id}
|
||||
<LogOut size="0.85em" class="opacity-60 shrink-0" />
|
||||
<span>Sign Out</span>
|
||||
<span class="normal-case font-normal opacity-70 truncate">
|
||||
{$ae_loc?.user?.username ?? person_display ?? ''}
|
||||
</span>
|
||||
{:else}
|
||||
<ShieldUser size="0.85em" class="opacity-60 shrink-0" />
|
||||
<span>User Sign In</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="opacity-50">{sec_signin ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
@@ -290,11 +302,20 @@
|
||||
class="w-full flex items-center justify-between px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
onclick={() => sec_access = !sec_access}
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<span class="fas fa-shield-alt opacity-60"></span>
|
||||
Access / Passcode
|
||||
{#if access_label}
|
||||
<span class="normal-case font-normal text-primary-600 dark:text-primary-400 ml-1">({access_label})</span>
|
||||
<span class="flex items-center gap-1.5 min-w-0">
|
||||
{#if $ae_loc?.access_type && $ae_loc?.access_type !== 'anonymous'}
|
||||
{#if $ae_loc?.user_access_type && $ae_loc?.access_type !== $ae_loc?.user_access_type}
|
||||
<ShieldMinus size="0.85em" class="opacity-60 shrink-0" />
|
||||
<span>Elevated Access</span>
|
||||
<span class="normal-case font-normal text-primary-600 dark:text-primary-400 truncate">({access_label})</span>
|
||||
{:else}
|
||||
<ShieldEllipsis size="0.85em" class="opacity-60 shrink-0" />
|
||||
<span>Access</span>
|
||||
<span class="normal-case font-normal text-primary-600 dark:text-primary-400 truncate">({access_label})</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<ShieldUser size="0.85em" class="opacity-60 shrink-0" />
|
||||
<span>Enter Passcode</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="opacity-50">{sec_access ? '▲' : '▼'}</span>
|
||||
@@ -330,29 +351,18 @@
|
||||
<span class="opacity-50">{sec_appearance ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
{#if sec_appearance}
|
||||
<div class="px-3 pb-3 space-y-3">
|
||||
<div class="px-3 pb-3 pt-1 space-y-3">
|
||||
|
||||
<!-- Theme name -->
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500 uppercase tracking-wide">Theme</div>
|
||||
<select
|
||||
bind:value={$ae_loc.theme_name}
|
||||
onchange={(e) => document.documentElement.setAttribute('data-theme', (e.target as HTMLSelectElement).value)}
|
||||
class="select select-sm w-full text-xs"
|
||||
>
|
||||
{#each theme_options as opt}
|
||||
<option value={opt.value}>{opt.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Dark / Light mode + Font size (side by side) -->
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<!-- Quick actions row: mode + font (most commonly used) -->
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm flex-1 preset-tonal-secondary hover:preset-filled-secondary-500 transition-all"
|
||||
class="btn btn-sm flex-1 transition-all
|
||||
{$ae_loc?.theme_mode === 'dark'
|
||||
? 'preset-tonal-secondary hover:preset-filled-secondary-500'
|
||||
: 'preset-tonal-warning hover:preset-filled-warning-500'}"
|
||||
onclick={toggle_theme_mode}
|
||||
title="Toggle light/dark mode"
|
||||
title="Toggle light/dark mode (currently: {$ae_loc?.theme_mode ?? 'unknown'})"
|
||||
>
|
||||
{#if $ae_loc?.theme_mode === 'dark'}
|
||||
<Moon size="1em" class="shrink-0" />
|
||||
@@ -369,10 +379,24 @@
|
||||
title={font_title}
|
||||
>
|
||||
<span class="font-bold text-sm leading-none">{font_label}</span>
|
||||
<span class="text-xs ml-1">Font</span>
|
||||
<span class="text-xs ml-1 opacity-70">Font</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Theme name select -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="text-xs text-gray-400 dark:text-gray-500 font-medium">Theme</div>
|
||||
<select
|
||||
bind:value={$ae_loc.theme_name}
|
||||
onchange={(e) => document.documentElement.setAttribute('data-theme', (e.target as HTMLSelectElement).value)}
|
||||
class="select w-full text-sm"
|
||||
>
|
||||
{#each theme_options as opt (opt.value)}
|
||||
<option value={opt.value}>{opt.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user