07 - Criando a interface do usuário

Criar o arquivo app/admin/users/page.tsx

// app/admin/users/page.tsx
import { supabaseServer } from "@/lib/supabaseServer";
import { supabaseAdmin } from "@/lib/supabaseAdmin";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

/**
 * Server action: cria usuário + (opcional) vincula a um grupo
 */
async function createUserAction(formData: FormData) {
  "use server";

  const email = String(formData.get("email") ?? "").trim();
  const password = String(formData.get("password") ?? "").trim();
  const groupId = String(formData.get("group_id") ?? "");

  if (!email || !password) {
    console.error("Email e senha são obrigatórios");
    return;
  }

  // Cria o usuário no Supabase Auth
  const { data, error } = await supabaseAdmin.auth.admin.createUser({
    email,
    password,
    email_confirm: true,
  });

  if (error || !data.user) {
    console.error("Erro ao criar usuário:", error?.message);
    return;
  }

  // Se veio um grupo, já vincula o usuário a ele
  if (groupId) {
    const { error: relError } = await supabaseAdmin
      .from("app_group_users")
      .insert({
        group_id: groupId,
        user_id: data.user.id,
      });

    if (relError) {
      console.error("Erro ao vincular grupo:", relError.message);
    }
  }

  revalidatePath("/admin/users");
}

/**
 * Server action: adiciona um grupo a um usuário existente
 */
async function addUserToGroupAction(formData: FormData) {
  "use server";

  const userId = String(formData.get("user_id") ?? "");
  const groupId = String(formData.get("group_id") ?? "");

  if (!userId || !groupId) return;

  const { error } = await supabaseAdmin
    .from("app_group_users")
    .insert({ user_id: userId, group_id: groupId });

  if (error) {
    console.error("Erro ao vincular grupo a usuário:", error.message);
  }

  revalidatePath("/admin/users");
}

