From 92b63435f2226ea1d73fb7219cf91f0b5067d730 Mon Sep 17 00:00:00 2001 From: Dao Ho <84757503+Dao-Ho@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:08:54 -0700 Subject: [PATCH 1/4] feat: page skeletons --- frontend/package-lock.json | 10 ++ frontend/package.json | 1 + .../src/app/(portal)/admin/builder/page.tsx | 7 ++ .../src/app/(portal)/admin/cycles/page.tsx | 7 ++ .../src/app/(portal)/admin/roles/page.tsx | 7 ++ .../(portal)/applicant/applications/page.tsx | 7 ++ frontend/src/app/(portal)/layout.tsx | 21 +++++ .../app/(portal)/reviewer/applicants/page.tsx | 7 ++ .../app/(portal)/reviewer/dashboard/page.tsx | 7 ++ frontend/src/app/globals.css | 2 + frontend/src/app/layout.tsx | 4 +- frontend/src/app/page.tsx | 66 +------------ frontend/src/components/nav/NavItem.tsx | 33 +++++++ frontend/src/components/nav/Sidebar.tsx | 92 +++++++++++++++++++ frontend/src/lib/mock-user.ts | 7 ++ frontend/src/types/roles.ts | 10 ++ frontend/src/types/user.ts | 5 + 17 files changed, 228 insertions(+), 65 deletions(-) create mode 100644 frontend/src/app/(portal)/admin/builder/page.tsx create mode 100644 frontend/src/app/(portal)/admin/cycles/page.tsx create mode 100644 frontend/src/app/(portal)/admin/roles/page.tsx create mode 100644 frontend/src/app/(portal)/applicant/applications/page.tsx create mode 100644 frontend/src/app/(portal)/layout.tsx create mode 100644 frontend/src/app/(portal)/reviewer/applicants/page.tsx create mode 100644 frontend/src/app/(portal)/reviewer/dashboard/page.tsx create mode 100644 frontend/src/components/nav/NavItem.tsx create mode 100644 frontend/src/components/nav/Sidebar.tsx create mode 100644 frontend/src/lib/mock-user.ts create mode 100644 frontend/src/types/roles.ts create mode 100644 frontend/src/types/user.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e565acd..f61b4d6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "lucide-react": "^1.18.0", "next": "16.2.9", "react": "19.2.4", "react-dom": "19.2.4" @@ -4961,6 +4962,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.18.0.tgz", + "integrity": "sha512-LZDb7H/0YfM+RJncD0hDQRCAu+vSGODqpe35TuVI8EuXaRjkczbsx7p8dY4J87F/MUSj6bpYqeI8nw8qXaAdmA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/frontend/package.json b/frontend/package.json index 826911c..8e83e30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "lint": "eslint" }, "dependencies": { + "lucide-react": "^1.18.0", "next": "16.2.9", "react": "19.2.4", "react-dom": "19.2.4" diff --git a/frontend/src/app/(portal)/admin/builder/page.tsx b/frontend/src/app/(portal)/admin/builder/page.tsx new file mode 100644 index 0000000..769e70b --- /dev/null +++ b/frontend/src/app/(portal)/admin/builder/page.tsx @@ -0,0 +1,7 @@ +export default function BuilderPage() { + return ( +
+

App Builder

+
+ ); +} diff --git a/frontend/src/app/(portal)/admin/cycles/page.tsx b/frontend/src/app/(portal)/admin/cycles/page.tsx new file mode 100644 index 0000000..ca084eb --- /dev/null +++ b/frontend/src/app/(portal)/admin/cycles/page.tsx @@ -0,0 +1,7 @@ +export default function CyclesPage() { + return ( +
+

Cycles

+
+ ); +} diff --git a/frontend/src/app/(portal)/admin/roles/page.tsx b/frontend/src/app/(portal)/admin/roles/page.tsx new file mode 100644 index 0000000..9fb33bb --- /dev/null +++ b/frontend/src/app/(portal)/admin/roles/page.tsx @@ -0,0 +1,7 @@ +export default function RolesPage() { + return ( +
+

Roles

+
+ ); +} diff --git a/frontend/src/app/(portal)/applicant/applications/page.tsx b/frontend/src/app/(portal)/applicant/applications/page.tsx new file mode 100644 index 0000000..713e545 --- /dev/null +++ b/frontend/src/app/(portal)/applicant/applications/page.tsx @@ -0,0 +1,7 @@ +export default function ApplicationsPage() { + return ( +
+

My Applications

+
+ ); +} diff --git a/frontend/src/app/(portal)/layout.tsx b/frontend/src/app/(portal)/layout.tsx new file mode 100644 index 0000000..a2708a9 --- /dev/null +++ b/frontend/src/app/(portal)/layout.tsx @@ -0,0 +1,21 @@ +import Sidebar from "@/components/nav/Sidebar"; +import { mockUser } from "@/lib/mock-user"; +import { getRoles } from "@/types/roles"; + +export default function PortalLayout({ + children, +}: { + children: React.ReactNode; +}) { + // TODO: replace mockUser with session user from auth + const roles = getRoles(mockUser); + + return ( +
+ +
+ {children} +
+
+ ); +} diff --git a/frontend/src/app/(portal)/reviewer/applicants/page.tsx b/frontend/src/app/(portal)/reviewer/applicants/page.tsx new file mode 100644 index 0000000..6905fed --- /dev/null +++ b/frontend/src/app/(portal)/reviewer/applicants/page.tsx @@ -0,0 +1,7 @@ +export default function ApplicantsPage() { + return ( +
+

Applicants

+
+ ); +} diff --git a/frontend/src/app/(portal)/reviewer/dashboard/page.tsx b/frontend/src/app/(portal)/reviewer/dashboard/page.tsx new file mode 100644 index 0000000..f6722e0 --- /dev/null +++ b/frontend/src/app/(portal)/reviewer/dashboard/page.tsx @@ -0,0 +1,7 @@ +export default function DashboardPage() { + return ( +
+

Dashboard

+
+ ); +} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index a2dc41e..a7af88f 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -8,6 +8,8 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); + --color-brand-blue: #1477f8; + --color-brand-white: #ffffff; --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 976eb90..461a21d 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Generate Portal", + description: "Application portal for Generate NU", }; export default function RootLayout({ diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 3f36f7c..107f3e5 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,65 +1,5 @@ -import Image from "next/image"; +import { redirect } from "next/navigation"; -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
- ); +export default function RootPage() { + redirect("/reviewer/dashboard"); } diff --git a/frontend/src/components/nav/NavItem.tsx b/frontend/src/components/nav/NavItem.tsx new file mode 100644 index 0000000..60483d5 --- /dev/null +++ b/frontend/src/components/nav/NavItem.tsx @@ -0,0 +1,33 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type { LucideIcon } from "lucide-react"; + +interface NavItemProps { + href: string; + label: string; + icon: LucideIcon; +} + +export default function NavItem({ href, label, icon: Icon }: NavItemProps) { + const pathname = usePathname(); + const isActive = pathname === href || pathname.startsWith(href + "/"); + + return ( + + + {label} + + ); +} diff --git a/frontend/src/components/nav/Sidebar.tsx b/frontend/src/components/nav/Sidebar.tsx new file mode 100644 index 0000000..dd8ce02 --- /dev/null +++ b/frontend/src/components/nav/Sidebar.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { + LayoutDashboard, + Users, + FileText, + RefreshCw, + Settings, + Layers, +} from "lucide-react"; +import NavItem from "./NavItem"; +import type { Role } from "@/types/roles"; + +interface SidebarProps { + roles: Role[]; + nuid?: string; +} + +type NavSection = { label: string; items: { href: string; label: string; icon: typeof FileText }[] }; + +const sectionsByRole: Record = { + applicant: { + label: "Applications", + items: [ + { href: "/applicant/applications", label: "My Applications", icon: FileText }, + ], + }, + reviewer: { + label: "Review", + items: [ + { href: "/reviewer/dashboard", label: "Dashboard", icon: LayoutDashboard }, + { href: "/reviewer/applicants", label: "Applicants", icon: Users }, + ], + }, + admin: { + label: "Admin", + items: [ + { href: "/admin/cycles", label: "Cycles", icon: RefreshCw }, + { href: "/admin/builder", label: "App Builder", icon: Layers }, + { href: "/admin/roles", label: "Roles", icon: Settings }, + ], + }, +}; + +// Display order: reviewer sections before applicant, admin last +const roleOrder: Role[] = ["reviewer", "applicant", "admin"]; + +export default function Sidebar({ roles, nuid }: SidebarProps) { + const sections = roleOrder + .filter((role) => roles.includes(role)) + .map((role) => sectionsByRole[role]); + + return ( + + ); +} diff --git a/frontend/src/lib/mock-user.ts b/frontend/src/lib/mock-user.ts new file mode 100644 index 0000000..49607ae --- /dev/null +++ b/frontend/src/lib/mock-user.ts @@ -0,0 +1,7 @@ +import type { User } from "@/types/user"; + +export const mockUser: User = { + nuid: "002139999", + is_reviewer: true, + is_admin: false, +}; diff --git a/frontend/src/types/roles.ts b/frontend/src/types/roles.ts new file mode 100644 index 0000000..3f99ea2 --- /dev/null +++ b/frontend/src/types/roles.ts @@ -0,0 +1,10 @@ +import type { User } from "./user"; + +export type Role = "applicant" | "reviewer" | "admin"; + +export function getRoles(user: User): Role[] { + const roles: Role[] = ["applicant"]; + if (user.is_reviewer) roles.push("reviewer"); + if (user.is_admin) roles.push("admin"); + return roles; +} diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts new file mode 100644 index 0000000..6eca3df --- /dev/null +++ b/frontend/src/types/user.ts @@ -0,0 +1,5 @@ +export interface User { + nuid: string; + is_reviewer: boolean; + is_admin: boolean; +} From 234f8edcbaddc5780c5025bfe633bf7b3c3ebf95 Mon Sep 17 00:00:00 2001 From: Dao Ho <84757503+Dao-Ho@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:09:06 -0700 Subject: [PATCH 2/4] format --- frontend/AGENTS.md | 2 + frontend/eslint.config.mjs | 18 +++--- frontend/next.config.ts | 6 +- frontend/postcss.config.mjs | 6 +- .../src/app/(portal)/admin/builder/page.tsx | 2 +- .../src/app/(portal)/admin/cycles/page.tsx | 2 +- .../src/app/(portal)/admin/roles/page.tsx | 2 +- .../(portal)/applicant/applications/page.tsx | 2 +- frontend/src/app/(portal)/layout.tsx | 12 ++-- .../app/(portal)/reviewer/applicants/page.tsx | 2 +- .../app/(portal)/reviewer/dashboard/page.tsx | 2 +- frontend/src/app/globals.css | 2 +- frontend/src/app/layout.tsx | 30 ++++----- frontend/src/app/page.tsx | 4 +- frontend/src/components/nav/NavItem.tsx | 26 ++++---- frontend/src/components/nav/Sidebar.tsx | 61 +++++++++++-------- frontend/src/lib/mock-user.ts | 6 +- frontend/src/types/roles.ts | 12 ++-- frontend/src/types/user.ts | 6 +- 19 files changed, 108 insertions(+), 95 deletions(-) diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 8bd0e39..c153a9b 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -1,5 +1,7 @@ + # This is NOT the Next.js you know This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index 05e726d..7fef4d3 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -1,6 +1,6 @@ -import { defineConfig, globalIgnores } from "eslint/config"; -import nextVitals from "eslint-config-next/core-web-vitals"; -import nextTs from "eslint-config-next/typescript"; +import { defineConfig, globalIgnores } from 'eslint/config' +import nextVitals from 'eslint-config-next/core-web-vitals' +import nextTs from 'eslint-config-next/typescript' const eslintConfig = defineConfig([ ...nextVitals, @@ -8,11 +8,11 @@ const eslintConfig = defineConfig([ // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: - ".next/**", - "out/**", - "build/**", - "next-env.d.ts", + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', ]), -]); +]) -export default eslintConfig; +export default eslintConfig diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 66e1566..b0d9a6a 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,8 +1,8 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next' const nextConfig: NextConfig = { /* config options here */ reactCompiler: true, -}; +} -export default nextConfig; +export default nextConfig diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs index 61e3684..ae85b2f 100644 --- a/frontend/postcss.config.mjs +++ b/frontend/postcss.config.mjs @@ -1,7 +1,7 @@ const config = { plugins: { - "@tailwindcss/postcss": {}, + '@tailwindcss/postcss': {}, }, -}; +} -export default config; +export default config diff --git a/frontend/src/app/(portal)/admin/builder/page.tsx b/frontend/src/app/(portal)/admin/builder/page.tsx index 769e70b..a537585 100644 --- a/frontend/src/app/(portal)/admin/builder/page.tsx +++ b/frontend/src/app/(portal)/admin/builder/page.tsx @@ -3,5 +3,5 @@ export default function BuilderPage() {

