Skip to content

Setup the app and implement UI#1

Merged
prakashsingha merged 3 commits into
mainfrom
setup-and-ui
Apr 25, 2026
Merged

Setup the app and implement UI#1
prakashsingha merged 3 commits into
mainfrom
setup-and-ui

Conversation

@prakashsingha

@prakashsingha prakashsingha commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Book upload UI with PDF + optional cover support and voice selection
    • Search input for library filtering and a responsive book grid on the homepage
    • New hero landing, navigation bar, book cards and upload form components
    • Clerk-based auth UI and visual pricing-plan sync
  • Style

    • Comprehensive theme redesign, dark-mode class system, new fonts and global utilities
    • Updated app branding to "Bookify" and refreshed UI tokens and component styles

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d12de01-0e3d-48cc-a56b-e34c72e9313b

📥 Commits

Reviewing files that changed from the base of the PR and between 3aaf21a and 33d852f.

📒 Files selected for processing (6)
  • .stylelintrc.json
  • app/globals.css
  • app/layout.tsx
  • components/ClerkPricingActivePlanHighlight.tsx
  • components/Search.tsx
  • components/ui/button.tsx

📝 Walkthrough

Walkthrough

Adds a full frontend scaffold for "Bookify": new pages (home, book upload), Clerk authentication and middleware, UI components (Hero, Navbar, BookCard, UploadForm, Search), form validation with Zod/react-hook-form, a comprehensive global CSS theme, constants/types, utilities, and related config/package updates.

Changes

Cohort / File(s) Summary
Pages
app/(root)/page.tsx, app/(root)/books/new/page.tsx, app/page.tsx
Adds a new home page and a book creation page (upload UI); removes previous landing app/page.tsx.
Global Layout & Fonts
app/layout.tsx, app/globals.css
Wraps app in ClerkProvider, adds Navbar and Clerk pricing highlight component, imports additional Google fonts, rebrands metadata, and replaces theme with a class-based dark mode plus extensive Tailwind CSS variables/utilities (large CSS delta).
Navigation & Auth Middleware
components/Navbar.tsx, proxy.ts
Adds client-side Navbar integrating Clerk auth UI and active-link logic; adds Clerk middleware with route matcher config.
Core UI Components
components/HeroSection.tsx, components/BookCard.tsx, components/UploadForm.tsx, components/Search.tsx, components/ClerkPricingActivePlanHighlight.tsx
Introduces hero, book card, client upload form (react-hook-form + zod), search with debounced URL routing, and a DOM-sync component to highlight active Clerk pricing card.
UI Primitives & Form Helpers
components/ui/button.tsx, components/ui/Input.tsx, components/ui/form.tsx
Adds Button with CVA variants, Input primitive, and a react-hook-form wrapper (FormField/FormItem/FormControl/FormMessage etc.) for consistent form wiring.
Lib: Validation, Constants, Utils
lib/zod.ts, lib/constants.ts, lib/utils.ts, lib/assistant-id.ts
Adds Zod upload schema and inferred types, centralized constants (sampleBooks, file limits, voice options, Clerk appearance overrides, VAPI config), cn class helper, and an env-assisted getAssistantId() helper.
Type Declarations
types.d.ts
Adds shared TypeScript interfaces/types for DB documents, form values, UI props, and session/subscription results.
Tooling & Config
components.json, next.config.ts, .stylelintrc.json, package.json
Adds Shadcn UI config, allows remote Open Library images, Stylelint rule for Tailwind at-rules, and new dependencies (Clerk, react-hook-form, zod, CVA, clsx, tailwind-merge, lucide-react, etc.).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through fonts and color streams,
Wove forms of Zod and upload dreams,
Clerk lights the door, the Navbar hums tune,
Books bloom at dawn beneath the moon,
Hop in — the shelves are calling soon! 📚✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Setup the app and implement UI' is directly related to the main changes in the PR. The changeset includes app initialization (Clerk integration, layout restructuring, metadata updates), UI components (Navbar, HeroSection, BookCard, UploadForm, Search), styling (globals.css overhaul with Tailwind and dark mode support), and configuration files (components.json, next.config.ts). The title accurately captures the primary scope.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch setup-and-ui

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

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (7)
next.config.ts (1)

5-11: Optional hardening: narrow the allowed image path.

