mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
live data shows from database now make buttons work@
This commit is contained in:
parent
c3ae5d3cf5
commit
4044665c96
4 changed files with 112 additions and 31 deletions
|
|
@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue