live data shows from database now make buttons work@

This commit is contained in:
Jun-te Kim 2025-09-08 12:41:40 +00:00
parent c3ae5d3cf5
commit 4044665c96
4 changed files with 112 additions and 31 deletions

View file

@ -1,6 +1,7 @@
import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
import { portfolio, portfolioUsers } from "@/app/db/schema/portfolio";
import { portfolio, portfolioUsers} from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
import {
recommendation,
recommendationMaterials,
@ -161,3 +162,41 @@ export async function DELETE(request: NextRequest, props: { params: Promise<{ po
);
}
}
// Get colloborators (users) that have access to the portfolio
export async function GET(
_req: NextRequest,
props: { params: Promise<{ portfolioId: string }> }
) {
const { portfolioId } = await props.params;
try {
const rows = await db
.select({
userId: portfolioUsers.userId,
role: portfolioUsers.role,
name: user.firstName,
email: user.email,
})
.from(portfolioUsers)
.leftJoin(user, eq(user.id, portfolioUsers.userId))
.where(eq(portfolioUsers.portfolioId, BigInt(portfolioId)));
// Explicitly normalize BigInts to strings
const collaborators = rows.map((r) => ({
userId: r.userId ? r.userId.toString() : null,
role: r.role,
name: r.name ?? null,
email: r.email ?? "",
}));
return NextResponse.json({ users: collaborators }, { status: 200 });
} catch (err) {
console.error("GET /users error:", err);
return NextResponse.json(
{ error: "Failed to fetch users" },
{ status: 500 }
);
}
}

View file

@ -460,7 +460,7 @@ export default function PortfolioSettings({
</TableBody>
</Table>
</div>
<UsersPermissionsCard/>
<UsersPermissionsCard portfolioId={portfolioId}/>
<div className="rounded-md border border-red-500 mt-2">
<Table>
<TableHead className="text-lg text-brandblue">Danger Zone:</TableHead>

View file

@ -1,3 +1,5 @@
"use client";
import {
Table,
TableBody,
@ -9,29 +11,68 @@ import {
import { Input } from "@/app/shadcn_components/ui/input";
import { Button } from "@/app/shadcn_components/ui/button";
import { useState } from "react";
import { useState, useEffect } from "react";
import { Role, RoleDropdown, Collaborator } from "@/app/portfolio/[slug]/(portfolio)/settings/roles";
import { useQuery } from "@tanstack/react-query";
export function UsersPermissionsCard({
}: {
}) {
async function getPortfolioUsers(portfolioId: string): Promise<Collaborator[]> {
const res = await fetch(`/api/portfolio/${portfolioId}`, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!res.ok) throw new Error("Failed to fetch users");
const json = await res.json();
const users = Array.isArray(json) ? json : json.users; // support both shapes
// Guard + shape to Collaborator[]
return Array.isArray(users)
? users
.filter((u: any) => u.role !== "creator") // 👈 filter out creator
.map((u: any) => ({
userId: String(u.userId),
name: u.name ?? null,
email: u.email ?? "",
role: u.role,
}))
: [];
}
export function UsersPermissionsCard({ portfolioId }: { portfolioId: string }) {
const [inviteEmail, setInviteEmail] = useState("");
const [inviteRole, setInviteRole] = useState<Role>("read");
const collaborators: Collaborator[] = [
{ name: "Name 1", email: "name1@example.com", role: "read" },
{ name: "Name 2", email: "name2@example.com", role: "read" },
];
const {
data: collaborators = [],
isLoading,
isFetching,
refetch,
} = useQuery({
queryKey: ["portfolioUsers", portfolioId],
queryFn: () => getPortfolioUsers(portfolioId),
enabled: !!portfolioId, // only run when id is present
refetchOnWindowFocus: false, // optional: avoid surprise refetch logs
onSuccess: (data) => {
console.log("Fetched users for portfolio:", data);
},
onError: (err) => {
console.error("Error fetching users:", err);
},
});
function handleInvite() {
console.log("handle invite")
}
function onChangeRole(email:string, role:Role) {
console.log(`on change role ${email} ${role}`)
console.log("handle invite");
// TODO: POST invite -> then refetch()
}
function onRemove(email:string) {
console.log(`remove user ${email}`)
function onChangeRole(email: string, role: Role) {
console.log(`on change role ${email} ${role}`);
// TODO: PATCH role -> then refetch()
}
function onRemove(email: string) {
console.log(`remove user ${email}`);
// TODO: DELETE user -> then refetch()
}
return (
@ -43,6 +84,11 @@ export function UsersPermissionsCard({
Users Permission:
<p className="text-xs text-gray-500">Add users and manage roles</p>
</TableHead>
<TableCell className="text-right">
<Button variant="outline" onClick={() => refetch()} disabled={isFetching || isLoading}>
{isFetching || isLoading ? "Loading..." : "Refresh Users"}
</Button>
</TableCell>
</TableRow>
{/* Invite row */}
@ -75,9 +121,7 @@ export function UsersPermissionsCard({
<TableRow>
<TableHead className="text-brandblue">
Current users
<p className="text-xs text-gray-500">
Update roles or remove access
</p>
<p className="text-xs text-gray-500">Update roles or remove access</p>
</TableHead>
<TableCell colSpan={2}>
<div className="rounded-md border border-gray-200">
@ -91,7 +135,11 @@ export function UsersPermissionsCard({
</TableRow>
</TableHeader>
<TableBody>
{collaborators.length === 0 ? (
{isLoading ? (
<TableRow>
<TableCell colSpan={4} className="text-sm text-gray-500">Loading</TableCell>
</TableRow>
) : collaborators.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className="text-sm text-gray-500">
No users yet.
@ -103,17 +151,10 @@ export function UsersPermissionsCard({
<TableCell>{c.name || "—"}</TableCell>
<TableCell>{c.email}</TableCell>
<TableCell className="min-w-40">
<RoleDropdown
value={c.role}
onChange={(r) => onChangeRole(c.email, r)}
/>
<RoleDropdown value={c.role} onChange={(r) => onChangeRole(c.email, r)} />
</TableCell>
<TableCell className="text-right">
<Button
variant="destructive"
className="bg-red-700"
onClick={() => onRemove(c.email)}
>
<Button variant="destructive" className="bg-red-700" onClick={() => onRemove(c.email)}>
Remove
</Button>
</TableCell>
@ -129,4 +170,4 @@ export function UsersPermissionsCard({
</Table>
</div>
);
}
}

View file

@ -12,6 +12,7 @@ const ROLE_OPTIONS = ["read", "write"] as const;
export type Role = typeof ROLE_OPTIONS[number];
export type Collaborator = {
userId: string;
name?: string | null;
email: string;
role: Role;