Skip to content

feat: complete frontend build#2

Merged
onerandomdevv merged 4 commits into
devfrom
feature/frontend
May 3, 2026
Merged

feat: complete frontend build#2
onerandomdevv merged 4 commits into
devfrom
feature/frontend

Conversation

@onerandomdevv
Copy link
Copy Markdown
Contributor

@onerandomdevv onerandomdevv commented May 3, 2026

  • Root layout, fonts, and global styles
  • UI primitives: Button, Badge, Input, Textarea, Card
  • Navbar and Footer with brand assets
  • Home page: Hero, Products, Latest Releases, Hackathon strip
  • About page
  • Products list and individual product pages
  • Blog/Updates list and individual post pages
  • Team page
  • Careers page with application form
  • Contact page with contact form
  • 404 page
  • Admin dashboard: all CRUD pages, TipTap editor, image upload

What does this PR do?

Type of change

  • Feature
  • Bug fix
  • Config / setup
  • Refactor
  • Docs

Checklist

  • I have read AGENTS.md
  • pnpm build passes locally with no errors
  • No TypeScript errors (pnpm tsc --noEmit)
  • No hardcoded secrets or API keys
  • All new API routes check for admin session before executing
  • No UI libraries were installed
  • Fonts are loaded via next/font/google only
  • pnpm was used (not npm or yarn)

Screenshots (if UI changes)

Notes for reviewer

Summary by CodeRabbit

  • New Features
    • Complete product catalog with individual product pages and details
    • Blog section ("Updates") for published articles with category filtering
    • Team member directory with bios and social profiles
    • Career page with job listings and application forms
    • Contact form with social media links and location information
    • Company information page with facts and achievements
    • Admin dashboard for managing content

- Root layout, fonts, and global styles
- UI primitives: Button, Badge, Input, Textarea, Card
- Navbar and Footer with brand assets
- Home page: Hero, Products, Latest Releases, Hackathon strip
- About page
- Products list and individual product pages
- Blog/Updates list and individual post pages
- Team page
- Careers page with application form
- Contact page with contact form
- 404 page
- Admin dashboard: all CRUD pages, TipTap editor, image upload
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

@onerandomdevv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 50 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf17be1d-9fe8-4c2a-a9f7-86e4896024d3

📥 Commits

Reviewing files that changed from the base of the PR and between 97e6e5b and efa94c7.

📒 Files selected for processing (24)
  • src/app/(public)/blog/[slug]/page.tsx
  • src/app/(public)/products/[slug]/page.tsx
  • src/app/admin/applications/page.tsx
  • src/app/admin/blog/[id]/page.tsx
  • src/app/admin/blog/page.tsx
  • src/app/admin/careers/[id]/page.tsx
  • src/app/admin/careers/page.tsx
  • src/app/admin/dashboard/page.tsx
  • src/app/admin/layout.tsx
  • src/app/admin/messages/page.tsx
  • src/app/admin/products/[id]/page.tsx
  • src/app/admin/products/page.tsx
  • src/app/admin/team/[id]/page.tsx
  • src/app/admin/team/page.tsx
  • src/components/admin/ApplicationsManager.tsx
  • src/components/admin/MessagesManager.tsx
  • src/components/blog/UpdatesList.tsx
  • src/components/contact/ContactForm.tsx
  • src/components/layout/AdminSidebar.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Navbar.tsx
  • src/components/ui/Input.tsx
  • src/components/ui/Textarea.tsx
  • src/lib/admin-auth.ts
📝 Walkthrough

Walkthrough

This PR implements a complete website and admin panel for CodedDevs, a technology company. It introduces public-facing pages for home, products, blog, team, careers, and contact alongside an admin dashboard for managing content and applications. The implementation includes documentation updates defining design direction, database schema, API routes, and component specifications.

Changes

Website & Admin Platform Implementation

Layer / File(s) Summary
Documentation & Specification
AGENTS.md, CLAUDE.md
Design direction rules (editorial tone, no animations/gradients/dark mode), folder structure, database schema definitions, environment variables, and admin API endpoint specifications.
Public Home & Featured Data
src/app/(public)/page.tsx
Homepage fetches featured products and latest published blog posts; renders hero section, featured products grid, latest releases section, and hackathon recognition strip.
Blog Implementation
src/app/(public)/blog/page.tsx, src/app/(public)/blog/[slug]/page.tsx, src/components/blog/*
Blog listing fetches all published posts; dynamic blog post page with static generation, metadata generation, and TipTap-based rich text rendering via new PostContent component.
Products Catalog
src/app/(public)/products/page.tsx, src/app/(public)/products/[slug]/page.tsx, src/components/sections/ProductsSection.tsx
Products listing fetches and displays featured products; dynamic product pages with static generation, external links, cover images, and status badges.
Careers & Applications
src/app/(public)/careers/page.tsx, src/components/careers/ApplicationForm.tsx
Careers page fetches open roles; each role includes an expandable application form that submits to /api/careers/apply.
Informational Pages
src/app/(public)/about/page.tsx, src/app/(public)/contact/page.tsx, src/app/(public)/team/page.tsx, src/components/contact/ContactForm.tsx
About page displays company facts; contact page includes contact form and social links; team page fetches and displays team members with optional photo, role, bio, and social links.
Public Layout & Navigation
src/app/(public)/layout.tsx, src/components/layout/Navbar.tsx, src/components/layout/Footer.tsx, src/app/not-found.tsx
Public layout wraps pages with navbar and footer; navbar includes responsive mobile menu and active link highlighting; footer includes company/product links and social icons; 404 page with branded logo and return link.
Admin Pages & Dashboard
src/app/admin/dashboard/page.tsx, src/app/admin/blog/*, src/app/admin/products/*, src/app/admin/team/*, src/app/admin/careers/*, src/app/admin/applications/page.tsx, src/app/admin/messages/page.tsx
Admin dashboard displays key metrics (team count, products, posts, open careers, unread messages, pending applications) and lists recent unread messages and pending applications; individual CRUD pages for managing blog posts, products, team members, and careers.
Admin Layout & Sidebar
src/app/admin/layout.tsx, src/components/layout/AdminSidebar.tsx
Admin layout includes fixed left sidebar with navigation items and sign-out button; main content area offset by sidebar width; sidebar uses active route detection for styling.
Forms & Data Management
src/components/admin/ResourceForms.tsx, src/components/admin/ApplicationsManager.tsx, src/components/admin/MessagesManager.tsx, src/components/admin/RichTextEditor.tsx, src/components/admin/ImageUpload.tsx
Unified resource forms for team members, products, blog posts, and careers with shared submission logic; applications manager with status filtering and updates; messages manager with mark-read and delete actions; TipTap-based rich text editor with image upload support; image upload control for cover images.
Shared UI Components
src/components/admin/AdminDeleteButton.tsx, src/components/ui/Button.tsx, src/components/ui/Input.tsx, src/components/ui/Textarea.tsx, src/components/ui/Card.tsx, src/components/ui/Badge.tsx
Delete button with confirmation; button with loading/disabled states and asChild support for link composition; input with label and error display; textarea with validation error support; card container with optional header/footer; badge component with status variants.
Section Components
src/components/sections/HeroSection.tsx, src/components/sections/LatestReleasesSection.tsx, src/components/sections/HackathonStrip.tsx
Hero section with headline and call-to-action buttons; latest releases section displaying recent blog posts with category-specific CTAs; hackathon recognition strip.
Styling & Configuration
src/app/globals.css, tailwind.config.ts, src/app/layout.tsx
Global CSS imports Tailwind and sets base body/heading fonts; Tailwind config extends with semantic color tokens (bg, surface, border, primary, etc.) backed by CSS variables and includes generic font family fallbacks; metadata updated with favicon.

Sequence Diagram(s)

sequenceDiagram
    participant User as User<br/>(Browser)
    participant PublicPage as Public Page<br/>(Next.js)
    participant DB as Database
    participant AdminPage as Admin Page<br/>(Next.js)

    rect rgba(0, 150, 200, 0.5)
    Note over User,DB: Public Content Flow
    User->>PublicPage: Request /products
    PublicPage->>DB: SELECT * FROM products<br/>ORDER BY order_index
    DB-->>PublicPage: Product rows
    PublicPage-->>User: Rendered products grid<br/>with cards & links
    
    User->>PublicPage: Click product link
    PublicPage->>DB: SELECT * FROM products<br/>WHERE slug = ?
    DB-->>PublicPage: Product details
    PublicPage-->>User: Rendered product page<br/>with images & description
    end

    rect rgba(150, 0, 200, 0.5)
    Note over User,DB: Admin Management Flow
    User->>AdminPage: Request /admin/products
    AdminPage->>DB: SELECT * FROM products<br/>ORDER BY order_index
    DB-->>AdminPage: Product rows
    AdminPage-->>User: Rendered products table<br/>with edit/delete actions
    
    User->>AdminPage: Click edit product
    AdminPage->>DB: SELECT * FROM products<br/>WHERE id = ?
    DB-->>AdminPage: Product record
    AdminPage-->>User: Rendered product form<br/>with current values
    
    User->>AdminPage: Submit form changes
    AdminPage->>DB: UPDATE products<br/>SET name = ?, ...
    DB-->>AdminPage: Success/Error response
    AdminPage-->>User: Refresh page & show<br/>success/error message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Feature/backend setup #1: Introduces backend infrastructure (database schema, API route handlers, middleware) that this PR builds upon for public page rendering and admin CRUD operations.

Poem

🐰 A website blooms, with pages and forms,
Admin dashboards tame the data storms,
Rich text editors and teams on display,
CodedDevs shines bright—hip-hop, hooray!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/frontend

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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (7)
src/app/admin/careers/page.tsx-45-50 (1)

45-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the actions cell as a table cell.

Putting flex directly on the <td> changes its display behavior and can break table sizing/alignment. Wrap the buttons in an inner flex container instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/careers/page.tsx` around lines 45 - 50, The actions cell
currently applies layout classes directly to the <td>, which alters table
rendering; remove layout classes like "flex gap-2" from the <td> and keep only
table-related classes (e.g., "px-4 py-3"), then wrap the Button, Link and
AdminDeleteButton elements in an inner container (e.g., a <div> with "flex
gap-2") to handle spacing and alignment; update the element containing Button,
Link and AdminDeleteButton so the <td> remains a table cell and the inner div
provides the flex layout.
src/app/admin/blog/page.tsx-57-62 (1)

57-62: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the actions cell as a table cell.

Putting flex directly on the <td> changes its display behavior and can break table sizing/alignment. Wrap the buttons in an inner flex container instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/blog/page.tsx` around lines 57 - 62, The actions cell currently
applies "flex" to the <td> which alters table layout; remove the flex from the
<td> (keep padding classes like "px-4 py-3") and wrap the Button and
AdminDeleteButton in an inner element with the flex layout (e.g., add a <div
className="flex gap-2"> around <Button asChild>... and <AdminDeleteButton
endpoint=...>), keeping existing components and Link usage intact (refer to the
<td>, <Button asChild>, <Link href={`/admin/blog/${post.id}`}>Edit</Link>, and
<AdminDeleteButton endpoint={`/api/admin/blog/${post.id}`} />).
src/app/admin/products/page.tsx-45-50 (1)

45-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the actions cell as a table cell.

Putting flex directly on the <td> changes its display behavior and can break table sizing/alignment. Wrap the buttons in an inner flex container instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/products/page.tsx` around lines 45 - 50, The actions cell
currently applies "flex" directly to the <td>, which alters table layout; remove
the flex/gap classes from the <td> and instead add an inner wrapper (e.g., a
<div> with className="flex gap-2 px-0 py-0" or similar) that contains the
<Button asChild> (Link to `/admin/products/${product.id}`) and
<AdminDeleteButton endpoint={`/api/admin/products/${product.id}`} /> so the <td>
remains a proper table cell while the buttons retain their horizontal spacing
and styles.
src/components/ui/Input.tsx-16-45 (1)

16-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Generate a stable id for labeled/error inputs.

Relying on props.name leaves htmlFor, aria-describedby, and the error <p> id broken when callers pass neither id nor name. This is a small but real accessibility regression on a shared form primitive.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Input.tsx` around lines 16 - 45, The component currently
sets inputId = id ?? props.name which breaks labeling/aria when both are absent;
generate a stable fallback id inside the Input component (e.g., const
generatedId = useId() or useRef(generateUnique()) on first render) and then set
inputId = id ?? props.name ?? generatedId; update all uses (label htmlFor, input
id, aria-describedby, and error <p> id) to rely on this inputId so labeled/error
inputs remain accessible even if callers omit id and name.
src/components/ui/Textarea.tsx-16-33 (1)

16-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure a stable fallback ID for label/error associations.

If neither id nor name is provided, the label and error text lose semantic linkage. Add a generated fallback id (e.g., useId) so htmlFor/aria-describedby always point to a real element.

Suggested patch
-import type { TextareaHTMLAttributes } from "react";
+import { useId, type TextareaHTMLAttributes } from "react";
@@
 export default function Textarea({
@@
 }: TextareaProps) {
-  const textareaId = id ?? props.name;
+  const generatedId = useId();
+  const textareaId = id ?? props.name ?? generatedId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/Textarea.tsx` around lines 16 - 33, The Textarea component
can lose label/error associations when both id and props.name are missing;
generate a stable fallback id by importing and calling React's useId and setting
textareaId = id ?? props.name ?? useId(), then use that textareaId for htmlFor,
id, and aria-describedby (and the error element id like `${textareaId}-error`)
so label and aria attributes always point to a real element; update imports to
include useId and ensure textareaId is computed once at the top of the
component.
src/app/(public)/products/[slug]/page.tsx-155-157 (1)

155-157: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Paragraph key can collide when description repeats text.

Using the paragraph string as key can duplicate keys and cause unstable rendering in edge cases.

Suggested patch
-            {product.description.split(/\n{2,}/).map((paragraph) => (
-              <p key={paragraph}>{paragraph}</p>
+            {product.description.split(/\n{2,}/).map((paragraph, index) => (
+              <p key={`${index}-${paragraph.slice(0, 24)}`}>{paragraph}</p>
             ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(public)/products/[slug]/page.tsx around lines 155 - 157, The map
over product.description uses the paragraph string as the React key which can
collide for repeated paragraphs; change the key to a stable unique identifier by
using the map index or a derived index-based key (e.g., use map((paragraph, idx)
=> ...) and set key={`paragraph-${idx}`}) so each <p> rendered in the
product.description.split(...) mapping has a unique, stable key instead of the
raw paragraph text.
src/components/contact/ContactForm.tsx-107-133 (1)

107-133: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard response.json() so non-JSON responses don't throw.

If /api/contact returns an empty body or HTML error page, the current parse step bypasses the !response.ok handling and drops into the generic catch-all path.

🛠 Suggested fix
-      const result: unknown = await response.json();
+      let result: unknown = null;
+      try {
+        result = await response.json();
+      } catch {
+        result = null;
+      }

       if (!response.ok) {
         setSubmitError(getErrorMessage(result));
         setSubmitState("idle");
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/contact/ContactForm.tsx` around lines 107 - 133, The call to
response.json() can throw on empty or non-JSON responses; update the fetch
handling in ContactForm so you first check Content-Type
(response.headers.get("content-type") includes "application/json") or wrap
response.json() in a try/catch/conditional to safely parse only when JSON is
present — if not JSON, fall back to using response.statusText or await
response.text() as the error payload; then use that payload with
getErrorMessage(result) and setSubmitError/setSubmitState as before and ensure
parsing errors don't bypass the !response.ok branch.
🧹 Nitpick comments (4)
src/components/layout/AdminSidebar.tsx (1)

78-78: ⚡ Quick win

Use “Updates” as the visible label for blog navigation.

The route can remain /admin/blog, but the displayed label should follow the project-wide naming rule.

♻️ Suggested change
-  { href: "/admin/blog", label: "Blog", icon: <DocumentIcon /> },
+  { href: "/admin/blog", label: "Updates", icon: <DocumentIcon /> },

As per coding guidelines "{src/app/(public)/blog/**/*.tsx,src/components/**/*.tsx}: Blog routes use /blog URL internally but display as 'Updates' to users".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/AdminSidebar.tsx` at line 78, In the AdminSidebar
component update the navigation entry that currently reads { href:
"/admin/blog", label: "Blog", icon: <DocumentIcon /> } so the visible label is
"Updates" while keeping the href as "/admin/blog"; locate the nav items array
(inside AdminSidebar) and change the label value for the object with href
"/admin/blog" to "Updates" to conform to the project-wide naming rule.
src/components/layout/Navbar.tsx (1)

