The shell layout was rendering an operator-style header (Discover / Matches / Profile / Safety / Devtools links + auth slot) on every `/dating/*` route, including login, register, reset and the product pages. That bar reads as a developer dashboard, not as the navigation of a real dating app. Layout (`+layout.svelte`): - The header now mounts only when the route is under `/dating/admin` or `/dating/devtools` — i.e. the moderation / instrumentation surfaces, where operator-grade nav is correct. On every other surface the layout renders the children directly with the App composed and the session probe still running. - Auth pages (login, register, reset, mfa) end up showing only their centered card; product pages (discover, matches, chat, profile, photos) get the full viewport with no chrome above. `_components/AppNav.svelte`: - Bottom-tab navigation for the product surface — Descubre / Matches / Perfil / Seguridad + Salir. Sticky-bottom on mobile, sits as a rounded bar at the foot of the viewport on desktop. Mounted by Discover, Matches and Profile so the user can still cross between top-level sections without the operator bar. Chat keeps its own back-arrow (it's full-screen). Photos returns via the back link in its header. Auth routes need no nav. Gates: 1695 tests + check (0/0) + build + bundle 22.52 KB + aliases — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>master
parent
35f484ea28
commit
9a1aa14ef7
@ -0,0 +1,124 @@
|
||||
<!--
|
||||
Bottom-tab navigation for the product surface (Discover, Matches,
|
||||
Profile, Safety, plus a logout). Pages opt-in by mounting it; the
|
||||
operator-style header is rendered by the layout only on
|
||||
admin/devtools, so this is the regular user's only cross-section
|
||||
nav.
|
||||
|
||||
Bottom-fixed on mobile, sticky on desktop. Tab labels stay short
|
||||
so the bar is finger-sized on phones.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { getNexoContext } from '../_lib/context';
|
||||
|
||||
const nexo = getNexoContext();
|
||||
|
||||
const items = [
|
||||
{ href: '/dating/discover', label: 'Descubre', icon: '✦' },
|
||||
{ href: '/dating/matches', label: 'Matches', icon: '♡' },
|
||||
{ href: '/dating/profile', label: 'Perfil', icon: '◔' },
|
||||
{ href: '/dating/safety', label: 'Seguridad', icon: '⚐' }
|
||||
] as const;
|
||||
|
||||
function isCurrent(href: string): boolean {
|
||||
return page.url.pathname === href || page.url.pathname.startsWith(`${href}/`);
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await nexo.api.logout();
|
||||
} finally {
|
||||
nexo.setSession({ authenticated: false });
|
||||
await goto('/dating/login', { replaceState: true });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="appnav" aria-label="Nexo">
|
||||
<ul>
|
||||
{#each items as item (item.href)}
|
||||
<li>
|
||||
<a href={item.href} aria-current={isCurrent(item.href) ? 'page' : undefined}>
|
||||
<span class="icon" aria-hidden="true">{item.icon}</span>
|
||||
<span class="label">{item.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
<li class="spacer" aria-hidden="true"></li>
|
||||
<li>
|
||||
<button type="button" class="logout" onclick={logout}>
|
||||
<span class="icon" aria-hidden="true">⏻</span>
|
||||
<span class="label">Salir</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.appnav {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
margin: 1.5rem -1rem -1rem;
|
||||
background: color-mix(in srgb, canvas 92%, currentColor 8%);
|
||||
border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
|
||||
}
|
||||
.appnav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.4rem 0.6rem;
|
||||
display: flex;
|
||||
gap: 0.15rem;
|
||||
max-width: 40rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.appnav a,
|
||||
.appnav button {
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.78rem;
|
||||
min-width: 3.6rem;
|
||||
}
|
||||
.appnav a[aria-current='page'] {
|
||||
background: color-mix(in srgb, currentColor 10%, transparent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.appnav .icon {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.appnav .logout {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.appnav .logout:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.appnav {
|
||||
position: static;
|
||||
margin: 1.5rem 0 0;
|
||||
border-top: none;
|
||||
border: 1px solid color-mix(in srgb, currentColor 10%, transparent);
|
||||
border-radius: 12px;
|
||||
background: color-mix(in srgb, canvas 96%, currentColor 4%);
|
||||
}
|
||||
.appnav ul {
|
||||
padding: 0.4rem 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in new issue