Skip to content

feat: DR-7749 client page#7703

Merged
carlagn merged 10 commits intomainfrom
feat/DR-7749-client
Mar 30, 2026
Merged

feat: DR-7749 client page#7703
carlagn merged 10 commits intomainfrom
feat/DR-7749-client

Conversation

@carlagn
Copy link
Copy Markdown
Contributor

@carlagn carlagn commented Mar 25, 2026

Summary by CodeRabbit

  • New Features

    • Launched a new client landing page featuring an interactive API explorer where users can browse Prisma examples in TypeScript and JavaScript.
    • Added showcase sections displaying supported databases and popular frameworks.
    • Introduced quick-access links to Prisma Studio and Prisma Migrate.
  • Style

    • Refined pricing page dropdown alignment and typography spacing.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Mar 30, 2026 8:38pm
docs Ready Ready Preview, Comment Mar 30, 2026 8:38pm
eclipse Ready Ready Preview, Comment Mar 30, 2026 8:38pm
site Ready Ready Preview, Comment Mar 30, 2026 8:38pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces a new Prisma Client landing page featuring an interactive API explorer with code samples, showcases supported databases and frameworks, and includes feature callouts for Prisma Studio and Migrate. Minor refinements are also applied to existing card and pricing components.

Changes

Cohort / File(s) Summary
New Client Landing Page
apps/site/src/app/client/page.tsx
New Next.js page exporting metadata and a full-featured Client component. Renders hero section, API explorer, two-column card layout with rich content, technology showcase cards for databases/frameworks, and feature callouts with call-to-action buttons.
Client API Explorer Components
apps/site/src/components/client/api.tsx, apps/site/src/components/client/api-data.ts, apps/site/src/components/client/technology.tsx
Introduces API explorer functionality with static data structure containing Prisma client usage examples across read/write/advanced operations. The API component manages language selection (TS/JS) and category filtering, asynchronously highlighting code samples via getHighlighter(). Technology component wraps buttons with tooltips for framework/database links.
Existing Component Updates
apps/site/src/components/homepage/card-section/card-section.tsx, apps/site/src/app/pricing/pricing-hero-plans.tsx
CardSection interface extended with optional noShadow property to conditionally suppress image shadows. PricingHeroPlans adjusts dropdown alignment, refactors className ternary logic, normalizes spacing in price suffix, and reflows disclaimer text.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat(site): add pricing page #7708: Both this PR and the retrieved PR modify apps/site/src/app/pricing/pricing-hero-plans.tsx, where this PR applies minor formatting and alignment refinements to the existing pricing hero component.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: DR-7749 client page' directly references the feature being implemented and the ticket number, making it clear what this PR accomplishes.

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


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

@argos-ci
Copy link
Copy Markdown

argos-ci bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Mar 30, 2026, 8:46 PM

Copy link
Copy Markdown
Contributor

@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: 8

🧹 Nitpick comments (6)
apps/site/src/components/client/api.tsx (2)

148-173: CodeUIItems is defined inside the component — recreated every render.

Defining a component inside another component means it gets a new identity on each render. This can cause unnecessary unmounting/remounting of the sub-tree. Move it outside API or memoize it.

Move component outside or convert to a function

Either move CodeUIItems to module scope (outside API), passing funcSelected and setFuncSelected as props, or inline it as a render function (without the JSX component pattern):