40-40: ⚡ Quick win

Align spacing utilities with the allowed spacing scale.

gap-7 and gap-1.5 are outside the approved spacing list for component files.

♻️ Suggested change
-        <nav className="hidden items-center gap-7 md:flex" aria-label="Primary">
+        <nav className="hidden items-center gap-6 md:flex" aria-label="Primary">
...
-          <span className="flex flex-col gap-1.5" aria-hidden="true">
+          <span className="flex flex-col gap-2" aria-hidden="true">

As per coding guidelines "src/components/**/*.tsx: Use Tailwind spacing units (4, 8, 12, 16, 20, 24, 32, 48, 64, 96) for all padding and margin".

Also applies to: 71-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/Navbar.tsx` at line 40, The nav spacing utilities
(gap-7 and gap-1.5) in the Navbar component violate the approved Tailwind
spacing scale; update the className(s) in Navbar.tsx (the <nav ...> and the
other occurrence around line 71) to use nearest allowed spacing tokens (replace
gap-7 with gap-6 or gap-8 and replace gap-1.5 with gap-2 or gap-4) so all
padding/margin/gap utilities conform to the approved list for
src/components/**/*.tsx.
src/components/layout/Footer.tsx (1)

97-117: ⚡ Quick win

Use the heading font for the footer section titles.

These h2 elements are styled with font-sans, which conflicts with the typography rule for headings. As per coding guidelines, "Use JetBrains Mono for headings (H1-H4) and IBM Plex Sans for body text per typography table."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/Footer.tsx` around lines 97 - 117, The two footer
section headings in Footer (the <h2> elements for "Company" and "Products") are
using the body font class "font-sans"; replace that class with the project's
heading-font utility for H1–H4 (the JetBrains Mono heading font used across the
app) so headings follow the typography rule — update the className on both <h2>
elements (currently containing "font-sans text-sm font-medium text-[`#121F38`]")
to use the heading font utility instead while keeping the rest of the classes
intact.
src/components/blog/UpdatesList.tsx (1)

