Skip to content

Conversation

@Suraj-kumar00
Copy link
Owner

@Suraj-kumar00 Suraj-kumar00 commented Jul 25, 2025

Add Razorpay payments, study mode, search, and Prisma migration
this resolves #2 #3

#NEW

Summary by CodeRabbit

  • New Features

    • Added Razorpay payment integration for subscription management and payments, including order creation and payment verification.
    • Introduced a global search feature for flashcards and decks with advanced filtering and recent search history.
    • Added study mode for flashcards with progress tracking, spaced repetition, and session management.
    • Implemented error boundary, loading spinner, optimistic button, and skeleton UI components for enhanced user experience.
    • Added new API endpoints for deck/flashcard CRUD, study session management, search, and payment processing.
  • Enhancements

    • Refactored flashcard and deck management to use a new service layer and Prisma ORM for improved reliability.
    • Redesigned pricing page with updated plans, billing cycle toggle, and Razorpay payment flow.
    • Improved flashcard browsing and study UI with keyboard/touch navigation, responsive design, and accessibility.
    • Centralized and standardized type definitions for models, API, and UI components.
  • Bug Fixes

    • Fixed authentication handling to comply with Clerk v6, including async user checks in middleware and API routes.
    • Improved error handling and validation for API endpoints and payment flows.
  • Chores

    • Upgraded major dependencies (Next.js 15, React 19, Prisma, Clerk, etc.).
    • Migrated environment variables, removed unused files, and updated project scripts.
    • Cleaned up and reorganized codebase, removing deprecated Supabase and Stripe logic.
  • Documentation

    • Updated and consolidated type definitions for better maintainability and type safety.
  • Style

    • Refined UI elements, spacing, and responsive layouts across components.

This release delivers a robust flashcard and subscription experience with improved performance, reliability, and user interaction.

@vercel
Copy link
Contributor

vercel bot commented Jul 25, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
flash-fathom-ai ❌ Failed (Inspect) Jul 25, 2025 6:23pm

@coderabbitai
Copy link

coderabbitai bot commented Jul 25, 2025

Walkthrough

This update implements a major refactor and feature expansion for a flashcard application. Key changes include: introducing Razorpay payment integration and subscription management, replacing Stripe and Supabase with Prisma and Clerk-based authentication, modularizing flashcard and deck management, adding global search and study session features, improving UI and error handling, and centralizing type definitions.

Changes

