Commit 7d940b5f authored by Grant's avatar Grant
Browse files

admin: add role management

parent 70ef1bf0
Loading
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import {
  Button,
  Input,
  ProgressCircle,
  Switch,
  TextArea,
} from "@heroui/react";
import { ArrowLeft, Ban, MessageCircle, ShieldQuestion } from "lucide-react";
@@ -49,6 +50,10 @@ export const AccountDetailPage = () => {
  const [userIPs, setUserIPs] = useState<IPAddress[]>([]);
  const [userBan, setUserBan] = useState<BanInfo | null>(null);

  const [roleSaving, setRoleSaving] = useState(false);
  const [isAdmin, setAdmin] = useState(false);
  const [isMod, setMod] = useState(false);

  // Ban form
  const [banExpiresAt, setBanExpiresAt] = useState("");
  const [banPublicNote, setBanPublicNote] = useState("");
@@ -71,6 +76,8 @@ export const AccountDetailPage = () => {
      );
      if (userResult.status === 200 && userResult.data.success) {
        setUser(userResult.data.user);
        setAdmin(userResult.data.user.isAdmin);
        setMod(userResult.data.user.isModerator);
      } else {
        handleError(userResult.status, userResult.data);
      }
@@ -98,6 +105,27 @@ export const AccountDetailPage = () => {
    fetchData();
  }, [sub]);

  const saveRoles = async () => {
    if (!sub) return;
    setRoleSaving(true);
    const result = await api(
      `/api/admin/users/user/${encodeURIComponent(sub)}/role`,
      "PUT",
      {
        admin: isAdmin,
        moderator: isMod,
      },
    );
    if (result.status === 200 && result.data.success) {
      toast.success(`Updated roles for ${user?.username ?? sub}`);
      setAdmin(Boolean((result.data.user as any).isAdmin));
      setMod(Boolean((result.data.user as any).isMod));
    } else {
      handleError(result.status, result.data);
    }
    setRoleSaving(false);
  };

  const saveBan = async () => {
    if (!sub) return;
    setBanSaving(true);
@@ -218,6 +246,33 @@ export const AccountDetailPage = () => {
            </div>
          </div>

          <div>
            <h4 className="text-lg font-semibold mb-2 flex items-center gap-2">
              Roles
            </h4>
            <div>
              <Switch isSelected={isAdmin} onChange={(e) => setAdmin(e)}>
                <Switch.Content>
                  <Switch.Control>
                    <Switch.Thumb />
                  </Switch.Control>
                  Admin
                </Switch.Content>
              </Switch>
              <Switch isSelected={isMod} onChange={(e) => setMod(e)}>
                <Switch.Content>
                  <Switch.Control>
                    <Switch.Thumb />
                  </Switch.Control>
                  Moderator
                </Switch.Content>
              </Switch>
              <Button size="sm" onPress={saveRoles} isPending={roleSaving}>
                Save roles
              </Button>
            </div>
          </div>

          {/* Ban Section */}
          <div>
            <h4 className="text-lg font-semibold mb-2 flex items-center gap-2">