From 659aa4f66a354ddbcd0a9bec4b24c7e60a96761c Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Fri, 21 Aug 2026 17:56:43 +0100 Subject: [PATCH 1/8] feat: update navbar for special roles Signed-off-by: andyrodrigues30 --- app/src/components/Navbar.tsx | 69 +++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/app/src/components/Navbar.tsx b/app/src/components/Navbar.tsx index 8f7bd6d1..9cb5a7ca 100644 --- a/app/src/components/Navbar.tsx +++ b/app/src/components/Navbar.tsx @@ -1,4 +1,4 @@ -import { Menu, Search, User, X } from "lucide-react"; +import { Menu, Search, Shield, User, X } from "lucide-react"; import { Link, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -16,25 +16,37 @@ import { import { Separator } from "@/components/ui/separator"; import { Logo } from "@/components/Logo"; -// `roles`, when set, hides the item from anyone holding none of them. -const navItems: Array<{ label: string; to: string; roles?: Array }> = [ +const navItems: Array<{ label: string; to: string }> = [ { label: "Browse", to: "/browse" }, { label: "Subjects", to: "/subjects" }, { label: "Objectives", to: "/objectives" }, { label: "Todo", to: "/todos" }, - { label: "Review", to: "/review", roles: ["verifier", "admin"] }, ]; // Profile isn't here because its link needs the signed-in username. const profileItems = [{ label: "Settings", to: "/settings" }]; +// `roles`, when set, hides the item from anyone holding none of them. +const sepcialRoleItems: Array<{ + label: string; + to: string; + roles?: Array; +}> = [ + { + label: "Review Queue", + to: "/review", + roles: ["verifier", "lead-verifier", "admin"], + }, + { label: "Dashboard", to: "/dashboard", roles: ["lead-verifier", "admin"] }, +]; + export function Navbar() { const [mobileOpen, setMobileOpen] = useState(false); const [query, setQuery] = useState(""); const { session, roles, currentProfile } = useAuth(); const navigate = useNavigate(); - const visibleNavItems = navItems.filter( + const visibleRoleItems = sepcialRoleItems.filter( (item) => !item.roles || item.roles.some((r) => roles.includes(r)) ); @@ -62,7 +74,7 @@ export function Navbar() {
+ {roles.length > 0 && ( +
+ + + + + + + {visibleRoleItems.map((item) => ( + + + {item.label} + + + ))} + + +
+ )} + {session ? ( /* Desktop Profile Dropdown */
@@ -189,7 +227,7 @@ export function Navbar() { {/* Nav */}
- {visibleNavItems.map((item) => ( + {navItems.map((item) => ( + {roles.length > 0 && ( + <> + {visibleRoleItems.map((item) => ( + setMobileOpen(false)} + className="py-2 font-mono text-sm text-muted-foreground uppercase hover:text-foreground" + > + {item.label} + + ))} + + )} + + + {session ? ( <> {currentProfile && ( From 86fb32671d6465ca59f800b22941ff57352760bd Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Fri, 21 Aug 2026 20:44:35 +0100 Subject: [PATCH 2/8] feat: add sidebar page layouts Signed-off-by: andyrodrigues30 --- app/src/components/Navbar.tsx | 4 +- .../components/sidebar/DashboardSidebar.tsx | 113 ++++++++++++++++++ app/src/routeTree.gen.ts | 113 ++++++++++++++++++ app/src/routes/dashboard.tsx | 29 +++++ app/src/routes/dashboard/assignments.tsx | 19 +++ app/src/routes/dashboard/index.tsx | 7 ++ app/src/routes/dashboard/members.tsx | 19 +++ app/src/routes/dashboard/roles.tsx | 19 +++ 8 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 app/src/components/sidebar/DashboardSidebar.tsx create mode 100644 app/src/routes/dashboard.tsx create mode 100644 app/src/routes/dashboard/assignments.tsx create mode 100644 app/src/routes/dashboard/index.tsx create mode 100644 app/src/routes/dashboard/members.tsx create mode 100644 app/src/routes/dashboard/roles.tsx diff --git a/app/src/components/Navbar.tsx b/app/src/components/Navbar.tsx index 9cb5a7ca..246ae61d 100644 --- a/app/src/components/Navbar.tsx +++ b/app/src/components/Navbar.tsx @@ -1,4 +1,4 @@ -import { Menu, Search, Shield, User, X } from "lucide-react"; +import { Menu, Search, ShieldCogCorner, User, X } from "lucide-react"; import { Link, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -119,7 +119,7 @@ export function Navbar() { size="icon" className="h-9 w-9 rounded-md" > - + diff --git a/app/src/components/sidebar/DashboardSidebar.tsx b/app/src/components/sidebar/DashboardSidebar.tsx new file mode 100644 index 00000000..adc3ef6b --- /dev/null +++ b/app/src/components/sidebar/DashboardSidebar.tsx @@ -0,0 +1,113 @@ +import { Link, useRouterState } from "@tanstack/react-router"; +import { FileCheckCorner, Shield, Users } from "lucide-react"; +import type { LucideProps } from "lucide-react"; +import type { ForwardRefExoticComponent, RefAttributes } from "react"; +import { cn } from "@/lib/utils"; +import { Separator } from "@/components/ui/separator"; +import { useAuth } from "@/lib/authContext"; + +const items: Array<{ + label: string; + to: string; + icon: ForwardRefExoticComponent< + Omit & RefAttributes + >; + roles?: Array; +}> = [ + { + label: "Members", + to: "/dashboard/members", + icon: Users, + roles: ["admin"], + }, + { + label: "Roles", + to: "/dashboard/roles", + icon: Shield, + roles: ["admin"], + }, + { + label: "Verifier Assignments", + to: "/dashboard/assignments", + icon: FileCheckCorner, + roles: ["lead-verifier", "admin"], + }, +]; + +export const DashboardSidebar = () => { + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + + const { roles } = useAuth(); + + const isAdmin = roles.includes("admin"); + const isLeadVerifier = roles.includes("lead-verifier"); + + const visibleItems = items.filter( + (item) => !item.roles || item.roles.some((r) => roles.includes(r)) + ); + + return ( + + ); +}; + +export const DashboardTabs = () => { + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + + return ( + + ); +}; diff --git a/app/src/routeTree.gen.ts b/app/src/routeTree.gen.ts index 975881c1..b5fadd33 100644 --- a/app/src/routeTree.gen.ts +++ b/app/src/routeTree.gen.ts @@ -20,6 +20,7 @@ import { Route as ProfileRouteImport } from './routes/profile' import { Route as ObjectivesRouteImport } from './routes/objectives' import { Route as LoginRouteImport } from './routes/login' import { Route as ForgotPasswordRouteImport } from './routes/forgot-password' +import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as ContributeRouteImport } from './routes/contribute' import { Route as BrowseRouteImport } from './routes/browse' import { Route as IndexRouteImport } from './routes/index' @@ -28,6 +29,7 @@ import { Route as SettingsIndexRouteImport } from './routes/settings.index' import { Route as ReviewIndexRouteImport } from './routes/review.index' import { Route as ObjectivesIndexRouteImport } from './routes/objectives.index' import { Route as GuidesIndexRouteImport } from './routes/guides/index' +import { Route as DashboardIndexRouteImport } from './routes/dashboard/index' import { Route as SubjectsSlugRouteImport } from './routes/subjects.$slug' import { Route as SettingsProfileRouteImport } from './routes/settings.profile' import { Route as SettingsAppearanceRouteImport } from './routes/settings.appearance' @@ -36,6 +38,9 @@ import { Route as ReviewCaseIdRouteImport } from './routes/review.$caseId' import { Route as ProfileUsernameRouteImport } from './routes/profile.$username' import { Route as ObjectivesSlugRouteImport } from './routes/objectives/$slug' import { Route as GuidesSlugRouteImport } from './routes/guides/$slug' +import { Route as DashboardRolesRouteImport } from './routes/dashboard/roles' +import { Route as DashboardMembersRouteImport } from './routes/dashboard/members' +import { Route as DashboardAssignmentsRouteImport } from './routes/dashboard/assignments' import { Route as ObjectivesSlugIndexRouteImport } from './routes/objectives/$slug/index' import { Route as GuidesSlugIndexRouteImport } from './routes/guides/$slug/index' import { Route as GuidesSlugWalkthroughRouteImport } from './routes/guides/$slug/walkthrough' @@ -99,6 +104,11 @@ const ForgotPasswordRoute = ForgotPasswordRouteImport.update({ path: '/forgot-password', getParentRoute: () => rootRouteImport, } as any) +const DashboardRoute = DashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => rootRouteImport, +} as any) const ContributeRoute = ContributeRouteImport.update({ id: '/contribute', path: '/contribute', @@ -139,6 +149,11 @@ const GuidesIndexRoute = GuidesIndexRouteImport.update({ path: '/guides/', getParentRoute: () => rootRouteImport, } as any) +const DashboardIndexRoute = DashboardIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => DashboardRoute, +} as any) const SubjectsSlugRoute = SubjectsSlugRouteImport.update({ id: '/$slug', path: '/$slug', @@ -179,6 +194,21 @@ const GuidesSlugRoute = GuidesSlugRouteImport.update({ path: '/guides/$slug', getParentRoute: () => rootRouteImport, } as any) +const DashboardRolesRoute = DashboardRolesRouteImport.update({ + id: '/roles', + path: '/roles', + getParentRoute: () => DashboardRoute, +} as any) +const DashboardMembersRoute = DashboardMembersRouteImport.update({ + id: '/members', + path: '/members', + getParentRoute: () => DashboardRoute, +} as any) +const DashboardAssignmentsRoute = DashboardAssignmentsRouteImport.update({ + id: '/assignments', + path: '/assignments', + getParentRoute: () => DashboardRoute, +} as any) const ObjectivesSlugIndexRoute = ObjectivesSlugIndexRouteImport.update({ id: '/', path: '/', @@ -223,6 +253,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/browse': typeof BrowseRoute '/contribute': typeof ContributeRoute + '/dashboard': typeof DashboardRouteWithChildren '/forgot-password': typeof ForgotPasswordRoute '/login': typeof LoginRoute '/objectives': typeof ObjectivesRouteWithChildren @@ -234,6 +265,9 @@ export interface FileRoutesByFullPath { '/subjects': typeof SubjectsRouteWithChildren '/todos': typeof TodosRoute '/verify-email': typeof VerifyEmailRoute + '/dashboard/assignments': typeof DashboardAssignmentsRoute + '/dashboard/members': typeof DashboardMembersRoute + '/dashboard/roles': typeof DashboardRolesRoute '/guides/$slug': typeof GuidesSlugRouteWithChildren '/objectives/$slug': typeof ObjectivesSlugRouteWithChildren '/profile/$username': typeof ProfileUsernameRoute @@ -242,6 +276,7 @@ export interface FileRoutesByFullPath { '/settings/appearance': typeof SettingsAppearanceRoute '/settings/profile': typeof SettingsProfileRoute '/subjects/$slug': typeof SubjectsSlugRoute + '/dashboard/': typeof DashboardIndexRoute '/guides/': typeof GuidesIndexRoute '/objectives/': typeof ObjectivesIndexRoute '/review/': typeof ReviewIndexRoute @@ -266,12 +301,16 @@ export interface FileRoutesByTo { '/reset-password': typeof ResetPasswordRoute '/todos': typeof TodosRoute '/verify-email': typeof VerifyEmailRoute + '/dashboard/assignments': typeof DashboardAssignmentsRoute + '/dashboard/members': typeof DashboardMembersRoute + '/dashboard/roles': typeof DashboardRolesRoute '/profile/$username': typeof ProfileUsernameRoute '/review/$caseId': typeof ReviewCaseIdRoute '/settings/account': typeof SettingsAccountRoute '/settings/appearance': typeof SettingsAppearanceRoute '/settings/profile': typeof SettingsProfileRoute '/subjects/$slug': typeof SubjectsSlugRoute + '/dashboard': typeof DashboardIndexRoute '/guides': typeof GuidesIndexRoute '/objectives': typeof ObjectivesIndexRoute '/review': typeof ReviewIndexRoute @@ -290,6 +329,7 @@ export interface FileRoutesById { '/': typeof IndexRoute '/browse': typeof BrowseRoute '/contribute': typeof ContributeRoute + '/dashboard': typeof DashboardRouteWithChildren '/forgot-password': typeof ForgotPasswordRoute '/login': typeof LoginRoute '/objectives': typeof ObjectivesRouteWithChildren @@ -301,6 +341,9 @@ export interface FileRoutesById { '/subjects': typeof SubjectsRouteWithChildren '/todos': typeof TodosRoute '/verify-email': typeof VerifyEmailRoute + '/dashboard/assignments': typeof DashboardAssignmentsRoute + '/dashboard/members': typeof DashboardMembersRoute + '/dashboard/roles': typeof DashboardRolesRoute '/guides/$slug': typeof GuidesSlugRouteWithChildren '/objectives/$slug': typeof ObjectivesSlugRouteWithChildren '/profile/$username': typeof ProfileUsernameRoute @@ -309,6 +352,7 @@ export interface FileRoutesById { '/settings/appearance': typeof SettingsAppearanceRoute '/settings/profile': typeof SettingsProfileRoute '/subjects/$slug': typeof SubjectsSlugRoute + '/dashboard/': typeof DashboardIndexRoute '/guides/': typeof GuidesIndexRoute '/objectives/': typeof ObjectivesIndexRoute '/review/': typeof ReviewIndexRoute @@ -328,6 +372,7 @@ export interface FileRouteTypes { | '/' | '/browse' | '/contribute' + | '/dashboard' | '/forgot-password' | '/login' | '/objectives' @@ -339,6 +384,9 @@ export interface FileRouteTypes { | '/subjects' | '/todos' | '/verify-email' + | '/dashboard/assignments' + | '/dashboard/members' + | '/dashboard/roles' | '/guides/$slug' | '/objectives/$slug' | '/profile/$username' @@ -347,6 +395,7 @@ export interface FileRouteTypes { | '/settings/appearance' | '/settings/profile' | '/subjects/$slug' + | '/dashboard/' | '/guides/' | '/objectives/' | '/review/' @@ -371,12 +420,16 @@ export interface FileRouteTypes { | '/reset-password' | '/todos' | '/verify-email' + | '/dashboard/assignments' + | '/dashboard/members' + | '/dashboard/roles' | '/profile/$username' | '/review/$caseId' | '/settings/account' | '/settings/appearance' | '/settings/profile' | '/subjects/$slug' + | '/dashboard' | '/guides' | '/objectives' | '/review' @@ -394,6 +447,7 @@ export interface FileRouteTypes { | '/' | '/browse' | '/contribute' + | '/dashboard' | '/forgot-password' | '/login' | '/objectives' @@ -405,6 +459,9 @@ export interface FileRouteTypes { | '/subjects' | '/todos' | '/verify-email' + | '/dashboard/assignments' + | '/dashboard/members' + | '/dashboard/roles' | '/guides/$slug' | '/objectives/$slug' | '/profile/$username' @@ -413,6 +470,7 @@ export interface FileRouteTypes { | '/settings/appearance' | '/settings/profile' | '/subjects/$slug' + | '/dashboard/' | '/guides/' | '/objectives/' | '/review/' @@ -431,6 +489,7 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute BrowseRoute: typeof BrowseRoute ContributeRoute: typeof ContributeRoute + DashboardRoute: typeof DashboardRouteWithChildren ForgotPasswordRoute: typeof ForgotPasswordRoute LoginRoute: typeof LoginRoute ObjectivesRoute: typeof ObjectivesRouteWithChildren @@ -525,6 +584,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ForgotPasswordRouteImport parentRoute: typeof rootRouteImport } + '/dashboard': { + id: '/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof DashboardRouteImport + parentRoute: typeof rootRouteImport + } '/contribute': { id: '/contribute' path: '/contribute' @@ -581,6 +647,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof GuidesIndexRouteImport parentRoute: typeof rootRouteImport } + '/dashboard/': { + id: '/dashboard/' + path: '/' + fullPath: '/dashboard/' + preLoaderRoute: typeof DashboardIndexRouteImport + parentRoute: typeof DashboardRoute + } '/subjects/$slug': { id: '/subjects/$slug' path: '/$slug' @@ -637,6 +710,27 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof GuidesSlugRouteImport parentRoute: typeof rootRouteImport } + '/dashboard/roles': { + id: '/dashboard/roles' + path: '/roles' + fullPath: '/dashboard/roles' + preLoaderRoute: typeof DashboardRolesRouteImport + parentRoute: typeof DashboardRoute + } + '/dashboard/members': { + id: '/dashboard/members' + path: '/members' + fullPath: '/dashboard/members' + preLoaderRoute: typeof DashboardMembersRouteImport + parentRoute: typeof DashboardRoute + } + '/dashboard/assignments': { + id: '/dashboard/assignments' + path: '/assignments' + fullPath: '/dashboard/assignments' + preLoaderRoute: typeof DashboardAssignmentsRouteImport + parentRoute: typeof DashboardRoute + } '/objectives/$slug/': { id: '/objectives/$slug/' path: '/' @@ -689,6 +783,24 @@ declare module '@tanstack/react-router' { } } +interface DashboardRouteChildren { + DashboardAssignmentsRoute: typeof DashboardAssignmentsRoute + DashboardMembersRoute: typeof DashboardMembersRoute + DashboardRolesRoute: typeof DashboardRolesRoute + DashboardIndexRoute: typeof DashboardIndexRoute +} + +const DashboardRouteChildren: DashboardRouteChildren = { + DashboardAssignmentsRoute: DashboardAssignmentsRoute, + DashboardMembersRoute: DashboardMembersRoute, + DashboardRolesRoute: DashboardRolesRoute, + DashboardIndexRoute: DashboardIndexRoute, +} + +const DashboardRouteWithChildren = DashboardRoute._addFileChildren( + DashboardRouteChildren, +) + interface ObjectivesSlugRouteChildren { ObjectivesSlugIndexRoute: typeof ObjectivesSlugIndexRoute ObjectivesSlugRevisionsRevisionIdRoute: typeof ObjectivesSlugRevisionsRevisionIdRoute @@ -799,6 +911,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, BrowseRoute: BrowseRoute, ContributeRoute: ContributeRoute, + DashboardRoute: DashboardRouteWithChildren, ForgotPasswordRoute: ForgotPasswordRoute, LoginRoute: LoginRoute, ObjectivesRoute: ObjectivesRouteWithChildren, diff --git a/app/src/routes/dashboard.tsx b/app/src/routes/dashboard.tsx new file mode 100644 index 00000000..14818346 --- /dev/null +++ b/app/src/routes/dashboard.tsx @@ -0,0 +1,29 @@ +import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { + DashboardSidebar, + DashboardTabs, +} from "@/components/sidebar/DashboardSidebar"; +import { requireSession } from "@/lib/auth"; + +export const Route = createFileRoute("/dashboard")({ + ssr: false, + beforeLoad: requireSession, + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+
+ {/* Left Sidebar Navigation */} + + + {/* Right Content Area */} +
+ + +
+
+
+ ); +} diff --git a/app/src/routes/dashboard/assignments.tsx b/app/src/routes/dashboard/assignments.tsx new file mode 100644 index 00000000..bd032410 --- /dev/null +++ b/app/src/routes/dashboard/assignments.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/dashboard/assignments")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+
+

+ Manage Verifier Assignments +

+
+ +
+
+ ); +} diff --git a/app/src/routes/dashboard/index.tsx b/app/src/routes/dashboard/index.tsx new file mode 100644 index 00000000..aa46ac6e --- /dev/null +++ b/app/src/routes/dashboard/index.tsx @@ -0,0 +1,7 @@ +import { createFileRoute, redirect } from "@tanstack/react-router"; + +export const Route = createFileRoute("/dashboard/")({ + beforeLoad: () => { + throw redirect({ to: "/dashboard/members" }); + }, +}); diff --git a/app/src/routes/dashboard/members.tsx b/app/src/routes/dashboard/members.tsx new file mode 100644 index 00000000..0bdbde87 --- /dev/null +++ b/app/src/routes/dashboard/members.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/dashboard/members")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+
+

+ Manage Members +

+
+ +
+
+ ); +} diff --git a/app/src/routes/dashboard/roles.tsx b/app/src/routes/dashboard/roles.tsx new file mode 100644 index 00000000..35ce4d4c --- /dev/null +++ b/app/src/routes/dashboard/roles.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/dashboard/roles")({ + component: RouteComponent, +}); + +function RouteComponent() { + return ( +
+
+

+ Manage Roles +

+
+ +
+
+ ); +} From 224b51a076c126a9d256b3cc5479cc5911ce2b1c Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Fri, 21 Aug 2026 20:46:40 +0100 Subject: [PATCH 3/8] feat: fix review queue heading size Signed-off-by: andyrodrigues30 --- app/src/routes/review.index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/routes/review.index.tsx b/app/src/routes/review.index.tsx index acf14599..df9228e6 100644 --- a/app/src/routes/review.index.tsx +++ b/app/src/routes/review.index.tsx @@ -28,7 +28,7 @@ function Shell({ children }: { children: React.ReactNode }) {
-

+

Review Queue

From 9b725f00ddaac406acbc82a657688d0a98c546b6 Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Sat, 22 Aug 2026 00:19:38 +0100 Subject: [PATCH 4/8] feat: create manage members page Signed-off-by: andyrodrigues30 --- .../{ => tables}/ActivityColumnFilters.tsx | 0 .../{profile => tables}/ActivityTable.tsx | 2 +- app/src/components/tables/MembersTable.tsx | 164 ++++++++++++++++++ app/src/routes/dashboard/assignments.tsx | 2 +- app/src/routes/dashboard/members.tsx | 42 ++++- app/src/routes/dashboard/roles.tsx | 2 +- app/src/routes/profile.$username.tsx | 2 +- 7 files changed, 204 insertions(+), 10 deletions(-) rename app/src/components/{ => tables}/ActivityColumnFilters.tsx (100%) rename app/src/components/{profile => tables}/ActivityTable.tsx (99%) create mode 100644 app/src/components/tables/MembersTable.tsx diff --git a/app/src/components/ActivityColumnFilters.tsx b/app/src/components/tables/ActivityColumnFilters.tsx similarity index 100% rename from app/src/components/ActivityColumnFilters.tsx rename to app/src/components/tables/ActivityColumnFilters.tsx diff --git a/app/src/components/profile/ActivityTable.tsx b/app/src/components/tables/ActivityTable.tsx similarity index 99% rename from app/src/components/profile/ActivityTable.tsx rename to app/src/components/tables/ActivityTable.tsx index 84a994bd..f0c1a098 100644 --- a/app/src/components/profile/ActivityTable.tsx +++ b/app/src/components/tables/ActivityTable.tsx @@ -16,7 +16,7 @@ import { ChoiceColumnFilter, DateColumnFilter, TextColumnFilter, -} from "@/components/ActivityColumnFilters"; +} from "@/components/tables/ActivityColumnFilters"; import { Table, TableBody, diff --git a/app/src/components/tables/MembersTable.tsx b/app/src/components/tables/MembersTable.tsx new file mode 100644 index 00000000..cf1d4dff --- /dev/null +++ b/app/src/components/tables/MembersTable.tsx @@ -0,0 +1,164 @@ +import { useState } from "react"; +import { Checkbox } from "../ui/checkbox"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +export const MembersTable = () => { + const [selectedIds, setSelectedIds] = useState>(new Set()); + + const profiles = [ + { + id: "001", + username: "andrea", + display_name: "Andrea", + bio: "Software Engineer", + date_created: "08-14-2026", + date_updated: "08-21-2026", + is_afk: false, + is_suspended: false, + }, + { + id: "002", + username: "bob", + display_name: "Bob", + bio: "Writer", + date_created: "08-14-2026", + date_updated: "08-21-2026", + is_afk: true, + is_suspended: false, + }, + ]; + + const allSelected = + profiles.length > 0 && + profiles.every((profile) => selectedIds.has(profile.id)); + + function toggleProfile(profileId: string) { + setSelectedIds((current) => { + const next = new Set(current); + + if (next.has(profileId)) { + next.delete(profileId); + } else { + next.add(profileId); + } + + return next; + }); + } + + function toggleAll() { + setSelectedIds((current) => { + if (allSelected) { + const next = new Set(current); + profiles.forEach((profile) => next.delete(profile.id)); + return next; + } + + return new Set(profiles.map((profile) => profile.id)); + }); + } + + return ( + + + + + + + + + Username + + + + Display Name + + + + Bio + + + + Date Created + + + + Date Updated + + + + AFK + + + + Suspended + + + + + + {profiles.map((profile) => ( + + + toggleProfile(profile.id)} + aria-label={`Select ${profile.username}`} + /> + + + + {profile.username} + + + + {profile.display_name} + + + + {profile.bio || "—"} + + + + {profile.date_created} + + + + {profile.date_updated} + + + + + {profile.is_afk ? "true" : "false"} + + + + + + {profile.is_suspended ? "true" : "false"} + + + + ))} + +
+ ); +}; diff --git a/app/src/routes/dashboard/assignments.tsx b/app/src/routes/dashboard/assignments.tsx index bd032410..f530b22f 100644 --- a/app/src/routes/dashboard/assignments.tsx +++ b/app/src/routes/dashboard/assignments.tsx @@ -6,7 +6,7 @@ export const Route = createFileRoute("/dashboard/assignments")({ function RouteComponent() { return ( -
+

Manage Verifier Assignments diff --git a/app/src/routes/dashboard/members.tsx b/app/src/routes/dashboard/members.tsx index 0bdbde87..3beac88a 100644 --- a/app/src/routes/dashboard/members.tsx +++ b/app/src/routes/dashboard/members.tsx @@ -1,4 +1,13 @@ import { createFileRoute } from "@tanstack/react-router"; +import { Ban, Ellipsis, SquareArrowRightExit } from "lucide-react"; +import { MembersTable } from "@/components/tables/MembersTable"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; export const Route = createFileRoute("/dashboard/members")({ component: RouteComponent, @@ -6,14 +15,35 @@ export const Route = createFileRoute("/dashboard/members")({ function RouteComponent() { return ( -
-
-

- Manage Members -

+
+
+
+

+ Manage Members +

+
+ +
+ + + +
-
+
+
+ +
+
); } diff --git a/app/src/routes/dashboard/roles.tsx b/app/src/routes/dashboard/roles.tsx index 35ce4d4c..02820772 100644 --- a/app/src/routes/dashboard/roles.tsx +++ b/app/src/routes/dashboard/roles.tsx @@ -6,7 +6,7 @@ export const Route = createFileRoute("/dashboard/roles")({ function RouteComponent() { return ( -
+

Manage Roles diff --git a/app/src/routes/profile.$username.tsx b/app/src/routes/profile.$username.tsx index 89c9c642..96dea483 100644 --- a/app/src/routes/profile.$username.tsx +++ b/app/src/routes/profile.$username.tsx @@ -4,7 +4,7 @@ import type { ProfileActivitySearch } from "@bluelearn/schemas"; import { getAvatarUrl, getInitials } from "@/lib/profile"; import { getProfilePage } from "@/lib/api/identity"; import { cn } from "@/lib/utils"; -import { ActivityTable } from "@/components/profile/ActivityTable"; +import { ActivityTable } from "@/components/tables/ActivityTable"; import { Separator } from "@/components/ui/separator"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; From cd36bfb4746a706dcd24d6bedaf9299fc202d06a Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Sat, 22 Aug 2026 00:33:50 +0100 Subject: [PATCH 5/8] feat: add manage roles table Signed-off-by: andyrodrigues30 --- app/src/components/tables/RolesTable.tsx | 146 +++++++++++++++++++++++ app/src/routes/dashboard/members.tsx | 13 +- app/src/routes/dashboard/roles.tsx | 39 +++++- 3 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 app/src/components/tables/RolesTable.tsx diff --git a/app/src/components/tables/RolesTable.tsx b/app/src/components/tables/RolesTable.tsx new file mode 100644 index 00000000..fd3c0317 --- /dev/null +++ b/app/src/components/tables/RolesTable.tsx @@ -0,0 +1,146 @@ +import { useState } from "react"; +import { Checkbox } from "../ui/checkbox"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +export const RolesTable = () => { + const [selectedIds, setSelectedIds] = useState>(new Set()); + + const roleData = [ + { + id: "001", + username: "andrea", + roles: ["admin", "curator"], + date_created: "08-14-2026", + date_updated: "08-21-2026", + is_afk: false, + }, + { + id: "002", + username: "bob", + roles: ["verifier"], + date_created: "08-14-2026", + date_updated: "08-21-2026", + is_afk: true, + }, + ]; + + const allSelected = + roleData.length > 0 && + roleData.every((profile) => selectedIds.has(profile.id)); + + function toggleUser(userId: string) { + setSelectedIds((current) => { + const next = new Set(current); + + if (next.has(userId)) { + next.delete(userId); + } else { + next.add(userId); + } + + return next; + }); + } + + function toggleAll() { + setSelectedIds((current) => { + if (allSelected) { + const next = new Set(current); + roleData.forEach((user) => next.delete(user.id)); + return next; + } + + return new Set(roleData.map((user) => user.id)); + }); + } + + return ( + + + + + + + + + Username + + + + Roles + + + + Date Created + + + + Date Updated + + + + AFK + + + + + + {roleData.map((user) => ( + + + toggleUser(user.id)} + aria-label={`Select ${user.username}`} + /> + + + + {user.username} + + + + {user.roles.map((role) => ( + + {role} + + ))} + + + + {user.date_created} + + + + {user.date_updated} + + + + + {user.is_afk ? "true" : "false"} + + + + ))} + +
+ ); +}; diff --git a/app/src/routes/dashboard/members.tsx b/app/src/routes/dashboard/members.tsx index 3beac88a..10635903 100644 --- a/app/src/routes/dashboard/members.tsx +++ b/app/src/routes/dashboard/members.tsx @@ -1,13 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Ban, Ellipsis, SquareArrowRightExit } from "lucide-react"; +import { Ban } from "lucide-react"; import { MembersTable } from "@/components/tables/MembersTable"; import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; export const Route = createFileRoute("/dashboard/members")({ component: RouteComponent, @@ -24,11 +18,6 @@ function RouteComponent() {

- - + + + + +
-
+
+
+ +
+
); } From 6a24dfe5d4aae984552a1db26c3a366d6e2bd245 Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Sat, 22 Aug 2026 00:47:40 +0100 Subject: [PATCH 6/8] feat: add action button on verifier assignmement page Signed-off-by: andyrodrigues30 --- .../components/icons/UserRoundArrowLeft.tsx | 21 +++++++++++++++++++ app/src/routes/dashboard/assignments.tsx | 12 +---------- 2 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 app/src/components/icons/UserRoundArrowLeft.tsx diff --git a/app/src/components/icons/UserRoundArrowLeft.tsx b/app/src/components/icons/UserRoundArrowLeft.tsx new file mode 100644 index 00000000..067f7686 --- /dev/null +++ b/app/src/components/icons/UserRoundArrowLeft.tsx @@ -0,0 +1,21 @@ +export const UserRoundArrowLeft = () => { + return ( + + + + + + + ); +}; diff --git a/app/src/routes/dashboard/assignments.tsx b/app/src/routes/dashboard/assignments.tsx index f530b22f..0e9f2ad7 100644 --- a/app/src/routes/dashboard/assignments.tsx +++ b/app/src/routes/dashboard/assignments.tsx @@ -5,15 +5,5 @@ export const Route = createFileRoute("/dashboard/assignments")({ }); function RouteComponent() { - return ( -
-
-

- Manage Verifier Assignments -

-
- -
-
- ); + return
Hello "/dashboard/assignments"!
; } From 100c825255d74c9bc8fd9ad7aeef60dbe46cea3b Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Sat, 22 Aug 2026 07:41:19 +0100 Subject: [PATCH 7/8] feat: update user table statuses Signed-off-by: andyrodrigues30 --- app/src/components/tables/MembersTable.tsx | 23 ++++------------------ app/src/components/tables/RolesTable.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/app/src/components/tables/MembersTable.tsx b/app/src/components/tables/MembersTable.tsx index cf1d4dff..8f0726c0 100644 --- a/app/src/components/tables/MembersTable.tsx +++ b/app/src/components/tables/MembersTable.tsx @@ -21,8 +21,7 @@ export const MembersTable = () => { bio: "Software Engineer", date_created: "08-14-2026", date_updated: "08-21-2026", - is_afk: false, - is_suspended: false, + status: "active", }, { id: "002", @@ -31,8 +30,7 @@ export const MembersTable = () => { bio: "Writer", date_created: "08-14-2026", date_updated: "08-21-2026", - is_afk: true, - is_suspended: false, + status: "suspended", }, ]; @@ -99,11 +97,7 @@ export const MembersTable = () => { - AFK - - - - Suspended + Status @@ -144,16 +138,7 @@ export const MembersTable = () => { variant="outline" className="mono-micro rounded-full border border-badge-border bg-badge tracking-[0.08em] text-badge-foreground" > - {profile.is_afk ? "true" : "false"} - - - - - - {profile.is_suspended ? "true" : "false"} + {profile.status} diff --git a/app/src/components/tables/RolesTable.tsx b/app/src/components/tables/RolesTable.tsx index fd3c0317..529e1ef9 100644 --- a/app/src/components/tables/RolesTable.tsx +++ b/app/src/components/tables/RolesTable.tsx @@ -20,7 +20,7 @@ export const RolesTable = () => { roles: ["admin", "curator"], date_created: "08-14-2026", date_updated: "08-21-2026", - is_afk: false, + status: "active", }, { id: "002", @@ -28,7 +28,7 @@ export const RolesTable = () => { roles: ["verifier"], date_created: "08-14-2026", date_updated: "08-21-2026", - is_afk: true, + status: "suspended", }, ]; @@ -91,7 +91,7 @@ export const RolesTable = () => { - AFK + Status @@ -111,7 +111,7 @@ export const RolesTable = () => { {user.username} - + {user.roles.map((role) => ( { variant="outline" className="mono-micro rounded-full border border-badge-border bg-badge tracking-[0.08em] text-badge-foreground" > - {user.is_afk ? "true" : "false"} + {user.status} From 01351d677b48997ddd25af7384eb171c85b5f5e1 Mon Sep 17 00:00:00 2001 From: andyrodrigues30 Date: Sat, 22 Aug 2026 07:45:16 +0100 Subject: [PATCH 8/8] feat: create assignments page Signed-off-by: andyrodrigues30 --- app/src/routes/dashboard/assignments.tsx | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/routes/dashboard/assignments.tsx b/app/src/routes/dashboard/assignments.tsx index 0e9f2ad7..d03ac159 100644 --- a/app/src/routes/dashboard/assignments.tsx +++ b/app/src/routes/dashboard/assignments.tsx @@ -1,9 +1,34 @@ import { createFileRoute } from "@tanstack/react-router"; +import { UserRoundArrowLeft } from "@/components/icons/UserRoundArrowLeft"; +import { Button } from "@/components/ui/button"; export const Route = createFileRoute("/dashboard/assignments")({ component: RouteComponent, }); function RouteComponent() { - return
Hello "/dashboard/assignments"!
; + return ( +
+
+
+

+ Manage Guide Assignments +

+
+ +
+ +
+
+ +
+
+ {/* AssignmentsTable */} +
+
+
+ ); }