Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update introduces new React components and layouts for the dashboard's clients, invoices, and settings sections, along with their respective pages. It adds a POST API route for handling Clerk webhooks via Svix. The Prisma schema is updated with a new Boolean field in the User model, and a reusable Prisma client instance is provided. Package scripts and dependencies are also adjusted. The onboarding page is refactored to dynamically load an external onboarding component, which is newly created to handle multi-step onboarding logic. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User Browser
participant OnboardingPage as Onboarding Page (dynamic)
participant OnboardingComp as Onboarding Component
User->>OnboardingPage: Load onboarding page
OnboardingPage->>OnboardingComp: Dynamically import component
OnboardingComp->>User: Render onboarding UI with questions and options
User->>OnboardingComp: Select answers, navigate steps
OnboardingComp->>OnboardingComp: Update state and progress
OnboardingComp->>Console: Log answers on finish (placeholder for submission)
sequenceDiagram
participant Clerk as Clerk/Svix
participant API as /api/clerk/webhook
participant SvixLib as Svix Webhook
participant App as Application Logic
Clerk->>API: POST webhook with headers & body
API->>API: Extract Svix headers
API->>API: Parse request body as JSON
API->>SvixLib: Verify payload with headers & secret
alt Verification fails
SvixLib-->>API: Throws error
API-->>Clerk: 400 Bad Request
else Verification succeeds
SvixLib-->>API: Returns verified event
API->>App: Switch on event type (user.created, user.updated, user.deleted, etc.)
App-->>API: (Log or handle event)
API-->>Clerk: 200 OK
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–25 minutes Possibly related PRs
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
prisma/schema.prisma (1)
31-41: Field nameonBoardingDoneis likely a typo and is inconsistent with existing camel-case rules“Onboarding” is a single word; the capital B suggests two words.
Recommend renaming toonboardingDone(lower-case b) for consistency withcreatedAt,updatedAt,imageUrl, etc.- onBoardingDone Boolean @default(false) + onboardingDone Boolean @default(false)If data already exists in prod you must run a migration with
ALTER TABLE … RENAME COLUMN …or use@@mapto keep the physical column name unchanged.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
prisma/schema.prisma(1 hunks)src/app/(api)/api/clerk/webhook/route.ts(1 hunks)src/lib/prisma.ts(1 hunks)
🔇 Additional comments (1)
prisma/schema.prisma (1)
1-3: Removing thegenerator clientoutputpath can break consumers expecting the old locationAny build scripts or imports that referenced the custom output directory will now fail.
Please double-check allimport { PrismaClient } from '<old-path>'statements and CI scripts that cache the generated client.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/app/api/clerk/webhook/route.ts (3)
44-44: Replaceanytype with proper Clerk webhook event type.Using
anyeliminates type safety benefits. Consider defining or importing proper types for Clerk webhook events.-const webhookEvent = evt as any; +// TODO: Import proper Clerk webhook event types +const webhookEvent = evt as ClerkWebhookEvent;
15-15: Fix typo in error message.Minor spelling error in the error message.
-return new Response("Error occured -- no svix headers", { +return new Response("Error occurred -- no svix headers", {
38-38: Fix typo in error message.Minor spelling error in the error message.
-return new Response("Error occured", { +return new Response("Error occurred", {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (9)
package.json(3 hunks)src/app/(dashboard)/clients/layout.tsx(1 hunks)src/app/(dashboard)/clients/page.tsx(1 hunks)src/app/(dashboard)/invoices/layout.tsx(1 hunks)src/app/(dashboard)/invoices/page.tsx(1 hunks)src/app/(dashboard)/settings/layout.tsx(1 hunks)src/app/(dashboard)/settings/page.tsx(1 hunks)src/app/api/clerk/webhook/route.ts(1 hunks)src/lib/prisma.ts(1 hunks)
✅ Files skipped from review due to trivial changes (4)
- src/app/(dashboard)/invoices/layout.tsx
- src/app/(dashboard)/clients/layout.tsx
- src/app/(dashboard)/invoices/page.tsx
- src/app/(dashboard)/settings/layout.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: in next.js dashboard layouts, when user authentication fails, redirect users to the home page using ...
Learnt from: Izume01
PR: Izume01/Invoicify#6
File: src/app/(dashboard)/dashboard/layout.tsx:12-14
Timestamp: 2025-07-28T20:02:46.247Z
Learning: In Next.js dashboard layouts, when user authentication fails, redirect users to the home page using `redirect('/')` from 'next/navigation' instead of showing error messages for better UX.
Applied to files:
src/app/(dashboard)/clients/page.tsxsrc/app/(dashboard)/settings/page.tsx
🔇 Additional comments (8)
package.json (4)
10-10: LGTM! Automatic Prisma client generation.The
postinstallscript ensures the Prisma client is generated after package installation, which is essential for the updated schema to work correctly.
13-14: LGTM! Clean build commands for better development experience.The clean scripts help prevent Next.js caching issues during development and ensure fresh builds by removing the
.nextdirectory.
31-31: LGTM! Svix dependency for webhook verification.The
svixpackage is correctly added to support webhook verification in the Clerk webhook handler.
40-40: LGTM! Rimraf for cross-platform file deletion.The
rimrafdev dependency enables the clean scripts to work across different operating systems.src/app/(dashboard)/clients/page.tsx (1)
1-24: LGTM! Well-structured dashboard page component.The component follows React best practices with proper use of client directive, clear component structure, and appropriate placeholder content for future functionality. The Tailwind styling is consistent and accessible.
src/lib/prisma.ts (1)
1-10: LGTM! Correct Prisma client pattern for Next.js.The implementation properly follows the recommended pattern to prevent connection exhaustion in Next.js development by reusing the Prisma client instance via global storage. This matches Prisma's official documentation for Next.js integration.
src/app/(dashboard)/settings/page.tsx (1)
1-24: LGTM! Consistent dashboard page implementation.The settings page follows the same well-structured pattern as other dashboard pages, maintaining consistency in the codebase. Good use of placeholder content and proper React/Tailwind practices.
src/app/api/clerk/webhook/route.ts (1)
53-67: LGTM! Well-structured event handling with room for expansion.The switch statement properly handles different Clerk events with clear logging and placeholder comments for future implementation. This provides a solid foundation for user lifecycle management.
| const body = JSON.stringify(payload); | ||
|
|
||
| // Create a new Svix instance with your secret. | ||
| const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET || ""); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Validate webhook secret exists before creating Webhook instance.
Using an empty string fallback could cause silent failures. The webhook secret should be validated to exist.
-const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET || "");
+const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
+if (!webhookSecret) {
+ console.error("CLERK_WEBHOOK_SECRET is not set");
+ return new Response("Server configuration error", { status: 500 });
+}
+const wh = new Webhook(webhookSecret);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET || ""); | |
| const webhookSecret = process.env.CLERK_WEBHOOK_SECRET; | |
| if (!webhookSecret) { | |
| console.error("CLERK_WEBHOOK_SECRET is not set"); | |
| return new Response("Server configuration error", { status: 500 }); | |
| } | |
| const wh = new Webhook(webhookSecret); |
🤖 Prompt for AI Agents
In src/app/api/clerk/webhook/route.ts at line 25, the webhook secret is
currently defaulting to an empty string if the environment variable is missing,
which can cause silent failures. Before creating the Webhook instance, add a
check to ensure process.env.CLERK_WEBHOOK_SECRET is defined and not empty. If it
is missing, throw an error or handle it appropriately to prevent proceeding with
an invalid secret.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/components/layouts/Onboarding.tsx (3)
12-43: Fix variable naming - should be pluralThe variable name should be
onboardingQuestions(plural with proper camelCase) since it contains multiple questions.-const onboardingquestion = [ +const onboardingQuestions = [Also update all references to this variable throughout the component.
51-58: Handle completion logic and validate answersThe TODO comment indicates this needs backend implementation. Additionally, users can proceed without selecting answers, which may cause undefined values in the answers array.
Would you like me to help implement the completion logic with proper validation? Consider adding answer validation:
const nextStep = () => { + if (!answers[currentStep]) { + // Show validation error or prevent navigation + return; + } if (currentStep < onboardingquestion.length - 1) { setCurrentStep(currentStep + 1); } else { // Submit logic or redirect console.log("All answers submitted:", answers); } };
45-146: Consider extracting questions to a configuration fileThe onboarding questions are currently hardcoded in the component. For better maintainability and potential A/B testing, consider moving them to a separate configuration file or fetching them from an API.
This would allow:
- Easy updates without modifying the component
- A/B testing different onboarding flows
- Personalization based on user type
- Potential backend-driven configuration
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/(onboarding)/onboarding/page.tsx(1 hunks)src/components/layouts/Onboarding.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: in next.js dashboard layouts, when user authentication fails, redirect users to the home page using ...
Learnt from: Izume01
PR: Izume01/Invoicify#6
File: src/app/(dashboard)/dashboard/layout.tsx:12-14
Timestamp: 2025-07-28T20:02:46.247Z
Learning: In Next.js dashboard layouts, when user authentication fails, redirect users to the home page using `redirect('/')` from 'next/navigation' instead of showing error messages for better UX.
Applied to files:
src/app/(onboarding)/onboarding/page.tsxsrc/components/layouts/Onboarding.tsx
🔇 Additional comments (1)
src/app/(onboarding)/onboarding/page.tsx (1)
1-3: Verify the need to disable SSRDisabling server-side rendering can impact SEO and initial page load performance. Please ensure this is necessary (e.g., if the component uses browser-only APIs).
Consider enabling SSR if the component doesn't require browser-specific APIs:
-const OnboardingPage = dynamic(() => import("@/components/layouts/Onboarding"), { ssr: false }); +const OnboardingPage = dynamic(() => import("@/components/layouts/Onboarding"));
| export const page = () => { | ||
| return <OnboardingPage />; | ||
| } No newline at end of file |
There was a problem hiding this comment.
Remove redundant named export - this breaks Next.js conventions
The named export page is redundant and doesn't follow Next.js app directory conventions. Next.js expects a default export for page components.
Apply this diff to fix the issue:
-
-
-export const page = () => {
- return <OnboardingPage />;
-}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const page = () => { | |
| return <OnboardingPage />; | |
| } |
🤖 Prompt for AI Agents
In src/app/(onboarding)/onboarding/page.tsx around lines 8 to 10, the page
component is exported as a named export `page`, which breaks Next.js conventions
that require a default export for page components. Change the named export to a
default export by replacing `export const page` with `export default` to comply
with Next.js app directory requirements.
| </div> | ||
|
|
||
| <div className="my-6 sm:my-10"> | ||
| <h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2">Welcome, [First Name]! 👋</h1> |
There was a problem hiding this comment.
Replace placeholder with actual user data
The welcome message contains a hardcoded placeholder "[First Name]". This should display the actual user's name.
Consider passing user data as props:
-const Onboarding = () => {
+interface OnboardingProps {
+ userName?: string;
+}
+
+const Onboarding = ({ userName = "there" }: OnboardingProps) => {And update the welcome message:
-<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2">Welcome, [First Name]! 👋</h1>
+<h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2">Welcome, {userName}! 👋</h1>📝 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.
| <h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2">Welcome, [First Name]! 👋</h1> | |
| // Add this at the top of src/components/layouts/Onboarding.tsx | |
| interface OnboardingProps { | |
| userName?: string; | |
| } | |
| -const Onboarding = () => { | |
| +const Onboarding = ({ userName = "there" }: OnboardingProps) => { | |
| return ( | |
| <div> | |
| {/* …other markup… */} | |
| - <h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2">Welcome, [First Name]! 👋</h1> | |
| + <h1 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-2"> | |
| + Welcome, {userName}! 👋 | |
| + </h1> | |
| {/* …other markup… */} | |
| </div> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
In src/components/layouts/Onboarding.tsx at line 86, replace the hardcoded
placeholder "[First Name]" in the welcome message with the actual user's first
name. Pass the user data as props to the Onboarding component and update the JSX
to dynamically render the user's first name instead of the placeholder.
| {onboardingquestion[currentStep].options.map((option, index) => ( | ||
| <button | ||
| key={index} | ||
| className={`w-full text-left p-3 sm:p-4 rounded-lg transition-all duration-200 border-2 text-sm sm:text-base ${ | ||
| answers[currentStep] === option | ||
| ? "bg-orange-600 border-orange-500 text-white font-semibold" | ||
| : "bg-gray-800 border-gray-700 hover:bg-gray-700 hover:scale-105 hover:border-gray-600" | ||
| }`} | ||
| onClick={() => { | ||
| const newAnswers = [...answers]; | ||
| newAnswers[currentStep] = option; | ||
| setAnswers(newAnswers); | ||
| }} | ||
| > | ||
| {option} | ||
| </button> | ||
| ))} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enhance accessibility for option buttons
The option buttons lack proper accessibility attributes and keyboard navigation support.
Add ARIA attributes and improve keyboard navigation:
<button
key={index}
+ aria-pressed={answers[currentStep] === option}
+ role="radio"
+ aria-label={`Select ${option}`}
className={`w-full text-left p-3 sm:p-4 rounded-lg transition-all duration-200 border-2 text-sm sm:text-base ${
answers[currentStep] === option
? "bg-orange-600 border-orange-500 text-white font-semibold"
: "bg-gray-800 border-gray-700 hover:bg-gray-700 hover:scale-105 hover:border-gray-600"
}`}
onClick={() => {
const newAnswers = [...answers];
newAnswers[currentStep] = option;
setAnswers(newAnswers);
}}
>
{option}
</button>📝 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.
| {onboardingquestion[currentStep].options.map((option, index) => ( | |
| <button | |
| key={index} | |
| className={`w-full text-left p-3 sm:p-4 rounded-lg transition-all duration-200 border-2 text-sm sm:text-base ${ | |
| answers[currentStep] === option | |
| ? "bg-orange-600 border-orange-500 text-white font-semibold" | |
| : "bg-gray-800 border-gray-700 hover:bg-gray-700 hover:scale-105 hover:border-gray-600" | |
| }`} | |
| onClick={() => { | |
| const newAnswers = [...answers]; | |
| newAnswers[currentStep] = option; | |
| setAnswers(newAnswers); | |
| }} | |
| > | |
| {option} | |
| </button> | |
| ))} | |
| {onboardingquestion[currentStep].options.map((option, index) => ( | |
| <button | |
| key={index} | |
| + aria-pressed={answers[currentStep] === option} | |
| + role="radio" | |
| + aria-label={`Select ${option}`} | |
| className={`w-full text-left p-3 sm:p-4 rounded-lg transition-all duration-200 border-2 text-sm sm:text-base ${ | |
| answers[currentStep] === option | |
| ? "bg-orange-600 border-orange-500 text-white font-semibold" | |
| : "bg-gray-800 border-gray-700 hover:bg-gray-700 hover:scale-105 hover:border-gray-600" | |
| }`} | |
| onClick={() => { | |
| const newAnswers = [...answers]; | |
| newAnswers[currentStep] = option; | |
| setAnswers(newAnswers); | |
| }} | |
| > | |
| {option} | |
| </button> | |
| ))} |
🤖 Prompt for AI Agents
In src/components/layouts/Onboarding.tsx around lines 104 to 120, the option
buttons lack accessibility features. To fix this, add appropriate ARIA
attributes such as aria-pressed to indicate selection state, ensure each button
has a clear accessible name, and verify keyboard navigation works by using
native button elements properly. Also, consider adding role="radio" or grouping
buttons with role="radiogroup" if they represent mutually exclusive options to
improve screen reader support.
Summary by CodeRabbit