App Builder

- ); + ) } diff --git a/frontend/src/app/(portal)/admin/cycles/page.tsx b/frontend/src/app/(portal)/admin/cycles/page.tsx index ca084eb..702bf28 100644 --- a/frontend/src/app/(portal)/admin/cycles/page.tsx +++ b/frontend/src/app/(portal)/admin/cycles/page.tsx @@ -3,5 +3,5 @@ export default function CyclesPage() {

Cycles

- ); + ) } diff --git a/frontend/src/app/(portal)/admin/roles/page.tsx b/frontend/src/app/(portal)/admin/roles/page.tsx index 9fb33bb..1c4a3e1 100644 --- a/frontend/src/app/(portal)/admin/roles/page.tsx +++ b/frontend/src/app/(portal)/admin/roles/page.tsx @@ -3,5 +3,5 @@ export default function RolesPage() {

Roles

- ); + ) } diff --git a/frontend/src/app/(portal)/applicant/applications/page.tsx b/frontend/src/app/(portal)/applicant/applications/page.tsx index 713e545..143adc7 100644 --- a/frontend/src/app/(portal)/applicant/applications/page.tsx +++ b/frontend/src/app/(portal)/applicant/applications/page.tsx @@ -3,5 +3,5 @@ export default function ApplicationsPage() {

My Applications

- ); + ) } diff --git a/frontend/src/app/(portal)/layout.tsx b/frontend/src/app/(portal)/layout.tsx index a2708a9..d6c4384 100644 --- a/frontend/src/app/(portal)/layout.tsx +++ b/frontend/src/app/(portal)/layout.tsx @@ -1,14 +1,14 @@ -import Sidebar from "@/components/nav/Sidebar"; -import { mockUser } from "@/lib/mock-user"; -import { getRoles } from "@/types/roles"; +import Sidebar from '@/components/nav/Sidebar' +import { mockUser } from '@/lib/mock-user' +import { getRoles } from '@/types/roles' export default function PortalLayout({ children, }: { - children: React.ReactNode; + children: React.ReactNode }) { // TODO: replace mockUser with session user from auth - const roles = getRoles(mockUser); + const roles = getRoles(mockUser) return (
@@ -17,5 +17,5 @@ export default function PortalLayout({ {children}
- ); + ) } diff --git a/frontend/src/app/(portal)/reviewer/applicants/page.tsx b/frontend/src/app/(portal)/reviewer/applicants/page.tsx index 6905fed..b0d91ff 100644 --- a/frontend/src/app/(portal)/reviewer/applicants/page.tsx +++ b/frontend/src/app/(portal)/reviewer/applicants/page.tsx @@ -3,5 +3,5 @@ export default function ApplicantsPage() {

Applicants

- ); + ) } diff --git a/frontend/src/app/(portal)/reviewer/dashboard/page.tsx b/frontend/src/app/(portal)/reviewer/dashboard/page.tsx index f6722e0..7a594d1 100644 --- a/frontend/src/app/(portal)/reviewer/dashboard/page.tsx +++ b/frontend/src/app/(portal)/reviewer/dashboard/page.tsx @@ -3,5 +3,5 @@ export default function DashboardPage() {

Dashboard

- ); + ) } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index a7af88f..5628d86 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,4 +1,4 @@ -@import "tailwindcss"; +@import 'tailwindcss'; :root { --background: #ffffff; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 461a21d..03ed81d 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,33 +1,33 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import type { Metadata } from 'next' +import { Geist, Geist_Mono } from 'next/font/google' +import './globals.css' const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); + variable: '--font-geist-sans', + subsets: ['latin'], +}) const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); + variable: '--font-geist-mono', + subsets: ['latin'], +}) export const metadata: Metadata = { - title: "Generate Portal", - description: "Application portal for Generate NU", -}; + title: 'Generate Portal', + description: 'Application portal for Generate NU', +} export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - {children} + {children} - ); + ) } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 107f3e5..d65b2c3 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,5 @@ -import { redirect } from "next/navigation"; +import { redirect } from 'next/navigation' export default function RootPage() { - redirect("/reviewer/dashboard"); + redirect('/reviewer/dashboard') } diff --git a/frontend/src/components/nav/NavItem.tsx b/frontend/src/components/nav/NavItem.tsx index 60483d5..0b6652c 100644 --- a/frontend/src/components/nav/NavItem.tsx +++ b/frontend/src/components/nav/NavItem.tsx @@ -1,33 +1,33 @@ -"use client"; +'use client' -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import type { LucideIcon } from "lucide-react"; +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import type { LucideIcon } from 'lucide-react' interface NavItemProps { - href: string; - label: string; - icon: LucideIcon; + href: string + label: string + icon: LucideIcon } export default function NavItem({ href, label, icon: Icon }: NavItemProps) { - const pathname = usePathname(); - const isActive = pathname === href || pathname.startsWith(href + "/"); + const pathname = usePathname() + const isActive = pathname === href || pathname.startsWith(href + '/') return ( {label} - ); + ) } diff --git a/frontend/src/components/nav/Sidebar.tsx b/frontend/src/components/nav/Sidebar.tsx index dd8ce02..6601ca7 100644 --- a/frontend/src/components/nav/Sidebar.tsx +++ b/frontend/src/components/nav/Sidebar.tsx @@ -1,4 +1,4 @@ -"use client"; +'use client' import { LayoutDashboard, @@ -7,54 +7,65 @@ import { RefreshCw, Settings, Layers, -} from "lucide-react"; -import NavItem from "./NavItem"; -import type { Role } from "@/types/roles"; +} from 'lucide-react' +import NavItem from './NavItem' +import type { Role } from '@/types/roles' interface SidebarProps { - roles: Role[]; - nuid?: string; + roles: Role[] + nuid?: string } -type NavSection = { label: string; items: { href: string; label: string; icon: typeof FileText }[] }; +type NavSection = { + label: string + items: { href: string; label: string; icon: typeof FileText }[] +} const sectionsByRole: Record = { applicant: { - label: "Applications", + label: 'Applications', items: [ - { href: "/applicant/applications", label: "My Applications", icon: FileText }, + { + href: '/applicant/applications', + label: 'My Applications', + icon: FileText, + }, ], }, reviewer: { - label: "Review", + label: 'Review', items: [ - { href: "/reviewer/dashboard", label: "Dashboard", icon: LayoutDashboard }, - { href: "/reviewer/applicants", label: "Applicants", icon: Users }, + { + href: '/reviewer/dashboard', + label: 'Dashboard', + icon: LayoutDashboard, + }, + { href: '/reviewer/applicants', label: 'Applicants', icon: Users }, ], }, admin: { - label: "Admin", + label: 'Admin', items: [ - { href: "/admin/cycles", label: "Cycles", icon: RefreshCw }, - { href: "/admin/builder", label: "App Builder", icon: Layers }, - { href: "/admin/roles", label: "Roles", icon: Settings }, + { href: '/admin/cycles', label: 'Cycles', icon: RefreshCw }, + { href: '/admin/builder', label: 'App Builder', icon: Layers }, + { href: '/admin/roles', label: 'Roles', icon: Settings }, ], }, -}; +} // Display order: reviewer sections before applicant, admin last -const roleOrder: Role[] = ["reviewer", "applicant", "admin"]; +const roleOrder: Role[] = ['reviewer', 'applicant', 'admin'] export default function Sidebar({ roles, nuid }: SidebarProps) { const sections = roleOrder .filter((role) => roles.includes(role)) - .map((role) => sectionsByRole[role]); + .map((role) => sectionsByRole[role]) return ( - ); + ) } diff --git a/frontend/src/lib/mock-user.ts b/frontend/src/lib/mock-user.ts index 49607ae..71ea2fe 100644 --- a/frontend/src/lib/mock-user.ts +++ b/frontend/src/lib/mock-user.ts @@ -1,7 +1,7 @@ -import type { User } from "@/types/user"; +import type { User } from '@/types/user' export const mockUser: User = { - nuid: "002139999", + nuid: '002139999', is_reviewer: true, is_admin: false, -}; +} diff --git a/frontend/src/types/roles.ts b/frontend/src/types/roles.ts index 3f99ea2..e2fdf85 100644 --- a/frontend/src/types/roles.ts +++ b/frontend/src/types/roles.ts @@ -1,10 +1,10 @@ -import type { User } from "./user"; +import type { User } from './user' -export type Role = "applicant" | "reviewer" | "admin"; +export type Role = 'applicant' | 'reviewer' | 'admin' export function getRoles(user: User): Role[] { - const roles: Role[] = ["applicant"]; - if (user.is_reviewer) roles.push("reviewer"); - if (user.is_admin) roles.push("admin"); - return roles; + const roles: Role[] = ['applicant'] + if (user.is_reviewer) roles.push('reviewer') + if (user.is_admin) roles.push('admin') + return roles } diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index 6eca3df..a2ab816 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -1,5 +1,5 @@ export interface User { - nuid: string; - is_reviewer: boolean; - is_admin: boolean; + nuid: string + is_reviewer: boolean + is_admin: boolean } From 04e491db6c94fd992156de82ccdba892415573c6 Mon Sep 17 00:00:00 2001 From: Dao Ho <84757503+Dao-Ho@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:40:57 -0700 Subject: [PATCH 3/4] first + last name mocking --- frontend/src/app/(portal)/layout.tsx | 6 +++++- frontend/src/components/nav/Sidebar.tsx | 10 ++++++---- frontend/src/lib/mock-user.ts | 2 ++ frontend/src/types/user.ts | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/(portal)/layout.tsx b/frontend/src/app/(portal)/layout.tsx index d6c4384..a9e2148 100644 --- a/frontend/src/app/(portal)/layout.tsx +++ b/frontend/src/app/(portal)/layout.tsx @@ -12,7 +12,11 @@ export default function PortalLayout({ return (
- +
{children}
diff --git a/frontend/src/components/nav/Sidebar.tsx b/frontend/src/components/nav/Sidebar.tsx index 6601ca7..9738a36 100644 --- a/frontend/src/components/nav/Sidebar.tsx +++ b/frontend/src/components/nav/Sidebar.tsx @@ -13,7 +13,8 @@ import type { Role } from '@/types/roles' interface SidebarProps { roles: Role[] - nuid?: string + firstName?: string + lastName?: string } type NavSection = { @@ -56,7 +57,7 @@ const sectionsByRole: Record = { // Display order: reviewer sections before applicant, admin last const roleOrder: Role[] = ['reviewer', 'applicant', 'admin'] -export default function Sidebar({ roles, nuid }: SidebarProps) { +export default function Sidebar({ roles, firstName, lastName }: SidebarProps) { const sections = roleOrder .filter((role) => roles.includes(role)) .map((role) => sectionsByRole[role]) @@ -91,10 +92,11 @@ export default function Sidebar({ roles, nuid }: SidebarProps) {
- {nuid ? nuid.slice(-2) : 'NU'} + {firstName?.[0] ?? 'F'} + {lastName?.[0] ?? 'L'}
- {nuid ?? 'NUID'} + {firstName && lastName ? `${firstName} ${lastName}` : 'Unknown'}
diff --git a/frontend/src/lib/mock-user.ts b/frontend/src/lib/mock-user.ts index 71ea2fe..07d2353 100644 --- a/frontend/src/lib/mock-user.ts +++ b/frontend/src/lib/mock-user.ts @@ -2,6 +2,8 @@ import type { User } from '@/types/user' export const mockUser: User = { nuid: '002139999', + first_name: 'First', + last_name: 'Last', is_reviewer: true, is_admin: false, } diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index a2ab816..ca56993 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -1,5 +1,7 @@ export interface User { nuid: string + first_name: string + last_name: string is_reviewer: boolean is_admin: boolean } From 6289cff13d8659d8ac0f4f96e4598b908d2a44ae Mon Sep 17 00:00:00 2001 From: Dao Ho <84757503+Dao-Ho@users.noreply.github.com> Date: Sun, 14 Jun 2026 19:48:18 -0700 Subject: [PATCH 4/4] abstracted sidebar user into component --- frontend/src/components/nav/Sidebar.tsx | 36 +++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/nav/Sidebar.tsx b/frontend/src/components/nav/Sidebar.tsx index 9738a36..d5b981f 100644 --- a/frontend/src/components/nav/Sidebar.tsx +++ b/frontend/src/components/nav/Sidebar.tsx @@ -57,6 +57,28 @@ const sectionsByRole: Record = { // Display order: reviewer sections before applicant, admin last const roleOrder: Role[] = ['reviewer', 'applicant', 'admin'] +function SidebarUser({ + firstName, + lastName, +}: { + firstName: string + lastName: string +}) { + return ( +
+
+
+ {firstName[0]} + {lastName[0]} +
+ + {firstName} {lastName} + +
+
+ ) +} + export default function Sidebar({ roles, firstName, lastName }: SidebarProps) { const sections = roleOrder .filter((role) => roles.includes(role)) @@ -89,17 +111,9 @@ export default function Sidebar({ roles, firstName, lastName }: SidebarProps) { {/* User */} -
-
-
- {firstName?.[0] ?? 'F'} - {lastName?.[0] ?? 'L'} -
- - {firstName && lastName ? `${firstName} ${lastName}` : 'Unknown'} - -
-
+ {firstName && lastName && ( + + )} ) }