add a creator role and gate a dedicated Creator Dashboard + APIs behind it. Here’s a tight, copy-paste prompt for the Replit team to ship it.

Replit prompt — Creator role + dashboard (Stripe Connect ready)
Goal

Add a role-based “Creator Marketplace” area that only users with the creator role can access. Creators get a dashboard, Stripe Connect onboarding, upload endpoints, and earnings links. Admins can grant/remove the creator role.

Backend (Node/Express)
1) Middleware: role guard

server/middleware/requireRole.js

module.exports = function requireRole(...allowed) {
  return (req, res, next) => {
    const roles = req.user?.roles || [];
    const ok = roles.some(r => allowed.includes(r)) || roles.includes('admin');
    if (!req.user) return res.status(401).json({ error: 'Auth required' });
    if (!ok) return res.status(403).json({ error: 'Forbidden' });
    next();
  };
};

2) Minimal user store helpers (replace with real DB)

server/services/users.js

// Replace with your DB. This is a thin facade we can swap later.
const users = new Map(); // key: userId -> { id, email, roles:[], creator?:{ connectId?:string } }

function getUser(id) { return users.get(id); }
function ensureUser(id, seed={}) { if (!users.has(id)) users.set(id, { id, roles:[], ...seed }); return users.get(id); }
function addRole(id, role) { const u=ensureUser(id); if(!u.roles.includes(role)) u.roles.push(role); return u; }
function removeRole(id, role) { const u=ensureUser(id); u.roles = (u.roles||[]).filter(r=>r!==role); return u; }
function setCreatorConnectId(id, connectId){ const u=ensureUser(id); u.creator = { ...(u.creator||{}), connectId }; return u; }

module.exports = { getUser, ensureUser, addRole, removeRole, setCreatorConnectId };

3) Creator routes (dashboard, onboarding, login link)

server/routes/creatorRoutes.js

const express = require('express');
const Stripe = require('stripe');
const requireRole = require('../middleware/requireRole');
const { getUser, setCreatorConnectId } = require('../services/users');

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20' });
const router = express.Router();

// All routes require 'creator' role
router.use(requireRole('creator'));

// Dashboard summary (stub — replace with real stats)
router.get('/me', (req, res) => {
  const u = getUser(req.user.id) || { roles: [] };
  res.json({
    userId: req.user.id,
    roles: u.roles,
    creator: u.creator || {},
    stats: { items: 0, views: 0, revenue_cents: 0 },
  });
});

// Start/continue Stripe Connect onboarding (Express account)
router.post('/stripe/account-link', async (req, res) => {
  const userId = req.user.id;
  const email = req.user.email;
  const domain = process.env.DOMAIN_URL || process.env.FRONTEND_URL || 'http://localhost:3000';

  let connectId = getUser(userId)?.creator?.connectId;
  if (!connectId) {
    const acct = await stripe.accounts.create({ type: 'express', email });
    connectId = acct.id;
    setCreatorConnectId(userId, connectId);
  }

  const link = await stripe.accountLinks.create({
    account: connectId,
    refresh_url: `${domain}/creator/onboarding/refresh`,
    return_url: `${domain}/creator`,
    type: 'account_onboarding',
  });

  res.json({ url: link.url });
});

// Creator Stripe dashboard (payouts, balances)
router.post('/stripe/login-link', async (req, res) => {
  const connectId = getUser(req.user.id)?.creator?.connectId;
  if (!connectId) return res.status(400).json({ error: 'No Connect account' });
  const link = await stripe.accounts.createLoginLink(connectId);
  res.json({ url: link.url });
});

module.exports = router;

4) Admin helpers to grant/remove role

server/routes/adminCreatorRoutes.js

const express = require('express');
const requireRole = require('../middleware/requireRole');
const { addRole, removeRole, getUser } = require('../services/users');
const router = express.Router();

router.use(requireRole('admin'));

router.post('/grant-creator', (req, res) => {
  const { userId } = req.body || {};
  if (!userId) return res.status(400).json({ error: 'userId required' });
  const u = addRole(userId, 'creator');
  res.json({ ok: true, user: u });
});

router.post('/revoke-creator', (req, res) => {
  const { userId } = req.body || {};
  if (!userId) return res.status(400).json({ error: 'userId required' });
  const u = removeRole(userId, 'creator');
  res.json({ ok: true, user: u });
});

