Skip to content

[STU-189] Add shadcn/ui Table component and migrate DataTable#42

Open
BAWES wants to merge 3 commits into
mainfrom
feature/STU-189-shadcn-table-component
Open

[STU-189] Add shadcn/ui Table component and migrate DataTable#42
BAWES wants to merge 3 commits into
mainfrom
feature/STU-189-shadcn-table-component

Conversation

@BAWES
Copy link
Copy Markdown
Owner

@BAWES BAWES commented May 21, 2026

Summary

  • Adds the shadcn/ui Table component (Table, TableHeader, TableBody, TableRow, TableHead, TableCell, TableFooter, TableCaption)
  • Migrates the shared DataTable component to use shadcn/ui Table structural components instead of raw HTML elements
  • Unblocks STU-185 (page migration to shadcn/ui) by providing the table components needed across all role pages

Test plan

  • TypeScript check passes (npx tsc --noEmit)
  • ESLint passes (npm run lint)
  • Visual: verify admin companies table renders correctly
  • Visual: verify all DataTable consumers render without regressions (admin, candidate, inspector, staff pages)

🤖 Generated with Claude Code

Co-Authored-By: Paperclip noreply@paperclip.ing

Summary by CodeRabbit

  • Removed Features

    • Removed role-specific login URL paths.
  • UI/UX Updates

    • Redesigned landing page with improved visual layout and components.
    • Refactored login flow to focus on error handling and account detection.
    • Updated account selection interface with enhanced styling and accessibility.
    • Improved form components and overall page responsiveness.

Review Change Stack

BAWES and others added 3 commits May 22, 2026 04:52
…[STU-163]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ar login spec [STU-177]

Replace raw <label> elements with the shadcn/ui Label component for consistent
design token usage and accessibility (htmlFor/id pairing).

Tests: none (component swap, verified with tsc + lint + build)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces raw HTML table elements in the shared DataTable component with
shadcn/ui Table, TableHeader, TableBody, TableRow, TableHead, and TableCell.
This unblocks page migration work in STU-185 by providing the structural
table components needed for role pages.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
studenthub-next Error Error May 21, 2026 10:20pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Walkthrough

This PR refactors the authentication and public-facing UI layers from class-based CSS and role-driven logic to a component-driven architecture. It removes role-based login routing, introduces new UI primitives for forms and tables, simplifies the login flow to error-driven presentation, and migrates both the landing and login pages to use reusable Tailwind-styled components with Lucide React icons.

Changes

UI Component Migration and Login Flow Simplification

Layer / File(s) Summary
New UI component library: Label and Table
src/components/ui/label.tsx, src/components/ui/table.tsx
Introduces Label form component and eight table UI primitives (Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption). Each component merges Tailwind classes via cn, forwards element props, and adds data-slot attributes for styling consistency.
Login form and account chooser componentization
src/modules/auth/LoginForm.tsx
LoginForm removes the hint prop, refactors from class-based markup to Tailwind grid layout using Button, Input, Label components and a LogIn icon. VerifiedAccountChooser switches from plain <button> elements to styled Button components with variant="outline" and improved account display (name and email in nested text containers).
Login page simplification: error-driven flow and account notes
src/app/login/page.tsx
Removes role-based intent routing and role-to-hint mapping; searchParams now only accepts optional error. Replaces hardcoded account-detection articles with a roleNotes array rendered via map, wiring Lucide icons and label/detail pairs. Page structure switches to responsive grid-based layout with updated error message styling.
Landing page componentization: icons, cards, and responsive layout
src/app/page.tsx
Portal icons change from emoji-string map to typed Lucide React icon components. Navigation links now use Button components with asChild. Hero section rebuilt with Tailwind structure and updated decorative markup. Portal grid items wrapped in Card/CardContent with icon components, typography, and hover/transition effects. Benefits section uses component-based layout.
DataTable migration to UI component primitives
src/modules/workspace/DataTable.tsx
Replaces raw HTML table elements with Table, TableHeader, TableBody, TableRow, TableHead, TableCell components, preserving conditional logic for action columns and empty-state rendering with correct colSpan calculation.
CSS cleanup: remove class-based styling and adjust component-driven sizing
src/app/styles.css
Removes ~929 lines of class-based CSS for landing/portal/login layout (.landingShell, .landingNav, .loginPanel, .loginStack, .loginForm, .loginMasthead, etc.). Updates button/action component sizing to 44px minimum height. Removes responsive overrides across multiple media-query breakpoints (max-width 680px, 760px, 1040px, and mid-range 760px–1040px).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • BAWES/studenthub-codex#36: Modifies landing and portal rendering in src/app/page.tsx, including portal icon mapping and hero/benefits markup changes.
  • BAWES/studenthub-codex#4: Adds Tailwind v4 and PostCSS token mappings to src/app/styles.css, directly related to the stylesheet restructuring in this PR.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers Summary, Changes, and Test Plan sections with completed checks; however, Type checkboxes and most Checklist items are not marked, making compliance unclear. Mark the 'Refactor' and/or 'Design system / UI' Type checkbox and complete all applicable Checklist items to clarify compliance with repository standards.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the main changes: adding shadcn/ui Table component and migrating DataTable to use it, which aligns with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/STU-189-shadcn-table-component

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/styles.css

Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): "decorators", "decorators-legacy". (1:0)


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/styles.css (1)