75-75: ⚡ Quick win

Use an allowed spacing token for vertical rhythm consistency.

space-y-10 is outside the approved spacing set. Switch to an allowed token (for example space-y-8 or space-y-12) to stay consistent with the design system.

🎯 Minimal diff
-    <div className="space-y-10">
+    <div className="space-y-8">

As per coding guidelines "src/components/**/*.tsx: Use Tailwind spacing units (4, 8, 12, 16, 20, 24, 32, 48, 64, 96) for all padding and margin."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/blog/UpdatesList.tsx` at line 75, The div in the UpdatesList
component uses a non-approved spacing token "space-y-10"; replace that token
with an approved Tailwind spacing token such as "space-y-8" or "space-y-12" in
the className on the element in UpdatesList.tsx (look for the div with
className="space-y-10") to conform to the design-system spacing set
(4,8,12,16,20,24,32,48,64,96).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(public)/products/page.tsx:
- Around line 77-82: The primary product CTA currently uses an internal Link to
`/products/${product.slug}`; change it to open the product's external URL in a
new tab (use an anchor or Link configured with href={product.externalUrl} plus
target="_blank" and rel="noopener noreferrer") and, if product.externalUrl is
missing/falsey, hide the CTA entirely; update the component(s) referenced (the
Link usage in page.tsx and the ProductsSection component in
src/components/sections/ProductsSection.tsx) to check product.externalUrl before
rendering and to apply target="_blank" rel="noopener noreferrer" when rendering
the CTA.

In `@src/app/admin/applications/page.tsx`:
- Around line 7-23: AdminApplicationsPage is querying sensitive
careerApplications without an auth guard; add a NextAuth session check at the
start of the AdminApplicationsPage function (before the db.select call) by
calling the appropriate session helper (e.g., getServerSession / getSession with
your auth options) and if no valid session exists return a 401 JSON response.
Ensure the session check runs in the same server-side context as
AdminApplicationsPage and short-circuits the handler with a 401 JSON payload
when unauthorized.

In `@src/app/admin/blog/page.tsx`:
- Around line 19-23: AdminBlogPage currently queries the database
unconditionally; add an authentication gate at the start of AdminBlogPage to
validate the current session and immediately return a 401 response when
unauthenticated before calling db.select().from(blogPosts). Use your app’s
existing auth helper (e.g., getSession/getServerSession or your auth middleware)
inside AdminBlogPage to check the session, and only proceed to the
db.select().from(blogPosts).orderBy(desc(blogPosts.created_at)) call when the
session is valid; return a Response/NextResponse with status 401 (or throw an
appropriate unauthorized error) otherwise.

In `@src/app/admin/careers/page.tsx`:
- Around line 7-11: AdminCareersPage currently queries the database before
verifying the request is authenticated; add an auth gate at the start of the
AdminCareersPage function that checks the current session and immediately
returns a 401 response (or throws an unauthorized response) when no valid
session exists, and only proceed to run the
db.select().from(careers).orderBy(...) when the session is present; locate the
authentication helper you use across admin routes (e.g.,
getServerSession/getSession/requireAuth) and call it at the top of
AdminCareersPage before the careers query to enforce the admin-route auth
requirement.

In `@src/app/admin/dashboard/page.tsx`:
- Around line 81-83: Add an explicit server-side session validation at the start
of AdminDashboardPage before calling getDashboardData(): call your session
verification helper (e.g., getSession / verifyAdminSession) and if it returns no
valid session, immediately return the proper unauthorized handling (throw a 401
Response, redirect to login, or render an unauthorized UI) instead of loading
stats; ensure this gate is applied in AdminDashboardPage and mirrors the pattern
required by your admin routes so getDashboardData() only runs for authenticated
admin sessions.

In `@src/app/admin/layout.tsx`:
- Around line 9-18: Add a server-side session guard at the top of the admin
layout: import getServerSession (from next-auth) and your authOptions, and
import redirect from next/navigation; inside the default exported layout
function (the component that renders AdminSidebar and children) call await
getServerSession(req?/context?) or the app-router-compatible
getServerSession(authOptions) to verify a valid session, and if it returns null
call redirect('/admin/login') (or throw a redirect) before returning the JSX;
ensure you reference getServerSession, authOptions, redirect and the layout
component (the one that renders AdminSidebar and children) so admin pages are
protected server-side.

In `@src/app/admin/messages/page.tsx`:
- Around line 7-12: Add a server-side session guard at the top of
AdminMessagesPage before any DB access: call your NextAuth session helper (e.g.,
getServerSession or your custom requireAdminSession) inside AdminMessagesPage
and verify the session/role, and if not present/authorized perform a
redirect/throw (using next/navigation redirect or a 401) so the db.select()
against contactSubmissions only runs for authenticated admin users; update
AdminMessagesPage to check the session first (and reference
getServerSession/authOptions or your requireAdminSession helper) and bail out
before calling db or contactSubmissions.

In `@src/app/admin/products/page.tsx`:
- Around line 7-11: AdminProductsPage currently queries the DB unconditionally
which exposes products to unauthenticated users; add an auth gate at the start
of AdminProductsPage that validates the session (e.g., call your app's
session/auth helper) and if no valid session return a 401 response before
executing the db.select(). Ensure the check occurs before the call to
db.select().from(products) so unauthorized requests never reach the database
query.

In `@src/components/admin/ApplicationsManager.tsx`:
- Around line 119-125: The expansion toggle is currently bound to a
non-focusable <tr onClick> (using setOpenId and openId with application.id),
making it inaccessible to keyboard users; move the click handler into a
focusable control by adding a <button> (placed inside a <td> in the row) that
calls setOpenId(openId === application.id ? null : application.id), add
aria-expanded={openId === application.id} and an accessible label (e.g., "Show
details" / "Hide details"), and remove the onClick from the <tr>; apply the same
change for the other expandable block referenced around lines 131-155 so all
expansion toggles are keyboard- and screen-reader-accessible.
- Around line 3-5: In the ApplicationsManager component, replace inline ternary
Tailwind class strings on the filter buttons with the project's cn() helper: add
an import for cn from the utils module and update the className props on the
filter button elements (the buttons that toggle the filter state / use
setFilter) to use cn(condition, "base-classes", { "active-classes": condition })
instead of string interpolation; also update the other occurrence around the
filter buttons (the block noted at lines 96-100) so all conditional Tailwind
classes use cn().
- Around line 66-81: The updateStatus function currently calls fetch without
guarding against network exceptions—wrap the fetch + response handling inside a
try/catch in updateStatus (the async function updateStatus(id, status)) so any
thrown fetch errors are caught; in the catch block call window.alert (or set the
same failure UI path) and return, and keep the existing response.ok check after
the awaited fetch to handle HTTP error responses.

In `@src/components/admin/MessagesManager.tsx`:
- Around line 81-85: The table rows currently toggle expansion by clicking the
<tr> with onClick using setOpenId/openId and message.id, which is inaccessible
to keyboard users; replace the clickable <tr> with a focusable control (e.g., a
button placed inside the row) or add keyboard handlers and proper ARIA on an
interactive element: move the onClick logic to a button element (or add
onKeyDown that handles Enter/Space) and include aria-expanded={openId ===
message.id} and aria-controls pointing to the expandable content ID to ensure
keyboard focus and screen-reader visibility; apply the same change for the other
rows referenced around the instances using setOpenId/openId/message.id (lines
noted in the comment).
- Around line 32-49: The markAsRead function currently relies only on
response.ok and can throw uncaught fetch errors; wrap the fetch call and
response handling for markAsRead (and the similar handler around lines 51-66) in
a try/catch, await the fetch inside the try, check response.ok and show
window.alert on non-ok, and in the catch log the error (console.error or process
logger) and show a user-friendly alert so no rejections bubble up; only update
state via setItems after a successful response.

In `@src/components/sections/ProductsSection.tsx`:
- Around line 51-53: The product links in the ProductsSection component use the
Link element for product navigation but are missing the required new-tab
attributes; update both Link usages that render
href={`/products/${product.slug}`} (and the other product Link) to include
target="_blank" and rel="noopener noreferrer" so product links open in a new tab
safely; locate the Link elements in ProductsSection (the ones referencing
product.slug) and add those attributes to each Link element.

---

Minor comments:
In `@src/app/`(public)/products/[slug]/page.tsx:
- Around line 155-157: The map over product.description uses the paragraph
string as the React key which can collide for repeated paragraphs; change the
key to a stable unique identifier by using the map index or a derived
index-based key (e.g., use map((paragraph, idx) => ...) and set
key={`paragraph-${idx}`}) so each <p> rendered in the
product.description.split(...) mapping has a unique, stable key instead of the
raw paragraph text.

In `@src/app/admin/blog/page.tsx`:
- Around line 57-62: The actions cell currently applies "flex" to the <td> which
alters table layout; remove the flex from the <td> (keep padding classes like
"px-4 py-3") and wrap the Button and AdminDeleteButton in an inner element with
the flex layout (e.g., add a <div className="flex gap-2"> around <Button
asChild>... and <AdminDeleteButton endpoint=...>), keeping existing components
and Link usage intact (refer to the <td>, <Button asChild>, <Link
href={`/admin/blog/${post.id}`}>Edit</Link>, and <AdminDeleteButton
endpoint={`/api/admin/blog/${post.id}`} />).

In `@src/app/admin/careers/page.tsx`:
- Around line 45-50: The actions cell currently applies layout classes directly
to the <td>, which alters table rendering; remove layout classes like "flex
gap-2" from the <td> and keep only table-related classes (e.g., "px-4 py-3"),
then wrap the Button, Link and AdminDeleteButton elements in an inner container
(e.g., a <div> with "flex gap-2") to handle spacing and alignment; update the
element containing Button, Link and AdminDeleteButton so the <td> remains a
table cell and the inner div provides the flex layout.

In `@src/app/admin/products/page.tsx`:
- Around line 45-50: The actions cell currently applies "flex" directly to the
<td>, which alters table layout; remove the flex/gap classes from the <td> and
instead add an inner wrapper (e.g., a <div> with className="flex gap-2 px-0
py-0" or similar) that contains the <Button asChild> (Link to
`/admin/products/${product.id}`) and <AdminDeleteButton
endpoint={`/api/admin/products/${product.id}`} /> so the <td> remains a proper
table cell while the buttons retain their horizontal spacing and styles.

In `@src/components/contact/ContactForm.tsx`:
- Around line 107-133: The call to response.json() can throw on empty or
non-JSON responses; update the fetch handling in ContactForm so you first check
Content-Type (response.headers.get("content-type") includes "application/json")
or wrap response.json() in a try/catch/conditional to safely parse only when
JSON is present — if not JSON, fall back to using response.statusText or await
response.text() as the error payload; then use that payload with
getErrorMessage(result) and setSubmitError/setSubmitState as before and ensure
parsing errors don't bypass the !response.ok branch.

In `@src/components/ui/Input.tsx`:
- Around line 16-45: The component currently sets inputId = id ?? props.name
which breaks labeling/aria when both are absent; generate a stable fallback id
inside the Input component (e.g., const generatedId = useId() or
useRef(generateUnique()) on first render) and then set inputId = id ??
props.name ?? generatedId; update all uses (label htmlFor, input id,
aria-describedby, and error <p> id) to rely on this inputId so labeled/error
inputs remain accessible even if callers omit id and name.

In `@src/components/ui/Textarea.tsx`:
- Around line 16-33: The Textarea component can lose label/error associations
when both id and props.name are missing; generate a stable fallback id by
importing and calling React's useId and setting textareaId = id ?? props.name ??
useId(), then use that textareaId for htmlFor, id, and aria-describedby (and the
error element id like `${textareaId}-error`) so label and aria attributes always
point to a real element; update imports to include useId and ensure textareaId
is computed once at the top of the component.

---

Nitpick comments:
In `@src/components/blog/UpdatesList.tsx`:
- Line 75: The div in the UpdatesList component uses a non-approved spacing
token "space-y-10"; replace that token with an approved Tailwind spacing token
such as "space-y-8" or "space-y-12" in the className on the element in
UpdatesList.tsx (look for the div with className="space-y-10") to conform to the
design-system spacing set (4,8,12,16,20,24,32,48,64,96).

In `@src/components/layout/AdminSidebar.tsx`:
- Line 78: In the AdminSidebar component update the navigation entry that
currently reads { href: "/admin/blog", label: "Blog", icon: <DocumentIcon /> }
so the visible label is "Updates" while keeping the href as "/admin/blog";
locate the nav items array (inside AdminSidebar) and change the label value for
the object with href "/admin/blog" to "Updates" to conform to the project-wide
naming rule.

In `@src/components/layout/Footer.tsx`:
- Around line 97-117: The two footer section headings in Footer (the <h2>
elements for "Company" and "Products") are using the body font class
"font-sans"; replace that class with the project's heading-font utility for
H1–H4 (the JetBrains Mono heading font used across the app) so headings follow
the typography rule — update the className on both <h2> elements (currently
containing "font-sans text-sm font-medium text-[`#121F38`]") to use the heading
font utility instead while keeping the rest of the classes intact.