router.get('/user/:id', (req, res) => res.json(getUser(req.params.id) || null));

module.exports = router;

5) Wire routes

server.js

const creatorRoutes = require('./server/routes/creatorRoutes');
const adminCreatorRoutes = require('./server/routes/adminCreatorRoutes');

// ... keep webhook BEFORE json, as you already do
app.use('/api/admin/creators', adminCreatorRoutes);
app.use('/api/creator', creatorRoutes);

Frontend (React + TS)
1) Role guard

src/components/auth/RequireRole.tsx

import { Navigate } from "react-router-dom";
import { useAuth } from "@/contexts/AuthContext"; // must expose currentUser with roles:string[]

export default function RequireRole({ roles, children }: { roles: string[]; children: JSX.Element }) {
  const { currentUser, loading } = useAuth();
  if (loading) return null;
  const has = currentUser?.roles?.some((r: string) => roles.includes(r)) || currentUser?.roles?.includes('admin');
  return has ? children : <Navigate to="/unauthorized" replace />;
}

2) Creator Dashboard page

src/pages/creator/CreatorDashboard.tsx

import { useEffect, useState } from "react";

export default function CreatorDashboard() {
  const [me, setMe] = useState<any>(null);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    fetch("/api/creator/me").then(r=>r.json()).then(setMe).catch(()=>{});
  }, []);

  const onboard = async () => {
    setBusy(true);
    const r = await fetch("/api/creator/stripe/account-link", { method: "POST" });
    const d = await r.json();
    setBusy(false);
    if (d.url) window.location.href = d.url;
  };

  const openStripe = async () => {
    const r = await fetch("/api/creator/stripe/login-link", { method: "POST" });
    const d = await r.json();
    if (d.url) window.location.href = d.url;
  };

  const connected = Boolean(me?.creator?.connectId);

  return (
    <div className="max-w-4xl mx-auto space-y-6">
      <h1 className="text-2xl font-bold">Creator Dashboard</h1>

      {!connected ? (
        <div className="border rounded p-4 bg-amber-50">
          <div className="font-medium mb-2">Finish payouts setup</div>
          <p className="text-sm mb-3">Connect your Stripe Express account to receive payouts.</p>
          <button onClick={onboard} disabled={busy} className="px-4 py-2 bg-black text-white rounded">
            {busy ? "Redirecting…" : "Start Stripe Onboarding"}
          </button>
        </div>
      ) : (
        <div className="border rounded p-4">
          <div className="font-medium mb-2">Payouts</div>
          <button onClick={openStripe} className="px-3 py-2 border rounded">Open Stripe Dashboard</button>
        </div>
      )}

      <div className="border rounded p-4">
        <div className="font-medium mb-2">Your stats</div>
        <div className="text-sm text-muted-foreground">
          Items: {me?.stats?.items ?? 0} · Views: {me?.stats?.views ?? 0} · Revenue: ${(me?.stats?.revenue_cents ?? 0 / 100).toFixed(2)}
        </div>
      </div>
    </div>
  );
}

3) Route + menu

src/App.tsx (or your router file)

import RequireRole from "@/components/auth/RequireRole";
import CreatorDashboard from "@/pages/creator/CreatorDashboard";

// ...
<Route
  path="/creator"
  element={
    <RequireRole roles={['creator']}>
      <CreatorDashboard />
    </RequireRole>
  }
/>


src/components/Header.tsx (show nav if creator)

const { currentUser } = useAuth();
const isCreator = currentUser?.roles?.includes('creator') || currentUser?.roles?.includes('admin');
// ...
{isCreator && (
  <a href="/creator" className="text-sm px-3 py-2 rounded hover:bg-gray-100">Creator</a>
)}

How you grant access

Admin calls POST /api/admin/creators/grant-creator { userId } (or build a tiny admin UI).

After the role is set, the user sees Creator in the header and can open /creator.

From there they can finish Stripe Connect onboarding and later upload assets (you can reuse your existing upload pipeline and just scope by ownerId).

Notes

This is role-based from day one (roles live on user.roles), so expanding to reviewer, partner, etc. is trivial.

Payouts use Stripe Express accounts. Webhooks you already set up will continue to work for purchases; when you implement revenue splits you’ll call transfer_data/application_fee_amount at Checkout or execute separate Transfers.