Ahh—there’s the culprit:

storage.getUserById is not a function → your server route is calling a helper that either:

doesn’t exist in src/server/storage/*, or

is exported under a different name (getUserByID, findUserById, etc.), or

storage is the wrong object (import path mismatch), so you’re hitting {}.

Because that throws on the server, your POST /role returns 500, and the client shows the red toast.

Here’s a fast, bullet-proof fix: bypass the flaky storage helper and talk to the DB directly (Prisma example). Keep your current client shape (POST /api/admin/role with { userId, role }).

✅ Drop-in server route (no storage helpers)

File: src/server/routes/adminUsers.ts (or wherever your admin routes live)

import express, { Request, Response } from "express";
import { z } from "zod";
import { prisma } from "../prisma";            // ← adjust path to your Prisma client
import { requireAuth, requireRole } from "../middleware/auth"; // ← adjust paths

const router = express.Router();

// Accept the exact roles you support
const RoleEnum = z.enum(["user", "admin", "owner"]);
const Body = z.object({
  userId: z.string().min(1),
  role: RoleEnum,
});

/**
 * POST /api/admin/role
 * Body: { userId: string, role: "user" | "admin" | "owner" }
 */
router.post("/role", requireAuth, requireRole("owner"), async (req: Request, res: Response) => {
  try {
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) {
      return res.status(400).json({ error: "Invalid payload" });
    }
    const { userId, role } = parsed.data;

    // Ensure target exists
    const target = await prisma.user.findUnique({ where: { id: userId } });
    if (!target) {
      return res.status(404).json({ error: "User not found" });
    }

    // Guard: don't demote the last owner
    if (target.role === "owner" && role !== "owner") {
      const owners = await prisma.user.count({ where: { role: "owner" } });
      if (owners <= 1) {
        return res.status(400).json({ error: "Cannot demote the last owner" });
      }
    }

    const updated = await prisma.user.update({
      where: { id: userId },
      data: { role },
      select: { id: true, email: true, role: true },
    });

    return res.json({ ok: true, user: updated });
  } catch (err: any) {
    console.error("[POST /api/admin/role] error:", err);
    return res.status(500).json({ error: "Server error updating role" });
  }
});

export default router;


Wire it:

// src/server/index.ts
import adminUsersRoutes from "./routes/adminUsers";
app.use("/api/admin", adminUsersRoutes);

🧹 If you prefer to keep the storage layer

Make sure the functions actually exist and are exported:

File: src/server/storage/index.ts

export async function getUserById(id: string) {
  return prisma.user.findUnique({ where: { id } });
}

export async function updateUserRole(id: string, role: "user" | "admin" | "owner") {
  return prisma.user.update({ where: { id }, data: { role } });
}


Then in your route, import exactly those names:

import * as storage from "../storage";
// ...
const target = await storage.getUserById(userId);
const updated = await storage.updateUserRole(userId, role);


Double-check for typos: getUserById vs getUserByID vs findUserById.