web: account avatars, member /profile, framed admin panel
- an account can carry a picture: POST /api/auth/avatar (raw bytes, sniffed, replaces and unlinks the old file) and the public GET /api/users/:id/avatar. It rides wherever the account is named — the landing chip, the studio TopBar, the profile form. - new /profile page for members, sharing one Profile form (picture, email, password) with the admin drawer. - /admin is now one bordered frame whose left column is Profile / User account / Pictures / Close. Pictures lists every photo in the system with the slot that shows it; User account lists each account's name, email, picture and contribution count. - account control opens a menu: Admin page + Log out for an admin, Profile + Log out for a member.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useI18n, LANGS } from '../i18n/I18nProvider';
|
||||
import { ACCENTS, FONT_SETS, useTheme } from '../theme/ThemeProvider';
|
||||
import { Avatar } from './Avatar';
|
||||
import type { User } from '../api';
|
||||
|
||||
// Header: the app's TopBar minus everything camera-only, plus the two controls
|
||||
@@ -123,18 +124,35 @@ export function TopBar({
|
||||
</div>
|
||||
|
||||
<div className="menu-wrap">
|
||||
<button type="button" className="btn ghost" onClick={() => setMenu(menu === 'account' ? null : 'account')}>
|
||||
{user ? user.email.split('@')[0] : t('nav.guest')}
|
||||
<button
|
||||
type="button"
|
||||
className="btn ghost account-btn"
|
||||
data-key="nav-account"
|
||||
aria-expanded={menu === 'account'}
|
||||
onClick={() => setMenu(menu === 'account' ? null : 'account')}
|
||||
>
|
||||
{user ? (
|
||||
<>
|
||||
<Avatar user={user} small />
|
||||
{user.email.split('@')[0]}
|
||||
</>
|
||||
) : (
|
||||
t('nav.guest')
|
||||
)}
|
||||
</button>
|
||||
{menu === 'account' ? (
|
||||
<div className="popover">
|
||||
{user ? (
|
||||
<>
|
||||
<p className="hint">{t('auth.loggedInAs', { email: user.email })}</p>
|
||||
{/* The account control opens the one page that account can act
|
||||
on: moderation for an admin, its own profile for a member. */}
|
||||
{user.admin ? (
|
||||
<a className="btn" data-key="nav-admin" href="/admin">{t('nav.admin')}</a>
|
||||
) : null}
|
||||
<button type="button" className="btn" onClick={onLogout}>{t('nav.logout')}</button>
|
||||
) : (
|
||||
<a className="btn" data-key="nav-profile" href="/profile">{t('nav.profile')}</a>
|
||||
)}
|
||||
<button type="button" className="btn" data-key="nav-logout" onClick={onLogout}>{t('nav.logout')}</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user