chore: polish navbar icons and form styling
This commit is contained in:
parent
f8162ecba4
commit
6d74532cbf
3 changed files with 108 additions and 15 deletions
|
|
@ -40,6 +40,7 @@
|
||||||
- Home page shows a rolling feed of latest listings; navbar + CTA link to browse.
|
- Home page shows a rolling feed of latest listings; navbar + CTA link to browse.
|
||||||
- Listing creation form captures address details, coordinates, amenities (incl. EV/AC), and cover image choice.
|
- Listing creation form captures address details, coordinates, amenities (incl. EV/AC), and cover image choice.
|
||||||
- Documentation moved to `docs/`; PlantUML diagrams rendered to SVG and embedded in docs pages (draw.io sources kept for architecture/infra).
|
- Documentation moved to `docs/`; PlantUML diagrams rendered to SVG and embedded in docs pages (draw.io sources kept for architecture/infra).
|
||||||
|
- UI polish: navbar buttons gained icons, consistent button sizing, and form fields restyled for alignment.
|
||||||
- HTTPS redirect middleware applied to staging/prod ingress.
|
- HTTPS redirect middleware applied to staging/prod ingress.
|
||||||
- FI/EN localization with navbar language toggle; UI strings translated; Approvals link shows pending count badge.
|
- FI/EN localization with navbar language toggle; UI strings translated; Approvals link shows pending count badge.
|
||||||
- Soft rejection/removal states for users/listings with timestamps; owner listing removal; login redirects home; listing visibility hides removed/not-published.
|
- Soft rejection/removal states for users/listings with timestamps; owner listing removal; login redirects home; listing visibility hides removed/not-published.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,78 @@ import { useI18n } from './I18nProvider';
|
||||||
|
|
||||||
type SessionUser = { id: string; email: string; role: string; status: string };
|
type SessionUser = { id: string; email: string; role: string; status: string };
|
||||||
|
|
||||||
|
function Icon({ name }: { name: string }) {
|
||||||
|
const common = { width: 16, height: 16, stroke: 'currentColor', fill: 'none', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round' };
|
||||||
|
switch (name) {
|
||||||
|
case 'profile':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<circle cx="12" cy="8" r="4" />
|
||||||
|
<path d="M5 20c0-3.3 3.1-6 7-6s7 2.7 7 6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'list':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M9 6h11" />
|
||||||
|
<path d="M9 12h11" />
|
||||||
|
<path d="M9 18h11" />
|
||||||
|
<path d="M4 6h0.01" />
|
||||||
|
<path d="M4 12h0.01" />
|
||||||
|
<path d="M4 18h0.01" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'plus':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M12 5v14" />
|
||||||
|
<path d="M5 12h14" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'logout':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M9 21H6a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3" />
|
||||||
|
<path d="M16 17l5-5-5-5" />
|
||||||
|
<path d="M21 12H9" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'login':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M15 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3" />
|
||||||
|
<path d="M10 17l5-5-5-5" />
|
||||||
|
<path d="M15 12H3" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'users':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||||
|
<circle cx="9" cy="7" r="4" />
|
||||||
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
||||||
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'check':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path d="M20 6L9 17l-5-5" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'globe':
|
||||||
|
return (
|
||||||
|
<svg {...common} viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
<path d="M3 12h18" />
|
||||||
|
<path d="M12 3a15.3 15.3 0 0 1 4 9 15.3 15.3 0 0 1-4 9 15.3 15.3 0 0 1-4-9 15.3 15.3 0 0 1 4-9z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
const { t, locale, setLocale } = useI18n();
|
const { t, locale, setLocale } = useI18n();
|
||||||
const [user, setUser] = useState<SessionUser | null>(null);
|
const [user, setUser] = useState<SessionUser | null>(null);
|
||||||
|
|
@ -62,7 +134,7 @@ export default function NavBar() {
|
||||||
{t('brand')}
|
{t('brand')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/listings" className="button secondary">
|
<Link href="/listings" className="button secondary">
|
||||||
{t('navBrowse')}
|
<Icon name="list" /> {t('navBrowse')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<nav style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<nav style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
|
@ -72,18 +144,18 @@ export default function NavBar() {
|
||||||
{user.email} · {user.role}
|
{user.email} · {user.role}
|
||||||
</span>
|
</span>
|
||||||
<Link href="/me" className="button secondary">
|
<Link href="/me" className="button secondary">
|
||||||
{t('navProfile')}
|
<Icon name="profile" /> {t('navProfile')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/listings/mine" className="button secondary">
|
<Link href="/listings/mine" className="button secondary">
|
||||||
{t('navMyListings')}
|
<Icon name="list" /> {t('navMyListings')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/listings/new" className="button secondary">
|
<Link href="/listings/new" className="button secondary">
|
||||||
{t('navNewListing')}
|
<Icon name="plus" /> {t('navNewListing')}
|
||||||
</Link>
|
</Link>
|
||||||
{showApprovals ? (
|
{showApprovals ? (
|
||||||
<>
|
<>
|
||||||
<Link href="/admin/pending" className="button secondary">
|
<Link href="/admin/pending" className="button secondary">
|
||||||
{t('navApprovals')}
|
<Icon name="check" /> {t('navApprovals')}
|
||||||
{pendingCount > 0 ? (
|
{pendingCount > 0 ? (
|
||||||
<span style={{ marginLeft: 6, background: '#ff7043', color: '#fff', borderRadius: 10, padding: '2px 6px', fontSize: 12 }}>
|
<span style={{ marginLeft: 6, background: '#ff7043', color: '#fff', borderRadius: 10, padding: '2px 6px', fontSize: 12 }}>
|
||||||
{t('approvalsBadge', { count: pendingCount })}
|
{t('approvalsBadge', { count: pendingCount })}
|
||||||
|
|
@ -92,27 +164,29 @@ export default function NavBar() {
|
||||||
</Link>
|
</Link>
|
||||||
{isAdmin ? (
|
{isAdmin ? (
|
||||||
<Link href="/admin/users" className="button secondary">
|
<Link href="/admin/users" className="button secondary">
|
||||||
{t('navUsers')}
|
<Icon name="users" /> {t('navUsers')}
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<button className="button secondary" onClick={logout}>
|
<button className="button secondary" onClick={logout}>
|
||||||
{t('navLogout')}
|
<Icon name="logout" /> {t('navLogout')}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Link href="/auth/login" className="button secondary">
|
<Link href="/auth/login" className="button secondary">
|
||||||
{t('navLogin')}
|
<Icon name="login" /> {t('navLogin')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/auth/register" className="button">
|
<Link href="/auth/register" className="button">
|
||||||
{t('navSignup')}
|
<Icon name="plus" /> {t('navSignup')}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginLeft: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginLeft: 8 }}>
|
||||||
<span style={{ fontSize: 12, color: '#555' }}>{t('navLanguage')}:</span>
|
<span style={{ fontSize: 12, color: '#555', display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<Icon name="globe" /> {t('navLanguage')}:
|
||||||
|
</span>
|
||||||
<button className="button secondary" onClick={() => setLocale('fi')} style={{ padding: '4px 8px', opacity: locale === 'fi' ? 1 : 0.7 }}>
|
<button className="button secondary" onClick={() => setLocale('fi')} style={{ padding: '4px 8px', opacity: locale === 'fi' ? 1 : 0.7 }}>
|
||||||
FI
|
FI
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -75,15 +75,20 @@ p {
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: var(--accent);
|
display: inline-flex;
|
||||||
color: #0b1224;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 10px 16px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: none;
|
border: none;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform 120ms ease, box-shadow 180ms ease;
|
background: var(--accent);
|
||||||
|
color: #0b1224;
|
||||||
|
transition: transform 120ms ease, box-shadow 180ms ease, background 120ms ease;
|
||||||
box-shadow: 0 15px 40px rgba(34, 211, 238, 0.16);
|
box-shadow: 0 15px 40px rgba(34, 211, 238, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,18 +316,31 @@ code {
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 12px;
|
padding: 12px 14px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(148, 163, 184, 0.35);
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
transition: border-color 120ms ease, box-shadow 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue