Skip to content

Commit

Permalink
fix types
Browse files Browse the repository at this point in the history
  • Loading branch information
owens1127 committed Dec 3, 2024
1 parent 7060946 commit 036d9c4
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 153 deletions.
137 changes: 8 additions & 129 deletions apps/web/app/(pages)/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,135 +1,14 @@
"use client";
import AdminDashboard from "@good-dog/components/admin/AdminDashboard";
import { HydrateClient, trpc } from "@good-dog/trpc/server";

import { useState } from "react";
export const dynamic = "force-dynamic";

import type { GetProcedureOutput } from "@good-dog/trpc/utils";
import { DataTable } from "@good-dog/components/admin/DataTable";
import Loading from "@good-dog/components/loading/Loading";
import { trpc } from "@good-dog/trpc/client";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@good-dog/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@good-dog/ui/tabs";

import NotFound from "~/not-found";

type AdminDataTypes = GetProcedureOutput<"adminData">;
interface DataColumn<T extends keyof AdminDataTypes> {
accessorKey: keyof AdminDataTypes[T][number];
header: string;
cell?: (value: AdminDataTypes[T][keyof AdminDataTypes[T]]) => JSX.Element;
}

const columns = {
users: [
{ accessorKey: "firstName", header: "First Name" },
{ accessorKey: "lastName", header: "Last Name" },
{ accessorKey: "email", header: "Email" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "stageName", header: "Stage Name" },
{ accessorKey: "isSongWriter", header: "Songwriter?" },
{ accessorKey: "isAscapAffiliated", header: "ASCAP Affiliated?" },
{ accessorKey: "isBmiAffiliated", header: "BMI Affiliated?" },
{ accessorKey: "createdAt", header: "Date of Creation" },
{ accessorKey: "updatedAt", header: "Date Last Updated" },
],
groups: [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "createdAt", header: "Date of Creation" },
{ accessorKey: "updatedAt", header: "Date Last Updated" },
],
groupInvites: [
{ accessorKey: "email", header: "Email" },
{ accessorKey: "firstName", header: "First Name" },
{ accessorKey: "lastName", header: "Last Name" },
{ accessorKey: "stageName", header: "Stage Name" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "isSongWriter", header: "Songwriter?" },
{ accessorKey: "isAscapAffiliated", header: "ASCAP Affiliated?" },
{ accessorKey: "isBmiAffiliated", header: "BMI Affiliated?" },
{ accessorKey: "createdAt", header: "Date of Creation" },
],
} as const satisfies { [T in keyof AdminDataTypes]: DataColumn<T>[] };

export default function AdminDashboard() {
const [activeTab, setActiveTab] = useState("users");
const { data, isPending, isError } = trpc.adminData.useQuery();
if (isPending) {
return <Loading />;
}
if (isError) {
return NotFound;
}
const userData = data.users;
const groupData = data.groups;
const groupInvitesData = data.groupInvites;
export default async function AdminPage() {
void trpc.adminData.prefetch();

return (
<div className="bg-good-dog-violet pb-10">
<div className="mx-10">
<h1 className="mb-6 text-7xl font-bold text-white">Admin Dashboard</h1>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList>
<TabsTrigger className="text-xl" value="users">
Users
</TabsTrigger>
<TabsTrigger className="text-xl" value="groups">
Groups
</TabsTrigger>
<TabsTrigger className="text-xl" value="invites">
Invites
</TabsTrigger>
</TabsList>
<div className="pt-2">
<TabsContent className="text-3xl" value="users">
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
<CardDescription className="text-xl">
Manage user accounts in the system.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable columns={columns.users} data={userData} />
</CardContent>
</Card>
</TabsContent>
<TabsContent className="text-3xl" value="groups">
<Card>
<CardHeader>
<CardTitle>Groups</CardTitle>
<CardDescription className="text-xl">
Manage user groups and permissions.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable columns={columns.groups} data={groupData} />
</CardContent>
</Card>
</TabsContent>
<TabsContent className="text-3xl" value="invites">
<Card>
<CardHeader>
<CardTitle>Invites</CardTitle>
<CardDescription className="text-xl">
Manage pending invitations.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable
columns={columns.groupInvites}
data={groupInvitesData}
/>
</CardContent>
</Card>
</TabsContent>
</div>
</Tabs>
</div>
</div>
<HydrateClient>
<AdminDashboard />
</HydrateClient>
);
}
86 changes: 86 additions & 0 deletions packages/components/src/admin/AdminDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import { useState } from "react";

import { trpc } from "@good-dog/trpc/client";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@good-dog/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@good-dog/ui/tabs";

import { DataTable } from "./DataTable";

export default function AdminDashboard() {
const [activeTab, setActiveTab] = useState("users");
const [data] = trpc.adminData.useSuspenseQuery();

const userData = data.users;
const groupData = data.groups;
const groupInvitesData = data.groupInvites;

return (
<div className="bg-good-dog-violet pb-10">
<div className="mx-10">
<h1 className="mb-6 text-7xl font-bold text-white">Admin Dashboard</h1>
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList>
<TabsTrigger className="text-xl" value="users">
Users
</TabsTrigger>
<TabsTrigger className="text-xl" value="groups">
Groups
</TabsTrigger>
<TabsTrigger className="text-xl" value="invites">
Invites
</TabsTrigger>
</TabsList>
<div className="pt-2">
<TabsContent className="text-3xl" value="users">
<Card>
<CardHeader>
<CardTitle>Users</CardTitle>
<CardDescription className="text-xl">
Manage user accounts in the system.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable table="users" data={userData} />
</CardContent>
</Card>
</TabsContent>
<TabsContent className="text-3xl" value="groups">
<Card>
<CardHeader>
<CardTitle>Groups</CardTitle>
<CardDescription className="text-xl">
Manage user groups and permissions.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable table="groups" data={groupData} />
</CardContent>
</Card>
</TabsContent>
<TabsContent className="text-3xl" value="invites">
<Card>
<CardHeader>
<CardTitle>Invites</CardTitle>
<CardDescription className="text-xl">
Manage pending invitations.
</CardDescription>
</CardHeader>
<CardContent>
<DataTable table="groupInvites" data={groupInvitesData} />
</CardContent>
</Card>
</TabsContent>
</div>
</Tabs>
</div>
</div>
);
}
69 changes: 48 additions & 21 deletions packages/components/src/admin/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,74 @@ import {
TableRow,
} from "@good-dog/ui/table";

type AdminDataTypes = GetProcedureOutput<"adminData">;
// interface DataColumn<T extends keyof AdminDataTypes> {
// accessorKey: keyof AdminDataTypes[T][number];
// header: string;
// cell?: (value: string) => JSX.Element;
// }
export type AdminDataTypes = {
[T in keyof GetProcedureOutput<"adminData">]: GetProcedureOutput<"adminData">[T][number];
};

interface DataColumn<T extends keyof AdminDataTypes> {
accessorKey: keyof AdminDataTypes[T];
accessorKey: keyof AdminDataTypes[T] & string;
header: string;
cell?: (value: AdminDataTypes[T][keyof AdminDataTypes[T]]) => JSX.Element;
cell?: (
value: AdminDataTypes[T][keyof AdminDataTypes[T] & string],
) => React.ReactNode;
}

const columns: { [T in keyof AdminDataTypes]: DataColumn<T>[] } = {
users: [
{ accessorKey: "firstName", header: "First Name" },
{ accessorKey: "lastName", header: "Last Name" },
{ accessorKey: "email", header: "Email" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "stageName", header: "Stage Name" },
{ accessorKey: "isSongWriter", header: "Songwriter?" },
{ accessorKey: "isAscapAffiliated", header: "ASCAP Affiliated?" },
{ accessorKey: "isBmiAffiliated", header: "BMI Affiliated?" },
{ accessorKey: "createdAt", header: "Date of Creation" },
{ accessorKey: "updatedAt", header: "Date Last Updated" },
],
groups: [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "createdAt", header: "Date of Creation" },
{ accessorKey: "updatedAt", header: "Date Last Updated" },
],
groupInvites: [
{ accessorKey: "email", header: "Email" },
{ accessorKey: "firstName", header: "First Name" },
{ accessorKey: "lastName", header: "Last Name" },
{ accessorKey: "stageName", header: "Stage Name" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "isSongWriter", header: "Songwriter?" },
{ accessorKey: "isAscapAffiliated", header: "ASCAP Affiliated?" },
{ accessorKey: "isBmiAffiliated", header: "BMI Affiliated?" },
{ accessorKey: "createdAt", header: "Date of Creation" },
],
};

interface DataTableProps<T extends keyof AdminDataTypes> {
columns: DataColumn<T>[];
table: T;
data: AdminDataTypes[T][];
}

export function DataTable<T extends keyof AdminDataTypes>({
columns,
table,
data,
}: DataTableProps<T>) {
return (
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead key={String(column.accessorKey)}>
{column.header}
</TableHead>
{columns[table].map((column) => (
<TableHead key={column.accessorKey}>{column.header}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, rowIndex) => (
<TableRow key={rowIndex}>
{columns.map((column) => (
<TableCell key={String(column.accessorKey)}>
{column.cell
? column.cell(row[column.accessorKey])
: (row[column.accessorKey] as React.ReactNode)}
{data.map((entry, idx) => (
<TableRow key={idx}>
{columns[table].map((column) => (
<TableCell key={column.accessorKey}>
{column.cell?.(entry[column.accessorKey]) ??
String(entry[column.accessorKey])}
</TableCell>
))}
</TableRow>
Expand Down
4 changes: 2 additions & 2 deletions packages/trpc/src/internal/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ export const authenticatedProcedureBuilder = baseProcedureBuilder.use(
export const adminAuthenticatedProcedureBuilder =
authenticatedProcedureBuilder.use(async ({ ctx, next }) => {
if (ctx.session.user.role !== "ADMIN") {
throw new TRPCError({ code: "UNAUTHORIZED" });
throw new TRPCError({ code: "FORBIDDEN" });
}

return next();
return next({ ctx });
});

// This middleware is used to prevent authenticated users from accessing a resource
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/shad/button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { VariantProps } from "class-variance-authority";
import * as React from "react";
import React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva } from "class-variance-authority";

Expand Down

0 comments on commit 036d9c4

Please sign in to comment.