export default async function AdminUsersPage() {
  const supabase = await supabaseServer();

  // 1) Garante que está logado
  const { data: authData } = await supabase.auth.getUser();
  const user = authData.user;

  if (!user) {
    redirect("/login");
  }

  // 2) Restrição de acesso por e-mail (só você / admins)
  const allowedEmails = ["seu-email@dominio.com"]; // AJUSTE AQUI
  if (!allowedEmails.includes(user.email ?? "")) {
    redirect("/");
  }

  // 3) Carrega grupos
  const { data: groups, error: groupsError } = await supabaseAdmin
    .from("app_groups")
    .select("id, name, slug")
    .order("name", { ascending: true });

  if (groupsError) {
    console.error("Erro carregando grupos:", groupsError.message);
  }

  // 4) Carrega relações usuário × grupo
  const { data: groupUsers, error: gusError } = await supabaseAdmin
    .from("app_group_users")
    .select("group_id, user_id");

  if (gusError) {
    console.error("Erro carregando app_group_users:", gusError.message);
  }

  // 5) Carrega usuários do Supabase Auth (primeira página, até 1000)
  const { data: usersPage, error: usersError } =
    await supabaseAdmin.auth.admin.listUsers({
      perPage: 1000,
    });

  if (usersError) {
    console.error("Erro listando usuários:", usersError.message);
  }

  const users = usersPage?.users ?? [];

  // Map rápido: groupId -> group
  const groupMap = new Map(
    (groups ?? []).map((g) => [g.id, g] as const)
  );

  // Map: userId -> grupos[]
  const userGroupsMap = new Map<
    string,
    { id: string; name: string; slug: string }[]
  >();

  (groupUsers ?? []).forEach((rel) => {
    const g = groupMap.get(rel.group_id);
    if (!g) return;
    if (!userGroupsMap.has(rel.user_id)) {
      userGroupsMap.set(rel.user_id, []);
    }
    userGroupsMap.get(rel.user_id)!.push(g);
  });

  return (
    <main className="max-w-5xl mx-auto p-8 space-y-10">
      <header className="flex items-center justify-between gap-4">
        <div>
          <h1 className="text-2xl font-bold">Administração de Usuários</h1>
          <p className="text-sm text-gray-600">
            Criar usuários, atribuir grupos e visualizar as permissões atuais.
          </p>
        </div>
      </header>

      {/* Formulário de criação de usuário */}
      <section className="border rounded-lg p-4 space-y-4">
        <h2 className="font-semibold text-lg">Criar novo usuário</h2>

        <form action={createUserAction} className="grid gap-3 md:grid-cols-3">
          <div className="flex flex-col gap-1 md:col-span-1">
            <label className="text-xs font-medium">E-mail</label>
            <input
              name="email"
              type="email"
              required
              className="border rounded px-2 py-1 text-sm"
              placeholder="usuario@empresa.com"
            />
          </div>

          <div className="flex flex-col gap-1 md:col-span-1">
            <label className="text-xs font-medium">Senha inicial</label>
            <input
              name="password"
              type="password"
              required
              className="border rounded px-2 py-1 text-sm"
              placeholder="Senha temporária"
            />
          </div>

          <div className="flex flex-col gap-1 md:col-span-1">
            <label className="text-xs font-medium">
              Grupo inicial (opcional)
            </label>
            <select
              name="group_id"
              className="border rounded px-2 py-1 text-sm"
              defaultValue=""
            >
              <option value="">(nenhum)</option>
              {(groups ?? []).map((g) => (
                <option key={g.id} value={g.id}>
                  {g.name} ({g.slug})
                </option>
              ))}
            </select>
          </div>

          <div className="md:col-span-3">
            <button
              type="submit"
              className="mt-2 inline-flex items-center px-4 py-1.5 rounded bg-blue-600 text-white text-sm font-medium"
            >
              Criar usuário
            </button>
          </div>
        </form>
      </section>

      {/* Lista de usuários */}
      <section className="border rounded-lg p-4 space-y-4">
        <h2 className="font-semibold text-lg">Usuários cadastrados</h2>

        {users.length === 0 ? (
          <p className="text-sm text-gray-500">Nenhum usuário encontrado.</p>
        ) : (
          <div className="space-y-3">
            {users.map((u) => {
              const userGroups = userGroupsMap.get(u.id) ?? [];

              return (
                <div
                  key={u.id}
                  className="border rounded-md p-3 flex flex-col gap-3"
                >
                  <div className="flex flex-wrap justify-between gap-2">
                    <div>
                      <p className="font-medium text-sm">
                        {u.email ?? "(sem e-mail)"}
                      </p>
                      <p className="text-[11px] text-gray-500">
                        ID: {u.id}
                      </p>
                    </div>
                    <div className="text-[11px] text-gray-500 text-right">
                      <div>
                        Criado em:{" "}
                        {u.created_at
                          ? new Date(u.created_at).toLocaleString()
                          : "-"}
                      </div>
                      <div>Status: {u.user_metadata?.banned ? "Banido" : "Ativo"}</div>
                    </div>
                  </div>

                  <div className="flex flex-col gap-2">
                    <div>
                      <span className="text-xs font-semibold">
                        Grupos atuais:
                      </span>
                      {userGroups.length === 0 ? (
                        <span className="text-xs text-gray-500 ml-2">
                          (nenhum)
                        </span>
                      ) : (
                        <div className="mt-1 flex flex-wrap gap-1">
                          {userGroups.map((g) => (
                            <span
                              key={g.id}
                              className="px-2 py-0.5 rounded bg-gray-200 text-[11px]"
                            >
                              {g.name} ({g.slug})
                            </span>
                          ))}
                        </div>
                      )}
                    </div>

                    {/* Form para adicionar grupo a usuário existente */}
                    <form
                      action={addUserToGroupAction}
                      className="flex flex-wrap items-center gap-2"
                    >
                      <input type="hidden" name="user_id" value={u.id} />
                      <select
                        name="group_id"
                        className="border rounded px-2 py-1 text-xs"
                        defaultValue=""
                      >
                        <option value="">Adicionar a grupo...</option>
                        {(groups ?? []).map((g) => (
                          <option key={g.id} value={g.id}>
                            {g.name} ({g.slug})
                          </option>
                        ))}
                      </select>
                      <button
                        type="submit"
                        className="px-3 py-1 rounded bg-slate-700 text-white text-xs"
                      >
                        Adicionar grupo
                      </button>
                    </form>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </section>
    </main>
  );
}