+const CodeUIItems = ({
+  item,
+  blockType,
+  funcSelected,
+  setFuncSelected,
+}: {
+  item: any;
+  blockType: string;
+  funcSelected: any;
+  setFuncSelected: (func: any) => void;
+}) => {
+  const labelToDisplay = item.functions.filter(
+    (i: any) => i[blockType] && i[blockType].length > 0,
+  );
+  return (
+    <ul className="flex flex-wrap my-5 mx-0 gap-2 list-none p-0">
+      {labelToDisplay.map((func: any, fIndex: number) => (
+        <li key={fIndex}>
+          <div
+            className={cn(
+              "rounded-full px-3 py-2 bg-background-default border border-stroke-neutral text-base uppercase font-sans-display font-bold tracking-wide leading-4 cursor-pointer transition-colors duration-300 ease-in-out hover:bg-foreground-orm text-foreground-neutral hover:text-foreground-neutral-reverse",
+              funcSelected === func &&
+                "bg-foreground-orm text-foreground-reverse-neutral",
+            )}
+            onClick={() => setFuncSelected(func)}
+          >
+            {func.name}
+          </div>
+        </li>
+      ))}
+    </ul>
+  );
+};

 const API = () => {
   // ... existing code ...
-  const CodeUIItems = ({ item, blockType }: any) => {
-    // ... remove inline definition ...
-  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 148 - 173, The
CodeUIItems component is recreated on every render because it's defined inside
API; move CodeUIItems to module scope (outside the API component) and change it
to accept props for funcSelected and setFuncSelected (and item, blockType) so it
no longer closes over API state, or alternatively wrap it with
React.memo/useCallback to memoize it; update where CodeUIItems is used inside
API to pass the props (funcSelected, setFuncSelected, item, blockType)
accordingly so the sub-tree is stable across renders.

100-110: JavaScript code is highlighted as TypeScript.

When the user selects JavaScript, the code is still highlighted with lang: "typescript" (line 105). While TypeScript highlighting usually works for JS, it can occasionally produce odd results for JS-specific patterns. Consider using the selected language:

Use the correct language for highlighting
           for (let index = 0; index < codeBlockSelected.length; index++) {
             const html = await highlighter.codeToHtml(
               codeBlockSelected[index],
               {
-                lang: "typescript",
+                lang: selectedLang.value === "js" ? "javascript" : "typescript",
                 theme: "prisma-dark",
               },
             );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 100 - 110, The
highlighter is hardcoded to lang: "typescript" in the block that checks
codeBlockSelected and funcSelected; change the call to highlighter.codeToHtml to
use the actual selected language (e.g., derive a language string from
funcSelected or the user's selection instead of the literal "typescript") so
JavaScript selections use "javascript" (or the appropriate value) — update the
lang property passed to highlighter.codeToHtml and ensure the code path that
checks for "prismaCodeBlock" in funcSelected still uses the dynamic language.
apps/site/src/components/homepage/card-section/card-section.tsx (1)

68-71: Intentional asymmetry? noShadow only affects desktop images.

The noShadow flag is applied to the desktop image (line 68-71) but not to the mobile image (line 81). If this is by design — perhaps mobile always needs the shadow for visual separation — then this is fine. Otherwise, you may want to apply the same conditional logic to the mobile image.

If mobile should also respect noShadow
                   {item.mobileImageUrl && (
                     <img
-                      className="w-full h-auto shadow-[0_10px_25px_-5px_rgba(0,0,0,0.1)] sm:hidden"
+                      className={cn(
+                        "w-full h-auto shadow-[0_10px_25px_-5px_rgba(0,0,0,0.1)] sm:hidden",
+                        item.noShadow && "shadow-none",
+                      )}
                       src={

Also applies to: 79-88

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

In `@apps/site/src/components/homepage/card-section/card-section.tsx` around lines
68 - 71, The desktop image className uses the item.noShadow conditional but the
mobile image does not, causing asymmetry; update the mobile image's className
(the one rendering with "sm:hidden" / mobile view) to include the same
conditional (item.noShadow && "shadow-none") so both images respect the noShadow
flag, and mirror this change for the other similar occurrence referenced around
lines 79-88 in card-section.tsx.
apps/site/src/lib/shiki_prisma.ts (2)

222-244: Potential race condition in highlighter initialization.

If getHighlighter() is called multiple times concurrently before the first call completes, each call will see prisma_highlighter as null and create a new highlighter. While not catastrophic (the last one assigned wins), it wastes resources loading the highlighter multiple times.

A simple fix is to cache the promise instead of the result:

Cache the promise to prevent duplicate initialization
-let prisma_highlighter: Awaited<ReturnType<typeof createHighlighter>> | null =
-  null;
+let highlighterPromise: ReturnType<typeof createHighlighter> | null = null;

 async function getHighlighter() {
-  if (!prisma_highlighter) {
-    prisma_highlighter = await createHighlighter({
+  if (!highlighterPromise) {
+    highlighterPromise = createHighlighter({
       themes: [prismaTheme],
       langs: [
         "typescript",
         "javascript",
         "jsx",
         "tsx",
         "json",
         "bash",
         "sh",
         "prisma",
         "sql",
         "diff",
       ],
     });
   }
-  return prisma_highlighter;
+  return highlighterPromise;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/lib/shiki_prisma.ts` around lines 222 - 244, The current
getHighlighter implementation can trigger multiple concurrent createHighlighter
calls because prisma_highlighter is null until the first await completes; change
the caching to store the initialization Promise instead of the resolved
result—e.g., make prisma_highlighter hold the Promise returned by
createHighlighter (or introduce prisma_highlighterPromise) and on first call
assign prisma_highlighter = createHighlighter({...}) and then await it in
getHighlighter before returning the resolved highlighter; update the
prisma_highlighter type accordingly so subsequent concurrent calls reuse the
same Promise and avoid duplicate initializations.

246-246: Confusing export alias.

Exporting getHighlighter as prisma_highlighter is misleading — consumers might expect prisma_highlighter to be the highlighter instance, not a function that returns one. Consider either:

  1. Export with a clearer name like getPrismaHighlighter, or
  2. Keep the original name getHighlighter
Clearer export naming
-export { getHighlighter as prisma_highlighter };
+export { getHighlighter };

Then update the import in api.tsx:

-import { prisma_highlighter as getHighlighter } from "@/lib/shiki_prisma";
+import { getHighlighter } from "@/lib/shiki_prisma";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/lib/shiki_prisma.ts` at line 246, The export alias
prisma_highlighter is misleading because it exports the function getHighlighter
rather than a highlighter instance; rename the export to a clearer function name
like getPrismaHighlighter (or re-export the original getHighlighter name) and
update any imports (e.g., in api.tsx) to use the new name; specifically change
the export statement exporting getHighlighter as prisma_highlighter to export it
as getPrismaHighlighter (or export { getHighlighter }) and then update all
usages/imports that reference prisma_highlighter to the new identifier.
apps/site/src/components/client/technology.tsx (1)

9-9: Remove unused useState import.

The useState hook is imported but never used in this component. This is dead code that should be cleaned up.

Remove unused import
-import { useState } from "react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/technology.tsx` at line 9, Remove the unused
React hook import by deleting the named import "useState" (the import statement
`import { useState } from "react";`) from the component file so there is no dead
code; if other React imports are needed, keep only the necessary ones, otherwise
remove the entire import line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/site/src/app/client/page.tsx`:
- Around line 169-173: The hero heading in apps/site/src/app/client/page.tsx
contains the incorrect second line "Database Migrations" inside the <h1> (the
element with className "stretch-display text-6xl font-bold text-center
font-sans-display z-2 relative max-w-223 mx-auto"); replace or remove that stray
text so the hero copy matches the Prisma Client page (e.g., remove "Database
Migrations" or change it to a Client-relevant phrase) and ensure the <h1>
content only contains accurate Prisma Client messaging.
- Around line 297-300: The button in the Migrate section has incorrect copy:
locate the Button element (the JSX element with variant="orm" size="3xl" and
className="w-fit") and update its inner span text from "Learn more about Prisma
Studio" to "Learn more about Prisma Migrate" (or equivalent migrate-specific
copy) so the CTA matches the Prisma Migrate section.
- Around line 239-251: The mapped list is missing a React key on the outer
Technology component causing reconciliation issues; move the key prop from the
inner Action to the Technology element in the frameworks.list.map callback
(e.g., set key={fw.name} or key={fw.id} on <Technology ...>) and remove it from
<Action> so each Technology child has a stable unique key during rendering.
- Around line 216-227: The map rendering uses databases.list.map with the outer
component Technology but the key prop is incorrectly placed on the inner Action;
move the unique key (e.g., key={db.name} or a more robust unique id) from the
Action component to the Technology component so the Technology element is the
keyed list item, and remove the duplicate key from Action (update the JSX where
Technology and Action are rendered).

In `@apps/site/src/components/client/api-data.ts`:
- Line 146: The example transaction snippet is truncated and starts mid-function
(showing locate, geo, tx.user.update) so update the source in ui/explore to
produce a complete interactive transaction example: ensure the snippet includes
the opening prisma.$transaction(async (tx) => { ... }), creation or lookup of
bob (e.g., await tx.user.create or await tx.user.findUnique to set bob), the
locate function definition (locate) and the final tx.user.update call so the
flow is contiguous; then regenerate the auto-generated file
(apps/site/src/components/client/api-data.ts) so the rendered snippet contains
the full transaction sequence (prisma.$transaction, bob, locate, geo,
tx.user.update).
- Line 56: The variable prolificAuthors is typed incorrectly: the result of
prisma.user.findMany(...) is annotated as Category[] but should be User[];
update the type annotation for prolificAuthors (and any related declarations)
from Category[] to User[] to match the return type of prisma.user.findMany and
avoid confusing mismatched types in the example.

In `@apps/site/src/components/client/api.tsx`:
- Around line 231-236: The onValueChange handler calls
handleCodeBlockChange(apiItems.find(...)) without checking the find result;
since Array.prototype.find can return undefined and handleCodeBlockChange
assumes a valid item (it accesses item.functions[0]), guard this by storing the
result of apiItems.find(...) in a local variable, check that it is not undefined
(and optionally that item.functions?.length > 0) before calling
handleCodeBlockChange, and only call handleCodeBlockChange with a valid item or
handle the missing-case (e.g., no-op or show fallback).

In `@apps/site/src/components/client/technology.tsx`:
- Line 27: The className on the JSX element in
apps/site/src/components/client/technology.tsx contains conflicting font utility
classes (font-sans-display! and font-mono!); pick the intended font for this
component (e.g., if you want the display font keep font-sans-display! and remove
font-mono!, or vice versa) and update the className on the element (the
className prop in the Technology component) so only the chosen font utility
remains.

---

Nitpick comments:
In `@apps/site/src/components/client/api.tsx`:
- Around line 148-173: The CodeUIItems component is recreated on every render
because it's defined inside API; move CodeUIItems to module scope (outside the
API component) and change it to accept props for funcSelected and
setFuncSelected (and item, blockType) so it no longer closes over API state, or
alternatively wrap it with React.memo/useCallback to memoize it; update where
CodeUIItems is used inside API to pass the props (funcSelected, setFuncSelected,
item, blockType) accordingly so the sub-tree is stable across renders.
- Around line 100-110: The highlighter is hardcoded to lang: "typescript" in the
block that checks codeBlockSelected and funcSelected; change the call to
highlighter.codeToHtml to use the actual selected language (e.g., derive a
language string from funcSelected or the user's selection instead of the literal
"typescript") so JavaScript selections use "javascript" (or the appropriate
value) — update the lang property passed to highlighter.codeToHtml and ensure
the code path that checks for "prismaCodeBlock" in funcSelected still uses the
dynamic language.

In `@apps/site/src/components/client/technology.tsx`:
- Line 9: Remove the unused React hook import by deleting the named import
"useState" (the import statement `import { useState } from "react";`) from the
component file so there is no dead code; if other React imports are needed, keep
only the necessary ones, otherwise remove the entire import line.

In `@apps/site/src/components/homepage/card-section/card-section.tsx`:
- Around line 68-71: The desktop image className uses the item.noShadow
conditional but the mobile image does not, causing asymmetry; update the mobile
image's className (the one rendering with "sm:hidden" / mobile view) to include
the same conditional (item.noShadow && "shadow-none") so both images respect the
noShadow flag, and mirror this change for the other similar occurrence
referenced around lines 79-88 in card-section.tsx.

In `@apps/site/src/lib/shiki_prisma.ts`:
- Around line 222-244: The current getHighlighter implementation can trigger
multiple concurrent createHighlighter calls because prisma_highlighter is null
until the first await completes; change the caching to store the initialization
Promise instead of the resolved result—e.g., make prisma_highlighter hold the
Promise returned by createHighlighter (or introduce prisma_highlighterPromise)
and on first call assign prisma_highlighter = createHighlighter({...}) and then
await it in getHighlighter before returning the resolved highlighter; update the
prisma_highlighter type accordingly so subsequent concurrent calls reuse the
same Promise and avoid duplicate initializations.
- Line 246: The export alias prisma_highlighter is misleading because it exports
the function getHighlighter rather than a highlighter instance; rename the
export to a clearer function name like getPrismaHighlighter (or re-export the
original getHighlighter name) and update any imports (e.g., in api.tsx) to use
the new name; specifically change the export statement exporting getHighlighter
as prisma_highlighter to export it as getPrismaHighlighter (or export {
getHighlighter }) and then update all usages/imports that reference
prisma_highlighter to the new identifier.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: faa94581-bd12-4e50-b6a2-68091d7d5c68

📥 Commits

Reviewing files that changed from the base of the PR and between edda9c3 and 2779863.

⛔ Files ignored due to path filters (8)
  • apps/site/public/icons/technologies/mariadb.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mongodbsimple.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mysqlsimple.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0_light.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1_light.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/site/package.json
  • apps/site/src/app/client/page.tsx
  • apps/site/src/app/layout.tsx
  • apps/site/src/components/client/api-data.ts
  • apps/site/src/components/client/api.tsx
  • apps/site/src/components/client/technology.tsx
  • apps/site/src/components/homepage/card-section/card-section.tsx
  • apps/site/src/lib/shiki_prisma.ts

@ArthurGamby
Copy link
Copy Markdown
Contributor

Nice one also!
Love the overall look, really.

A few comments:

when clicking on the dropdown to select the stack we have a horizontal window width jump due to scrollbar being hide/shown. Same behaviour with the second dropdown
the hight of the stack dropdown is bigger than the button "get started". I think if they are on the same line they can be same height.
Maybe just a nit, but I feel like the tags are quite big comparing the rest of the text / titles. Is this on the figma file ? (again maybe I am missing some context, just my opinion).

slack thread here

Copy link
Copy Markdown
Contributor

@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: 6

🧹 Nitpick comments (2)
apps/site/src/components/client/api.tsx (2)

60-62: Consider adding proper types instead of any.

Using any here bypasses TypeScript's type checking. Since apiItems has a defined structure, you could derive proper types from it or define an interface for the function objects.

Example type definition
interface APIFunction {
  name: string;
  jsCodeBlocks?: string[];
  tsCodeBlocks?: string[];
  prismaCodeBlock?: string;
}

const [funcSelected, setFuncSelected] = useState<APIFunction>(
  apiItems[0].functions[0],
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 60 - 62, Replace the
use of the unsafe any type for funcSelected/setFuncSelected by defining or
deriving a concrete function type and use it as the generic for useState; e.g.,
create an APIFunction interface (or derive via typeof
apiItems[number]['functions'][number]) that includes name, jsCodeBlocks?,
tsCodeBlocks?, prismaCodeBlock? and then change the declaration of
funcSelected/setFuncSelected to use
useState<APIFunction>(apiItems[0].functions[0]) so TypeScript can type-check
usages of funcSelected throughout the component.

148-173: Consider extracting CodeUIItems outside the component.

This inner component is recreated on every render of API. While it works, extracting it to the module level would be more efficient and is a React best practice for components that don't need closure over parent state (which this one does need via funcSelected and setFuncSelected).

Given it depends on parent state via closure, an alternative is to memoize it with useCallback for the click handler, but frankly the current approach is fine for this use case—just something to be aware of for larger components.

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

In `@apps/site/src/components/client/api.tsx` around lines 148 - 173, CodeUIItems
is declared inside the API component causing it to be recreated each render;
extract CodeUIItems to module scope and convert it to a pure component that
accepts props (at minimum: item, blockType, funcSelected, setFuncSelected) so it
no longer closes over API state, or alternatively keep it inside but memoize the
click handler with useCallback to avoid recreating handlers; update all usages
in API to pass funcSelected and setFuncSelected into the relocated CodeUIItems
component (refer to the CodeUIItems name and the funcSelected/setFuncSelected
symbols to implement the prop changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/site/src/app/client/page.tsx`:
- Around line 279-282: The Button rendered in page.tsx (the <Button> component
with className "w-fit" that contains "Learn more about Prisma Studio" and the
fa-arrow-right icon) lacks an href/destination; update that Button to provide a
proper link to the Prisma Studio docs (e.g. https://www.prisma.io/studio) by
passing the href prop or wrapping it with your router/link component as
appropriate (or using an anchorVariant of the Button) so clicking "Learn more
about Prisma Studio" navigates to the documentation.
- Around line 12-15: The metadata object currently uses generic SITE_HOME_TITLE
and SITE_HOME_DESCRIPTION; update it to Prisma Client–specific values by
replacing SITE_HOME_TITLE and SITE_HOME_DESCRIPTION with client-focused
constants or string literals (e.g., PRISMA_CLIENT_TITLE,
PRISMA_CLIENT_DESCRIPTION or appropriate text) so the exported metadata in
page.tsx reflects the Prisma Client page; ensure any new constants are
defined/imported where metadata is declared (or inline the strings) and keep the
exported symbol name metadata unchanged.
- Around line 254-263: The two CTA Buttons (the Button elements with
variant="default-stronger" size="3xl" and variant="orm" size="3xl") are styled
like links but lack href props; add appropriate href values so they navigate:
set the first Button's href to your GitHub examples URL (e.g. your repo or
examples page) and set the second Button's href to the "Prisma in your stack"
destination (e.g. the internal stack/guides route such as "/guides" or the exact
page route used in the app); keep the same Button props and children so only the
href attribute is added.
- Around line 17-61: The databases object uses placeholder urls (url: "/") for
every entry; update each entry in the databases.list array so each database
(e.g., "PostgreSQL", "MySQL", "MariaDB", "SQLite", "SQL Server", "CockroachDB",
"PlanetScale", "MongoDB") points to the correct documentation or site page
instead of "/", for example "/postgres", "/mysql", "/mariadb", "/sqlite",
"/sqlserver", "/cockroachdb", "/planetscale", "/mongodb" (or other canonical
paths your app uses); edit the databases constant in
apps/site/src/app/client/page.tsx (the databases object and its list entries) to
replace the placeholder urls with the appropriate real routes or doc links.

In `@apps/site/src/components/client/api.tsx`:
- Around line 100-111: The code hardcodes lang: "typescript" when calling
highlighter.codeToHtml inside the block that checks codeBlockSelected and
funcSelected; change it to use the actual selected language (e.g., use
selectedLang or a small mapping such as selectedLang === "js" ? "javascript" :
selectedLang === "ts" ? "typescript" : selectedLang) so
highlighter.codeToHtml(codeBlockSelected[index], { lang: chosenLang, theme:
"prisma-dark" }) uses the correct language; update the call site where
highlighter.codeToHtml is invoked and ensure chosenLang falls back to
"typescript" only if no selection exists.
- Around line 197-202: The onValueChange handler currently calls handleChange
with the result of langOptions.find which may be undefined; modify the handler
(the onValueChange arrow inside the select logic) to guard the find result
before calling handleChange—only call handleChange when langOptions.find(...)
returns a truthy value (or fall back to a safe default object), so selectedLang
is never set to undefined and the render that uses selectedLang remains safe.

---

Nitpick comments:
In `@apps/site/src/components/client/api.tsx`:
- Around line 60-62: Replace the use of the unsafe any type for
funcSelected/setFuncSelected by defining or deriving a concrete function type
and use it as the generic for useState; e.g., create an APIFunction interface
(or derive via typeof apiItems[number]['functions'][number]) that includes name,
jsCodeBlocks?, tsCodeBlocks?, prismaCodeBlock? and then change the declaration
of funcSelected/setFuncSelected to use
useState<APIFunction>(apiItems[0].functions[0]) so TypeScript can type-check
usages of funcSelected throughout the component.
- Around line 148-173: CodeUIItems is declared inside the API component causing
it to be recreated each render; extract CodeUIItems to module scope and convert
it to a pure component that accepts props (at minimum: item, blockType,
funcSelected, setFuncSelected) so it no longer closes over API state, or
alternatively keep it inside but memoize the click handler with useCallback to
avoid recreating handlers; update all usages in API to pass funcSelected and
setFuncSelected into the relocated CodeUIItems component (refer to the
CodeUIItems name and the funcSelected/setFuncSelected symbols to implement the
prop changes).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b6573bd2-4882-4326-91ba-1ac01182381f

📥 Commits

Reviewing files that changed from the base of the PR and between 2779863 and adbf0ec.

⛔ Files ignored due to path filters (8)
  • apps/site/public/icons/technologies/mariadb.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mongodbsimple.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mysqlsimple.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0_light.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1_light.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/site/package.json
  • apps/site/src/app/client/page.tsx
  • apps/site/src/app/layout.tsx
  • apps/site/src/components/client/api-data.ts
  • apps/site/src/components/client/api.tsx
  • apps/site/src/components/client/technology.tsx
  • apps/site/src/components/homepage/card-section/card-section.tsx
  • apps/site/src/lib/shiki_prisma.ts
✅ Files skipped from review due to trivial changes (4)
  • apps/site/package.json
  • apps/site/src/app/layout.tsx
  • apps/site/src/components/client/technology.tsx
  • apps/site/src/components/client/api-data.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/site/src/components/homepage/card-section/card-section.tsx
  • apps/site/src/lib/shiki_prisma.ts

Copy link
Copy Markdown
Contributor

@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: 2

♻️ Duplicate comments (2)
apps/site/src/components/client/api.tsx (2)

198-202: ⚠️ Potential issue | 🟡 Minor

Guard the find() result before calling the setters.

Both selects pass find(...) straight into handlers that later dereference .value or .functions[0]. If either lookup misses, the next render fails at Line 75 or Line 177.

🛡️ Proposed fix
              onValueChange={(value: string | null) => {
                if (value) {
-                  handleChange(
-                    langOptions.find((lang: any) => lang.value === value),
-                  );
+                  const lang = langOptions.find((option) => option.value === value);
+                  if (lang) {
+                    handleChange(lang);
+                  }
                }
              }}
...
              onValueChange={(value: string | null) => {
                if (value) {
-                  handleCodeBlockChange(
-                    apiItems.find((item: any) => item.value === value),
-                  );
+                  const item = apiItems.find((option) => option.value === value);
+                  if (item) {
+                    handleCodeBlockChange(item);
+                  }
                }
              }}

Also applies to: 232-236

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

In `@apps/site/src/components/client/api.tsx` around lines 198 - 202, The
onValueChange callbacks pass langOptions.find(...) directly into handleChange
(and similarly in the other select) which can return undefined and later cause
dereferences of .value or .functions[0]; update the onValueChange handlers to
first store the result of langOptions.find(...) in a local (e.g., selectedLang),
check that selectedLang is non-null/undefined, and only then call handleChange
or the other setter; if the find misses, either bail early or call the setter
with a safe fallback (null/default) so downstream code that expects .value or
.functions[0] cannot crash.

100-106: ⚠️ Potential issue | 🟡 Minor

Use the selected grammar when highlighting JS examples.

When selectedLang.value === "js", Line 105 still sends "typescript" to Shiki, so the JavaScript tab can render with the wrong grammar.

💡 Proposed fix
+        const shikiLang =
+          selectedLang.value === "js" ? "javascript" : "typescript";
+
         if (codeBlockSelected && !("prismaCodeBlock" in funcSelected)) {
           for (let index = 0; index < codeBlockSelected.length; index++) {
             const html = await highlighter.codeToHtml(
               codeBlockSelected[index],
               {
-                lang: "typescript",
+                lang: shikiLang,
                 theme: "prisma-dark",
               },
             );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 100 - 106, The
highlighter call hardcodes lang: "typescript" causing JS examples to be
highlighted incorrectly; change the lang passed to highlighter.codeToHtml to
derive from selectedLang.value (mapping "js" to "javascript" if needed) and use
that variable instead of the literal "typescript" in the block where
codeBlockSelected, funcSelected and highlighter.codeToHtml are used so the JS
tab renders with the correct grammar.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/site/src/components/client/api.tsx`:
- Around line 206-224: The SelectTrigger is using a custom height/padding that
doesn't match the adjacent Button (Button size="2xl"), causing a visual height
mismatch; update the SelectTrigger to use the same height token as the
Button—either pass the same size prop if SelectTrigger supports it or adjust its
className padding/height to match the Button's size="2xl" token (make
SelectTrigger's height/padding match Button) so both controls sit on the same
height scale; refer to SelectTrigger and Button(size="2xl") in the component to
locate and change the styling.
- Around line 157-165: The clickable function chip is a non-focusable div;
replace it with a semantic button element (the element using cn and onClick
calling setNewBlocks) so it is keyboard-focusable and accessible, add an
appropriate ARIA state like aria-pressed or aria-selected that reflects
(funcSelected === func), preserve the current className logic and event handler
(onClick={() => setNewBlocks()}) for the button, and ensure any focus/hover
styles remain intact; update any surrounding markup or imports if necessary to
handle a button instead of a div.

---

Duplicate comments:
In `@apps/site/src/components/client/api.tsx`:
- Around line 198-202: The onValueChange callbacks pass langOptions.find(...)
directly into handleChange (and similarly in the other select) which can return
undefined and later cause dereferences of .value or .functions[0]; update the
onValueChange handlers to first store the result of langOptions.find(...) in a
local (e.g., selectedLang), check that selectedLang is non-null/undefined, and
only then call handleChange or the other setter; if the find misses, either bail
early or call the setter with a safe fallback (null/default) so downstream code
that expects .value or .functions[0] cannot crash.
- Around line 100-106: The highlighter call hardcodes lang: "typescript" causing
JS examples to be highlighted incorrectly; change the lang passed to
highlighter.codeToHtml to derive from selectedLang.value (mapping "js" to
"javascript" if needed) and use that variable instead of the literal
"typescript" in the block where codeBlockSelected, funcSelected and
highlighter.codeToHtml are used so the JS tab renders with the correct grammar.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d2b61d3-5202-4987-9e6c-7dd179862fcb

📥 Commits

Reviewing files that changed from the base of the PR and between adbf0ec and 80126e4.

📒 Files selected for processing (2)
  • apps/site/src/app/pricing/pricing-hero-plans.tsx
  • apps/site/src/components/client/api.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/site/src/app/pricing/pricing-hero-plans.tsx

ArthurGamby
ArthurGamby previously approved these changes Mar 30, 2026
Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (4)
apps/site/src/components/client/api.tsx (4)

197-201: ⚠️ Potential issue | 🟡 Minor

Guard find() before updating selected language.

On Line 200, langOptions.find(...) may return undefined; guard before calling handleChange.

Suggested fix
-                  handleChange(
-                    langOptions.find((lang: any) => lang.value === value),
-                  );
+                  const lang = langOptions.find((l: any) => l.value === value);
+                  if (lang) handleChange(lang);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 197 - 201, The callback
onValueChange should guard the result of langOptions.find(...) before invoking
handleChange to avoid passing undefined; update the onValueChange handler to
store the result of langOptions.find((lang) => lang.value === value) in a
variable, check that it is truthy (or provide a safe fallback object) and only
then call handleChange(foundLang), otherwise return or handle the missing option
case. This change touches the onValueChange anonymous function and the call site
of handleChange/langOptions.find.

100-106: ⚠️ Potential issue | 🟡 Minor

Use the selected language when calling the highlighter.

On Line 105, lang is hardcoded to "typescript", so JavaScript examples are still highlighted as TS when selectedLang.value === "js".

Suggested fix
-                lang: "typescript",
+                lang: selectedLang.value === "js" ? "javascript" : "typescript",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 100 - 106, The
highlighter is using a hardcoded lang "typescript" when calling
highlighter.codeToHtml for codeBlockSelected (inside the block guarded by
codeBlockSelected and !("prismaCodeBlock" in funcSelected)); change the call to
pass the actual selected language (e.g., selectedLang.value or a small mapping
from "js" to "javascript"/"typescript" as your highlighter expects) instead of
the hardcoded "typescript" so JS examples are highlighted correctly; update the
lang argument in the highlighter.codeToHtml call accordingly.

231-235: ⚠️ Potential issue | 🟡 Minor

Guard apiItems.find() result before use.

On Line 234, find can return undefined, and handleCodeBlockChange assumes a valid item (item.functions[0]).

Suggested fix
-                handleCodeBlockChange(
-                  apiItems.find((item: any) => item.value === value),
-                );
+                const item = apiItems.find((i: any) => i.value === value);
+                if (item) handleCodeBlockChange(item);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/client/api.tsx` around lines 231 - 235, The result
of apiItems.find in the onValueChange handler can be undefined; before calling
handleCodeBlockChange you must guard the found item (from apiItems.find(...))
and only call handleCodeBlockChange when the item is non-null, or provide a safe
fallback (e.g., return early or pass a default object) so that
handleCodeBlockChange (which accesses item.functions[0]) never receives
undefined; update the onValueChange callback to check the find result and handle
the undefined case appropriately.

158-167: ⚠️ Potential issue | 🟠 Major

Use a semantic button for function chips.

This interactive control is a clickable <div>, which hurts keyboard accessibility and state semantics. Please use a <button type="button"> with aria-pressed.

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

In `@apps/site/src/components/client/api.tsx` around lines 158 - 167, Replace the
interactive <div> used for function chips with a semantic <button
type="button">: keep the className logic currently using cn(...) and the onClick
handler that calls setNewBlocks(), but move it onto the button element and add
aria-pressed={funcSelected === func} so assistive tech sees toggle state; ensure
the same conditional styling that depends on funcSelected === func (and uses
func.name for label) is preserved and that keyboard focus/activation now works
natively via the button element.
🧹 Nitpick comments (1)
apps/site/src/components/homepage/card-section/card-section.tsx (1)

66-69: Desktop shadow logic looks good.

The use of cn() to conditionally append shadow-none is the right approach—tailwind-merge will correctly resolve the conflicting shadow utilities.

One thing to consider: the mobile image (line 79) still applies the shadow unconditionally. If a consumer sets noShadow: true, they might expect it to apply to both breakpoints. If this is intentional (different mobile visual treatment), you're all set. If not, you may want to apply the same pattern to the mobile <img>:

♻️ Optional: apply noShadow to mobile image as well
                   <img
-                    className="w-full h-auto shadow-[0_10px_25px_-5px_rgba(0,0,0,0.1)] sm:hidden"
+                    className={cn(
+                      "w-full h-auto shadow-[0_10px_25px_-5px_rgba(0,0,0,0.1)] sm:hidden",
+                      item.noShadow && "shadow-none",
+                    )}
                     src={
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/site/src/components/homepage/card-section/card-section.tsx` around lines
66 - 69, The mobile <img> currently always has the shadow class while the
desktop image uses cn(...) with item.noShadow to conditionally add
"shadow-none"; update the mobile image's className to use the same cn(...)
pattern (referencing cn and item.noShadow) so that when item.noShadow is true
the mobile image also gets "shadow-none" and tailwind-merge resolves the
conflicting shadow utilities.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/site/src/components/client/api.tsx`:
- Around line 197-201: The callback onValueChange should guard the result of
langOptions.find(...) before invoking handleChange to avoid passing undefined;
update the onValueChange handler to store the result of langOptions.find((lang)
=> lang.value === value) in a variable, check that it is truthy (or provide a
safe fallback object) and only then call handleChange(foundLang), otherwise
return or handle the missing option case. This change touches the onValueChange
anonymous function and the call site of handleChange/langOptions.find.
- Around line 100-106: The highlighter is using a hardcoded lang "typescript"
when calling highlighter.codeToHtml for codeBlockSelected (inside the block
guarded by codeBlockSelected and !("prismaCodeBlock" in funcSelected)); change
the call to pass the actual selected language (e.g., selectedLang.value or a
small mapping from "js" to "javascript"/"typescript" as your highlighter
expects) instead of the hardcoded "typescript" so JS examples are highlighted
correctly; update the lang argument in the highlighter.codeToHtml call
accordingly.
- Around line 231-235: The result of apiItems.find in the onValueChange handler
can be undefined; before calling handleCodeBlockChange you must guard the found
item (from apiItems.find(...)) and only call handleCodeBlockChange when the item
is non-null, or provide a safe fallback (e.g., return early or pass a default
object) so that handleCodeBlockChange (which accesses item.functions[0]) never
receives undefined; update the onValueChange callback to check the find result
and handle the undefined case appropriately.
- Around line 158-167: Replace the interactive <div> used for function chips
with a semantic <button type="button">: keep the className logic currently using
cn(...) and the onClick handler that calls setNewBlocks(), but move it onto the
button element and add aria-pressed={funcSelected === func} so assistive tech
sees toggle state; ensure the same conditional styling that depends on
funcSelected === func (and uses func.name for label) is preserved and that
keyboard focus/activation now works natively via the button element.

---

Nitpick comments:
In `@apps/site/src/components/homepage/card-section/card-section.tsx`:
- Around line 66-69: The mobile <img> currently always has the shadow class
while the desktop image uses cn(...) with item.noShadow to conditionally add
"shadow-none"; update the mobile image's className to use the same cn(...)
pattern (referencing cn and item.noShadow) so that when item.noShadow is true
the mobile image also gets "shadow-none" and tailwind-merge resolves the
conflicting shadow utilities.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf62a6dc-6025-48e7-88fe-03703399d474

📥 Commits

Reviewing files that changed from the base of the PR and between e7bf524 and a9ba05f.

⛔ Files ignored due to path filters (7)
  • apps/site/public/icons/technologies/mariadb.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mongodbsimple.svg is excluded by !**/*.svg
  • apps/site/public/icons/technologies/mysqlsimple.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_0_light.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1.svg is excluded by !**/*.svg
  • apps/site/public/illustrations/client/client_1_light.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • apps/site/src/app/client/page.tsx
  • apps/site/src/app/pricing/pricing-hero-plans.tsx
  • apps/site/src/components/client/api-data.ts
  • apps/site/src/components/client/api.tsx
  • apps/site/src/components/client/technology.tsx
  • apps/site/src/components/homepage/card-section/card-section.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/site/src/components/client/api-data.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/site/src/components/client/technology.tsx
  • apps/site/src/app/client/page.tsx

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.

2 participants