In `@src/components/layout/Navbar.tsx`:
- Line 40: The nav spacing utilities (gap-7 and gap-1.5) in the Navbar component
violate the approved Tailwind spacing scale; update the className(s) in
Navbar.tsx (the <nav ...> and the other occurrence around line 71) to use
nearest allowed spacing tokens (replace gap-7 with gap-6 or gap-8 and replace
gap-1.5 with gap-2 or gap-4) so all padding/margin/gap utilities conform to the
approved list for src/components/**/*.tsx.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 145ad31d-256b-4d28-92ef-e270daa097f0

📥 Commits

Reviewing files that changed from the base of the PR and between dbcdf97 and 97e6e5b.

📒 Files selected for processing (54)
  • AGENTS.md
  • CLAUDE.md
  • src/app/(public)/about/page.tsx
  • src/app/(public)/blog/[slug]/page.tsx
  • src/app/(public)/blog/page.tsx
  • src/app/(public)/careers/page.tsx
  • src/app/(public)/contact/page.tsx
  • src/app/(public)/layout.tsx
  • src/app/(public)/page.tsx
  • src/app/(public)/products/[slug]/page.tsx
  • src/app/(public)/products/page.tsx
  • src/app/(public)/team/page.tsx
  • src/app/admin/applications/page.tsx
  • src/app/admin/blog/[id]/page.tsx
  • src/app/admin/blog/new/page.tsx
  • src/app/admin/blog/page.tsx
  • src/app/admin/careers/[id]/page.tsx
  • src/app/admin/careers/new/page.tsx
  • src/app/admin/careers/page.tsx
  • src/app/admin/dashboard/page.tsx
  • src/app/admin/layout.tsx
  • src/app/admin/messages/page.tsx
  • src/app/admin/products/[id]/page.tsx
  • src/app/admin/products/new/page.tsx
  • src/app/admin/products/page.tsx
  • src/app/admin/team/[id]/page.tsx
  • src/app/admin/team/new/page.tsx
  • src/app/admin/team/page.tsx
  • src/app/globals.css
  • src/app/layout.tsx
  • src/app/not-found.tsx
  • src/components/admin/AdminDeleteButton.tsx
  • src/components/admin/ApplicationsManager.tsx
  • src/components/admin/ImageUpload.tsx
  • src/components/admin/MessagesManager.tsx
  • src/components/admin/ResourceForms.tsx
  • src/components/admin/RichTextEditor.tsx
  • src/components/blog/PostContent.tsx
  • src/components/blog/UpdatesList.tsx
  • src/components/careers/ApplicationForm.tsx
  • src/components/contact/ContactForm.tsx
  • src/components/layout/AdminSidebar.tsx
  • src/components/layout/Footer.tsx
  • src/components/layout/Navbar.tsx
  • src/components/sections/HackathonStrip.tsx
  • src/components/sections/HeroSection.tsx
  • src/components/sections/LatestReleasesSection.tsx
  • src/components/sections/ProductsSection.tsx
  • src/components/ui/Badge.tsx
  • src/components/ui/Button.tsx
  • src/components/ui/Card.tsx
  • src/components/ui/Input.tsx
  • src/components/ui/Textarea.tsx
  • tailwind.config.ts

