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
+26
View File
@@ -113,6 +113,7 @@ export const en: Dict = {
'photo.failed': 'Upload failed: {msg}',
'nav.admin': 'Admin',
'nav.profile': 'Profile',
'adm.title': 'Strip moderation',
'adm.subtitle': 'Photos contributed by users. Delete one to pull it off the landing page.',
'adm.upload': 'ADD A PHOTO',
@@ -134,6 +135,31 @@ export const en: Dict = {
'adm.clearConfirm': 'Delete all {n} contributions? This cannot be undone.',
'adm.removed': 'Deleted {n} photos.',
'adm.back': 'Back to home',
'adm.settings': 'SETTINGS',
'adm.tabPictures': 'Pictures',
'adm.tabUsers': 'User account',
'adm.tabProfile': 'Profile',
'adm.close': 'Close',
'adm.userPicture': 'Picture',
'adm.userName': 'Name',
'adm.userEmail': 'Email',
'adm.userRole': 'Role',
'adm.userPhotos': 'Photos',
'adm.userJoined': 'Joined',
'adm.roleAdmin': 'Admin',
'adm.roleMember': 'Member',
'adm.viewPhotos': 'VIEW PHOTOS',
'adm.noUsers': 'No accounts yet.',
'adm.showAll': 'ALL',
'adm.profileHint': 'Picture, email and password of the signed-in account. Changing the email or password needs the current password.',
'adm.changeAvatar': 'CHANGE PICTURE',
'adm.currentPassword': 'Current password',
'adm.newEmail': 'New email',
'adm.newPassword': 'New password',
'adm.save': 'SAVE',
'adm.saved': 'Profile saved.',
'profile.title': 'My profile',
'err.noWebgl':
'The browser refused a WebGL context — this build needs WebGL to process photos (the CPU path is thousands of times slower).',
+26
View File
@@ -15,6 +15,7 @@ export const vi = {
'nav.language': 'Ngôn ngữ',
'nav.back': 'Về trang chủ',
'nav.admin': 'Quản trị',
'nav.profile': 'Hồ sơ',
'nav.guest': 'Khách',
'act.undo': 'HOÀN TÁC',
@@ -137,6 +138,31 @@ export const vi = {
'adm.clearConfirm': 'Xoá toàn bộ {n} ảnh đóng góp? Không hoàn tác được.',
'adm.removed': 'Đã xoá {n} ảnh.',
'adm.back': 'Về trang chủ',
'adm.settings': 'CÀI ĐẶT',
'adm.tabPictures': 'Ảnh',
'adm.tabUsers': 'Người dùng',
'adm.tabProfile': 'Hồ sơ',
'adm.close': 'Đóng',
'adm.userPicture': 'Ảnh',
'adm.userName': 'Tên',
'adm.userEmail': 'Email',
'adm.userRole': 'Quyền',
'adm.userPhotos': 'Số ảnh',
'adm.userJoined': 'Ngày tạo',
'adm.roleAdmin': 'Quản trị',
'adm.roleMember': 'Thành viên',
'adm.viewPhotos': 'XEM ẢNH',
'adm.noUsers': 'Chưa có tài khoản nào.',
'adm.showAll': 'TẤT CẢ',
'adm.profileHint': 'Ảnh đại diện, email và mật khẩu của tài khoản đang đăng nhập. Đổi email hoặc mật khẩu cần mật khẩu hiện tại.',
'adm.changeAvatar': 'ĐỔI ẢNH ĐẠI DIỆN',
'adm.currentPassword': 'Mật khẩu hiện tại',
'adm.newEmail': 'Email mới',
'adm.newPassword': 'Mật khẩu mới',
'adm.save': 'LƯU',
'adm.saved': 'Đã lưu hồ sơ.',
'profile.title': 'Hồ sơ của tôi',
'err.noWebgl':
'Trình duyệt không cấp được WebGL — bản dựng này cần WebGL để xử lý ảnh (chạy CPU chậm gấp hàng nghìn lần).',