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:
2026-09-18 08:19:36 +07:00
parent 2917c034ed
commit 6bbf77860b
15 changed files with 908 additions and 79 deletions
+13 -4
View File
@@ -8,12 +8,21 @@ import { I18nProvider } from './i18n/I18nProvider';
import { Landing } from './Landing';
import { Workspace } from './App';
import { Admin } from './Admin';
import { ProfilePage } from './ProfilePage';
// Three routes, no router: the landing page, the workspace and the strip
// moderation screen. nginx serves index.html for all of them (SPA fallback),
// so this is just a pathname check.
// Four routes, no router: the landing page, the workspace, the strip moderation
// screen and the member's own profile. nginx serves index.html for all of them
// (SPA fallback), so this is just a pathname check.
const path = window.location.pathname;
const page = path.startsWith('/app') ? <Workspace /> : path.startsWith('/admin') ? <Admin /> : <Landing />;
const page = path.startsWith('/app') ? (
<Workspace />
) : path.startsWith('/admin') ? (
<Admin />
) : path.startsWith('/profile') ? (
<ProfilePage />
) : (
<Landing />
);
const root = document.getElementById('root');
if (!root) throw new Error('#root missing');