File(s) / Group Change Summary
.env.example Updated API keys and database URL; added Razorpay config variables.
next.config.js, next.config.mjs Migrated config to Next.js 15 stable; deleted obsolete .mjs file.
package.json, pnpm-workspace.yaml Updated scripts, dependencies, and added workspace config.
prisma/migrations/* Deleted old migrations; added new migration for payment/subscription schema.
prisma/schema.prisma Extended schema for payments, subscriptions, and updated user relations.
src/app/api/decks/*, src/app/api/flashcards/* Added API routes for deck CRUD and flashcard fetching.
src/app/api/generate/route.ts Refactored to use service layer, added auth and validation.
src/app/api/razorpay/* Added Razorpay order creation and payment verification endpoints.
src/app/api/search/route.ts Added global search API for flashcards and decks.
src/app/api/study/* Added study session start, record, and complete endpoints.
src/app/flashcards/* Refactored flashcard pages for modularity, study mode, and improved UI.
src/app/layout.tsx Made layout async, fixed imports, added Toaster.
src/app/pricing/page.ts Refactored for Razorpay, new plans, improved UI and feedback.
src/app/result/page.tsx Enhanced result page with Suspense, skeletons, and richer feedback.
src/components/FAQ.tsx, ThemeProvider.tsx Minor import/type fixes.
src/components/common/* Added ErrorBoundary, LoadingSpinner, OptimisticButton components.
src/components/core/Navigation.tsx Integrated GlobalSearch in navigation.
src/components/core/flash-card.tsx Refactored to use modular flashcard components and local storage.
src/components/decks/* Added DeckCard and DeckList components for deck display and deletion.
src/components/flashcards/* Added modular flashcard generator, viewer, and save dialog.
src/components/search/GlobalSearch.tsx Added a global search component with filters, highlighting, and recent searches.
src/components/study/StudyMode.tsx Added a study mode component for interactive flashcard review.
src/components/ui/* Added/updated badge, button, dialog, dropdown, input, progress, skeleton components.
src/config/site.ts Cleared out external links and support email.
src/hooks/useDebounce.ts, src/lib/hooks/* Added debounce, localStorage, and optimistic mutation hooks.
src/lib/analytics.ts Added Analytics singleton for event tracking.
src/lib/auth.ts, src/lib/database.ts, src/lib/db.ts, src/lib/prisma.ts, src/lib/flashcards.ts Switched to a unified Prisma client import; removed old DB files.
src/lib/services/flashcard.service.ts, payment.service.ts, user.service.ts Added service layers for flashcards, payments, and users.
src/lib/supabase.ts, src/types/supabase.ts Removed Supabase integration and types.
src/lib/utils/lazy-imports.ts, src/utils/get-razorpay.ts, src/utils/get-stripe.ts Added lazy loading utils and Razorpay loader; removed Stripe loader.
src/middleware.ts Updated for Clerk v6 auth, async user checks, and redirects.
src/types/index.d.ts, src/types/index.ts, src/types/prisma.d.ts Centralized and modernized type definitions; removed obsolete type files.
tsconfig.json Set target to ES2017, reformatted arrays.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant API
    participant FlashcardService
    participant PaymentService
    participant PrismaDB
    participant ClerkAuth
    participant Razorpay

    User->>UI: Interacts (e.g., generate flashcards, start study, subscribe)
    UI->>API: Sends request (e.g., /api/generate, /api/razorpay/create-order)
    API->>ClerkAuth: Authenticate user
    alt Authenticated
        API->>FlashcardService: (e.g., generateFlashcards, createDeckWithFlashcards)
        FlashcardService->>PrismaDB: Persist/read data
        API->>PaymentService: createPaymentOrder (for subscription)
        PaymentService->>Razorpay: Create order
        Razorpay-->>PaymentService: Order details
        PaymentService-->>API: Order info
        API-->>UI: Response (e.g., flashcards, order info)
    else Not Authenticated
        API-->>UI: 401 Unauthorized
    end
    UI->>Razorpay: Open checkout (for payment)
    Razorpay-->>UI: Payment result
    UI->>API: /api/razorpay/verify-payment (with payment details)
    API->>PaymentService: verifyPayment
    PaymentService->>Razorpay: Verify signature
    Razorpay-->>PaymentService: Verification result
    PaymentService->>PrismaDB: Update user/payment status
    API-->>UI: Verification result (subscription activated)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

  • Complexity: Extensive refactor, schema redesign, new payment integration, modularization, new features (search, study mode), and type centralization.
  • Scope: High—affects core business logic, database, authentication, payment, UI/UX, and error handling.
  • Volume: Dozens of files, many with high-complexity changes.

Poem

🐇✨

A rabbit hopped through fields of code,
Refactoring decks, where flashcards strode.
With Razorpay’s magic, subscriptions bloom,
Global search now banishes gloom!
Study mode shines, UI’s refined—
“Review me well!” each card opined.
Bravo, dear devs—your garden’s aligned!

🌱📚💳🔍

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

next.config.js

Oops! Something went wrong! :(

ESLint: 9.31.0

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

src/app/api/decks/save/route.ts

Oops! Something went wrong! :(

ESLint: 9.31.0

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

src/app/api/flashcards/[id]/route.ts

Oops! Something went wrong! :(

ESLint: 9.31.0

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

  • 51 others

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Suraj-kumar00 Suraj-kumar00 self-assigned this Jul 25, 2025
@Suraj-kumar00 Suraj-kumar00 added the enhancement New feature or request label Jul 25, 2025
@Suraj-kumar00 Suraj-kumar00 merged commit 60bfff5 into main Jul 25, 2025
2 of 10 checks passed
@Suraj-kumar00 Suraj-kumar00 deleted the feature branch July 25, 2025 18:29
Copy link

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

♻️ Duplicate comments (2)
prisma/schema.prisma (1)

107-110: plan / billingCycle repeat string smell identified above

The same enum recommendation applies to these columns to keep backend code and DB in sync.

prisma/migrations/20250725171641_complete_with_adding_razorpay/migration.sql (1)

113-118: Unique on nullable razorpayPaymentId repeats the Prisma-level problem

The raw migration mirrors the schema concern; multiple pending payments will fail. Drop the unique or convert it to a partial index:

CREATE UNIQUE INDEX "Payment_razorpayPaymentId_key"
ON "Payment" ("razorpayPaymentId")
WHERE "razorpayPaymentId" IS NOT NULL;
🧹 Nitpick comments (40)
src/config/site.ts (1)

12-15: Empty external links may break footer / SEO components

twitter, github, and portfolio are now empty strings.
If downstream components build <a> tags blindly, they’ll render invalid hrefs (href=""). Either:

  1. Remove the keys completely when not configured, or
  2. Update UI to hide the links when value is falsy.

Please verify behaviour before shipping.

src/app/api/study/complete/route.ts (1)

31-31: Document duration unit assumption.

The code assumes duration is in milliseconds but this isn't documented, which could lead to incorrect conversions if callers pass different units.

-        duration: Math.floor(duration / 1000), // Convert to seconds
+        duration: Math.floor(duration / 1000), // Convert from milliseconds to seconds
src/lib/hooks/useLocalStorage.ts (1)

1-31: Consider adding storage event synchronization.

Multiple instances of this hook with the same key won't stay synchronized when localStorage is updated from other tabs or components.

Add a storage event listener to synchronize across instances:

  useEffect(() => {
    try {
      if (typeof window === 'undefined') return;
      
      const item = window.localStorage.getItem(key);
      if (item) {
        setStoredValue(JSON.parse(item));
      }
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
    }
+
+    const handleStorageChange = (e: StorageEvent) => {
+      if (e.key === key && e.newValue !== null) {
+        try {
+          setStoredValue(JSON.parse(e.newValue));
+        } catch (error) {
+          console.warn(`Error parsing storage event for key "${key}":`, error);
+        }
+      }
+    };
+
+    window.addEventListener('storage', handleStorageChange);
+    return () => window.removeEventListener('storage', handleStorageChange);
  }, [key]);
src/app/api/decks/[id]/route.ts (1)

18-23: Consider adding cascade deletion documentation.

While Prisma will likely handle cascade deletion of related flashcards through schema constraints, it would be helpful to document this behavior.

-    // Delete deck and all associated flashcards
+    // Delete deck and all associated flashcards (cascaded by database constraints)
    await prisma.deck.delete({
      where: {
        id: params.id,
        userId: userId // Ensure user owns the deck
      }
    });
.env.example (3)

14-14: Remove unnecessary quotes from environment variable value.

Environment variable values in .env files don't need quotes unless they contain spaces or special characters.

-GEMINI_API_KEY=""
+GEMINI_API_KEY=

25-25: Remove quotes from DATABASE_URL.

Database URLs typically don't require quotes in .env files.

-DATABASE_URL="postgresql://postgres:[email protected]:5432/postgres"
+DATABASE_URL=postgresql://postgres:[email protected]:5432/postgres

27-30: Fix Razorpay configuration formatting and ordering.

Multiple formatting issues: unnecessary quotes and incorrect key ordering for Next.js public variables.

 # Razorpay Configuration
+NEXT_PUBLIC_RAZORPAY_KEY_ID=your_razorpay_key_id
-RAZORPAY_KEY_ID="your_razorpay_key_id"
-RAZORPAY_KEY_SECRET="your_razorpay_key_secret"
-NEXT_PUBLIC_RAZORPAY_KEY_ID="your_razorpay_key_id"
+RAZORPAY_KEY_ID=your_razorpay_key_id
+RAZORPAY_KEY_SECRET=your_razorpay_key_secret
+
src/components/decks/DeckCard.tsx (1)

27-31: Consider adding error handling for date formatting.

The date formatting could potentially throw an error if deck.createdAt is invalid. Consider adding error handling or validation.

-                  Created: {new Date(deck.createdAt).toLocaleDateString()}
+                  Created: {new Date(deck.createdAt).toLocaleDateString() || 'Invalid date'}
src/lib/database.ts (1)

16-25: Consider adding connection cleanup and more robust error handling.

The test connection function is useful, but consider these improvements:

 export async function testDatabaseConnection() {
   try {
     await prisma.$connect()
     console.log('✅ Database connected successfully')
     return true
   } catch (error) {
-    console.error('❌ Database connection failed:', error)
+    console.error('❌ Database connection failed:', error instanceof Error ? error.message : 'Unknown error')
     return false
+  } finally {
+    await prisma.$disconnect()
   }
 }
src/lib/hooks/useOptimisticMutation.ts (1)

44-44: Consider improving error type assertion.

The error casting could be more robust to handle edge cases where the error might not be an Error instance.

-      onError?.(error as Error, variables);
+      onError?.(error instanceof Error ? error : new Error('Unknown error'), variables);
src/app/api/decks/save/route.ts (1)

37-38: Consider adding request validation for required fields.

Add validation to ensure the request body exists and contains the expected structure before parsing:

+    if (!body) {
+      const error: ApiError = { error: 'Request body is required' };
+      return NextResponse.json(error, { status: 400 });
+    }
+
     const validatedData = saveFlashcardsSchema.parse(body);
src/app/api/study/record/route.ts (2)

55-72: Simple but effective spaced repetition algorithm.

The algorithm provides a reasonable starting point for spaced repetition with different intervals based on performance and difficulty. Consider enhancing it with:

  • More sophisticated algorithms like SM-2 or Anki's algorithm
  • User-specific learning patterns
  • Exponential backoff for repeated failures

For now, the current implementation is functional and appropriate for an MVP.


62-71: Consider adding validation for difficulty values.

The switch statement should handle invalid difficulty values more explicitly:

   switch (difficulty) {
     case 'EASY':
       return baseInterval * 7; // 7 days
     case 'MEDIUM':
       return baseInterval * 3; // 3 days
     case 'HARD':
       return baseInterval * 1; // 1 day
     default:
-      return baseInterval * 2; // 2 days
+      console.warn(`Unknown difficulty: ${difficulty}, defaulting to 2 days`);
+      return baseInterval * 2; // 2 days for unknown difficulty
   }
src/app/layout.tsx (1)

20-20: Consider the timing of async conversion.

While preparing the layout for future async operations is thoughtful, converting to async without immediate need could be considered premature. However, the commented examples provide good guidance for when this becomes necessary.

Also applies to: 25-27

src/utils/get-razorpay.ts (1)

1-5: Consider using proper Razorpay types.

The global Window interface extension is necessary, but using any for the Razorpay property reduces type safety. Consider installing @types/razorpay or defining a more specific interface if available.

 declare global {
   interface Window {
-    Razorpay: any;
+    Razorpay: typeof import('razorpay') | undefined;
   }
 }
src/components/decks/DeckList.tsx (1)

18-49: Robust deletion handler with excellent error handling.

The implementation includes all necessary safeguards:

  • Race condition prevention
  • Comprehensive error handling
  • User feedback via toast notifications
  • Proper state cleanup

Consider adding response status checking for more specific error messages:

       if (!response.ok) {
-        throw new Error('Failed to delete deck');
+        const errorData = await response.json().catch(() => ({}));
+        throw new Error(errorData.message || 'Failed to delete deck');
       }
src/components/ui/badge.tsx (1)

1-4: Consider adding "use client" directive.

The component imports are clean, but consider whether this component needs the "use client" directive for consistency with other UI components, especially if it will be used in interactive contexts.

src/components/flashcards/FlashcardSaveDialog.tsx (1)

44-61: Consider adding request timeout and better error handling

The fetch request lacks timeout handling and could benefit from more specific error handling.

Add timeout and improve error handling:

    setIsLoading(true);
    try {
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
+      
      const response = await fetch('/api/decks/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
+        signal: controller.signal,
        body: JSON.stringify({
          name: deckName.trim(),
          flashcards: flashcards.map(card => ({
            question: card.question,
            answer: card.answer
          }))
        }),
      });

+      clearTimeout(timeoutId);
+      
      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Failed to save flashcards');
      }
src/middleware.ts (1)

14-26: Excellent migration to Clerk v6 async pattern.

The middleware correctly implements the new Clerk v6 approach by:

  • Using async/await with auth()
  • Manual authentication checks instead of protect()
  • Preserving the original URL in redirect parameters for better UX

Consider adding error handling around the auth() call for robustness:

export default clerkMiddleware(async (auth, request) => {
- const { userId } = await auth()
+ let userId;
+ try {
+   ({ userId } = await auth());
+ } catch (error) {
+   console.error('Auth error in middleware:', error);
+   return NextResponse.redirect(new URL('/sign-in', request.url));
+ }
src/lib/analytics.ts (1)

2-10: Consider improving the singleton pattern.

The singleton implementation works but could be more robust:

export class Analytics {
  private static instance: Analytics;
+ 
+ private constructor() {} // Prevent external instantiation
  
  static getInstance(): Analytics {
    if (!Analytics.instance) {
      Analytics.instance = new Analytics();
    }
    return Analytics.instance;
  }

Alternatively, consider a simpler module-level singleton approach which is more idiomatic in modern JavaScript.

src/components/common/ErrorBoundary.tsx (1)

84-96: Solid HOC implementation with minor naming improvement opportunity.

The higher-order component provides an easy way to wrap components with error boundaries. Consider improving the display name for better debugging:

export function withErrorBoundary<T extends object>(
  Component: React.ComponentType<T>,
  errorFallback?: ReactNode
) {
- return function WrappedComponent(props: T) {
+ const WrappedComponent = function(props: T) {
    return (
      <ErrorBoundary fallback={errorFallback}>
        <Component {...props} />
      </ErrorBoundary>
    );
  };
+ 
+ WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`;
+ return WrappedComponent;
}
src/components/flashcards/FlashcardViewer.tsx (2)

7-7: Remove unused import

The Flashcard type is imported but not used in this component. The interface uses inline type definition instead.

-import type { Flashcard } from "@/types";

24-25: Optimize useCallback usage for handleFlip

The current implementation recreates the handler on every flip change, defeating the optimization purpose. Use the functional update pattern instead.

-  const handleFlip = useCallback(() => setFlip(!flip), [flip]);
+  const handleFlip = useCallback(() => setFlip(prev => !prev), []);
next.config.js (1)

48-50: Remove unnecessary alias spread

The empty spread operation for config.resolve.alias doesn't add any value.

-    config.resolve.alias = {
-      ...config.resolve.alias,
-    };
src/app/api/search/route.ts (2)

40-44: Simplify difficulty filter logic.

The conditional filter using an empty object works but could be cleaner.

-            // Apply difficulty filter if specified
-            filters.difficulty.length > 0 ? {
-              difficulty: { in: filters.difficulty }
-            } : {},
+            // Apply difficulty filter if specified
+            ...(filters.difficulty.length > 0 && {
+              difficulty: { in: filters.difficulty }
+            }),

113-119: Consider more specific error messages.

The error handling could provide more context about the failure type.

   } catch (error) {
     console.error('Search error:', error);
+    
+    if (error instanceof Error && error.message.includes('Clerk')) {
+      return NextResponse.json(
+        { error: 'Authentication error' },
+        { status: 401 }
+      );
+    }
+    
     return NextResponse.json(
       { error: 'Search failed' },
       { status: 500 }
     );
   }
src/lib/services/flashcard.service.ts (2)

21-21: Consider API key security implications.

The API key is included in the URL as a query parameter. While this follows Gemini's API pattern, be aware that URLs may be logged in various places (proxies, servers, etc.), potentially exposing the key.


66-81: Consider consolidating markdown cleanup logic.

While the current implementation works, the multiple similar replacements could be consolidated for maintainability.

-      content = content
-        .replace(/```json\s*/g, '')         // Remove ```json markers
-        .replace(/```\s*/g, '')             // Remove ``` markers
-        .replace(/``````json\s*/g, '')      // Remove 6-backtick json markers
-        .replace(/``````\s*/g, '')          // Remove 6-backtick variations with whitespace
-        .trim();
-
-      // Additional cleanup for any remaining markdown artifacts
-      if (content.startsWith('json\n')) {
-        content = content.substring(5);
-      }
-      if (content.startsWith('json ')) {
-        content = content.substring(5);
-      }
+      // Remove all markdown code block markers and json prefixes
+      content = content
+        .replace(/`{3,6}(?:json)?\s*/g, '')  // Remove 3-6 backticks with optional json
+        .replace(/^json[\s\n]/i, '')         // Remove json prefix
+        .trim();
src/app/flashcards/[id]/page.tsx (1)

127-129: Consider fetching actual deck name from the data.

The deck name is hardcoded to "Flashcard Set" but could be retrieved from the deck data for better UX.

-          if (mappedFlashcards.length > 0) {
-            setDeckName(`Flashcard Set`);
-          }
+          if (mappedFlashcards.length > 0 && mappedFlashcards[0].deck) {
+            setDeckName(mappedFlashcards[0].deck.name || 'Flashcard Set');
+          }
src/components/study/StudyMode.tsx (1)

187-197: Consider tracking skipped cards in the backend

Skipped cards are only tracked locally in stats but not recorded individually in the backend like answered cards. This might limit analytics capabilities.

Consider recording skipped cards to the backend for better learning analytics and to identify potentially problematic flashcards.

src/lib/services/payment.service.ts (2)

25-31: Consider making pricing configurable

Hardcoded pricing values reduce flexibility. Consider loading from configuration or database.

 private getPricing() {
+  // TODO: Load from configuration or database
   return {
     monthly: { Pro: 500, Organizational: 1500 },
     yearly: { Pro: 5000, Organizational: 15000 }
   };
 }

59-75: Signature verification implemented correctly with room for improvement

The HMAC signature verification is correctly implemented. However, consider importing crypto at the top and adding env var validation.

Move the crypto import to the top of the file and add validation:

+import crypto from 'crypto';

 async verifyPayment(paymentData: {
   razorpay_order_id: string;
   razorpay_payment_id: string;
   razorpay_signature: string;
 }): Promise<boolean> {
-  const crypto = require('crypto');
+  if (!process.env.RAZORPAY_KEY_SECRET) {
+    throw new Error('Razorpay secret key not configured');
+  }
   
   const body = paymentData.razorpay_order_id + '|' + paymentData.razorpay_payment_id;
   const expectedSignature = crypto
-    .createHmac('sha256', process.env.RAZORPAY_KEY_SECRET!)
+    .createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
     .update(body.toString())
     .digest('hex');
src/lib/services/user.service.ts (2)

6-8: Update outdated comment referencing Supabase

The comment mentions "Supabase database" but the code uses Prisma. Update for accuracy.

 /**
-  * Ensure user exists in Supabase database with REAL Clerk data (no dummy users)
+  * Ensure user exists in database with REAL Clerk data (no dummy users)
  */

185-210: Consider selective data loading for performance

This method loads all related data including nested relations, which could impact performance for users with many decks and study sessions.

Consider adding options to selectively load relations:

-async getUserByClerkId(userId: string) {
+async getUserByClerkId(userId: string, options?: {
+  includeDecks?: boolean;
+  includeFlashcards?: boolean;
+  includeStudySessions?: boolean;
+}) {
   try {
     const user = await prisma.user.findUnique({
       where: { clerkUserId: userId },
       include: {
-        decks: {
+        decks: options?.includeDecks ? {
           include: {
             flashcards: true
           }
-        },
-        flashcards: true,
-        studySessions: {
+        } : false,
+        flashcards: options?.includeFlashcards ?? false,
+        studySessions: options?.includeStudySessions ? {
           include: {
             records: true
           }
-        }
+        } : false
       }
     });
src/app/pricing/page.tsx (1)

187-192: Populate Razorpay prefill data for better UX

The prefill object is empty but could be populated with user data from Clerk to save users from entering their details.

+// Get user data from Clerk
+const user = await currentUser();

 prefill: {
-  name: '',
-  email: '',
-  contact: '',
+  name: user ? `${user.firstName || ''} ${user.lastName || ''}`.trim() : '',
+  email: user?.primaryEmailAddress?.emailAddress || '',
+  contact: '', // Could be fetched from user metadata if stored
 },
package.json (3)

10-12: Prisma script names LGTM but missing postinstall hook

The helper scripts are useful, but remember that prisma generate must run after each npm i / pnpm i to keep the client in sync. A lightweight way is:

   "scripts": {
+    "postinstall": "prisma generate",

This prevents runtime “Could not find … PrismaClient” errors on fresh installs.


31-32: @typescript-eslint/parser should be a dev dependency

The parser is only required at lint time; keeping it in dependencies bloats the production bundle size in serverless environments.

-    "@typescript-eslint/parser": "^8.38.0",
+    // move to devDependencies

44-46: Razorpay SDK pulls in crypto at build time – check edge runtime compatibility

If you target the Vercel edge runtime (or any Web Worker-style environment) the razorpay Node SDK will break because of native crypto/http usage. You may need the lightweight razorpay/dist/razorpay.es browser build or a server-only API route.

Confirm your deployment targets before shipping.

src/types/index.ts (1)

139-140: Utility type already exists in TS 5.1

PartialBy and RequiredBy patterns are now upstream. Optional<T, K> is fine but consider importing from 'type-fest' instead of re-inventing unless tree-shaking is critical.

prisma/migrations/20250725171641_complete_with_adding_razorpay/migration.sql (1)

131-142: Foreign keys use RESTRICT – consider ON DELETE CASCADE for user clean-up

If a user deletes their Clerk account you’ll have to orphan-clean decks, flashcards, payments manually. Cascading simplifies this and keeps referential integrity.

Adjust FK clauses before running in production.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b6bfe7 and b0f529c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • .env.example (2 hunks)
  • next.config.js (1 hunks)
  • next.config.mjs (0 hunks)
  • package.json (2 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • prisma/migrations/20241111182400_init/migration.sql (0 hunks)
  • prisma/migrations/20241111182813_init/migration.sql (0 hunks)
  • prisma/migrations/20241111183630_init/migration.sql (0 hunks)
  • prisma/migrations/20250725171641_complete_with_adding_razorpay/migration.sql (5 hunks)
  • prisma/schema.prisma (5 hunks)
  • src/app/api/decks/[id]/route.ts (1 hunks)
  • src/app/api/decks/route.ts (1 hunks)
  • src/app/api/decks/save/route.ts (1 hunks)
  • src/app/api/flashcards/[id]/route.ts (1 hunks)
  • src/app/api/generate/route.ts (1 hunks)
  • src/app/api/razorpay/create-order/route.ts (1 hunks)
  • src/app/api/razorpay/verify-payment/route.ts (1 hunks)
  • src/app/api/search/route.ts (1 hunks)
  • src/app/api/study/complete/route.ts (1 hunks)
  • src/app/api/study/record/route.ts (1 hunks)
  • src/app/api/study/start/route.ts (1 hunks)
  • src/app/flashcards/[id]/page.tsx (3 hunks)
  • src/app/flashcards/page.tsx (4 hunks)
  • src/app/layout.tsx (2 hunks)
  • src/app/pricing/page.tsx (3 hunks)
  • src/app/result/page.tsx (3 hunks)
  • src/components/FAQ.tsx (1 hunks)
  • src/components/ThemeProvider.tsx (1 hunks)
  • src/components/common/ErrorBoundary.tsx (1 hunks)
  • src/components/common/LoadingSpinner.tsx (1 hunks)
  • src/components/common/OptimisticButton.tsx (1 hunks)
  • src/components/core/Navigation.tsx (1 hunks)
  • src/components/core/flash-card.tsx (1 hunks)
  • src/components/decks/DeckCard.tsx (1 hunks)
  • src/components/decks/DeckList.tsx (1 hunks)
  • src/components/flashcards/FlashcardGenerator.tsx (1 hunks)
  • src/components/flashcards/FlashcardSaveDialog.tsx (1 hunks)
  • src/components/flashcards/FlashcardViewer.tsx (1 hunks)
  • src/components/search/GlobalSearch.tsx (1 hunks)
  • src/components/study/StudyMode.tsx (1 hunks)
  • src/components/ui/badge.tsx (1 hunks)
  • src/components/ui/button.tsx (1 hunks)
  • src/components/ui/dialog.tsx (1 hunks)
  • src/components/ui/dropdown-menu.tsx (4 hunks)
  • src/components/ui/input.tsx (1 hunks)
  • src/components/ui/progress.tsx (1 hunks)
  • src/components/ui/skeleton-cards.tsx (1 hunks)
  • src/components/ui/skeleton.tsx (1 hunks)
  • src/config/site.ts (1 hunks)
  • src/hooks/useDebounce.ts (1 hunks)
  • src/lib/analytics.ts (1 hunks)
  • src/lib/auth.ts (1 hunks)
  • src/lib/database.ts (1 hunks)
  • src/lib/db.ts (0 hunks)
  • src/lib/flashcards.ts (1 hunks)
  • src/lib/hooks/useLocalStorage.ts (1 hunks)
  • src/lib/hooks/useOptimisticMutation.ts (1 hunks)
  • src/lib/prisma.ts (0 hunks)
  • src/lib/services/flashcard.service.ts (1 hunks)
  • src/lib/services/payment.service.ts (1 hunks)
  • src/lib/services/user.service.ts (1 hunks)
  • src/lib/supabase.ts (0 hunks)
  • src/lib/utils/lazy-imports.ts (1 hunks)
  • src/middleware.ts (1 hunks)
  • src/types/index.d.ts (0 hunks)
  • src/types/index.ts (1 hunks)
  • src/types/prisma.d.ts (0 hunks)
  • src/types/supabase.ts (0 hunks)
  • src/utils/get-razorpay.ts (1 hunks)
  • src/utils/get-stripe.ts (0 hunks)
  • tsconfig.json (2 hunks)
💤 Files with no reviewable changes (11)
  • next.config.mjs
  • src/types/prisma.d.ts
  • src/utils/get-stripe.ts
  • src/lib/db.ts
  • src/lib/prisma.ts
  • prisma/migrations/20241111183630_init/migration.sql
  • prisma/migrations/20241111182813_init/migration.sql
  • prisma/migrations/20241111182400_init/migration.sql
  • src/types/index.d.ts
  • src/types/supabase.ts
  • src/lib/supabase.ts
🧰 Additional context used
🧬 Code Graph Analysis (27)
src/app/api/decks/route.ts (2)
src/app/api/flashcards/[id]/route.ts (1)
  • GET (5-37)
src/lib/services/flashcard.service.ts (1)
  • flashcardService (301-301)
src/app/api/study/complete/route.ts (3)
src/app/api/study/record/route.ts (1)
  • POST (5-52)
src/app/api/study/start/route.ts (1)
  • POST (6-50)
src/lib/database.ts (1)
  • prisma (7-9)
src/app/api/decks/[id]/route.ts (1)
src/lib/database.ts (1)
  • prisma (7-9)
src/components/decks/DeckCard.tsx (3)
src/types/index.ts (1)
  • DeckCardProps (118-121)
src/components/ui/card.tsx (2)
  • Card (79-79)
  • CardContent (79-79)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/core/Navigation.tsx (1)
src/components/search/GlobalSearch.tsx (1)
  • GlobalSearch (39-308)
src/app/api/study/record/route.ts (3)
src/app/api/study/complete/route.ts (1)
  • POST (5-43)
src/app/api/study/start/route.ts (1)
  • POST (6-50)
src/lib/database.ts (1)
  • prisma (7-9)
src/components/ui/input.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/skeleton.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/lib/hooks/useOptimisticMutation.ts (1)
src/components/ui/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
src/components/common/ErrorBoundary.tsx (3)
src/lib/analytics.ts (1)
  • error (33-39)
src/components/ui/card.tsx (4)
  • Card (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
  • CardContent (79-79)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/app/flashcards/page.tsx (1)
src/components/decks/DeckList.tsx (1)
  • DeckList (14-72)
src/app/api/study/start/route.ts (4)
src/app/api/study/complete/route.ts (1)
  • POST (5-43)
src/app/api/study/record/route.ts (1)
  • POST (5-52)
src/lib/services/user.service.ts (1)
  • userService (212-212)
src/lib/database.ts (1)
  • prisma (7-9)
src/components/common/LoadingSpinner.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/app/api/flashcards/[id]/route.ts (2)
src/app/api/decks/route.ts (1)
  • GET (5-28)
src/lib/database.ts (1)
  • prisma (7-9)
next.config.js (1)
src/middleware.ts (1)
  • config (28-33)
src/components/ui/progress.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/app/layout.tsx (2)
src/lib/utils.ts (1)
  • cn (4-6)
src/components/ui/toaster.tsx (1)
  • Toaster (13-35)
src/components/flashcards/FlashcardSaveDialog.tsx (6)
src/types/index.ts (1)
  • FlashcardInput (52-55)
src/components/ui/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
src/components/ui/dialog.tsx (5)
  • Dialog (112-112)
  • DialogContent (117-117)
  • DialogHeader (118-118)
  • DialogTitle (120-120)
  • DialogFooter (119-119)
src/components/ui/label.tsx (1)
  • Label (26-26)
src/components/ui/input.tsx (1)
  • Input (22-22)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/lib/services/payment.service.ts (1)
src/types/index.ts (3)
  • SubscriptionPlan (90-90)
  • BillingCycle (91-91)
  • RazorpayOrderResponse (98-103)
src/components/ui/badge.tsx (1)
src/lib/utils.ts (1)
  • cn (4-6)
src/app/api/razorpay/create-order/route.ts (1)
src/lib/database.ts (1)
  • prisma (7-9)
src/app/flashcards/[id]/page.tsx (6)
src/types/index.ts (1)
  • Flashcard (27-27)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/components/ui/skeleton-cards.tsx (1)
  • FlashcardSkeleton (4-42)
src/components/study/StudyMode.tsx (1)
  • StudyMode (30-346)
src/components/ui/card.tsx (4)
  • Card (79-79)
  • CardHeader (79-79)
  • CardTitle (79-79)
  • CardContent (79-79)
src/components/ui/badge.tsx (1)
  • Badge (36-36)
src/lib/services/flashcard.service.ts (4)
src/types/index.ts (4)
  • CreateFlashcardSetRequest (73-76)
  • CreateFlashcardSetResponse (78-83)
  • DeckWithFlashcards (47-49)
  • Flashcard (27-27)
src/lib/services/user.service.ts (1)
  • userService (212-212)
src/lib/database.ts (1)
  • prisma (7-9)
src/components/core/flash-card.tsx (1)
  • Flashcard (16-80)
src/components/study/StudyMode.tsx (6)
src/components/ui/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
src/components/ui/skeleton-cards.tsx (1)
  • StudyModeSkeleton (67-108)
src/components/ui/progress.tsx (1)
  • Progress (28-28)
src/components/ui/card.tsx (2)
  • Card (79-79)
  • CardContent (79-79)
src/components/ui/badge.tsx (1)
  • Badge (36-36)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/types/index.ts (1)
src/components/core/flash-card.tsx (1)
  • Flashcard (16-80)
src/components/flashcards/FlashcardGenerator.tsx (5)
src/components/ui/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
src/components/ui/form.tsx (5)
  • Form (171-171)
  • FormField (177-177)
  • FormItem (172-172)
  • FormControl (174-174)
  • FormMessage (176-176)
src/components/ui/label.tsx (1)
  • Label (26-26)
src/components/ui/textarea.tsx (1)
  • Textarea (24-24)
src/components/ui/button.tsx (1)
  • Button (56-56)
src/lib/services/user.service.ts (1)
src/lib/database.ts (1)
  • prisma (7-9)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 14-14: [QuoteCharacter] The value has quote characters (', ")


[warning] 25-25: [QuoteCharacter] The value has quote characters (', ")


[warning] 28-28: [QuoteCharacter] The value has quote characters (', ")


[warning] 29-29: [QuoteCharacter] The value has quote characters (', ")


[warning] 30-30: [EndingBlankLine] No blank line at the end of the file


[warning] 30-30: [QuoteCharacter] The value has quote characters (', ")


[warning] 30-30: [UnorderedKey] The NEXT_PUBLIC_RAZORPAY_KEY_ID key should go before the RAZORPAY_KEY_ID key

🪛 Biome (2.1.2)
src/lib/database.ts

[error] 7-7: Shouldn't redeclare 'prisma'. Consider to delete it or rename it.

'prisma' is defined here:

(lint/suspicious/noRedeclare)

src/components/search/GlobalSearch.tsx

[error] 258-258: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)


[error] 264-264: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (148)
src/config/site.ts (1)

16-16: Support email cleared – double-check user-facing flows

mailSupport is now empty. Any “Contact support” links or mailto buttons will fail.
If a support channel is still expected, populate a valid address or gate the UI behind a presence check.

src/components/FAQ.tsx (1)

3-3: Good TypeScript typing improvement!

Adding the explicit JSX type import enhances type safety for JSX elements and aligns with modern TypeScript best practices.

src/hooks/useDebounce.ts (1)

1-17: Well-implemented debounce hook!

This generic hook follows React best practices with proper dependency arrays, cleanup functions, and TypeScript generics. It will be valuable for optimizing search functionality and other rapid input scenarios.

src/components/ui/skeleton.tsx (1)

1-15: Solid skeleton loading component implementation!

The component follows UI primitive best practices with proper TypeScript typing, flexible props handling, and appropriate default styling for loading states.

src/components/ui/input.tsx (2)

5-5: Good typing simplification!

Using React.ComponentProps<"input"> is more direct and follows modern React TypeScript patterns compared to custom interfaces.


11-11: Improved responsive design and file input styling!

The font size changes (text-base with md:text-sm) provide better mobile UX, and file:text-foreground ensures consistent theming for file inputs.

src/lib/flashcards.ts (1)

1-1: Database import and Prisma export verified

The import change to @/lib/database is correct, and src/lib/database.ts properly exports the prisma client.

  • Confirmed export const prisma in src/lib/database.ts with global caching as expected.

No further action required.

src/components/ThemeProvider.tsx (1)

5-6: LGTM! Correct import path update for next-themes upgrade.

The import path change from the internal "next-themes/dist/types" to the public "next-themes" API correctly aligns with the next-themes upgrade to v0.4.6. This follows best practices by using the official public API instead of internal implementation details.

src/lib/auth.ts (2)

5-5: LGTM! Correct async auth() call for Clerk v6.

The addition of await to the auth() call is necessary for Clerk v6 compatibility, which now returns a Promise. This change is consistent with the authentication updates mentioned across the API routes.


2-2: Database import path and Prisma client export verified – ready to merge

  • Confirmed src/lib/database.ts exists and exports prisma as a singleton.

LGTM!

src/components/ui/dialog.tsx (1)

24-24: LGTM! Minor whitespace cleanup.

Removed extraneous space in the className string for cleaner code. This formatting improvement doesn't affect functionality.

src/components/ui/button.tsx (1)

8-8: LGTM! Excellent button styling enhancements.

The additions improve button functionality and consistency:

  • gap-2: Provides proper spacing between button content
  • [&_svg]:pointer-events-none: Prevents SVG click interference
  • [&_svg]:size-4 [&_svg]:shrink-0: Ensures consistent icon sizing and prevents flex shrinkage

These changes align with modern UI component best practices for buttons containing icons.

src/components/core/Navigation.tsx (1)

1-6: Incomplete component structure - clarify if this is documentation or a component.

This file appears to contain a code snippet or example rather than a complete React component. If this is meant to be the actual Navigation component, it's missing:

  • Component declaration/function
  • Export statement
  • Proper React component structure

If this is intended as the complete Navigation component, please apply this structure:

+import React from 'react';
 import GlobalSearch from '@/components/search/GlobalSearch';

-// In your navigation component, add:
-<div className="flex-1 max-w-xl mx-4">
-  <GlobalSearch />
-</div>
+export default function Navigation() {
+  return (
+    <nav className="flex items-center justify-between p-4">
+      {/* Other navigation elements */}
+      <div className="flex-1 max-w-xl mx-4">
+        <GlobalSearch />
+      </div>
+    </nav>
+  );
+}

If this is documentation showing how to integrate GlobalSearch, consider moving it to a README or documentation file.

src/app/api/decks/route.ts (4)

1-3: Clean import structure.

The imports are well-organized and follow the established patterns from the service layer refactor.


5-8: Correct Clerk v6 authentication pattern.

The await auth() usage is properly implemented and consistent with Clerk v6 requirements as noted in the comment.


10-15: Proper authentication handling.

The unauthorized check and response format are consistent with the established API patterns across the codebase.


17-28: Well-structured service integration and error handling.

The implementation correctly uses the service layer and provides comprehensive error handling with appropriate logging and response codes.

tsconfig.json (4)

3-7: Improved readability with multi-line formatting.

The formatting change makes the library configuration more readable and maintainable.


25-28: Consistent formatting improvement.

The multi-line format for the path alias is more consistent with the rest of the configuration.


29-29: Appropriate ES target for modern features.

ES2017 provides good balance of modern JavaScript features (async/await, object spread) while maintaining broad compatibility.


31-39: Consistent multi-line formatting.

The formatting changes improve readability and maintainability of the TypeScript configuration.

src/app/api/study/start/route.ts (5)

1-4: Well-organized imports following established patterns.

The imports correctly use the new database module and service layer, consistent with the architectural refactor.


6-15: Consistent authentication implementation.

The auth pattern matches the established Clerk v6 requirements and provides appropriate error responses.


26-27: Good practice ensuring user existence.

The ensureUserExists call properly maintains data consistency between Clerk and the database.


29-36: Database operation structure is correct.

The study session creation follows proper patterns, though the missing deckId field was noted in the previous comment.


38-50: Proper response formatting and error handling.

The success response provides necessary data (sessionId) and error handling follows established patterns with appropriate logging and status codes.

src/app/flashcards/page.tsx (4)

8-11: Good modularization and type safety.

The DeckList component import and Deck type usage improve code organization and type safety. The SRP comment demonstrates good architectural awareness.


41-43: Proper state management callback.

The handleDeckDeleted callback correctly uses functional state updates to maintain state consistency after deck deletion.


56-58: Minor formatting improvement.

The multi-line format improves readability of the sign-in prompt text.


73-74: Excellent component composition and separation of concerns.

The refactor to use DeckList with proper props and callback delegation improves code organization and reusability.

src/app/api/decks/[id]/route.ts (1)

5-25: Solid implementation with proper security measures.

The DELETE handler correctly implements:

  • Async params handling for Next.js App Router
  • Clerk v6 async authentication
  • User ownership verification in the database query
  • Proper error responses
src/app/api/flashcards/[id]/route.ts (1)

5-28: Excellent implementation following established patterns.

The GET handler demonstrates:

  • Correct async params handling for Next.js App Router
  • Proper Clerk v6 async authentication
  • User ownership verification through the userId filter
  • Logical ordering by creation date for UI consistency
  • Clean response structure
src/components/decks/DeckCard.tsx (4)

1-8: Clean imports and type safety implementation.

The imports are well-organized and properly typed. Good use of type imports for better tree-shaking and clear separation of concerns.


10-16: Proper event handling for nested interactive elements.

The delete handler correctly prevents the Link navigation when the delete button is clicked, avoiding conflicting interactions. The null-safe check for onDelete is appropriate.


30-30: Excellent null-safe access pattern for optional array.

The use of optional chaining with fallback (deck.flashcards?.length || 0) properly handles cases where the flashcards array might not be loaded or undefined.


34-44: Well-implemented conditional delete functionality with accessibility.

The conditional rendering of the delete button with proper accessibility attributes (aria-label) and visual feedback (opacity transition on hover) demonstrates good UX practices.

src/lib/database.ts (2)

3-13: Standard Next.js Prisma singleton pattern implemented correctly.

This is the recommended approach for Prisma client initialization in Next.js applications to prevent multiple instances during development hot reloading. The static analysis warning about redeclaration is a false positive - this pattern is explicitly documented in Prisma's Next.js guide.


7-9: Good logging configuration for development and debugging.

The logging configuration for queries, errors, and warnings will be helpful for debugging database operations, especially during development.

src/lib/hooks/useOptimisticMutation.ts (3)

4-10: Well-designed interface with proper generic constraints.

The interface design is clean and flexible, allowing for type-safe mutations with optional callback functions. The generic types <T, R> properly represent input and return types.


22-49: Excellent implementation of optimistic update pattern.

The hook correctly implements the optimistic update pattern:

  1. Applies optimistic update immediately
  2. Executes the mutation
  3. Handles success/error scenarios appropriately
  4. Reverts optimistic changes on error
  5. Provides user feedback via toast notifications

The error handling is robust, including type checking and proper error propagation.


49-49: Verify dependency array completeness.

The dependency array includes all referenced variables. This is correct for preventing stale closures while avoiding unnecessary re-renders.

src/app/api/decks/save/route.ts (4)

11-17: Comprehensive input validation with Zod schema.

The validation schema properly constrains input data with appropriate limits and requirements. The satisfies constraint ensures type compatibility with the interface.


21-27: Proper authentication implementation with Clerk v6.

The async auth() call is correctly implemented for Clerk v6, and the unauthorized response properly returns structured error data matching the ApiError interface.


29-35: Good separation of concerns using service layer.

The use of flashcardService.createDeckWithFlashcards() properly abstracts the business logic away from the API route, making the code more maintainable and testable.


40-49: Excellent error handling with detailed validation feedback.

The Zod error handling provides detailed field-level validation errors, which will be helpful for client-side form validation feedback. The error structure matches the ApiError interface consistently.

src/app/layout.tsx (4)

6-7: Good fix on the ThemeProvider import path.

The correction from "../components/ThemeProvider" to a proper relative path resolves a potential import issue.


11-11: LGTM - Toaster import added correctly.

The import follows the established pattern and is necessary for the toast notification system being integrated.


17-17: Typo fixed in metadata description.

Good catch fixing "generatro" to "generator" in the application description.


32-32: Good cleanup removing trailing space.

The className string cleanup improves code quality and the cn utility properly handles class merging.

src/utils/get-razorpay.ts (3)

7-27: Well-implemented script loading utility.

The implementation includes all necessary safeguards:

  • Early return if script already loaded
  • Proper error handling with onload/onerror
  • Duplicate prevention by checking existing script tags
  • Clean Promise-based API

21-25: Excellent duplicate prevention logic.

The check for existing script tags prevents unnecessary duplicate loads and potential conflicts. The attribute selector is precise and the immediate resolution is the correct behavior.


29-30: Good export pattern with clear documentation.

Providing both named and default exports gives consumers flexibility in how they import the utility. The comment clearly explains the intention.

src/components/decks/DeckList.tsx (4)

1-12: Well-structured component with clear responsibility.

The imports are clean, the interface is properly typed, and the single responsibility principle is explicitly documented. Good foundation for the component.


14-16: Good component setup with proper state management.

The props destructuring is clean, and tracking the deletion state prevents concurrent deletion attempts. Toast integration is appropriate for user feedback.


51-59: Excellent empty state design.

The empty state provides a user-friendly message that encourages action, with appropriate centered styling and muted text color for visual hierarchy.


61-72: Clean list rendering implementation.

The deck mapping is properly implemented with correct key usage and prop passing. The spacing utility class provides good visual separation between cards.

src/components/ui/progress.tsx (4)

1-7: Clean imports with appropriate dependencies.

The use of Radix UI provides accessibility out of the box, and the utility imports are all necessary for the component implementation.


8-11: Excellent TypeScript typing with proper ref forwarding.

The forwardRef implementation correctly types the component with Radix UI primitives, ensuring full prop compatibility and proper ref handling.


12-19: Well-designed root container styling.

The default styles provide a solid foundation with proper overflow handling and rounded corners. The class merging allows for customization while maintaining good defaults.


20-24: Correct progress calculation with smooth animations.

The transform calculation properly handles the progress visualization, and the fallback to 0 for undefined values prevents errors. The transition provides good UX.

src/components/ui/badge.tsx (3)

6-24: Comprehensive variant system with excellent accessibility.

The badge variants are well-designed with:

  • Proper focus states for accessibility
  • Consistent hover interactions
  • Good color contrast following design system patterns
  • Sensible default variant

26-28: Well-typed interface combining DOM and variant props.

The interface properly extends HTML div attributes while including variant props, providing full type safety for consumers.


30-36: Clean component implementation with flexible exports.

The Badge component properly handles prop spreading and class merging. Exporting both the component and variants provides flexibility for consumers who might need direct access to the variant classes.

src/app/api/generate/route.ts (6)

2-4: LGTM: Good modularization with proper imports

The imports properly separate concerns with Clerk authentication, service layer, and TypeScript types. This aligns well with the architectural improvements mentioned in the PR.


6-6: LGTM: Proper function signature with typed Request

The function signature correctly uses the typed Request parameter instead of any, improving type safety.


8-13: LGTM: Proper async authentication implementation

The authentication implementation correctly uses await with Clerk's auth() function and provides a proper JSON error response for unauthorized access.


15-20: LGTM: Comprehensive input validation

The input validation properly checks for empty/whitespace-only text and returns appropriate error responses with consistent error structure.


22-26: LGTM: Clean service delegation with proper typing

The delegation to flashcardService.generateFlashcards follows single responsibility principle and the response is properly typed with FlashcardGenerationResponse.


30-33: LGTM: Improved error handling with proper typing

The error handling provides detailed error messages while maintaining consistent API response structure with the ApiError type.

src/components/common/OptimisticButton.tsx (3)

8-17: LGTM: Well-designed component interface

The props interface is comprehensive and follows React best practices with proper TypeScript typing. The optional props have sensible defaults.


19-31: LGTM: Clean component setup with proper state management

The component properly destructures props with defaults and uses appropriate state typing for the button states.


48-67: LGTM: Excellent UI feedback implementation

The button rendering logic provides clear visual feedback for all states with appropriate icons, colors, and disabled states. The conditional rendering and styling are well-implemented.

src/components/common/LoadingSpinner.tsx (3)

4-8: LGTM: Clean and flexible component interface

The props interface provides good flexibility with size variants, optional text, and custom className support.


10-31: LGTM: Well-implemented LoadingSpinner component

The component properly implements size variants, conditional text rendering, and uses appropriate CSS classes for animations and styling. The use of the cn utility for class merging is correct.


34-40: LGTM: Useful PageLoader component

The PageLoader component provides a convenient wrapper for full-page loading states with sensible defaults and proper centering.

src/components/ui/dropdown-menu.tsx (5)

30-30: LGTM: Improved spacing and SVG handling

The addition of gap-2 provides better spacing between items, and [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 ensures consistent SVG behavior and sizing.


37-37: LGTM: Consistent icon positioning

The ChevronRight icon uses ml-auto for proper right alignment in the submenu trigger.


50-50: LGTM: Enhanced animation and scroll behavior

The addition of origin-[--radix-dropdown-menu-content-transform-origin] and overflow handling improves the user experience with proper animation origins and scroll behavior.


68-68: LGTM: Consistent scroll and animation improvements

The max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto overflow-x-hidden and animation origin improvements provide better responsive behavior.


86-86: LGTM: Improved item styling consistency

The styling improvements with gap-2 and SVG handling provide consistent spacing and icon behavior across dropdown menu items.

src/components/flashcards/FlashcardSaveDialog.tsx (7)

17-22: LGTM: Well-defined component interface

The props interface clearly defines the required dependencies and callback functions for the dialog component.


24-32: LGTM: Proper component setup and hooks usage

The component correctly initializes state and uses the toast hook for user feedback.


34-42: LGTM: Good input validation with user feedback

The validation properly checks for empty/whitespace-only deck names and provides clear error messaging via toast.


63-71: LGTM: Good success handling with proper cleanup

The success handling provides user feedback, resets state, and properly calls the callback functions.


72-82: LGTM: Comprehensive error handling

The error handling provides detailed error messages and ensures loading state is properly reset in the finally block.


84-87: LGTM: Proper dialog cleanup on close

The handleClose function correctly resets form state when the dialog is closed.


89-131: LGTM: Well-structured dialog UI with proper accessibility

The dialog implementation uses proper semantic HTML with labels, appropriate button states, and good user experience patterns. The form layout and button styling are consistent with the design system.

src/middleware.ts (2)

1-12: LGTM! Clean migration to Clerk v6 with proper route configuration.

The import of NextResponse and the route matcher configuration are correctly structured for the new async middleware pattern. The public routes are appropriately defined to exclude authentication requirements for sign-in/up flows, pricing, and public content.


28-34: Standard and correct middleware configuration.

The matcher configuration properly excludes static assets while including API routes, which is essential for authentication enforcement across the application.

src/lib/analytics.ts (3)

12-20: Well-structured foundation for analytics tracking.

The method correctly handles SSR by checking for window and provides a clean interface for event tracking. The console logging is appropriate for development, and the structure supports easy integration with production analytics services.


22-39: Excellent helper methods with good abstractions.

The specialized methods provide clean interfaces for common analytics scenarios:

  • page() standardizes page view tracking
  • timing() enables performance monitoring
  • error() captures comprehensive error context including stack traces

The consistent delegation to track() maintains a unified event structure.


42-42: Good practice exporting a ready-to-use singleton instance.

This provides a clean API for consumers and follows common patterns from popular libraries.

src/app/api/razorpay/create-order/route.ts (5)

32-86: Excellent authentication and validation implementation.

The validation logic is comprehensive and secure:

  • Proper Clerk v6 async authentication
  • Robust plan and billing cycle validation against predefined constants
  • Clear, informative error messages
  • Safe amount lookup with fallback handling

The extensive logging will be valuable for debugging payment issues in production.


89-122: Excellent defensive programming with fallback strategies.

The user management logic demonstrates good architectural thinking:

  • Upsert pattern ensures user exists before payment processing
  • Fallback logic handles potential schema evolution gracefully
  • Continues processing even if user operations fail, which is appropriate since the user might already exist
  • Clear logging distinguishes between warnings and errors

This resilient approach will handle database schema changes and edge cases well.


123-144: Solid Razorpay integration following best practices.

The order creation is well-structured:

  • Proper error handling around external API calls
  • Comprehensive order metadata in notes for tracking
  • Unique receipt generation with timestamp and user ID
  • Appropriate error response for payment configuration issues

146-164: Appropriate error handling prioritizing payment flow.

The payment record creation correctly treats database logging as non-critical:

  • Uses warnings instead of errors to allow payment processing to continue
  • Recognizes that the Razorpay order remains valid even if local record creation fails
  • Good separation of concerns between payment processing and internal tracking

This approach ensures payment flows aren't broken by database issues.


166-193: Well-structured response and comprehensive error handling.

The final response handling demonstrates good API design:

  • Includes all necessary data for frontend Razorpay integration
  • Comprehensive success logging aids debugging
  • Generic error responses protect internal implementation details
  • Appropriate HTTP status codes for different error scenarios

The extensive logging throughout the entire endpoint will be valuable for production troubleshooting.

src/components/common/ErrorBoundary.tsx (3)

8-31: Excellent React error boundary implementation.

The component correctly implements the error boundary pattern:

  • Proper TypeScript typing for props and state
  • getDerivedStateFromError for clean state updates
  • componentDidCatch for side effects and logging
  • Optional onError callback enables integration with external error reporting

Follows React best practices and official documentation patterns.


33-35: Good UX with retry functionality.

The reset method provides users a way to recover from transient errors without requiring a full page refresh, improving the overall user experience.


37-81: Well-designed fallback UI with excellent UX considerations.

The error boundary UI demonstrates thoughtful design:

  • Respects custom fallback props for flexibility
  • Consistent with the app's design system using Card components
  • Development-only error details aid debugging without exposing sensitive info in production
  • Multiple recovery options (retry vs. full refresh) give users control
  • Appropriate visual hierarchy with icons and clear messaging

The accessibility and user experience are well-considered.

src/lib/utils/lazy-imports.ts (2)

5-21: All lazy-loaded component paths verified

The script confirms that all referenced files exist:

  • src/components/core/flash-card.tsx
  • src/components/core/generate.tsx
  • src/components/FAQ.tsx
  • src/app/pricing/page.tsx

No changes required.


23-32: ✔️ Confirmed dynamic import path exists; ready to merge

– Verified that src/utils/get-razorpay.ts is present and correctly imported.
– No further changes required.

src/components/flashcards/FlashcardViewer.tsx (1)

39-54: Well-implemented keyboard navigation

The keyboard event handling is properly implemented with correct cleanup and dependencies. Good use of preventDefault for space key to avoid page scrolling.

src/app/api/razorpay/verify-payment/route.ts (1)

6-44: Solid authentication and validation implementation

Good implementation with proper authentication checks, comprehensive field validation, and secure logging practices (truncating sensitive data).

next.config.js (2)

3-18: Proper Next.js 15 configuration migration

Good job updating the configuration for Next.js 15. The migration of serverExternalPackages and turbopack from experimental is correct, and the SVG handling setup is appropriate.


19-27: Good image optimization configuration

Excellent choice supporting modern image formats (WebP and AVIF) for better performance and allowing Unsplash images.

src/components/ui/skeleton-cards.tsx (1)

1-108: Well-structured skeleton components

Excellent implementation of skeleton loading states. The components accurately mirror their corresponding UI components with appropriate spacing and layout. Good use of the Skeleton primitive for consistent loading animations.

src/components/search/GlobalSearch.tsx (2)

39-94: Well-implemented search state management

Good implementation with debounced search, proper effect dependencies, and localStorage for recent searches. The useCallback optimization for performSearch is correctly implemented.


156-202: Clean filter implementation

The filter dropdown is well-implemented with proper state management using functional updates. Good UX with clear labels and icons.

src/components/flashcards/FlashcardGenerator.tsx (4)

1-25: Well-structured component setup with clear responsibility.

The component follows good practices with typed props interface and clear single responsibility principle documentation.


26-44: Proper form validation and state management.

Good implementation using zod schema validation with appropriate constraints and react-hook-form integration.


75-107: Clean and accessible form implementation.

The component properly uses form UI components with good accessibility practices (labels, IDs) and comprehensive loading state management.


48-51: No action required: /api/generate expects raw text
Confirmed that the API route uses await req.text() to parse the body, so sending body: values.text is correct.

src/app/api/search/route.ts (1)

70-98: Well-implemented deck search with useful metadata.

Good implementation that includes flashcard counts and handles missing descriptions gracefully.

src/lib/services/flashcard.service.ts (5)

1-10: Clean service architecture with proper imports.

Good separation of concerns with typed imports and clear documentation of additions.


35-62: Robust API integration with proper error handling.

Good implementation with comprehensive response validation and error checking.


83-147: Excellent error handling and data validation.

The implementation includes comprehensive JSON parsing with fallback strategies, thorough validation of flashcard structure, and user-friendly error messages for different failure scenarios.


149-235: Well-implemented deck operations with proper user validation.

Excellent implementation with atomic deck/flashcard creation, comprehensive error handling that distinguishes authentication issues, and proper user existence validation.


237-302: Consistent implementation across all service methods.

The remaining methods follow the established pattern with proper user validation, error handling, and logging. The singleton export is appropriate for this stateless service.

src/app/flashcards/[id]/page.tsx (3)

1-42: Proper hooks usage and comprehensive state management.

Good implementation with all hooks at the top level (no conditional calls) and safe parameter extraction handling various formats.


44-106: Excellent implementation of navigation handlers and keyboard support.

Proper use of useCallback with correct dependencies and comprehensive keyboard navigation with proper cleanup.


216-392: Excellent responsive UI with comprehensive interaction support.

The implementation provides great UX with:

  • Multiple navigation methods (buttons, keyboard, swipe)
  • Responsive design with mobile-first considerations
  • Clear mode switching UI
  • Proper empty state handling
src/app/result/page.tsx (3)

17-50: Proper implementation of Suspense-compatible component structure.

Excellent separation of the search params logic into ResultContent component, making it compatible with Suspense boundaries. Comprehensive error handling for all scenarios.


51-145: Excellent payment result UI with clear user guidance.

The implementation provides:

  • Clear visual feedback with appropriate icons and colors
  • Helpful action buttons for each scenario
  • Session ID display for support purposes
  • Fully responsive design

147-187: Well-implemented loading states with Suspense.

Great implementation of loading skeleton that matches the actual UI structure, providing smooth loading experience with React Suspense.

src/components/study/StudyMode.tsx (6)

1-22: Well-structured imports with proper optimization hooks

Good use of useCallback for performance optimization and comprehensive imports for UI components and icons.


23-29: Clean props interface definition

The props interface is well-structured with appropriate types for the study mode functionality.


82-107: Excellent error handling and user feedback in session initialization

Good implementation with proper useCallback usage, comprehensive error handling, and user-friendly toast notifications that allow continuing even if session initialization fails.


117-161: Robust answer handling with proper state management

Well-implemented answer submission with appropriate guards, error handling, and state updates. Good use of loading states to prevent double submissions.


198-291: Excellent UI implementation with comprehensive state handling

Great attention to detail with loading states, empty states, responsive design, and accessibility. The proper apostrophe escaping and platform-specific text (tap vs click) show good UX consideration.


292-347: Well-designed answer interface with excellent mobile UX

Great implementation with color-coded difficulty buttons, responsive text, and helpful mobile gesture hints. The disabled state during submission prevents race conditions.

src/components/core/flash-card.tsx (4)

1-20: Clean component architecture with proper error handling

Excellent use of component composition, error boundaries, and suspense for a robust user experience.


16-28: Smart use of local storage for draft persistence

Great implementation using localStorage to persist drafts, preventing data loss if users navigate away. The cleanup after successful save is properly handled.


29-69: Excellent component composition with proper loading and error states

Great use of React patterns including ErrorBoundary, Suspense, and conditional rendering. The authentication check for save functionality is properly implemented.


70-81: Clean navigation implementation

Simple and effective navigation to saved flashcards with consistent styling.

src/lib/services/payment.service.ts (2)

76-88: Good use of provider pattern with clear stub implementation

The stub Stripe provider properly implements the interface and clearly indicates it's not yet implemented. This follows SOLID principles well.


89-119: Excellent implementation of dependency injection and factory pattern

Great use of SOLID principles with dependency injection and factory pattern. This makes the code easily extensible for new payment providers.

src/lib/services/user.service.ts (3)

55-82: Excellent error handling for unique constraint violations

Great defensive programming with comprehensive error handling. The approach to update existing users when email conflicts occur is well thought out.


84-144: Well-implemented user synchronization with Clerk

Excellent use of upsert pattern for idempotent synchronization. Good validation and comprehensive logging for debugging.


145-181: Clean utility methods with proper fallback handling

Good implementation of utility methods with appropriate error handling and fallback values. The display name logic handles edge cases well.

src/app/pricing/page.tsx (6)

1-41: Well-structured TypeScript interfaces and imports

Excellent type definitions for pricing features and comprehensive imports for the payment flow implementation.


42-50: Clean state management setup

Good initialization of all necessary hooks and state for the pricing page functionality.


110-187: Excellent payment flow implementation with comprehensive user feedback

Great implementation with toast notifications at every step of the payment process. The delayed redirects ensure users see success messages.


225-371: Well-structured pricing data with clear feature differentiation

Excellent organization of pricing tiers with clear feature lists and appropriate visual differentiation. The data structure is maintainable and easy to update.


372-464: Excellent UI implementation with clear visual hierarchy

Great implementation of the billing toggle and pricing cards. The popular plan highlighting and yearly savings calculations are well done.


465-581: Comprehensive feature display with excellent UX patterns

Great implementation with clear feature indicators, helpful tooltips, and trust-building elements. The loading states and CTAs are well-designed.

package.json (2)

6-6: Turbopack is still experimental – confirm local & CI parity

Running next dev --turbopack locally but falling back to the webpack-based compiler in production (next build) can yield hard-to-diagnose discrepancies.
Verify that:

  1. Every imported file type is supported by Turbopack (e.g. MDX, SVG as ReactComponent, PostCSS).
  2. Source-map support and env-expansion behave the same in both compilers.

If parity cannot be guaranteed, consider gating Turbopack behind an env flag until Next 15 makes it the default.


58-60: Duplicate type packages risk “multiple React versions” warnings

@types/react / @types/react-dom are already transitively required by react@19. Keeping explicit versions here (and again under pnpm.overrides) easily drifts out of sync and re-introduces the “Hooks can only be called inside the body of a function component” runtime error.

Consider removing them from the top-level devDependencies and relying on React’s bundled types (or a single override if strictly necessary).

Comment on lines +1 to +5
onlyBuiltDependencies:
- '@clerk/shared'
- '@prisma/client'
- '@prisma/engines'
- prisma
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the onlyBuiltDependencies configuration purpose and package selection.

The onlyBuiltDependencies configuration restricts which dependencies get built in the workspace. Please verify:

  1. Whether this configuration is intended for performance optimization or compatibility reasons
  2. If these four packages (@clerk/shared, @prisma/client, @prisma/engines, prisma) are the complete set needed for your use case
  3. Whether other dependencies in the project might also need to be built

🏁 Script executed:

#!/bin/bash
# Verify pnpm workspace configuration and check for other potential dependencies that might need building
echo "Checking package.json for Prisma and Clerk related dependencies..."
cat package.json | jq '.dependencies, .devDependencies' | grep -E "(clerk|prisma)" || echo "No additional Clerk/Prisma deps found"

echo "Checking if there are other native dependencies that might need onlyBuiltDependencies..."
cat package.json | jq '.dependencies, .devDependencies' | grep -E "(node-gyp|native|canvas|sharp|sqlite)" || echo "No obvious native deps found"

Length of output: 738


Update onlyBuiltDependencies to include all Clerk packages

Based on your package.json, the workspace config currently only lists @clerk/shared, but you’re also using:

  • @clerk/clerk-react
  • @clerk/nextjs
  • @clerk/themes

Without adding these, PNPM won’t build them in your workspace, which can lead to unexpected resolutions or missing code at runtime. Please update pnpm-workspace.yaml accordingly:

 onlyBuiltDependencies:
-  - '@clerk/shared'
+  - '@clerk/shared'
+  - '@clerk/clerk-react'
+  - '@clerk/nextjs'
+  - '@clerk/themes'
   - '@prisma/client'
   - '@prisma/engines'
   - prisma

No other native or Prisma/Clerk packages were found in dependencies/devDependencies.

📝 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
onlyBuiltDependencies:
- '@clerk/shared'
- '@prisma/client'
- '@prisma/engines'
- prisma
onlyBuiltDependencies:
- '@clerk/shared'
- '@clerk/clerk-react'
- '@clerk/nextjs'
- '@clerk/themes'
- '@prisma/client'
- '@prisma/engines'
- prisma
🤖 Prompt for AI Agents
In pnpm-workspace.yaml at lines 1 to 5, the onlyBuiltDependencies list includes
only '@clerk/shared' from Clerk packages but is missing '@clerk/clerk-react',
'@clerk/nextjs', and '@clerk/themes'. Update the onlyBuiltDependencies array to
add these three Clerk packages to ensure PNPM builds all necessary Clerk
packages in the workspace and prevents runtime issues.

Comment on lines +18 to +26
// ✅ NEW: Subscription & Payment fields
subscriptionPlan String? @default("free") // "free", "basic", "pro"
subscriptionCycle String? // "monthly", "yearly"
subscriptionStatus String? @default("inactive") // "active", "inactive", "cancelled", "expired"
subscriptionId String? // Razorpay subscription ID (if using subscriptions)
paymentId String? // Last successful payment ID
subscriptionEndsAt DateTime? // When current subscription expires
subscriptionStartedAt DateTime? // When subscription was activated
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Free-text subscription fields invite data drift – use enums

subscriptionPlan, subscriptionCycle, and subscriptionStatus are declared as String?. Typos (‘mothly’, ‘montly’) will silently succeed and break billing logic. Define dedicated enums instead:

enum Plan   { FREE BASIC PRO }
enum Cycle  { MONTHLY YEARLY }
enum Status { ACTIVE INACTIVE CANCELLED EXPIRED }

subscriptionPlan   Plan      @default(FREE)
subscriptionCycle  Cycle
subscriptionStatus Status    @default(INACTIVE)
🤖 Prompt for AI Agents
In prisma/schema.prisma around lines 18 to 26, the subscriptionPlan,
subscriptionCycle, and subscriptionStatus fields are defined as optional
strings, which risks data drift due to typos. Replace these string fields with
enums by defining Plan, Cycle, and Status enums with the specified values, then
update the fields to use these enums with appropriate default values as shown in
the comment.

Comment on lines +30 to 37
flashcards Flashcard[]
decks Deck[]
studySessions StudySession[]
// ✅ NEW: Payment relation
payments Payment[]
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Relations reference clerkUserId but id remains primary – reconsider key strategy

Every child table now foreign-keys to User.clerkUserId. That’s fine, but:

  1. You still keep an unused surrogate PK id.
  2. Prisma cannot enforce ON UPDATE CASCADE for changes coming from Clerk because clerkUserId is not the primary key.

Simplify by making clerkUserId the PK (or use a composite [id, clerkUserId]) and drop the redundant string id if it’s never queried.

🤖 Prompt for AI Agents
In prisma/schema.prisma around lines 30 to 37, the User model keeps an unused
surrogate primary key `id` while relations reference `clerkUserId`, which is not
the primary key. To fix this, make `clerkUserId` the primary key of the User
model or create a composite primary key combining `id` and `clerkUserId`. Remove
the redundant `id` field if it is never queried to simplify the schema and
enable Prisma to enforce ON UPDATE CASCADE properly.

Comment on lines +100 to +107
userId String // References User.clerkUserId
razorpayOrderId String @unique
razorpayPaymentId String? @unique // Null until payment is completed
razorpaySignature String? // Payment signature for verification
amount Int // Amount in paise (₹1 = 100 paise)
currency String @default("INR")
plan String // "basic", "pro"
billingCycle String // "monthly", "yearly"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unique on nullable razorpayPaymentId can block multiple pending rows

PostgreSQL treats each NULL as distinct, but Prisma translates @unique to a regular unique constraint, meaning only one row with NULL is allowed. Until a payment is captured this column is NULL, so second attempts will violate the constraint.

Use @@index([razorpayPaymentId], map: "idx_payment_id_not_null") @db.NullsNotDistinct (Postgres 15+) or drop the uniqueness until after verification.

🤖 Prompt for AI Agents
In prisma/schema.prisma around lines 100 to 107, the razorpayPaymentId field is
marked as @unique but is nullable, which causes issues because Prisma enforces a
unique constraint that does not allow multiple NULLs. To fix this, remove the
@unique attribute from razorpayPaymentId and instead add a conditional index
using @@index([razorpayPaymentId], map: "idx_payment_id_not_null") with the
@db.NullsNotDistinct attribute if using PostgreSQL 15 or later, or alternatively
drop the uniqueness constraint until the payment is verified.

Comment on lines +18 to +26
const flashcards = await prisma.flashcard.findMany({
where: {
userId: userId,
deckId: params.id
},
orderBy: {
createdAt: 'asc'
}
});
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider adding deck ownership verification.

While the current query filters by userId for flashcards, it doesn't verify that the user owns the deck itself. This could expose flashcards if deck IDs are predictable.

Verify if deck ownership should be validated:


🏁 Script executed:

#!/bin/bash
# Check if there are other API routes that validate deck ownership
rg -A 10 -B 5 "deckId.*params\.id" --type ts

Length of output: 958


Ensure deck exists and is owned by the user before fetching flashcards

Add an explicit check at the top of your GET handler to verify the deck’s existence and ownership, returning a 404 if the deck doesn’t belong to the authenticated user. While filtering flashcards by userId prevents exposing another user’s cards, this makes the API’s behavior clear and distinguishes “no flashcards yet” from “deck not found.”

• File: src/app/api/flashcards/[id]/route.ts (before the prisma.flashcard.findMany call)
• Insert:

const deck = await prisma.deck.findUnique({
  where: { id: params.id }
});
if (!deck || deck.userId !== userId) {
  return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
🤖 Prompt for AI Agents
In src/app/api/flashcards/[id]/route.ts around lines 18 to 26, before calling
prisma.flashcard.findMany, add a check to verify that the deck with id params.id
exists and is owned by the authenticated user (userId). Use
prisma.deck.findUnique to fetch the deck by id, then if the deck is null or its
userId does not match the authenticated userId, return a 404 response with an
error message. This ensures the API clearly distinguishes between a non-existent
or unauthorized deck and an empty flashcard list.

Comment on lines +17 to +24
constructor() {
const Razorpay = require('razorpay');
this.razorpay = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID!,
key_secret: process.env.RAZORPAY_KEY_SECRET!,
});
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add environment variable validation to prevent runtime errors

The constructor uses non-null assertions on environment variables without validation, which could cause runtime errors if they're not set.

 constructor() {
   const Razorpay = require('razorpay');
+  
+  if (!process.env.RAZORPAY_KEY_ID || !process.env.RAZORPAY_KEY_SECRET) {
+    throw new Error('Razorpay environment variables are not configured');
+  }
+  
   this.razorpay = new Razorpay({
-    key_id: process.env.RAZORPAY_KEY_ID!,
-    key_secret: process.env.RAZORPAY_KEY_SECRET!,
+    key_id: process.env.RAZORPAY_KEY_ID,
+    key_secret: process.env.RAZORPAY_KEY_SECRET,
   });
 }
📝 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
constructor() {
const Razorpay = require('razorpay');
this.razorpay = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID!,
key_secret: process.env.RAZORPAY_KEY_SECRET!,
});
}
constructor() {
const Razorpay = require('razorpay');
if (!process.env.RAZORPAY_KEY_ID || !process.env.RAZORPAY_KEY_SECRET) {
throw new Error('Razorpay environment variables are not configured');
}
this.razorpay = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID,
key_secret: process.env.RAZORPAY_KEY_SECRET,
});
}
🤖 Prompt for AI Agents
In src/lib/services/payment.service.ts around lines 17 to 24, the constructor
uses non-null assertions on environment variables without validating their
presence, risking runtime errors. Add explicit checks to verify that
process.env.RAZORPAY_KEY_ID and process.env.RAZORPAY_KEY_SECRET are defined
before using them. If either is missing, throw a clear error to prevent the
service from initializing with invalid configuration.

Comment on lines +44 to +57
const order = await this.razorpay.orders.create({
amount: amount,
currency: 'INR',
receipt: `receipt_${Date.now()}`,
notes: { userId, plan, billingCycle: cycle }
});

return {
orderId: order.id,
amount: order.amount,
currency: order.currency,
keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID!
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for Razorpay API calls

The order creation doesn't handle potential Razorpay API errors, which could cause unhandled exceptions.

-const order = await this.razorpay.orders.create({
-  amount: amount,
-  currency: 'INR',
-  receipt: `receipt_${Date.now()}`,
-  notes: { userId, plan, billingCycle: cycle }
-});
+try {
+  const order = await this.razorpay.orders.create({
+    amount: amount,
+    currency: 'INR',
+    receipt: `receipt_${Date.now()}`,
+    notes: { userId, plan, billingCycle: cycle }
+  });
+  
+  if (!process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID) {
+    throw new Error('Public Razorpay key not configured');
+  }

   return {
     orderId: order.id,
     amount: order.amount,
     currency: order.currency,
-    keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID!
+    keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID
   };
+} catch (error) {
+  console.error('Failed to create Razorpay order:', error);
+  throw new Error('Payment order creation failed');
+}
📝 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
const order = await this.razorpay.orders.create({
amount: amount,
currency: 'INR',
receipt: `receipt_${Date.now()}`,
notes: { userId, plan, billingCycle: cycle }
});
return {
orderId: order.id,
amount: order.amount,
currency: order.currency,
keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID!
};
}
try {
const order = await this.razorpay.orders.create({
amount: amount,
currency: 'INR',
receipt: `receipt_${Date.now()}`,
notes: { userId, plan, billingCycle: cycle }
});
if (!process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID) {
throw new Error('Public Razorpay key not configured');
}
return {
orderId: order.id,
amount: order.amount,
currency: order.currency,
keyId: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID
};
} catch (error) {
console.error('Failed to create Razorpay order:', error);
throw new Error('Payment order creation failed');
}
}
🤖 Prompt for AI Agents
In src/lib/services/payment.service.ts around lines 44 to 57, the Razorpay order
creation call lacks error handling, risking unhandled exceptions if the API call
fails. Wrap the order creation code in a try-catch block, catch any errors
thrown by the Razorpay API, and handle them appropriately by logging the error
and either rethrowing it or returning a meaningful error response.

Comment on lines +34 to +37
// Simple lazy loading for dynamic imports without complex fallbacks
export const createSimpleLazy = (importPath: string) => {
return lazy(() => import(importPath));
};
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider removing or restricting this dynamic import helper.

The createSimpleLazy function has several potential issues:

  • String-based import paths bypass TypeScript checking
  • Can cause runtime errors with invalid paths
  • May interfere with bundler static analysis and tree shaking
  • Security risk if importPath comes from user input

Consider either:

  1. Removing this function if not actively used
  2. Restricting it to predefined paths with validation
  3. Using explicit lazy imports instead of dynamic string paths

🏁 Script executed:

#!/bin/bash
# Check if this function is actually used anywhere
echo "Searching for usage of createSimpleLazy..."
rg -n "createSimpleLazy" --type tsx --type ts --type jsx --type js

Length of output: 189


Let's re-run the search without file-type restrictions to locate any usages:


🏁 Script executed:

#!/bin/bash
echo "Searching for usage of createSimpleLazy in entire repo..."
rg -n "createSimpleLazy"

Length of output: 240


Remove unused createSimpleLazy helper

We ran a repository-wide search for createSimpleLazy and only its own definition was found (no invocations). Since this utility:

  • Bypasses TypeScript path validation
  • Can introduce runtime import errors
  • Hinders bundler optimizations and tree-shaking
  • May pose a security risk if misused

and isn’t used anywhere, please remove it (or replace it with explicit, type-safe lazy imports).

• File to update: src/lib/utils/lazy-imports.ts (lines 34–37)

🤖 Prompt for AI Agents
In src/lib/utils/lazy-imports.ts between lines 34 and 37, remove the entire
createSimpleLazy function since it is unused and can cause issues with type
safety, runtime errors, bundler optimizations, and security. Delete these lines
to clean up the code and avoid potential problems.

Comment on lines +47 to +50
export interface DeckWithFlashcards extends PrismaDeck {
flashcards: PrismaFlashcard[];
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Name collision: two “Deck” types with different shapes

You re-export PrismaDeck as DeckWithFlashcards and declare a custom interface Deck. Downstream imports (import { Deck }) will pick the interface, not the Prisma model, potentially causing subtle type mismatches.

Either:

export { Deck as PrismaDeck } from '@prisma/client'; // keep original
export type DeckSummary = { …minimal fields… };

or rename the custom one (DeckSummary) to avoid shadowing.

🤖 Prompt for AI Agents
In src/types/index.ts around lines 47 to 50, there is a naming collision between
the custom interface Deck and the PrismaDeck type, causing downstream imports of
Deck to refer to the interface instead of the Prisma model. To fix this, rename
the custom interface Deck to a distinct name like DeckSummary or another
descriptive name that avoids shadowing the PrismaDeck type, and update all
references accordingly to prevent type mismatches.

Comment on lines +90 to +97
export type SubscriptionPlan = 'Free' | 'Pro' | 'Organizational';
export type BillingCycle = 'monthly' | 'yearly';

export interface RazorpayOrderRequest {
plan: SubscriptionPlan;
billingCycle: BillingCycle;
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enum/string mismatch – plan casing diverges from DB

'Free' | 'Pro' | 'Organizational' does not line up with lowercase 'free' | 'basic' | 'pro' used in the database & runtime. Normalise both sides, e.g.:

-export type SubscriptionPlan = 'Free' | 'Pro' | 'Organizational';
+export type SubscriptionPlan = 'free' | 'basic' | 'pro';
🤖 Prompt for AI Agents
In src/types/index.ts around lines 90 to 97, the SubscriptionPlan type uses
capitalized strings ('Free', 'Pro', 'Organizational') that do not match the
lowercase strings ('free', 'basic', 'pro') used in the database and runtime.
Update the SubscriptionPlan type to use lowercase strings matching the database
values, and ensure any related code consuming this type is adjusted accordingly
for consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Backend and Database from Firebase Firestore to Supabase + PostgreSQL + Prisma ORM

2 participants