Comment on lines +77 to +82
<Link
href={`/products/${product.slug}`}
className="text-[#121F38] hover:text-[#1A2D4F]"
>
Learn more
</Link>
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 | 🟠 Major | ⚡ Quick win

Use external new-tab behavior for the primary product link.

Line 77 currently routes to an internal slug page. This conflicts with the product-link requirement. Make the primary CTA open the product’s external URL in a new tab (target="_blank" rel="noopener noreferrer"), or conditionally hide the CTA when no external URL exists.

As per coding guidelines {src/components/sections/ProductsSection.tsx,src/app/(public)/products/**/*.tsx}: Product links must always open in a new tab with target="_blank" rel="noopener noreferrer".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(public)/products/page.tsx around lines 77 - 82, The primary product
CTA currently uses an internal Link to `/products/${product.slug}`; change it to
open the product's external URL in a new tab (use an anchor or Link configured
with href={product.externalUrl} plus target="_blank" and rel="noopener
noreferrer") and, if product.externalUrl is missing/falsey, hide the CTA
entirely; update the component(s) referenced (the Link usage in page.tsx and the
ProductsSection component in src/components/sections/ProductsSection.tsx) to
check product.externalUrl before rendering and to apply target="_blank"
rel="noopener noreferrer" when rendering the CTA.