Consider adding pathname (e.g. /b/isbn/**) to reduce scope of remote image fetches.

💡 Example
       {
         protocol: "https",
         hostname: "covers.openlibrary.org",
+        pathname: "/b/isbn/**",
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.ts` around lines 5 - 11, The remote image pattern in
next.config.ts currently allows covers.openlibrary.org by protocol and hostname
only; tighten this by adding a pathname field (e.g. "/b/isbn/**") to the
images.remotePatterns entry so only the expected ISBN cover paths are
allowed—update the object that defines images.remotePatterns (the entry with
protocol: "https" and hostname: "covers.openlibrary.org") to include pathname:
"/b/isbn/**".
lib/zod.ts (1)

3-6: Consider consolidating schema constants to a single source of truth.

MAX_PDF_FILE_SIZE and voice IDs are defined locally while related constants exist in lib/constants.ts. Centralizing these avoids drift between UI options and validation rules.

Also applies to: 24-26

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

In `@lib/zod.ts` around lines 3 - 6, MAX_PDF_FILE_SIZE and voiceIds (and the other
schema constants referenced around lines 24-26) should be moved to the shared
constants module and imported from there instead of being redefined in
lib/zod.ts; update lib/constants.ts to export these constants (e.g., export
MAX_PDF_FILE_SIZE and export VOICE_IDS or similar), then replace the local
declarations in lib/zod.ts with imports and update any schema or validation
usage (references to MAX_PDF_FILE_SIZE and voiceIds) to use the imported symbols
so UI options and validation use the single source of truth.
lib/constants.ts (1)

105-143: Type-couple voice keys/categories/default to prevent drift.

voiceCategories and DEFAULT_VOICE are plain strings; tying them to keyof typeof voiceOptions improves safety.

💡 Suggested refactor
-export const voiceOptions = {
+export const voiceOptions = {
   ...
-};
+} as const;
+
+export type VoiceId = keyof typeof voiceOptions;

-export const voiceCategories = {
+export const voiceCategories: Record<"male" | "female", VoiceId[]> = {
   male: ["dave", "daniel", "chris"],
   female: ["rachel", "sarah"],
 };

-export const DEFAULT_VOICE = "rachel";
+export const DEFAULT_VOICE: VoiceId = "rachel";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/constants.ts` around lines 105 - 143, voiceOptions, voiceCategories and
DEFAULT_VOICE should be type-linked to avoid drift: add a derived type (e.g.
type VoiceKey = keyof typeof voiceOptions) and annotate voiceCategories and
DEFAULT_VOICE to use that type (e.g. voiceCategories as Record<'male'|'female',
VoiceKey[]> or a more general typed shape, and DEFAULT_VOICE: VoiceKey). Update
any places that import DEFAULT_VOICE or voiceCategories to satisfy the new types
if necessary. This ensures keys in voiceCategories and DEFAULT_VOICE are
constrained to the actual keys of voiceOptions.
components/ui/form.tsx (1)

93-104: Consider propagating ARIA attributes to the actual form control.

FormControl renders a div with id and ARIA attributes, but the actual input element inside doesn't receive these attributes directly. The htmlFor on FormLabel points to the div's id, not the input.

This works with the current dropzone implementation (where the div is the interactive element), but for standard inputs, the id should be on the <input> element for label clicking to focus the input.

For a more flexible approach, consider using Radix UI's Slot pattern here to pass these attributes to the child element:

function FormControl({ ...props }: React.ComponentProps<"div">) {
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
  const Comp = React.Children.count(props.children) === 1 ? Slot : "div";
  // ... rest of implementation
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/form.tsx` around lines 93 - 104, FormControl currently places
id and ARIA attributes on a wrapper div so labels (FormLabel) point to the div
instead of the real control; change FormControl to detect if it has a single
child and, when so, render attributes onto that child (use Radix Slot or
cloneElement) otherwise fall back to a div, and ensure you pass id,
aria-describedby and aria-invalid (from useFormField) to the child element so
labels and screen readers correctly target the actual input element; update
FormControl (and verify interaction with FormLabel) so the id used by formItemId
is applied to the focusable control rather than always to a wrapper div.
app/globals.css (2)

217-220: Consider extracting repeated font-family declaration to a utility class.

The font-family: "IBM Plex Serif", serif; declaration appears multiple times (.form-btn, .btn-primary, .library-cta-primary, etc.). This could be extracted to a reusable utility class.

♻️ Suggested refactor
.font-serif-brand {
  font-family: "IBM Plex Serif", serif;
}

Then apply via @apply font-serif-brand or use the class directly in components. This also aligns with the --font-serif CSS variable defined in the theme.

Also applies to: 278-281

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

In `@app/globals.css` around lines 217 - 220, Multiple components repeat
font-family: "IBM Plex Serif", serif (e.g. .form-btn, .btn-primary,
.library-cta-primary); extract this into a single utility class (e.g.
.font-serif-brand) that uses the --font-serif variable or the literal value,
then replace the repeated font-family declarations with `@apply` font-serif-brand
(or add the new class to the markup) in each selector (locate .form-btn,
.btn-primary, .library-cta-primary in the CSS and remove the inline font-family
there). Ensure the new utility is declared once in globals.css and used
consistently across the referenced selectors.

928-933: Fragile selector: :first-child assumes active plan card is always first.

As the comment notes, this selector will highlight the wrong card if Clerk changes the card order. A more robust approach would use JavaScript to detect the active plan and apply a class dynamically.

Consider wrapping the Clerk pricing component and using JavaScript to detect and mark the active plan card:

useEffect(() => {
  const activeCard = document.querySelector('.cl-pricingTableCard button:disabled')?.closest('.cl-pricingTableCard');
  activeCard?.classList.add('active-plan');
}, []);

Then style .active-plan instead of :first-child.

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

In `@app/globals.css` around lines 928 - 933, The CSS uses the fragile selector
.cl-pricingTableCard:first-child to mark the active plan; instead detect the
active card in JS and apply a stable class, e.g. in a useEffect look for
document.querySelector('.cl-pricingTableCard
button:disabled')?.closest('.cl-pricingTableCard') and add 'active-plan', then
replace the :first-child rule with .cl-pricingTableCard.active-plan { border:
2px solid `#663820` !important; } to ensure the right card is highlighted
regardless of DOM order.
components/UploadForm.tsx (1)

22-32: Voice options are duplicated from the schema's voiceIds.

The voiceOptions object hardcodes voice values that must stay in sync with voiceIds in lib/zod.ts. If someone adds a voice to the schema but forgets to update this component (or vice versa), validation will fail silently or options won't appear.

Consider exporting voiceIds from lib/zod.ts and deriving or validating voiceOptions against it.

♻️ Suggested approach

In lib/zod.ts, export voiceIds:

export const voiceIds = ["dave", "daniel", "chris", "rachel", "sarah"] as const;

Then in UploadForm.tsx, you can add a type-check:

+import { voiceIds } from "@/lib/zod";
+
 const voiceOptions = {
   male: [
-    { value: "dave", name: "Dave", description: "Warm and confident storyteller tone." },
+    { value: "dave" satisfies typeof voiceIds[number], name: "Dave", description: "Warm and confident storyteller tone." },
     // ... other options
   ],
   // ...
 } as const;

This ensures TypeScript will error if a voice value doesn't match the schema.

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

In `@components/UploadForm.tsx` around lines 22 - 32, voiceOptions currently
duplicates the schema's voiceIds causing drift; export the voiceIds const from
lib/zod.ts and import it into UploadForm.tsx, then derive or validate
voiceOptions from that authoritative list (e.g., build voiceOptions by mapping
voiceIds to option objects or assert that each value in voiceOptions matches the
exported voiceIds using the type typeof voiceIds[number]); update the
voiceOptions declaration (or add a runtime/compile-time check) so TypeScript
will error if a voice id is added/removed in the schema without corresponding
updates in UploadForm (reference symbols: voiceOptions and voiceIds).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`(root)/books/new/page.tsx:
- Line 10: Fix the typo in the subtitle string: locate the JSX in page.tsx that
contains the text "Upoad a PDF to generate your interactive interview" and
change "Upoad" to "Upload" so the user-facing subtitle reads "Upload a PDF to
generate your interactive interview".

In `@app/globals.css`:
- Around line 946-960: The CSS rule .cl-pricingTableCard:first-child
.cl-pricingTableCardContent::after currently hardcodes English via content:
"Your Current Plan" which breaks i18n; remove the content property from that
::after rule and instead render the indicator in React inside the pricing card
component (the element using class cl-pricingTableCardContent) so you can use
the app's translation hook/utility (e.g., t('your_current_plan')) and apply the
same styles via a dedicated element/class for the badge; update the component to
conditionally render the badge for the active/first plan and keep styling
(background-color, padding, border-radius, font-weight) in CSS, and add a short
TODO/tech-debt note if full i18n rollout is required.

In `@app/layout.tsx`:
- Around line 34-37: The CSS variable name set for the Mona_Sans font in
app/layout.tsx is misspelled; update the Mona_Sans call that assigns variable on
the monaSans constant to use "--font-mona-sans" (matching the reference in
app/globals.css) so the variable names align and the font applies correctly.
- Around line 11-12: The import of relative from "path" is incorrect for CSS;
remove the import statement for relative and replace any usage of the identifier
relative (e.g., inside the cn(...) call in app/layout.tsx) with the string
literal "relative" so the CSS class is applied correctly; ensure only the string
"relative" is passed to cn and no Node path functions remain referenced.
- Around line 52-70: Move the ClerkProvider inside the <body> so the root layout
renders <html> and <body> directly (wrap <Navbar /> and {children} with
ClerkProvider, not the entire html element), remove the accidental Node path
import "relative" and replace the token passed to cn(...) with the string
"relative" (ensure cn(...) includes "relative" CSS class rather than the
imported function), and rename the Mona Sans font variable from
"--font-mona-sana" to "--font-mona-sans" where the font is declared (update
monaSans.variable) so it matches the CSS variable used in globals.css; adjust
ibmPlexSerif.variable and monaSans.variable usage in the className call to use
the corrected variable names.

In `@components/Navbar.tsx`:
- Around line 67-75: The subscription Link is currently rendered only when
user?.firstName exists, which hides it for users with only a username; update
the render condition in the Show when="signed-in" block so the Link
(href="/subscription", className="nav-user-name") is shown whenever the user has
either user.username or user.firstName (e.g. check user?.username ||
user?.firstName) and keep the label fallback as user.username || user.firstName;
locate the block containing UserButton and the conditional using user?.firstName
to change the condition accordingly.
- Line 38: In the Navbar component update the nav element's Tailwind gap
utility: replace the incorrect token "gap7.5" in the className string (e.g., in
the nav with className "w-fit flex gap7.5 items-center") with the correct
hyphenated utility "gap-7.5" so Tailwind recognizes the spacing class.

In `@components/Search.tsx`:
- Around line 13-29: The effect that updates the URL uses router.push and
window.location.search and only seeds query from searchParams once; change
router.push to router.replace and use searchParams.toString() (not
window.location.search), add searchParams to the dependency array of the
debounced effect (use the same pathname and router deps), and add a separate
sync effect that watches searchParams and calls
setQuery(searchParams.get("query") || "") so the input updates on back/forward
navigation; keep the existing debounce timer logic and clearTimeout cleanup in
the debounced effect.

In `@components/ui/form.tsx`:
- Around line 62-71: The check for fieldContext happens after it's used, causing
a crash when fieldContext is the default empty object; move the null/undefined
guard for fieldContext (from FormFieldContext) to before any access to
fieldContext.name and before calling getFieldState; in function useFormField
ensure you validate fieldContext (and optionally itemContext) immediately and
throw the existing error if missing, then call useFormContext() and compute
fieldState via getFieldState(fieldContext.name, formState) only after confirming
fieldContext is present and has a name.

In `@lib/constants.ts`:
- Line 101: ASSISTANT_ID currently uses a non-null assertion which hides missing
config; replace the direct export of ASSISTANT_ID =
process.env.NEXT_PUBLIC_ASSISTANT_ID! with a runtime guard that reads
process.env.NEXT_PUBLIC_ASSISTANT_ID, throws a clear Error (e.g.,
"NEXT_PUBLIC_ASSISTANT_ID environment variable is required") if it's
undefined/empty, and otherwise exports the validated string; update any
references to ASSISTANT_ID if needed to use the validated export.

In `@lib/zod.ts`:
- Around line 16-21: The coverImage schema (z.instanceof(File) →
.refine(...).optional()) only checks MIME type and needs a size check: update
the refine chain on coverImage to also validate file.size against a max allowed
bytes (e.g., const MAX_IMAGE_BYTES = 5 * 1024 * 1024) and return a clear message
like "Image must be smaller than 5 MB" when too large; implement this either by
replacing the single refine with a combined predicate that checks both
file.type.startsWith("image/") and file.size <= MAX_IMAGE_BYTES, or add a second
.refine(...) after the MIME check that references the same File instance.

In `@package.json`:
- Line 19: Replace the invalid dependency "radix-ui" with the correct package
"@radix-ui/react-slot" in package.json and update the import site: in
components/ui/button.tsx change the import of Slot from "radix-ui" to import {
Slot } from "@radix-ui/react-slot"; ensure package.json dependency uses the
appropriate version (e.g., ^1.x) and run install so the Slot import resolves at
build/runtime.

In `@types.d.ts`:
- Line 5: Change the default zod import to the named import to match the
codebase: replace the existing import z from "zod" with the named import { z }
from "zod" so types and usages that expect the named symbol z (as in lib/zod.ts)
are consistent across the project.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 217-220: Multiple components repeat font-family: "IBM Plex Serif",
serif (e.g. .form-btn, .btn-primary, .library-cta-primary); extract this into a
single utility class (e.g. .font-serif-brand) that uses the --font-serif
variable or the literal value, then replace the repeated font-family
declarations with `@apply` font-serif-brand (or add the new class to the markup)
in each selector (locate .form-btn, .btn-primary, .library-cta-primary in the
CSS and remove the inline font-family there). Ensure the new utility is declared
once in globals.css and used consistently across the referenced selectors.
- Around line 928-933: The CSS uses the fragile selector
.cl-pricingTableCard:first-child to mark the active plan; instead detect the
active card in JS and apply a stable class, e.g. in a useEffect look for
document.querySelector('.cl-pricingTableCard
button:disabled')?.closest('.cl-pricingTableCard') and add 'active-plan', then
replace the :first-child rule with .cl-pricingTableCard.active-plan { border:
2px solid `#663820` !important; } to ensure the right card is highlighted
regardless of DOM order.

In `@components/ui/form.tsx`:
- Around line 93-104: FormControl currently places id and ARIA attributes on a
wrapper div so labels (FormLabel) point to the div instead of the real control;
change FormControl to detect if it has a single child and, when so, render
attributes onto that child (use Radix Slot or cloneElement) otherwise fall back
to a div, and ensure you pass id, aria-describedby and aria-invalid (from
useFormField) to the child element so labels and screen readers correctly target
the actual input element; update FormControl (and verify interaction with
FormLabel) so the id used by formItemId is applied to the focusable control
rather than always to a wrapper div.

In `@components/UploadForm.tsx`:
- Around line 22-32: voiceOptions currently duplicates the schema's voiceIds
causing drift; export the voiceIds const from lib/zod.ts and import it into
UploadForm.tsx, then derive or validate voiceOptions from that authoritative
list (e.g., build voiceOptions by mapping voiceIds to option objects or assert
that each value in voiceOptions matches the exported voiceIds using the type
typeof voiceIds[number]); update the voiceOptions declaration (or add a
runtime/compile-time check) so TypeScript will error if a voice id is
added/removed in the schema without corresponding updates in UploadForm
(reference symbols: voiceOptions and voiceIds).

In `@lib/constants.ts`:
- Around line 105-143: voiceOptions, voiceCategories and DEFAULT_VOICE should be
type-linked to avoid drift: add a derived type (e.g. type VoiceKey = keyof
typeof voiceOptions) and annotate voiceCategories and DEFAULT_VOICE to use that
type (e.g. voiceCategories as Record<'male'|'female', VoiceKey[]> or a more
general typed shape, and DEFAULT_VOICE: VoiceKey). Update any places that import
DEFAULT_VOICE or voiceCategories to satisfy the new types if necessary. This
ensures keys in voiceCategories and DEFAULT_VOICE are constrained to the actual
keys of voiceOptions.

In `@lib/zod.ts`:
- Around line 3-6: MAX_PDF_FILE_SIZE and voiceIds (and the other schema
constants referenced around lines 24-26) should be moved to the shared constants
module and imported from there instead of being redefined in lib/zod.ts; update
lib/constants.ts to export these constants (e.g., export MAX_PDF_FILE_SIZE and
export VOICE_IDS or similar), then replace the local declarations in lib/zod.ts
with imports and update any schema or validation usage (references to
MAX_PDF_FILE_SIZE and voiceIds) to use the imported symbols so UI options and
validation use the single source of truth.

In `@next.config.ts`:
- Around line 5-11: The remote image pattern in next.config.ts currently allows
covers.openlibrary.org by protocol and hostname only; tighten this by adding a
pathname field (e.g. "/b/isbn/**") to the images.remotePatterns entry so only
the expected ISBN cover paths are allowed—update the object that defines
images.remotePatterns (the entry with protocol: "https" and hostname:
"covers.openlibrary.org") to include pathname: "/b/isbn/**".
🪄 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 Plus

Run ID: 42a3a9ee-cc1c-417c-b058-75ab70580730

📥 Commits

Reviewing files that changed from the base of the PR and between cc9a3a0 and b3c41c5.

⛔ Files ignored due to path filters (15)
  • package-lock.json is excluded by !**/package-lock.json
  • public/assets/Gemini_Generated_Image_jlix6fjlix6fjlix (1) 1.png is excluded by !**/*.png
  • public/assets/banner.svg is excluded by !**/*.svg
  • public/assets/book-cover.svg is excluded by !**/*.svg
  • public/assets/book.png is excluded by !**/*.png
  • public/assets/desktop-banner.png is excluded by !**/*.png
  • public/assets/hero-illustration.png is excluded by !**/*.png
  • public/assets/image.png is excluded by !**/*.png
  • public/assets/loader.png is excluded by !**/*.png
  • public/assets/logo-full.svg is excluded by !**/*.svg
  • public/assets/logo.png is excluded by !**/*.png
  • public/assets/mobile-banner.png is excluded by !**/*.png
  • public/assets/open-book.svg is excluded by !**/*.svg
  • public/assets/tab-banner.png is excluded by !**/*.png
  • public/assets/wave.png is excluded by !**/*.png
📒 Files selected for processing (21)
  • app/(root)/books/new/page.tsx
  • app/(root)/page.tsx
  • app/globals.css
  • app/layout.tsx
  • app/page.tsx
  • components.json
  • components/BookCard.tsx
  • components/HeroSection.tsx
  • components/Navbar.tsx
  • components/Search.tsx
  • components/UploadForm.tsx
  • components/ui/Input.tsx
  • components/ui/button.tsx
  • components/ui/form.tsx
  • lib/constants.ts
  • lib/utils.ts
  • lib/zod.ts
  • next.config.ts
  • package.json
  • proxy.ts
  • types.d.ts
💤 Files with no reviewable changes (1)
  • app/page.tsx

Comment thread app/(root)/books/new/page.tsx Outdated
<section className="flex flex-col gap-5">
<h1 className="page-title-xl">Add a New Book</h1>
<p className="subtitle">
Upoad a PDF to generate your interactive interview

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in user-facing subtitle text.

Upoad should be Upload.

💡 Proposed fix
-            Upoad a PDF to generate your interactive interview
+            Upload a PDF to generate your interactive interview
📝 Committable suggestion

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

Suggested change
Upoad a PDF to generate your interactive interview
Upload a PDF to generate your interactive interview
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(root)/books/new/page.tsx at line 10, Fix the typo in the subtitle
string: locate the JSX in page.tsx that contains the text "Upoad a PDF to
generate your interactive interview" and change "Upoad" to "Upload" so the
user-facing subtitle reads "Upload a PDF to generate your interactive
interview".

Comment thread app/globals.css Outdated
Comment on lines 946 to 960
/* Add "Current Plan" indicator to active plan that doesn't have a button
Note: Hardcoded English text - if i18n support is needed, this should be
handled in JavaScript with translated strings rendered in the DOM. */
.cl-pricingTableCard:first-child .cl-pricingTableCardContent::after {
content: "Your Current Plan";
display: block;
text-align: center;
padding: 0.75rem 1.5rem;
margin-top: 1rem;
background-color: #f3e4c7 !important;
color: #212a3b !important;
font-weight: 600;
border-radius: 0.75rem;
font-size: 1.125rem;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English text in CSS will break i18n.

The content: "Your Current Plan" is hardcoded in CSS. As the comment acknowledges, this needs JavaScript handling for internationalization support.

If i18n is planned, track this as technical debt. The "Your Current Plan" indicator should be rendered via React with proper translation hooks rather than CSS ::after content.

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

In `@app/globals.css` around lines 946 - 960, The CSS rule
.cl-pricingTableCard:first-child .cl-pricingTableCardContent::after currently
hardcodes English via content: "Your Current Plan" which breaks i18n; remove the
content property from that ::after rule and instead render the indicator in
React inside the pricing card component (the element using class
cl-pricingTableCardContent) so you can use the app's translation hook/utility
(e.g., t('your_current_plan')) and apply the same styles via a dedicated
element/class for the badge; update the component to conditionally render the
badge for the active/first plan and keep styling (background-color, padding,
border-radius, font-weight) in CSS, and add a short TODO/tech-debt note if full
i18n rollout is required.

Comment thread app/layout.tsx Outdated
Comment thread app/layout.tsx
Comment thread app/layout.tsx Outdated
Comment thread components/ui/form.tsx
Comment thread lib/constants.ts Outdated
];

// Pre-configured VAPI assistant ID (hardcoded for this app)
export const ASSISTANT_ID = process.env.NEXT_PUBLIC_ASSISTANT_ID!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast if NEXT_PUBLIC_ASSISTANT_ID is missing.

Non-null assertion (!) masks configuration errors. Add an explicit guard so misconfiguration is caught immediately.

💡 Proposed fix
-export const ASSISTANT_ID = process.env.NEXT_PUBLIC_ASSISTANT_ID!;
+const assistantId = process.env.NEXT_PUBLIC_ASSISTANT_ID;
+if (!assistantId) {
+  throw new Error("Missing env: NEXT_PUBLIC_ASSISTANT_ID");
+}
+export const ASSISTANT_ID = assistantId;
📝 Committable suggestion

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

Suggested change
export const ASSISTANT_ID = process.env.NEXT_PUBLIC_ASSISTANT_ID!;
const assistantId = process.env.NEXT_PUBLIC_ASSISTANT_ID;
if (!assistantId) {
throw new Error("Missing env: NEXT_PUBLIC_ASSISTANT_ID");
}
export const ASSISTANT_ID = assistantId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/constants.ts` at line 101, ASSISTANT_ID currently uses a non-null
assertion which hides missing config; replace the direct export of ASSISTANT_ID
= process.env.NEXT_PUBLIC_ASSISTANT_ID! with a runtime guard that reads
process.env.NEXT_PUBLIC_ASSISTANT_ID, throws a clear Error (e.g.,
"NEXT_PUBLIC_ASSISTANT_ID environment variable is required") if it's
undefined/empty, and otherwise exports the validated string; update any
references to ASSISTANT_ID if needed to use the validated export.

Comment thread lib/zod.ts
Comment thread package.json Outdated
"clsx": "^2.1.1",
"lucide-react": "^1.8.0",
"next": "16.2.4",
"radix-ui": "^1.4.3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check how Radix is imported in source files
rg -nP --type=ts --type=tsx 'from\s+["'\''](`@radix-ui/react-`[^"'\''/]+|radix-ui)["'\'']'

# Check declared dependencies
jq '.dependencies | {"radix-ui": .["radix-ui"], "@radix-ui/react-slot": .["@radix-ui/react-slot"]}' package.json

# Verify package availability/version metadata (read-only)
npm view radix-ui version
npm view `@radix-ui/react-slot` version

Repository: prakashsingha/bookify

Length of output: 93


🏁 Script executed:

# Check package.json dependencies directly
cat package.json | jq '.dependencies'

# Search for Radix imports in TypeScript/JavaScript files with broader pattern
rg '@radix-ui|radix-ui' --type=ts -A 1 -B 1

# Also check with plain text search
rg 'from.*radix-ui' -A 1

Repository: prakashsingha/bookify

Length of output: 725


🏁 Script executed:

# List source files to understand the structure
fd -t f \( -name "*.ts" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.js" \) src/ | head -20

Repository: prakashsingha/bookify

Length of output: 296


🏁 Script executed:

# Find all Radix imports across the codebase
rg 'from\s+["\x27].*radix' --color=never

# Check if there are other component files importing from radix-ui
fd -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) | xargs grep -l "radix-ui" 2>/dev/null | head -10

Repository: prakashsingha/bookify

Length of output: 311


🌐 Web query:

npm package radix-ui Slot export

💡 Result:

The npm package for Radix UI's Slot utility is @radix-ui/react-slot. It provides the Slot component (also exported as Root), Slottable, and createSlot/createSlottable functions for merging props onto child elements, enabling asChild patterns in components. Install via: npm install @radix-ui/react-slot Usage example from official docs: import { Slot } from '@radix-ui/react-slot'; function Button({ asChild, ...props }) { const Comp = asChild ? Slot : 'button'; return <Comp {...props} />; } Source code exports: export { Slot, Slottable, Slot as Root }; export type { SlotProps }; Latest version as of Nov 2025: 1.2.4. Repository: https://github.com/radix-ui/primitives/tree/main/packages/react/slot. Official docs: https://www.radix-ui.com/primitives/docs/utilities/slot.

Citations:


Fix Radix UI dependency and import mismatch that will break the build.

The dependency "radix-ui": "^1.4.3" does not exist. The correct package is @radix-ui/react-slot. Update package.json to use "@radix-ui/react-slot" instead, and update the import in components/ui/button.tsx from import { Slot } from "radix-ui" to import { Slot } from "@radix-ui/react-slot". The current code will fail at build/runtime with a module not found error.

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

In `@package.json` at line 19, Replace the invalid dependency "radix-ui" with the
correct package "@radix-ui/react-slot" in package.json and update the import
site: in components/ui/button.tsx change the import of Slot from "radix-ui" to
import { Slot } from "@radix-ui/react-slot"; ensure package.json dependency uses
the appropriate version (e.g., ^1.x) and run install so the Slot import resolves
at build/runtime.

Comment thread types.d.ts Outdated
import { ReactNode } from "react";
import { Control, FieldPath, FieldValues } from "react-hook-form";
import { LucideIcon } from "lucide-react";
import z from "zod";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find default vs named zod imports
rg -nP --type=ts --type=tsx 'import\s+z\s+from\s+["'\'']zod["'\'']|import\s+\{\s*z\s*\}\s+from\s+["'\'']zod["'\'']'

Repository: prakashsingha/bookify

Length of output: 93


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Corrected search for zod imports - without the tsx file type
echo "=== Zod imports in project ==="
rg -n 'import\s+z\s+from\s+["'"'"']zod["'"'"']|import\s+\{\s*z\s*\}\s+from\s+["'"'"']zod["'"'"']' --type ts 2>/dev/null || rg -n 'import.*zod' 

echo ""
echo "=== tsconfig.json content ==="
if [ -f tsconfig.json ]; then cat -n tsconfig.json; else echo "Not found"; fi

echo ""
echo "=== package.json (zod section) ==="
if [ -f package.json ]; then jq '.dependencies, .devDependencies' package.json 2>/dev/null | grep -A 5 -B 5 zod || grep -i zod package.json; else echo "Not found"; fi

echo ""
echo "=== types.d.ts content ==="
if [ -f types.d.ts ]; then cat -n types.d.ts | head -20; else echo "Not found"; fi

Repository: prakashsingha/bookify

Length of output: 2107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full types.d.ts content ==="
cat -n types.d.ts

echo ""
echo "=== Check if 'z.' is used anywhere in types.d.ts ==="
grep -n 'z\.' types.d.ts || echo "No 'z.' usage found"

Repository: prakashsingha/bookify

Length of output: 4146


Use named Zod import for consistency with rest of codebase.

The project uses named import import { z } from "zod" in lib/zod.ts. For consistency, align types.d.ts Line 5 to use the same style.

Proposed fix
-import z from "zod";
+import { z } from "zod";

(Note: The default import works here due to esModuleInterop: true in tsconfig.json, but matching the named import style used elsewhere in the codebase is cleaner.)

📝 Committable suggestion

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

Suggested change
import z from "zod";
import { z } from "zod";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@types.d.ts` at line 5, Change the default zod import to the named import to
match the codebase: replace the existing import z from "zod" with the named
import { z } from "zod" so types and usages that expect the named symbol z (as
in lib/zod.ts) are consistent across the project.

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

✅ Created PR with unit tests: #2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
app/globals.css (1)

131-166: The new dark palette isn't reachable yet.

Nothing in app/layout.tsx or components/Navbar.tsx currently adds .dark to the root element, so these overrides never apply. Either wire theme state into <html> or defer this block until the app has a runtime entry point for it.

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

In `@app/globals.css` around lines 131 - 166, The .dark CSS variables are never
applied because no runtime code adds the "dark" class to the root element;
update your theme wiring in app/layout.tsx (or in components/Navbar.tsx's theme
toggle) to set or toggle document.documentElement.classList.add/remove('dark')
(or render <html className={theme}> server/client-side depending on your
approach) when the user selects dark mode, or move the .dark block into a
client-side stylesheet loaded after runtime so the class can be applied; target
the theme toggle logic in Navbar (theme change handler) or the root layout
(theme initialization/useEffect) to ensure the "dark" class is present on the
root element.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Around line 402-405: The stylelint rule is failing because raw declarations
immediately follow an `@apply` at-rule; for each affected selector (e.g.,
.vapi-cover-image and the other blocks noted) add a single blank line after the
`@apply` line so there is an empty line before the next raw declaration (the
box-shadow/property lines), ensuring each block has the `@apply` line, a blank
line, then the standard CSS declarations to satisfy
declaration-empty-line-before.
- Around line 4-6: Update the Stylelint config (.stylelintrc.json) to accept
Tailwind v4 at-rules by either disabling the unknown at-rule check (set
"at-rule-no-unknown" to false/null) or by extending a Tailwind-aware preset
(e.g., stylelint-config-tailwindcss) and adding the Tailwind directives to the
ignore list (include "@custom-variant" and "@theme" or "custom-variant" and
"theme" in the ignoreAtRules option) so the scss/at-rule-no-unknown errors for
`@custom-variant` and `@theme` are suppressed.
- Around line 932-937: Remove the heuristic selector that marks the first card
as active and only rely on the explicit marker: delete or stop using the
`.cl-pricingTableCard:first-child` rule and leave the style applied only to
`.cl-pricingTableCard.active-plan` (ensure the `.active-plan` class is added by
the pricing wrapper/JS instead of depending on :first-child); keep the existing
border rule on `.cl-pricingTableCard.active-plan` so only elements with the
explicit active marker get highlighted.

In `@components/Search.tsx`:
- Around line 46-52: The search input (the Input component with props
value={query} and onChange={(e) => setQuery(e.target.value)}) lacks an
accessible name; add an explicit aria-label (e.g., aria-label="Search books by
title or author") or associate a <label> with the input to ensure screen readers
can identify it, updating the Input props to include the aria-label or adding a
linked label element nearby.
- Around line 15-17: Remove the useEffect that calls
setQuery(searchParams.get("query") || "") to avoid stale-closure/dependency
issues; instead derive the initial query from searchParams synchronously (e.g.,
const initialQuery = searchParams.get("query") || "") and pass that into
useState for query, then update query only in the Input onChange handler and
push the debounced URL update from that handler (implement a debounce helper
used inside the onChange to update searchParams), and add aria-label="Search
books by title or author" to the Input element; reference the existing
useEffect, setQuery, searchParams, Input, and the component's onChange handler
when making these changes.

In `@components/ui/button.tsx`:
- Around line 42-61: The Button component currently renders a native <button>
(via Comp when asChild is false) without an explicit type, causing it to default
to "submit" in forms; update the Button render so that when asChild is false and
no type is provided in props, the rendered native button receives type="button"
(i.e., detect !asChild and props.type undefined and inject type="button" into
the props passed to Comp), leaving behavior unchanged when asChild is true or an
explicit type is supplied; check the Button function, Comp constant, and use of
{...props} to implement this guard.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 131-166: The .dark CSS variables are never applied because no
runtime code adds the "dark" class to the root element; update your theme wiring
in app/layout.tsx (or in components/Navbar.tsx's theme toggle) to set or toggle
document.documentElement.classList.add/remove('dark') (or render <html
className={theme}> server/client-side depending on your approach) when the user
selects dark mode, or move the .dark block into a client-side stylesheet loaded
after runtime so the class can be applied; target the theme toggle logic in
Navbar (theme change handler) or the root layout (theme
initialization/useEffect) to ensure the "dark" class is present on the root
element.
🪄 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 Plus

Run ID: ee2c52b5-683d-44af-9bf9-c102880fcc34

📥 Commits

Reviewing files that changed from the base of the PR and between b3c41c5 and 3aaf21a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • app/(root)/books/new/page.tsx
  • app/globals.css
  • app/layout.tsx
  • components/Navbar.tsx
  • components/Search.tsx
  • components/UploadForm.tsx
  • components/ui/button.tsx
  • components/ui/form.tsx
  • lib/assistant-id.ts
  • lib/constants.ts
  • lib/zod.ts
  • next.config.ts
  • package.json
  • types.d.ts
✅ Files skipped from review due to trivial changes (2)
  • next.config.ts
  • app/(root)/books/new/page.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • components/UploadForm.tsx
  • components/ui/form.tsx
  • package.json
  • components/Navbar.tsx
  • types.d.ts
  • lib/constants.ts
  • lib/zod.ts

Comment thread app/globals.css
Comment thread app/globals.css
Comment thread app/globals.css Outdated
Comment thread components/Search.tsx Outdated
Comment thread components/Search.tsx
Comment thread components/ui/button.tsx
@prakashsingha prakashsingha merged commit 413209f into main Apr 25, 2026
1 check was pending
This was referenced Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant