Feature/backend setup#1
Conversation
- Project scaffold and folder architecture - Neon PostgreSQL + Drizzle ORM schema (7 tables) - NextAuth.js v5 credentials auth + admin login page - Middleware route protection for /admin and /api/admin - Contact form API with Resend email notification - Career application API with email notification - Cloudinary image upload API (admin protected) - Full admin CRUD API routes (team, projects, blog, careers, applications, messages) - GitHub Actions CI workflow (type check + lint + build) - PR template, issue templates, CODEOWNERS - Environment variable setup
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (3)
📝 WalkthroughWalkthroughRenames "projects" → "products" across DB schema, migrations, types, and admin APIs; adds blog post category; scaffolds many public/admin pages and UI components as null-returning stubs; adds CODEOWNERS, issue/PR templates, CI/preview workflows; and updates tooling (local .env loader), middleware auth, and seed/email utilities. ChangesProducts Schema, Migrations & Admin APIs
Public & Admin Page / Component Scaffolding
Repository Governance & CI
Tooling, Middleware & Scripts
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant API as Next.js API Route
participant Auth as NextAuth (getToken)
participant DB as Database
Client->>API: Request (GET/POST/PUT/DELETE /api/admin/products)
API->>Auth: getToken(req, secret)
Auth-->>API: session / token
API->>DB: isAdminUser(query admin_users by email)
DB-->>API: isAdmin? (true/false)
alt Admin allowed
API->>DB: SELECT/INSERT/UPDATE/DELETE on products
DB-->>API: row(s) / result
API-->>Client: 200/201 response with product data
else Not admin
API-->>Client: 401 or 403 error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 40 minutes and 28 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 20
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/admin/products/[id]/route.ts (1)
27-31:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnforce admin authorization parity on item endpoints.
GET/PUT/DELETE /api/admin/products/[id]currently allow any authenticated session, while the collection route (src/app/api/admin/products/route.ts) enforcesadmin_usersmembership. Keep the same authorization gate here to avoid weaker access control on item-level operations.Suggested fix
-import { db, products } from "@/db"; +import { adminUsers, db, products } from "@/db"; +async function isAdminUser(email: string | null | undefined) { + if (!email) return false; + + const [adminUser] = await db + .select({ id: adminUsers.id }) + .from(adminUsers) + .where(eq(adminUsers.email, email)) + .limit(1); + + return Boolean(adminUser); +} export async function GET(_request: Request, { params }: RouteContext) { const session = await auth(); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } + if (!(await isAdminUser(session.user?.email))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } export async function PUT(request: Request, { params }: RouteContext) { const session = await auth(); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } + if (!(await isAdminUser(session.user?.email))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } export async function DELETE(_request: Request, { params }: RouteContext) { const session = await auth(); if (!session) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } + if (!(await isAdminUser(session.user?.email))) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + }Also applies to: 60-64, 100-104
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/admin/products/`[id]/route.ts around lines 27 - 31, The item endpoints (GET, PUT, DELETE in src/app/api/admin/products/[id]/route.ts) currently only check auth() for a session; update each handler (GET, PUT, DELETE) to enforce the same admin_users membership gate used by the collection route by checking the session's group/role membership (the same property checked in the collection route) after auth() and return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) when the session is not an admin; ensure you reuse the same check logic/condition (and message) so item-level authorization matches the collection-level authorization and keep the existing NextResponse usage.
🟡 Minor comments (1)
AGENTS.md-219-221 (1)
219-221:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the self-reference in the repo tree.
AGENTS.mdcurrently marksCLAUDE.mdas “This file”. That will send future edits to the wrong instruction doc.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@AGENTS.md` around lines 219 - 221, Replace the incorrect self-reference label "This file" for CLAUDE.md in the repo tree with an accurate descriptor (e.g., "Claude agent instructions" or "Agent-specific docs") so AGENTS.md no longer claims CLAUDE.md is the current file; update the line containing "CLAUDE.md # This file" to read something like "CLAUDE.md # Claude agent instructions" to avoid future edit confusion.
🧹 Nitpick comments (2)
src/components/ui/Badge.tsx (1)
1-3: ⚡ Quick winMake
Badgerender a minimal element instead of returningnull.A tiny typed
<span>wrapper keeps this primitive usable without adding complexity.Suggested fix
+import type { HTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; + +type BadgeProps = HTMLAttributes<HTMLSpanElement>; + -export default function Badge() { - return null; +export default function Badge({ className, ...props }: BadgeProps) { + return ( + <span + className={cn( + "inline-flex items-center rounded-md bg-[`#D1D6E0`] px-2 py-1 text-[`#121F38`]", + className + )} + {...props} + /> + ); }As per coding guidelines
**/*.{ts,tsx}: “Build all UI from scratch with Tailwind CSS”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/Badge.tsx` around lines 1 - 3, Badge currently returns null; change it to render a minimal typed <span> wrapper so the primitive is usable. Update the default export function Badge to accept standard span props (e.g., props: React.HTMLAttributes<HTMLSpanElement> & { children?: React.ReactNode }) and return <span {...props} className={tw-classes + (props.className||'')}>{props.children}</span> using simple Tailwind classes (e.g., inline-flex text-sm px-2 py-0.5 rounded) to satisfy the guideline; ensure the component is correctly typed with React and preserves passed className and children.src/components/ui/Textarea.tsx (1)
1-3: ⚡ Quick winRender a baseline
<textarea>component instead of returningnull.Keeping this as a no-op blocks reuse in forms.
Suggested fix
+import type { TextareaHTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; + +type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>; + -export default function Textarea() { - return null; +export default function Textarea({ className, ...props }: TextareaProps) { + return ( + <textarea + className={cn("rounded-md border border-[`#C4CAD6`] bg-white text-[`#121F38`]", className)} + {...props} + /> + ); }As per coding guidelines
**/*.{ts,tsx}: “Build all UI from scratch with Tailwind CSS”.🤖 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 1 - 3, The Textarea component currently returns null; replace it with a real textarea element so it can be reused in forms: implement Textarea to accept standard textarea props (e.g., React.TextareaHTMLAttributes<HTMLTextAreaElement>) and an optional className, forward a ref (use React.forwardRef or accept ref param) and render a <textarea {...props} ref={ref} className={merge default Tailwind classes with props.className}> with sensible defaults (like rows) so it’s styled per project Tailwind conventions and spreads remaining props for accessibility and behavior; refer to the Textarea component name to locate where to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/preview.yml:
- Around line 3-22: The current preview workflow triggers on "on: pull_request"
and the "preview" job's step named "Comment preview status" always posts the
fixed body "✅ CI passed. Vercel preview deployment will be available shortly.",
which is misleading; either change that step's body text to a neutral message
(e.g., "Vercel preview deployment will be available shortly." or "Preview job
started; check CI status for pass/fail.") replacing the existing body string, or
gate the workflow to run only after the CI workflow succeeds by replacing the
trigger with a workflow_run trigger for the CI workflow (completed + conclusion:
"success") so the comment only runs when CI passed; update the step (still named
"Comment preview status") accordingly if you gate the run.
In `@scripts/seed-admin.ts`:
- Around line 24-29: The script currently reads the admin password from
process.argv (const [email, password] = process.argv.slice(2)), which can expose
secrets; change it to require the password from a secure source such as an
environment variable (e.g., process.env.ADMIN_PASSWORD) or a secure prompt, keep
email from argv if desired, validate that process.env.ADMIN_PASSWORD is present,
and update any references to password in the script (seed logic, createAdmin
function calls) to use the new variable name to avoid using process.argv for
secrets.
In `@src/app/`(public)/about/page.tsx:
- Around line 1-3: The AboutPage currently returns null so the required WHO/WHY
narrative and company facts are missing; update the AboutPage component
(function AboutPage) to render a semantic About page that includes a short "Who
we are" paragraph, a "Why we exist" paragraph, and a facts section listing "RC
9426867", "Lagos, Nigeria", "Est. March 2026", and an explicit note about
open-source commitment; use accessible semantic elements (e.g., section, h1/h2,
p, ul) and ensure the component exports default AboutPage with static content
per the project guideline.
In `@src/app/`(public)/blog/[slug]/page.tsx:
- Around line 1-3: The BlogPostPage currently returns null so the /blog/[slug]
route is blank; change BlogPostPage to accept the Next.js route params (e.g.,
async function BlogPostPage({ params })) fetch the post by params.slug (use your
existing fetch/get helper such as getPostBySlug or the same data layer used by
the blog index), handle not-found by returning notFound() or a 404, and render
the post content including a page heading labeled "Updates" (per guidelines)
while keeping the URL as /blog/[slug]; ensure the component returns the full
JSX/TSX markup instead of null and reuses existing post rendering components if
available.
In `@src/app/`(public)/blog/page.tsx:
- Around line 1-3: The BlogPage component currently returns null and thus
removes the required "Updates" UI; update the BlogPage function to render a page
titled "Updates" (user-facing label) that fetches and displays published blog
posts ordered by published_at DESC, implements a category filter with options
"All", "Product Update", "Announcement", "Roadmap", and "Story", and applies the
selected category to filter the listed posts; ensure data fetching (server or
client) only returns published posts and sort by published_at descending, wire
the filter control to re-query or filter the result, and keep the route as
/blog.
In `@src/app/`(public)/careers/page.tsx:
- Around line 1-3: CareersPage currently returns null; replace it with a
rendered component that lists career openings when present and otherwise shows
the required empty state text "No open roles right now. Send us a message." with
a CTA link to /contact; implement a simple application form component inside
CareersPage (or referenced component) that POSTs to /api/careers/apply on submit
and includes fields for name, email, role, and resume, handling success/error
feedback; ensure the listing logic uses a prop/state or fetch to obtain openings
and the component/function names (CareersPage, ApplicationForm) are wired so the
page shows listings or the empty-state + CTA and the form submits to
/api/careers/apply.
In `@src/app/`(public)/contact/page.tsx:
- Around line 1-3: ContactPage currently returns null; replace it with a
functional contact page component that renders the required form (fields: Full
Name, Email, Subject, Message), posts submissions to /api/contact, handles and
displays a success/confirmation state after successful submit (showing the
company email codeddevs.team@gmail.com as contact), and includes social links
for GitHub (coded-devs), X (`@CodedDevs`), TikTok (`@CodedDevs`), YouTube
(`@CodedDevs`), and Instagram (`@codeddevs_`); update the ContactPage function to
manage form state, validation, submission (fetch/POST to /api/contact), success
indicator, and the social links UI.
In `@src/app/`(public)/page.tsx:
- Around line 1-3: The HomePage component currently returns null; replace its
return with the required hero and page content: render the exact hero headline
and subtext (as per guidelines) inside the HomePage function, include two CTA
buttons or links with the exact labels "See Our Products" (href "/products") and
"Get in Touch" (href "/contact"), add a plain-text hackathon achievements strip
(single-line text element), and include a two-sentence about teaser that
contains a "Meet the Team" link to "/team"; update the JSX returned by HomePage
(the default export function HomePage) to include those elements and accessible
semantics (headings, paragraphs, anchor/button elements with correct text and
hrefs).
In `@src/app/`(public)/products/page.tsx:
- Around line 1-3: ProductsPage currently returns null; replace it with a
component that queries the products table (server-side or via a data fetching
hook) and renders a list of product cards showing each product's name, tagline,
a status badge (e.g., active/disabled), and an external link, and make each card
link to its detail route (/products/[slug]) using the product.slug; ensure the
component named ProductsPage performs proper null/empty-state handling, maps
over the fetched products, and exports default ProductsPage instead of returning
null.
In `@src/app/`(public)/team/page.tsx:
- Around line 1-3: The TeamPage component currently returns null; replace it
with an implementation that queries/pulls the team_members ordered by
order_index and renders each member (in TeamPage) showing their photo (avatar
img), name, role, bio, and social links (e.g., map over social_links to render
anchor icons), and ensure the founders Kareem Aliameen (CEO), Yusuf Ibrahim
Ayinla (CTO), and Amoo Mustakheem Olamilekan (COO) are included in the list
(either from the data source or injected if missing) and appear in the correct
order; use a single TeamPage component render that maps over the ordered list
and outputs semantic markup for image, name, role and bio and clickable social
links.
In `@src/app/admin/blog/`[id]/page.tsx:
- Around line 1-3: EditBlogPostPage currently returns null so the admin edit
route has no UI; implement the edit surface by fetching the post by id and
rendering an edit form that submits updates to the backend. In EditBlogPostPage,
read the route param id, call your data loader (e.g., getBlogById or similar
server helper) and handle not-found/loading states, render a BlogForm (or create
one) populated with the post fields, and wire its submit handler to call your
update API (e.g., updateBlogPost or /api/admin/blog/[id] PATCH) with validation
and error handling; ensure the component updates the UI after save and shows
success/error messages.
In `@src/components/admin/DataTable.tsx`:
- Around line 1-3: The DataTable component currently returns null which prevents
rendering any admin data; update the DataTable function to return a renderable
table element (e.g., a table wrapper with header and body) that consumes
props/state or hooks for rows and columns, implement basic column headers and
map over the data to render rows, and ensure accessibility attributes and key
props are present; locate the DataTable function and replace the null return
with a JSX table (or a styled container with table semantics) and wire it to the
existing data source or props used by the admin table flow.
In `@src/components/admin/ImageUpload.tsx`:
- Around line 1-3: The ImageUpload component currently returns null so admins
cannot upload images; implement the Cloudinary upload UI and flow inside the
ImageUpload React component by replacing the no-op return with a UI (button or
form) that opens Cloudinary's upload widget or posts files to your Cloudinary
endpoint, manage local state for selectedFile/uploading/error, call the upload
function (e.g., a helper like uploadToCloudinary or a Cloudinary widget
callback) from the ImageUpload component, show progress/success feedback, and
invoke any provided callback/prop or emit an event with the uploaded image URL
so the parent/admin UI can use it; reference the ImageUpload component name and
ensure proper error handling and logging in the upload handler.
In `@src/components/admin/RichTextEditor.tsx`:
- Around line 1-3: RichTextEditor currently returns null; implement it as a
TipTap wrapper component named RichTextEditor that accepts a JSON value prop and
an onChange handler, initializes a TipTap editor via useEditor (with StarterKit
and any required extensions), renders EditorContent, and synchronizes state by
calling onChange(editor.getJSON()) on editor updates and by setting
editor.commands.setContent or editor.setJSON when the incoming value prop
changes; ensure the component exports default RichTextEditor and stores/outputs
JSON for blog content editing.
In `@src/components/layout/AdminSidebar.tsx`:
- Around line 1-3: AdminSidebar currently returns null so the admin navigation
is missing; replace the stubbed AdminSidebar component with a real sidebar
element that renders navigation links for "Dashboard", "Team", "Products",
"Blog", "Careers", "Applications", and "Messages" (keep the same exported
component name AdminSidebar). Implement a semantic <aside> containing a <nav>
and an ordered/unordered list of links (use your app's Link/NavLink component or
anchor tags) with clear text, accessible aria-labels/roles, and active-state
styling where applicable, ensuring the component still exports default
AdminSidebar. Ensure link targets match your route names (e.g.,
/admin/dashboard, /admin/team, /admin/products, /admin/blog, /admin/careers,
/admin/applications, /admin/messages) and preserve existing styling
conventions/classes used across other layout components.
In `@src/components/layout/Footer.tsx`:
- Around line 1-3: The Footer component currently returns null; replace that
with a minimal compliant footer element that applies the required styles: render
a <footer> (or <div>) element inside the Footer function and set
className="bg-[`#F4F5F8`] border-t border-[`#C4CAD6`]" (and any necessary
accessibility attributes like role="contentinfo" if desired); ensure the
exported Footer function returns this styled element instead of null so the
layout/style contract is satisfied.
In `@src/components/layout/Navbar.tsx`:
- Around line 1-3: Navbar currently returns null so the required homepage logo
is not rendered; update the Navbar component to return an anchor (<a>) linking
to "/" that contains the logo image from the public asset (use
/public/full-logo.png per guidelines), i.e. render the existing logo asset
rather than recreating it in code inside the Navbar function so the logo always
appears and links to the homepage.
In `@src/components/ui/Button.tsx`:
- Around line 1-3: Button currently returns null; change it to render a real
<button> element that accepts standard button props and children (e.g., function
Button({ className, children, ...props }:
React.ButtonHTMLAttributes<HTMLButtonElement>), use the existing cn(...) utility
to merge the incoming className with the repo primary button classes
"bg-[`#121F38`] text-white hover:bg-[`#1A2D4F`]" (and any base spacing/rounded
utility you use for primary CTAs), and spread ...props onto the button so it
supports onClick/disabled/aria etc.; keep the export default Button signature
and ensure you import React types and cn if not already present.
In `@src/components/ui/Card.tsx`:
- Around line 1-3: The Card component currently returns null; change the
exported function Card to render a wrapper element (e.g., a div) that accepts
and renders children and applies the required card tokens: bg-[`#F4F5F8`] border
border-[`#C4CAD6`] rounded-lg and an optional shadow-sm; ensure the function
signature (Card) forwards children and supports merging any incoming
className/props so consumers can compose additional classes or attributes.
In `@src/components/ui/Input.tsx`:
- Around line 1-3: The Input component currently returns null; replace it with a
real input element that applies the mandated styling and forwards props so forms
work: implement export default function Input(props:
React.InputHTMLAttributes<HTMLInputElement>) (or use React.forwardRef) and
render an <input> element that spreads {...props} and includes the required
classes/bg tokens "bg-white border border-[`#C4CAD6`] text-[`#121F38`] rounded-md"
(add any default padding/width as needed) so the Input component renders and
preserves expected form behavior.
---
Outside diff comments:
In `@src/app/api/admin/products/`[id]/route.ts:
- Around line 27-31: The item endpoints (GET, PUT, DELETE in
src/app/api/admin/products/[id]/route.ts) currently only check auth() for a
session; update each handler (GET, PUT, DELETE) to enforce the same admin_users
membership gate used by the collection route by checking the session's
group/role membership (the same property checked in the collection route) after
auth() and return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
when the session is not an admin; ensure you reuse the same check
logic/condition (and message) so item-level authorization matches the
collection-level authorization and keep the existing NextResponse usage.
---
Minor comments:
In `@AGENTS.md`:
- Around line 219-221: Replace the incorrect self-reference label "This file"
for CLAUDE.md in the repo tree with an accurate descriptor (e.g., "Claude agent
instructions" or "Agent-specific docs") so AGENTS.md no longer claims CLAUDE.md
is the current file; update the line containing "CLAUDE.md
# This file" to read something like "CLAUDE.md #
Claude agent instructions" to avoid future edit confusion.
---
Nitpick comments:
In `@src/components/ui/Badge.tsx`:
- Around line 1-3: Badge currently returns null; change it to render a minimal
typed <span> wrapper so the primitive is usable. Update the default export
function Badge to accept standard span props (e.g., props:
React.HTMLAttributes<HTMLSpanElement> & { children?: React.ReactNode }) and
return <span {...props} className={tw-classes +
(props.className||'')}>{props.children}</span> using simple Tailwind classes
(e.g., inline-flex text-sm px-2 py-0.5 rounded) to satisfy the guideline; ensure
the component is correctly typed with React and preserves passed className and
children.
In `@src/components/ui/Textarea.tsx`:
- Around line 1-3: The Textarea component currently returns null; replace it
with a real textarea element so it can be reused in forms: implement Textarea to
accept standard textarea props (e.g.,
React.TextareaHTMLAttributes<HTMLTextAreaElement>) and an optional className,
forward a ref (use React.forwardRef or accept ref param) and render a <textarea
{...props} ref={ref} className={merge default Tailwind classes with
props.className}> with sensible defaults (like rows) so it’s styled per project
Tailwind conventions and spreads remaining props for accessibility and behavior;
refer to the Textarea component name to locate where to update.
🪄 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: c2f29f1d-e36b-414e-a8a6-0ee427be8461
📒 Files selected for processing (65)
.github/CODEOWNERS.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/feature_request.md.github/pull_request_template.md.github/workflows/ci.yml.github/workflows/preview.ymlAGENTS.mdCLAUDE.mddrizzle.config.tsmiddleware.tsscripts/seed-admin.tssrc/app/(public)/about/page.tsxsrc/app/(public)/blog/[slug]/page.tsxsrc/app/(public)/blog/page.tsxsrc/app/(public)/careers/page.tsxsrc/app/(public)/contact/page.tsxsrc/app/(public)/layout.tsxsrc/app/(public)/page.tsxsrc/app/(public)/products/page.tsxsrc/app/(public)/projects/page.tsxsrc/app/(public)/team/page.tsxsrc/app/admin/applications/page.tsxsrc/app/admin/blog/[id]/page.tsxsrc/app/admin/blog/new/page.tsxsrc/app/admin/blog/page.tsxsrc/app/admin/careers/[id]/page.tsxsrc/app/admin/careers/new/page.tsxsrc/app/admin/careers/page.tsxsrc/app/admin/dashboard/page.tsxsrc/app/admin/messages/page.tsxsrc/app/admin/products/[id]/page.tsxsrc/app/admin/products/new/page.tsxsrc/app/admin/products/page.tsxsrc/app/admin/projects/[id]/page.tsxsrc/app/admin/projects/new/page.tsxsrc/app/admin/projects/page.tsxsrc/app/admin/team/[id]/page.tsxsrc/app/admin/team/new/page.tsxsrc/app/admin/team/page.tsxsrc/app/api/admin/blog/[id]/route.tssrc/app/api/admin/blog/route.tssrc/app/api/admin/products/[id]/route.tssrc/app/api/admin/products/route.tssrc/components/admin/DataTable.tsxsrc/components/admin/ImageUpload.tsxsrc/components/admin/RichTextEditor.tsxsrc/components/layout/AdminSidebar.tsxsrc/components/layout/Footer.tsxsrc/components/layout/Navbar.tsxsrc/components/sections/HackathonStrip.tsxsrc/components/sections/HeroSection.tsxsrc/components/sections/ProductsSection.tsxsrc/components/sections/ProjectsSection.tsxsrc/components/sections/TeamSection.tsxsrc/components/ui/Badge.tsxsrc/components/ui/Button.tsxsrc/components/ui/Card.tsxsrc/components/ui/Input.tsxsrc/components/ui/Textarea.tsxsrc/db/migrations/0001_left_toxin.sqlsrc/db/migrations/meta/0001_snapshot.jsonsrc/db/migrations/meta/_journal.jsonsrc/db/schema.tssrc/lib/email.tssrc/types/index.ts
💤 Files with no reviewable changes (5)
- src/app/admin/projects/new/page.tsx
- src/app/admin/projects/[id]/page.tsx
- src/app/(public)/projects/page.tsx
- src/app/admin/projects/page.tsx
- src/components/sections/ProjectsSection.tsx
| on: | ||
| pull_request: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| preview: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Comment preview status | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: "✅ CI passed. Vercel preview deployment will be available shortly.", | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== preview workflow trigger and message ==="
sed -n '1,120p' .github/workflows/preview.yml
echo
echo "=== CI workflow name and trigger ==="
sed -n '1,140p' .github/workflows/ci.yml
echo
echo "Expected verification result:"
echo "- preview.yml uses 'on: pull_request' and contains 'CI passed' text"
echo "- preview.yml does not gate on ci.yml success (no workflow_run-based trigger)"Repository: coded-devs/website
Length of output: 2085
Preview workflow unconditionally claims CI passed, even when CI fails.
This workflow runs independently on pull_request events and always posts "✅ CI passed" (Line 21), regardless of the CI workflow status. This can mislead reviewers when actual CI checks are failing.
Suggested fix (safe/low-effort)
- body: "✅ CI passed. Vercel preview deployment will be available shortly.",
+ body: "🧪 Preview workflow triggered. Vercel preview deployment will be available shortly.",For accurate messaging, either update the text to not claim CI success, or gate this workflow on CI success via workflow_run triggered on completed+success of the CI workflow.
📝 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.
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| preview: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Comment preview status | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: "✅ CI passed. Vercel preview deployment will be available shortly.", | |
| }); | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| preview: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Comment preview status | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: "🧪 Preview workflow triggered. Vercel preview deployment will be available shortly.", | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/preview.yml around lines 3 - 22, The current preview
workflow triggers on "on: pull_request" and the "preview" job's step named
"Comment preview status" always posts the fixed body "✅ CI passed. Vercel
preview deployment will be available shortly.", which is misleading; either
change that step's body text to a neutral message (e.g., "Vercel preview
deployment will be available shortly." or "Preview job started; check CI status
for pass/fail.") replacing the existing body string, or gate the workflow to run
only after the CI workflow succeeds by replacing the trigger with a workflow_run
trigger for the CI workflow (completed + conclusion: "success") so the comment
only runs when CI passed; update the step (still named "Comment preview status")
accordingly if you gate the run.
| const [email, password] = process.argv.slice(2); | ||
|
|
||
| if (!email || !password) { | ||
| throw new Error( | ||
| "Usage: pnpm dlx tsx scripts/seed-admin.ts admin@example.com secure-password", | ||
| ); |
There was a problem hiding this comment.
Avoid taking admin passwords from CLI arguments.
Line 24 reads password from process.argv, which can leak via shell history and process inspection. Read the password from an environment variable (or secure prompt) instead.
Suggested fix
- const [email, password] = process.argv.slice(2);
+ const [email] = process.argv.slice(2);
+ const password = process.env.SEED_ADMIN_PASSWORD;
if (!email || !password) {
throw new Error(
- "Usage: pnpm dlx tsx scripts/seed-admin.ts admin@example.com secure-password",
+ "Usage: SEED_ADMIN_PASSWORD=... pnpm dlx tsx scripts/seed-admin.ts admin@example.com",
);
}Based on learnings: "Never check secrets into the repo or include them in prompts; use environment variables for all sensitive values."
📝 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.
| const [email, password] = process.argv.slice(2); | |
| if (!email || !password) { | |
| throw new Error( | |
| "Usage: pnpm dlx tsx scripts/seed-admin.ts admin@example.com secure-password", | |
| ); | |
| const [email] = process.argv.slice(2); | |
| const password = process.env.SEED_ADMIN_PASSWORD; | |
| if (!email || !password) { | |
| throw new Error( | |
| "Usage: SEED_ADMIN_PASSWORD=... pnpm dlx tsx scripts/seed-admin.ts admin@example.com", | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/seed-admin.ts` around lines 24 - 29, The script currently reads the
admin password from process.argv (const [email, password] =
process.argv.slice(2)), which can expose secrets; change it to require the
password from a secure source such as an environment variable (e.g.,
process.env.ADMIN_PASSWORD) or a secure prompt, keep email from argv if desired,
validate that process.env.ADMIN_PASSWORD is present, and update any references
to password in the script (seed logic, createAdmin function calls) to use the
new variable name to avoid using process.argv for secrets.
| export default function AboutPage() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
/about route is empty and misses required company profile content.
Line 2 renders null, so the mandated WHO/WHY narrative and company facts are absent.
As per coding guidelines "src/app/(public)/about/page.tsx: About page explains WHO we are ... and WHY we exist, with company facts: RC 9426867, Lagos Nigeria, Est. March 2026; includes open-source commitment".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/`(public)/about/page.tsx around lines 1 - 3, The AboutPage currently
returns null so the required WHO/WHY narrative and company facts are missing;
update the AboutPage component (function AboutPage) to render a semantic About
page that includes a short "Who we are" paragraph, a "Why we exist" paragraph,
and a facts section listing "RC 9426867", "Lagos, Nigeria", "Est. March 2026",
and an explicit note about open-source commitment; use accessible semantic
elements (e.g., section, h1/h2, p, ul) and ensure the component exports default
AboutPage with static content per the project guideline.
| export default function BlogPostPage() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Blog detail route is non-functional (/blog/[slug] renders blank).
With return null on Line 2, opening a post URL shows no content, breaking the post detail path.
As per coding guidelines "Display the blog internally as 'Updates' in all user-facing labels (nav links, page headings, section titles); keep the URL and internal references as /blog".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/`(public)/blog/[slug]/page.tsx around lines 1 - 3, The BlogPostPage
currently returns null so the /blog/[slug] route is blank; change BlogPostPage
to accept the Next.js route params (e.g., async function BlogPostPage({ params
})) fetch the post by params.slug (use your existing fetch/get helper such as
getPostBySlug or the same data layer used by the blog index), handle not-found
by returning notFound() or a 404, and render the post content including a page
heading labeled "Updates" (per guidelines) while keeping the URL as
/blog/[slug]; ensure the component returns the full JSX/TSX markup instead of
null and reuses existing post rendering components if available.
| export default function BlogPage() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
/blog page currently drops all required “Updates” functionality.
Returning null on Line 2 leaves the route blank and omits the required listing, ordering, and filters.
As per coding guidelines "src/app/(public)/blog/page.tsx: Blog page displays as 'Updates' in user-facing labels with URL /blog; lists published blog posts ordered by published_at DESC; includes category filter (All, Product Update, Announcement, Roadmap, Story)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/`(public)/blog/page.tsx around lines 1 - 3, The BlogPage component
currently returns null and thus removes the required "Updates" UI; update the
BlogPage function to render a page titled "Updates" (user-facing label) that
fetches and displays published blog posts ordered by published_at DESC,
implements a category filter with options "All", "Product Update",
"Announcement", "Roadmap", and "Story", and applies the selected category to
filter the listed posts; ensure data fetching (server or client) only returns
published posts and sort by published_at descending, wire the filter control to
re-query or filter the result, and keep the route as /blog.
| export default function Footer() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Footer currently violates the required layout/style contract.
Line 2 returns null, so the footer style requirement is not being applied at all.
As per coding guidelines, src/components/layout/Footer.tsx must apply bg-[#F4F5F8] border-t border-[#C4CAD6].
Minimal compliant footer stub
export default function Footer() {
- return null;
+ return (
+ <footer className="border-t border-[`#C4CAD6`] bg-[`#F4F5F8`]">
+ <div className="mx-auto max-w-5xl px-6 py-6 text-sm text-[`#2C3A52`]">
+ © {new Date().getFullYear()} CODEDDEVS Technology LTD
+ </div>
+ </footer>
+ );
}📝 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.
| export default function Footer() { | |
| return null; | |
| } | |
| export default function Footer() { | |
| return ( | |
| <footer className="border-t border-[`#C4CAD6`] bg-[`#F4F5F8`]"> | |
| <div className="mx-auto max-w-5xl px-6 py-6 text-sm text-[`#2C3A52`]"> | |
| © {new Date().getFullYear()} CODEDDEVS Technology LTD | |
| </div> | |
| </footer> | |
| ); | |
| } |
🤖 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 1 - 3, The Footer component
currently returns null; replace that with a minimal compliant footer element
that applies the required styles: render a <footer> (or <div>) element inside
the Footer function and set className="bg-[`#F4F5F8`] border-t border-[`#C4CAD6`]"
(and any necessary accessibility attributes like role="contentinfo" if desired);
ensure the exported Footer function returns this styled element instead of null
so the layout/style contract is satisfied.
| export default function Navbar() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Navbar is missing the required homepage logo link.
Line 2 returns null, so the required /public/full-logo.png linked to / is not rendered.
Proposed fix
+import Image from "next/image";
+import Link from "next/link";
+
export default function Navbar() {
- return null;
+ return (
+ <nav className="w-full border-b border-[`#C4CAD6`] bg-white">
+ <div className="mx-auto flex max-w-5xl items-center px-6 py-4">
+ <Link href="/" aria-label="Go to homepage">
+ <Image
+ src="/full-logo.png"
+ alt="Coded Devs"
+ width={180}
+ height={40}
+ priority
+ />
+ </Link>
+ </div>
+ </nav>
+ );
}As per coding guidelines src/components/layout/Navbar.tsx: Use the logo file /public/full-logo.png in the Navbar always linked to / — never recreate the logo in code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/layout/Navbar.tsx` around lines 1 - 3, Navbar currently
returns null so the required homepage logo is not rendered; update the Navbar
component to return an anchor (<a>) linking to "/" that contains the logo image
from the public asset (use /public/full-logo.png per guidelines), i.e. render
the existing logo asset rather than recreating it in code inside the Navbar
function so the logo always appears and links to the homepage.
| export default function Button() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Button currently renders nothing, so the shared CTA primitive is unusable.
Please render an actual <button> with the repo’s primary button style and className merging via cn().
Suggested fix
+import type { ButtonHTMLAttributes } from "react";
+import { cn } from "@/lib/utils";
+
+type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
+
-export default function Button() {
- return null;
+export default function Button({ className, ...props }: ButtonProps) {
+ return (
+ <button
+ className={cn("bg-[`#121F38`] text-white hover:bg-[`#1A2D4F`]", className)}
+ {...props}
+ />
+ );
}As per coding guidelines **/*.{ts,tsx}: “Build all UI from scratch with Tailwind CSS” and “Apply primary button style: bg-[#121F38] text-white hover:bg-[#1A2D4F]”.
📝 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.
| export default function Button() { | |
| return null; | |
| } | |
| import type { ButtonHTMLAttributes, ReactNode } from "react"; | |
| import { cn } from "@/lib/utils"; | |
| type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & { | |
| children?: ReactNode; | |
| }; | |
| export default function Button({ className, children, ...props }: ButtonProps) { | |
| return ( | |
| <button | |
| className={cn("bg-[`#121F38`] text-white hover:bg-[`#1A2D4F`]", className)} | |
| {...props} | |
| > | |
| {children} | |
| </button> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/Button.tsx` around lines 1 - 3, Button currently returns
null; change it to render a real <button> element that accepts standard button
props and children (e.g., function Button({ className, children, ...props }:
React.ButtonHTMLAttributes<HTMLButtonElement>), use the existing cn(...) utility
to merge the incoming className with the repo primary button classes
"bg-[`#121F38`] text-white hover:bg-[`#1A2D4F`]" (and any base spacing/rounded
utility you use for primary CTAs), and spread ...props onto the button so it
supports onClick/disabled/aria etc.; keep the export default Button signature
and ensure you import React types and cn if not already present.
| export default function Card() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Card should render a container with the required card tokens, not null.
Right now any consumer receives no DOM output.
Suggested fix
+import type { HTMLAttributes } from "react";
+import { cn } from "@/lib/utils";
+
+type CardProps = HTMLAttributes<HTMLDivElement>;
+
-export default function Card() {
- return null;
+export default function Card({ className, ...props }: CardProps) {
+ return (
+ <div
+ className={cn("rounded-lg border border-[`#C4CAD6`] bg-[`#F4F5F8`] shadow-sm", className)}
+ {...props}
+ />
+ );
}As per coding guidelines **/*.{ts,tsx}: “Apply card style: bg-[#F4F5F8] border border-[#C4CAD6] rounded-lg with optional subtle shadow-sm”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/ui/Card.tsx` around lines 1 - 3, The Card component currently
returns null; change the exported function Card to render a wrapper element
(e.g., a div) that accepts and renders children and applies the required card
tokens: bg-[`#F4F5F8`] border border-[`#C4CAD6`] rounded-lg and an optional
shadow-sm; ensure the function signature (Card) forwards children and supports
merging any incoming className/props so consumers can compose additional classes
or attributes.
| export default function Input() { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Input is non-functional (null) and misses the required field styling contract.
This should render an <input> with the mandated tokens so form flows remain usable.
Suggested fix
+import type { InputHTMLAttributes } from "react";
+import { cn } from "@/lib/utils";
+
+type InputProps = InputHTMLAttributes<HTMLInputElement>;
+
-export default function Input() {
- return null;
+export default function Input({ className, ...props }: InputProps) {
+ return (
+ <input
+ className={cn("rounded-md border border-[`#C4CAD6`] bg-white text-[`#121F38`]", className)}
+ {...props}
+ />
+ );
}As per coding guidelines src/components/ui/Input.tsx: “Apply input field style: bg-white border border-[#C4CAD6] text-[#121F38] rounded-md”.
📝 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.
| export default function Input() { | |
| return null; | |
| } | |
| import type { InputHTMLAttributes } from "react"; | |
| import { cn } from "@/lib/utils"; | |
| type InputProps = InputHTMLAttributes<HTMLInputElement>; | |
| export default function Input({ className, ...props }: InputProps) { | |
| return ( | |
| <input | |
| className={cn("rounded-md border border-[`#C4CAD6`] bg-white text-[`#121F38`]", className)} | |
| {...props} | |
| /> | |
| ); | |
| } |
🤖 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 1 - 3, The Input component
currently returns null; replace it with a real input element that applies the
mandated styling and forwards props so forms work: implement export default
function Input(props: React.InputHTMLAttributes<HTMLInputElement>) (or use
React.forwardRef) and render an <input> element that spreads {...props} and
includes the required classes/bg tokens "bg-white border border-[`#C4CAD6`]
text-[`#121F38`] rounded-md" (add any default padding/width as needed) so the
Input component renders and preserves expected form behavior.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
.github/workflows/ci.yml (2)
22-24: ⚡ Quick win
pnpm/action-setup@v3is outdated — upgrade tov6.The official
pnpm/action-setupdocumentation and GitHub Marketplace now referencepnpm/action-setup@v6as the current version.v3is three major versions behind.⬆️ Proposed update
- uses: pnpm/action-setup@v3 + uses: pnpm/action-setup@v6🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 22 - 24, Replace the outdated GitHub Action reference pnpm/action-setup@v3 with the current release pnpm/action-setup@v6 in the workflow step that uses the pnpm action (the step containing uses: pnpm/action-setup@v3 and with: version: 10); update the uses value to pnpm/action-setup@v6 and keep/verify the existing with: version setting remains correct for Node version selection.
13-15: ⚡ Quick winNo
timeout-minutesset — jobs can block runners indefinitely ifpnpm buildorpnpm installhangs.A hung dependency fetch or infinite build loop will occupy the runner until GitHub's 6-hour hard limit. A reasonable cap (e.g., 15 minutes) reduces blast radius.
🛡️ Proposed addition
ci: runs-on: ubuntu-latest + timeout-minutes: 15🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml around lines 13 - 15, Add a per-job timeout to prevent runners from being blocked indefinitely: for the GitHub Actions job named "ci" (the jobs: ci block), add a timeout-minutes parameter (e.g., timeout-minutes: 15) to the job definition so the workflow aborts halted runs (apply under the ci job alongside runs-on).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 22-24: Replace the outdated GitHub Action reference
pnpm/action-setup@v3 with the current release pnpm/action-setup@v6 in the
workflow step that uses the pnpm action (the step containing uses:
pnpm/action-setup@v3 and with: version: 10); update the uses value to
pnpm/action-setup@v6 and keep/verify the existing with: version setting remains
correct for Node version selection.
- Around line 13-15: Add a per-job timeout to prevent runners from being blocked
indefinitely: for the GitHub Actions job named "ci" (the jobs: ci block), add a
timeout-minutes parameter (e.g., timeout-minutes: 15) to the job definition so
the workflow aborts halted runs (apply under the ci job alongside runs-on).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80832b2e-59f1-4494-8eeb-c69913e72568
📒 Files selected for processing (2)
.github/workflows/ci.ymlpnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (1)
- pnpm-workspace.yaml
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Summary by CodeRabbit
New Features
Documentation
Infrastructure