Comment thread src/app/admin/applications/page.tsx
Comment thread src/app/admin/blog/page.tsx
Comment thread src/app/admin/careers/page.tsx
Comment thread src/app/admin/dashboard/page.tsx
Comment thread src/components/admin/ApplicationsManager.tsx
Comment thread src/components/admin/ApplicationsManager.tsx Outdated
Comment thread src/components/admin/MessagesManager.tsx
Comment thread src/components/admin/MessagesManager.tsx Outdated
Comment on lines +51 to +53
<Link
href={`/products/${product.slug}`}
className="inline-block font-mono text-2xl font-semibold leading-[1.3] text-[#121F38] hover:text-[#1A2D4F]"
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 | 🟠 Major | ⚡ Quick win

Product detail links need the required new-tab attributes.

Both product links should include target="_blank" and rel="noopener noreferrer" to match the product-link policy.

Suggested patch
                     <Link
                       href={`/products/${product.slug}`}
+                      target="_blank"
+                      rel="noopener noreferrer"
                       className="inline-block font-mono text-2xl font-semibold leading-[1.3] text-[`#121F38`] hover:text-[`#1A2D4F`]"
                     >
@@
                   <Button asChild variant="ghost">
-                    <Link href={`/products/${product.slug}`}>Read more →</Link>
+                    <Link
+                      href={`/products/${product.slug}`}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                    >
+                      Read more →
+                    </Link>
                   </Button>
As per coding guidelines: `Product links must always open in a new tab with target="_blank" rel="noopener noreferrer"` for `src/components/sections/ProductsSection.tsx`.

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sections/ProductsSection.tsx` around lines 51 - 53, The
product links in the ProductsSection component use the Link element for product
navigation but are missing the required new-tab attributes; update both Link
usages that render href={`/products/${product.slug}`} (and the other product
Link) to include target="_blank" and rel="noopener noreferrer" so product links
open in a new tab safely; locate the Link elements in ProductsSection (the ones
referencing product.slug) and add those attributes to each Link element.

@onerandomdevv onerandomdevv merged commit 7c1cc16 into dev May 3, 2026
4 checks passed
@onerandomdevv onerandomdevv deleted the feature/frontend branch May 6, 2026 17:18
@coderabbitai coderabbitai Bot mentioned this pull request May 16, 2026
13 tasks
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