v3: added site-wide categories, added admin category management, various visual fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s
This commit is contained in:
@@ -37,6 +37,7 @@ import { PlaylistCard } from "../components/PlaylistCard.tsx";
|
||||
import { NewPlaylistForm } from "../components/NewPlaylistForm.tsx";
|
||||
import { PageShell } from "../components/PageShell.tsx";
|
||||
import { PageError } from "../components/PageError.tsx";
|
||||
import { CategoryManager } from "../components/CategoryManager.tsx";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||
import { useTheme } from "../hooks/useTheme.ts";
|
||||
@@ -227,9 +228,16 @@ export function UserPublicProfile() {
|
||||
// `settings` is own-profile only; `manage` requires the user:manage permission
|
||||
// on someone else's profile. Invalid tabs fall back to "dumps".
|
||||
const canManageUsers = can(me, "user:manage");
|
||||
const canManageCategories = can(me, "category:manage");
|
||||
// The "manage" tab is an admin/moderation area. On someone else's profile it
|
||||
// holds per-user tools (role management); on your own it holds global tools
|
||||
// (category management, with more to come).
|
||||
const showManageTab = isOwnProfile
|
||||
? canManageCategories
|
||||
: canManageUsers;
|
||||
const availableTabs: ProfileTab[] = PROFILE_TABS.filter((t) => {
|
||||
if (t === "settings") return isOwnProfile;
|
||||
if (t === "manage") return !isOwnProfile && canManageUsers;
|
||||
if (t === "manage") return showManageTab;
|
||||
return true;
|
||||
});
|
||||
const [tab, setTab] = useTabParam<ProfileTab>(availableTabs, "dumps");
|
||||
@@ -780,7 +788,10 @@ export function UserPublicProfile() {
|
||||
</div>
|
||||
<div className="profile-info">
|
||||
<div className="profile-info-scroll">
|
||||
<h1 className="profile-username">{profileUser.username}</h1>
|
||||
<div className="profile-username-row">
|
||||
<h1 className="profile-username">{profileUser.username}</h1>
|
||||
<RoleChip role={profileUser.role} />
|
||||
</div>
|
||||
{profileUser.invitedByUsername
|
||||
? (
|
||||
<p className="profile-invited-by">
|
||||
@@ -893,7 +904,7 @@ export function UserPublicProfile() {
|
||||
...(isOwnProfile
|
||||
? [{ key: "settings" as const, label: <Trans>Settings</Trans> }]
|
||||
: []),
|
||||
...(!isOwnProfile && canManageUsers
|
||||
...(showManageTab
|
||||
? [{ key: "manage" as const, label: <Trans>Manage</Trans> }]
|
||||
: []),
|
||||
]}
|
||||
@@ -1263,6 +1274,15 @@ export function UserPublicProfile() {
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{tab === "manage" && isOwnProfile && canManageCategories && (
|
||||
<section className="profile-section">
|
||||
<h2 className="profile-section-title">
|
||||
<Trans>Categories</Trans>
|
||||
</h2>
|
||||
<CategoryManager />
|
||||
</section>
|
||||
)}
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
@@ -1529,6 +1549,14 @@ const ROLE_OPTIONS: { value: Role; label: React.ReactNode }[] = [
|
||||
{ value: "admin", label: <Trans>Admin</Trans> },
|
||||
];
|
||||
|
||||
// Role badge shown next to the username. The default "user" role is implicit,
|
||||
// so only elevated roles get a chip to avoid labelling every profile.
|
||||
function RoleChip({ role }: { role: Role }) {
|
||||
if (role === "user") return null;
|
||||
const label = ROLE_OPTIONS.find((o) => o.value === role)?.label;
|
||||
return <span className={`profile-role-chip profile-role-chip--${role}`}>{label}</span>;
|
||||
}
|
||||
|
||||
interface RoleSwitcherProps {
|
||||
userId: string;
|
||||
currentRole: Role;
|
||||
|
||||
Reference in New Issue
Block a user