341-356: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove dangling selector commas in the portal chooser rules

Those trailing commas merge selector lists into the following rule, so declarations intended for the hero container also apply to child elements (and even .row in the mobile block). Specifically:

  • 341-356: .portalChooserHero h1, and .portalChooserHero p:not(.eyebrow), get merged into .portalChooserHero { ... }.
  • 5989-5998: .portalChooserHero h1, gets merged into .row { ... }.
  • 6223-6249: .portalChooserPromise span, .portalChooserHero h1, and .portalChooserPromise/span get merged into .portalChooserHero { padding-top: ... }.
Proposed cleanup
-.portalChooserHero h1,
-.portalChooserHero p:not(.eyebrow),
 .portalChooserHero {
   max-width: 820px;
   padding: 42px 0 12px;
 }
-.portalChooserPromise span,
-.portalChooserHero h1,
-.portalChooserPromise,
-.portalChooserPromise span,
 .portalChooserHero {
   max-width: 900px;
   padding-top: clamp(38px, 8vw, 92px);
 }
-  .portalChooserHero h1,
-
   .row {
     grid-template-columns: 1fr;
     min-height: 0;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/styles.css` around lines 341 - 356, There are dangling trailing
commas after selector lines like ".portalChooserHero h1," and
".portalChooserHero p:not(.eyebrow)," which cause those selectors to merge into
the next rule; remove the trailing commas so each selector list is complete
(e.g., change ".portalChooserHero h1," to ".portalChooserHero h1" or remove the
standalone selector lines entirely) and fix the same pattern for the other
occurrences referenced (the later ".portalChooserHero h1," in the .row rule and
the ".portalChooserPromise span", ".portalChooserHero h1",
".portalChooserPromise"/"span" group) so that declarations intended for
".portalChooserHero" or ".portalChooserPromise" only apply to those selectors
and not downstream rules.
🧹 Nitpick comments (2)
src/components/ui/table.tsx (1)

7-105: ⚡ Quick win

Consider forwarding refs for all table components.

None of the table components forward the ref prop. In React 19, ref is a regular prop that can be destructured and passed through. Consumers might need programmatic access to table DOM nodes for scroll management, focus handling, intersection observers, or measurements.

♻️ Example fix for Table component (apply pattern to all)
-function Table({ className, ...props }: React.ComponentProps<"table">) {
+function Table({ className, ref, ...props }: React.ComponentProps<"table">) {
   return (
     <div
       data-slot="table-container"
       className="relative w-full overflow-x-auto"
     >
       <table
+        ref={ref}
         data-slot="table"
         className={cn("w-full caption-bottom text-sm", className)}
         {...props}
       />
     </div>
   )
 }

Apply the same pattern to TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, and TableCaption by destructuring ref and passing it to the corresponding HTML element.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/table.tsx` around lines 7 - 105, All table components
(Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell,
TableCaption) currently ignore the ref prop; update each component to accept and
forward the ref (destructure ref from props and pass it to the underlying HTML
element) using the appropriate HTML element ref type (e.g.,
React.Ref<HTMLTableElement> for Table, HTMLTableSectionElement for
thead/tbody/tfoot, HTMLTableRowElement for tr, HTMLTableCellElement for th/td,
HTMLTableCaptionElement for caption) so consumers can access DOM nodes
programmatically.
src/components/ui/label.tsx (1)

4-14: ⚡ Quick win

Consider forwarding refs for programmatic access.

The Label component doesn't forward the ref prop. In React 19, ref is a regular prop that can be destructured and passed through. If consumers need to programmatically focus or measure the label element, they currently cannot access the DOM node.

♻️ Proposed fix to forward refs
-function Label({ className, ...props }: React.LabelHTMLAttributes<HTMLLabelElement>) {
+function Label({ className, ref, ...props }: React.LabelHTMLAttributes<HTMLLabelElement>) {
   return (
     <label
+      ref={ref}
       className={cn(
         "text-[13px] font-bold text-muted-foreground leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
         className
       )}
       {...props}
     />
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/label.tsx` around lines 4 - 14, The Label component
currently drops the ref so consumers can't access the DOM node; update the
component to forward the ref by accepting a ref prop (e.g., ref?:
React.Ref<HTMLLabelElement>) alongside the existing props or by using
React.forwardRef, and pass that ref through to the <label> element so
programmatic focus/measure works; modify the Label function signature and ensure
the ref is forwarded to the rendered label element (reference symbol: Label).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/table.tsx`:
- Line 1: Remove the top-level "use client" directive from this module so the
presentational table components (the Table-related components exported from this
file) can be used in Server Components; ensure there are no client-only features
(hooks, state, event handlers, or browser APIs) in any exported components in
this file and run the build/tests after removal to confirm no client-side
assumptions remain.

In `@src/modules/auth/LoginForm.tsx`:
- Line 5: Update the two relative internal imports in LoginForm.tsx to use the
project path alias (replace the "./" style imports); specifically change the
import source for chooseAccountAction and loginAction (currently imported from
"./actions") to use the "`@/`..." alias form, and likewise update the other
relative import referenced on line 9 to the equivalent "`@/`..." alias so all
internal TypeScript imports follow the project's alias convention.

In `@src/modules/workspace/DataTable.tsx`:
- Line 69: The TableCell rendering sets colSpan to columns.length + (rowHref ? 1
: 0) which can evaluate to 0 when columns is empty and rowHref is false; update
the colSpan calculation in the DataTable component (the TableCell at the shown
location) to ensure it is at least 1 (e.g., use Math.max(1, ... ) or conditional
fallback) so the TableCell never receives a zero span.

---

Outside diff comments:
In `@src/app/styles.css`:
- Around line 341-356: There are dangling trailing commas after selector lines
like ".portalChooserHero h1," and ".portalChooserHero p:not(.eyebrow)," which
cause those selectors to merge into the next rule; remove the trailing commas so
each selector list is complete (e.g., change ".portalChooserHero h1," to
".portalChooserHero h1" or remove the standalone selector lines entirely) and
fix the same pattern for the other occurrences referenced (the later
".portalChooserHero h1," in the .row rule and the ".portalChooserPromise span",
".portalChooserHero h1", ".portalChooserPromise"/"span" group) so that
declarations intended for ".portalChooserHero" or ".portalChooserPromise" only
apply to those selectors and not downstream rules.

---

Nitpick comments:
In `@src/components/ui/label.tsx`:
- Around line 4-14: The Label component currently drops the ref so consumers
can't access the DOM node; update the component to forward the ref by accepting
a ref prop (e.g., ref?: React.Ref<HTMLLabelElement>) alongside the existing
props or by using React.forwardRef, and pass that ref through to the <label>
element so programmatic focus/measure works; modify the Label function signature
and ensure the ref is forwarded to the rendered label element (reference symbol:
Label).

In `@src/components/ui/table.tsx`:
- Around line 7-105: All table components (Table, TableHeader, TableBody,
TableFooter, TableRow, TableHead, TableCell, TableCaption) currently ignore the
ref prop; update each component to accept and forward the ref (destructure ref
from props and pass it to the underlying HTML element) using the appropriate
HTML element ref type (e.g., React.Ref<HTMLTableElement> for Table,
HTMLTableSectionElement for thead/tbody/tfoot, HTMLTableRowElement for tr,
HTMLTableCellElement for th/td, HTMLTableCaptionElement for caption) so
consumers can access DOM nodes programmatically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c674adf9-631c-435c-8205-420fbd67a426

📥 Commits

Reviewing files that changed from the base of the PR and between ab0895d and 25717c4.

📒 Files selected for processing (8)
  • src/app/login/[role]/page.tsx
  • src/app/login/page.tsx
  • src/app/page.tsx
  • src/app/styles.css
  • src/components/ui/label.tsx
  • src/components/ui/table.tsx
  • src/modules/auth/LoginForm.tsx
  • src/modules/workspace/DataTable.tsx
💤 Files with no reviewable changes (1)
  • src/app/login/[role]/page.tsx

@@ -0,0 +1,116 @@
"use client"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Remove unnecessary "use client" directive.

These table components are pure presentational wrappers with no client-side features (hooks, state, browser APIs, or event handlers). The "use client" directive forces all consumers to cross the Server/Client boundary, preventing these primitives from being used in Server Components and increasing the JavaScript bundle size unnecessarily.

♻️ Proposed fix
-"use client"
-
 import * as React from "react"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"use client"
import * as React from "react"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/table.tsx` at line 1, Remove the top-level "use client"
directive from this module so the presentational table components (the
Table-related components exported from this file) can be used in Server
Components; ensure there are no client-only features (hooks, state, event
handlers, or browser APIs) in any exported components in this file and run the
build/tests after removal to confirm no client-side assumptions remain.


import { useActionState } from "react";
import { LogIn } from "lucide-react";
import { chooseAccountAction, loginAction } from "./actions";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Switch relative internal imports to @/ aliases.

Lines 5 and 9 still use relative internal imports; this should be normalized to the project alias convention.

♻️ Proposed fix
-import { chooseAccountAction, loginAction } from "./actions";
+import { chooseAccountAction, loginAction } from "`@/modules/auth/actions`";
 ...
-import type { LoginAccountChoice } from "./types";
+import type { LoginAccountChoice } from "`@/modules/auth/types`";

As per coding guidelines, "Use @/ path alias for all internal imports in TypeScript files".

Also applies to: 9-9

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/auth/LoginForm.tsx` at line 5, Update the two relative internal
imports in LoginForm.tsx to use the project path alias (replace the "./" style
imports); specifically change the import source for chooseAccountAction and
loginAction (currently imported from "./actions") to use the "`@/`..." alias form,
and likewise update the other relative import referenced on line 9 to the
equivalent "`@/`..." alias so all internal TypeScript imports follow the project's
alias convention.

<tr className="emptyTableRow">
<td colSpan={columns.length + (rowHref ? 1 : 0)}>
<TableRow className="emptyTableRow">
<TableCell colSpan={columns.length + (rowHref ? 1 : 0)}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard colSpan against zero columns.

colSpan can become 0 when columns is empty and rowHref is not provided, which is invalid for a table cell span.

Proposed fix
-                <TableCell colSpan={columns.length + (rowHref ? 1 : 0)}>
+                <TableCell colSpan={Math.max(1, columns.length + (rowHref ? 1 : 0))}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TableCell colSpan={columns.length + (rowHref ? 1 : 0)}>
<TableCell colSpan={Math.max(1, columns.length + (rowHref ? 1 : 0))}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/workspace/DataTable.tsx` at line 69, The TableCell rendering sets
colSpan to columns.length + (rowHref ? 1 : 0) which can evaluate to 0 when
columns is empty and rowHref is false; update the colSpan calculation in the
DataTable component (the TableCell at the shown location) to ensure it is at
least 1 (e.g., use Math.max(1, ... ) or conditional fallback) so the TableCell
never receives a zero span.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant