Skip to content

Feat/subscription plans#16

Merged
danarocha-br merged 103 commits intomainfrom
feat/subscription-plans
Feb 6, 2026
Merged

Feat/subscription plans#16
danarocha-br merged 103 commits intomainfrom
feat/subscription-plans

Conversation

@danarocha-br
Copy link
Owner

@danarocha-br danarocha-br commented Dec 9, 2025

Summary by CodeRabbit

  • New Features

    • Starter plan + full billing UI (checkout, portal, invoices), subscription sync, downgrade wizard, bulk-delete tools, account deletion, usage-alert cron/email.
    • Expanded usage limits (folders, public shares, video exports), video export lifecycle with per-export limits, hide-watermark preference and related UX, enhanced public share/embed view limits and URL shortening.
  • Improvements

    • Better OG previews, friendlier error pages and client error boundary, usage-aware dialogs and upgrade prompts, quicker cache/validation flows.
  • Bug Fixes

    • Various auth, billing sync, and data-consistency edge cases resolved.

…sage limit checks to use Supabase RPC functions.
… collection creation dialogs, and update usage limit service.
…ript with a CI workflow to ensure consistency.
… improved user resolution to the Stripe webhook handler.
…ates for profiles, snippets, and usage limits.
…component, client error boundary, and Next.js error pages.
…n actions by adding a new validation utility and integrating it into existing actions.
…d upgrade prompts and backend reconciliation.
…ation and update friendly error component UI.
…ibute for buttons, refine video export count logic, and correct snippet update action return.
…s, remote image patterns, and Cache-Control headers for static assets.
…ds, enhance validation schema for title, and integrate usage banner in navigation and home components.
… routes, enhancing security and access control. Additionally, improve rate limiting logic in enforceRateLimit function to handle user ID requirements more robustly.
…ionality in billing dialog. Enhance usage banner performance with memoization and improve rate limiter configuration for user ID handling.
…t Query v5, enhance type safety with UsageSummary, and make title in CreateAnimationInput optional.
@danarocha-br danarocha-br self-assigned this Dec 9, 2025
…ponse management. Implement try-catch for image response creation, ensuring fallback to a default image on failure. Remove unused Logo component to streamline the code.
…ic and replay controls. Implement loop animation feature for smoother transitions upon completion. Improve state management for animation playback and user interactions.
…ng and theme color mapping for code snippets. Introduce utility functions for truncating code and rendering styled segments, improving visual presentation in generated images. Add a new module for theme colors to support various syntax highlighting styles.
…xport components. Implement hooks for managing watermark display in the AnimationDownloadMenu, GifExporter, VideoExporter, and EmbedView. Enhance user options in the EditorOptionsMenu to toggle watermark visibility based on user plan, improving customization for exports and embeds.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/features/animation/video-exporter.tsx (1)

279-282: Theme name mismatch: nightOwl vs night-owl.

Same issue as in embed-view.tsx - the isDarkBackground array uses "nightOwl" but the theme key is "night-owl" in src/lib/themes-options.ts.

Apply this diff:

-  const isDarkBackground = ['sublime', 'hyper', 'dracula', 'monokai', 'nord', 'gotham', 'blue', 'nightOwl'].includes(
+  const isDarkBackground = ['sublime', 'hyper', 'dracula', 'monokai', 'nord', 'gotham', 'blue', 'night-owl'].includes(
     editorSettings.backgroundTheme
   );
src/app/api/og-image/route.tsx (2)

124-134: Theme key mismatch: nightOwl vs night-owl.

The themeBackgrounds map uses nightOwl (camelCase) but ogThemeColors in og-theme-colors.ts uses night-owl (kebab-case). This mismatch causes the night-owl theme to always fall back to the default gradient.

Additionally, several themes defined in ogThemeColors (github-dark, candy, peach, teal, flamingo, creamy, ice) have no corresponding gradient in themeBackgrounds.

 const themeBackgrounds: Record<string, string> = {
   sublime: "linear-gradient(135deg, #f472b6 0%, #a78bfa 50%, #60a5fa 100%)",
   hyper: "linear-gradient(135deg, #ec4899 0%, #8b5cf6 50%, #3b82f6 100%)",
   dracula: "linear-gradient(135deg, #ff79c6 0%, #bd93f9 50%, #6272a4 100%)",
   monokai: "linear-gradient(135deg, #f92672 0%, #66d9ef 50%, #a6e22e 100%)",
   nord: "linear-gradient(135deg, #88c0d0 0%, #81a1c1 50%, #5e81ac 100%)",
   gotham: "linear-gradient(135deg, #2aa889 0%, #599cab 50%, #4e5165 100%)",
   blue: "linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%)",
-  nightOwl: "linear-gradient(135deg, #c792ea 0%, #7fdbca 50%, #82aaff 100%)",
+  "night-owl": "linear-gradient(135deg, #c792ea 0%, #7fdbca 50%, #82aaff 100%)",
 };

267-274: Consider consolidating duplicate error handling or differentiating fallback behavior.

Both the inner (line 267-270) and outer (line 271-274) catch blocks return identical empty ImageResponse. If the intent is to handle Satori-specific errors differently, consider logging distinct messages or returning a more informative fallback image. Otherwise, the inner try/catch could be removed.

If keeping both, differentiate the error context:

     } catch (error) {
-      console.error("OG image generation failed", error);
+      console.error("Satori/ImageResponse rendering failed:", error);
       return new ImageResponse(<div />, size);
     }
   } catch (error) {
-    console.error("OG image generation failed", error);
+    console.error("OG image request processing failed:", error);
     return new ImageResponse(<div />, size);
   }
♻️ Duplicate comments (2)
src/features/code-editor/editor.tsx (2)

320-326: Add missing dependencies to useEffect.

This was flagged in a previous review. The useEffect is missing refetchUsage and isUsageLoading from its dependency array. React Query's refetch functions are stable and safe to include.

Apply this diff:

     useEffect(() => {
       if (user_id && !isUsageLoading) {
         refetchUsage();
       }
-    }, [user_id]); // Only depend on user_id to trigger on user change
+    }, [user_id, refetchUsage, isUsageLoading]);

608-616: Ref merging is correct but consider a comment.

This was noted in a previous review. The merged ref pattern handles both function and object refs correctly. A brief comment explaining the purpose would improve maintainability.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0da4690 and 8f8ca44.

📒 Files selected for processing (12)
  • src/actions/user/update-watermark-preference.ts (1 hunks)
  • src/app/api/og-image/route.tsx (7 hunks)
  • src/features/animation/animation-download-menu.tsx (1 hunks)
  • src/features/animation/embed-view.tsx (2 hunks)
  • src/features/animation/gif-exporter.tsx (3 hunks)
  • src/features/animation/hooks/use-watermark-visibility.ts (1 hunks)
  • src/features/animation/share-dialog/export-overlay.tsx (3 hunks)
  • src/features/animation/video-exporter.tsx (3 hunks)
  • src/features/code-editor/editor.tsx (10 hunks)
  • src/features/user-tools/ui/editor-options-menu/index.tsx (6 hunks)
  • src/features/user/queries.ts (2 hunks)
  • src/lib/og-theme-colors.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Feature flows are grouped in src/features/* (e.g., code-editor, snippets, share-code); keep new domain logic in its own feature folder.

Files:

  • src/features/animation/share-dialog/export-overlay.tsx
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/animation/animation-download-menu.tsx
  • src/features/animation/gif-exporter.tsx
  • src/features/animation/embed-view.tsx
  • src/features/animation/hooks/use-watermark-visibility.ts
  • src/features/user/queries.ts
  • src/features/code-editor/editor.tsx
  • src/features/animation/video-exporter.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Files:

  • src/features/animation/share-dialog/export-overlay.tsx
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/animation/animation-download-menu.tsx
  • src/lib/og-theme-colors.ts
  • src/actions/user/update-watermark-preference.ts
  • src/app/api/og-image/route.tsx
  • src/features/animation/gif-exporter.tsx
  • src/features/animation/embed-view.tsx
  • src/features/animation/hooks/use-watermark-visibility.ts
  • src/features/user/queries.ts
  • src/features/code-editor/editor.tsx
  • src/features/animation/video-exporter.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Let Prettier (via ESLint) handle formatting; avoid manual style deviations.

Files:

  • src/features/animation/share-dialog/export-overlay.tsx
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/animation/animation-download-menu.tsx
  • src/lib/og-theme-colors.ts
  • src/actions/user/update-watermark-preference.ts
  • src/app/api/og-image/route.tsx
  • src/features/animation/gif-exporter.tsx
  • src/features/animation/embed-view.tsx
  • src/features/animation/hooks/use-watermark-visibility.ts
  • src/features/user/queries.ts
  • src/features/code-editor/editor.tsx
  • src/features/animation/video-exporter.tsx
src/{lib,utils}/**

📄 CodeRabbit inference engine (AGENTS.md)

Cross-cutting helpers live in src/lib (config, analytics, query client) and src/utils (Supabase client, formatting).

Files:

  • src/lib/og-theme-colors.ts
src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js App Router lives in src/app (routes, layouts, metadata, providers). Shared stores sit in src/app/store.

Files:

  • src/app/api/og-image/route.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.

Applied to files:

  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/animation/animation-download-menu.tsx
  • src/features/user/queries.ts
  • src/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.

Applied to files:

  • src/features/user/queries.ts
  • src/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.

Applied to files:

  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Applied to files:

  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).

Applied to files:

  • src/features/code-editor/editor.tsx
🧬 Code graph analysis (8)
src/features/user-tools/ui/editor-options-menu/index.tsx (2)
src/app/store/index.ts (1)
  • useUserStore (46-53)
src/features/user/queries.ts (3)
  • useWatermarkPreference (102-122)
  • useUserPlan (55-79)
  • useUpdateWatermarkPreference (124-177)
src/features/animation/animation-download-menu.tsx (5)
src/lib/config/plans.ts (1)
  • PlanId (8-8)
src/features/login/index.tsx (1)
  • LoginDialog (28-107)
src/components/ui/upgrade-dialog/index.tsx (1)
  • UpgradeDialog (105-369)
src/features/animation/gif-exporter.tsx (1)
  • GifExporter (34-317)
src/features/animation/share-dialog/export-overlay.tsx (1)
  • ExportOverlay (23-83)
src/actions/user/update-watermark-preference.ts (2)
src/lib/config/plans.ts (1)
  • PlanId (8-8)
src/lib/services/user-preferences.ts (1)
  • updateWatermarkPreference (76-138)
src/features/animation/embed-view.tsx (4)
src/features/animation/hooks/use-watermark-visibility.ts (1)
  • useWatermarkVisibility (19-46)
src/lib/themes-options.ts (1)
  • themes (1-74)
src/lib/fonts-options.ts (1)
  • fonts (1-58)
src/lib/utils/index.ts (1)
  • cn (10-12)
src/features/animation/hooks/use-watermark-visibility.ts (1)
src/features/user/queries.ts (1)
  • useWatermarkPreference (102-122)
src/features/user/queries.ts (5)
src/lib/services/usage-limits.ts (1)
  • UsageSummary (30-55)
src/lib/sentry-utils.ts (1)
  • reportQueryError (11-32)
src/lib/config/plans.ts (1)
  • PlanId (8-8)
src/lib/services/user-preferences.ts (2)
  • WatermarkPreference (4-8)
  • getWatermarkPreference (16-66)
src/actions/user/update-watermark-preference.ts (1)
  • updateWatermarkPreferenceAction (17-81)
src/features/code-editor/editor.tsx (6)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/app/store/index.ts (2)
  • EditorState (14-23)
  • useEditorStore (68-162)
src/features/animation/hooks/use-watermark-visibility.ts (1)
  • useWatermarkVisibility (19-46)
src/features/user/queries.ts (2)
  • useUserUsage (31-53)
  • USAGE_QUERY_KEY (11-11)
src/features/snippets/queries.ts (1)
  • CreateSnippetProps (44-52)
src/lib/services/usage-limits-cache.ts (1)
  • getUsageLimitsCacheProvider (155-160)
src/features/animation/video-exporter.tsx (3)
src/features/animation/index.ts (1)
  • AnimationFrame (6-6)
src/lib/utils/index.ts (1)
  • cn (10-12)
src/components/ui/logo/index.tsx (1)
  • Logo (7-134)
🔇 Additional comments (34)
src/actions/user/update-watermark-preference.ts (3)

1-11: LGTM!

The server directive, imports, and result type definition are well-structured. The UpdateWatermarkPreferenceResult type provides a clear contract for callers with appropriate optional fields.


17-33: LGTM!

Authentication flow correctly uses the Supabase server client and handles both auth errors and missing user cases with a clear error message.


73-81: LGTM!

Success return and catch block follow consistent patterns. The error logging includes a helpful context tag for debugging.

src/features/code-editor/editor.tsx (6)

69-116: Well-structured utility for upgrade error detection.

The isUpgradeError function properly handles multiple error shapes (objects with code/type/message, strings) and uses explicit keyword matching. This is a clean approach for classifying errors that should trigger upgrade prompts.


118-139: Good extraction of rollback logic.

The revertEditorState helper consolidates the rollback pattern previously duplicated in onError and onSettled. The null-filtering with type guard is correct.


169-202: MutationObserver pattern is appropriate for cross-component DOM sync.

The observer correctly watches for data-exporting attribute changes set by the export menu. The cleanup disconnects properly. However, the initial check on line 196 happens synchronously before the observer is attached, which is fine for initialization.


352-433: Mutation typing and rollback pattern look correct.

The useMutation now properly types ActionResult<Snippet> and stores previousEditorState and editorId in context for rollback. The onSettled handler correctly checks for actionResult?.error before reverting state. Cache clearing before query invalidation prevents race conditions.


552-563: Good defensive checks before save.

The validation for user session and usage data loading state prevents saves when data isn't ready. The error messages are clear and actionable.


792-811: Watermark rendering looks correct.

The watermark conditionally renders based on shouldShowWatermark and uses group-data-[exporting=true]/export:flex to show only during exports. The positioning and styling are appropriate.

src/features/animation/share-dialog/export-overlay.tsx (1)

20-34: Clean prop forwarding for watermark visibility.

The hideWatermark prop is correctly added to the interface with a sensible default of false and properly forwarded to VideoExporter.

src/features/animation/gif-exporter.tsx (2)

26-43: Watermark visibility control properly implemented.

The hideWatermark prop is correctly added with default false and used to conditionally render the watermark block.


281-312: Watermark rendering logic is correct.

The conditional !hideWatermark check properly gates watermark rendering. The inline styles ensure consistent rendering during GIF export where CSS classes may not be captured correctly by the canvas.

src/features/animation/embed-view.tsx (1)

218-235: Watermark renders correctly for embeds.

The watermark is conditionally rendered based on shouldShowWatermark. Since useWatermarkVisibility() is called without a userId (embed context doesn't have the owner's ID), it defaults to showing the watermark, which is the safe behavior noted in the comment on line 46-47.

src/features/animation/hooks/use-watermark-visibility.ts (1)

1-46: Well-designed hook with safe defaults.

The hook correctly implements watermark visibility logic:

  • Shows watermark by default when loading or no data (safe default)
  • Only hides when user has PRO plan AND has explicitly enabled the preference
  • Uses useMemo appropriately for the computed result
  • Good JSDoc documentation
src/features/animation/video-exporter.tsx (2)

27-51: Clean prop addition and dimension extraction.

The hideWatermark prop is correctly added with default false. Extracting width and height at the component level from getResolutionDimensions is cleaner and avoids redundant calls.


346-371: Watermark conditional rendering is correct.

The !hideWatermark check properly gates watermark rendering. The inline styles ensure consistent capture during video export.

src/features/animation/animation-download-menu.tsx (5)

36-52: State initialization is well-organized.

The component correctly initializes watermark visibility and UI state. The upgradeContext state properly tracks limit information for the upgrade dialog.


95-154: Optimistic increment pattern addresses the TOCTOU race condition.

The export flow now performs an atomic increment via incrementAndVerifyExportLimit() before starting the export, with proper rollback via decrementExportCount() on failure. This addresses the previously identified race condition where multiple concurrent exports could bypass limits.


270-276: Consistent cancellation handling for GIF exports.

The onCancelled callback properly decrements the export count, addressing the previously identified inconsistency between GIF and video export cancellation handling.


295-300: Consistent cancellation handling for video exports.

The ExportOverlay's onCancelled now includes the same decrement logic as the GIF exporter, ensuring consistent behavior across both export paths.


186-222: Clean dialog and dropdown integration.

The LoginDialog and UpgradeDialog are correctly wired with their respective state. The dropdown menu items properly trigger handleExport with the appropriate format.

src/features/user/queries.ts (6)

1-9: LGTM!

Imports are well-organized and all appear necessary for the hooks and utilities defined in this file.


14-29: LGTM!

The fetch function correctly handles user ID resolution with proper authentication checks.


31-53: LGTM!

The error handling pattern via useEffect correctly adapts to React Query v5's removal of onError from useQuery, and properly delegates to the shared reportQueryError utility. The staleTime and enabled configurations are appropriate.


55-79: LGTM!

Consistent implementation with useUserUsage, correctly deriving the plan from the usage data with appropriate error handling.


102-122: LGTM!

Consistent implementation following the established patterns in this file for query hooks with proper error handling.


147-156: Note: Optimistic update skipped when preference data is null.

If previousPreference is null (e.g., user hasn't fetched preferences yet or column doesn't exist), the optimistic update won't apply. This is acceptable defensive behavior, but users in this state won't see immediate UI feedback until the mutation completes.

src/lib/og-theme-colors.ts (1)

400-408: LGTM!

The fallback mechanism and helper function are well-structured. The getThemeColors function correctly returns theme-specific colors or falls back to the default.

src/app/api/og-image/route.tsx (4)

2-3: LGTM!

The imports for syntax highlighting utilities and theme colors are correctly added and align with the cross-cutting helpers guideline for src/lib. Based on coding guidelines, these utilities are appropriately placed.


107-122: LGTM!

The syntax highlighting pipeline is well-structured with clear separation: truncation → highlighting → rendering. The theme colors integration correctly applies the background from the resolved theme.


240-256: LGTM!

The watermark implementation uses only Satori-compatible CSS properties (position, color, fontSize, fontWeight, letterSpacing). The previous review concern about unsupported filter and transform properties no longer applies to this simplified implementation.


221-236: LGTM!

The dynamic rendering of syntax-highlighted segments is correctly implemented. The empty line handling with a transparent space character (line 234) ensures consistent line heights. Using array indices as keys is acceptable here since the content is static during render.

src/features/user-tools/ui/editor-options-menu/index.tsx (2)

98-124: Well-structured watermark preference handler.

The sign-in check, PRO-only gating, and user feedback via toasts are well implemented. The mutation callbacks appropriately layer user-facing feedback on top of the data-layer error handling already present in the hook.

Note: The PRO check on line 104 relies on isPro being accurate, which is affected by the race condition flagged in the previous comment (lines 63-67). Once that is addressed, this handler will work correctly.


172-191: Clean watermark control UI with appropriate feature gating.

The conditional rendering, PRO indicator, and state-based disabling create a clear user experience. The menu item correctly communicates the PRO-only nature of the feature.

The disabled condition on line 181 should also include plan loading state as noted in the earlier comment (lines 63-67) to prevent interaction during plan data fetch.

Comment on lines +65 to +71
if (!result.success) {
return {
success: false,
error: result.error || 'Failed to update preference',
requiresUpgrade: result.error?.includes('PRO'),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Fragile string matching for requiresUpgrade detection.

Line 69 uses result.error?.includes('PRO') to detect if an upgrade is required. This is brittle — if the service's error message changes (e.g., "Upgrade to Pro plan" or localization), this detection will fail silently.

Consider either:

  1. Have updateWatermarkPreference return a requiresUpgrade field explicitly.
  2. Use a more specific pattern match or error code.
-        requiresUpgrade: result.error?.includes('PRO'),
+        // Note: relies on service error containing 'PRO' or 'Upgrade'
+        requiresUpgrade: result.error?.toLowerCase().includes('upgrade'),

Or better, extend the service result type:

// In user-preferences.ts
Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>
🤖 Prompt for AI Agents
In src/actions/user/update-watermark-preference.ts around lines 65 to 71, the
code uses fragile string matching result.error?.includes('PRO') to set
requiresUpgrade; update the service and callsite so the service returns an
explicit requiresUpgrade boolean (or an error code) in its result type (e.g.,
Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>) and
then use result.requiresUpgrade at the callsite; adjust the types/imports
accordingly and fall back to a safer pattern match only if the explicit flag is
absent.

…g Sentry integration and simplifying OG image generation. Refactor syntax highlighting to use lightweight pattern matching instead of highlight.js, and streamline theme color mappings for improved performance. Enhance Next.js configuration for package imports to further reduce bundle size.
…pet organization. Introduce multi-slide code animations export options and improve code snippet management by allowing users to save and organize snippets into collections. Update changelog component for better display and error handling in API requests.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/api/og-image/route.tsx (1)

38-55: Empty catch block silently suppresses database fetch errors.

The dynamic import and database fetch at lines 38-54 are wrapped in a try-catch that silently ignores all errors (line 55). This makes debugging difficult when slug-based fetches fail due to import errors, network issues, or database problems.

Add minimal error logging:

-      } catch {}
+      } catch (error) {
+        console.error("Failed to fetch shared link data:", error);
+      }
next.config.mjs (1)

132-156: Invalid X-Frame-Options value.

X-Frame-Options: ALLOWALL is not a valid value. The standard only defines DENY, SAMEORIGIN, or ALLOW-FROM (obsolete). Browsers will ignore this invalid header.

Since you already have Content-Security-Policy: frame-ancestors * which allows embedding from any origin, you should either omit X-Frame-Options or remove it entirely as CSP takes precedence in modern browsers.

Apply this diff to remove the invalid header:

       {
         source: "/animate/embed/:slug",
         headers: [
           {
             key: "Content-Security-Policy",
             value: "frame-ancestors *",
           },
-          {
-            key: "X-Frame-Options",
-            value: "ALLOWALL",
-          },
         ],
       },
       {
         source: "/animate/shared/:slug",
         headers: [
           {
             key: "Content-Security-Policy",
             value: "frame-ancestors *",
           },
-          {
-            key: "X-Frame-Options",
-            value: "ALLOWALL",
-          },
         ],
       },
♻️ Duplicate comments (1)
src/app/api/og-image/route.tsx (1)

10-11: Type definition is incomplete for editor property.

A previous review noted that backgroundTheme should be included in the Payload type's editor property. While the current code doesn't access backgroundTheme (uses hardcoded "sublime"), adding it now would enable the theme flexibility suggested in lines 72-74.

Update the type to support theme customization:

-type Payload = { slides?: Slide[]; editor?: { fontFamily?: string } };
+type Payload = { slides?: Slide[]; editor?: { fontFamily?: string; backgroundTheme?: string } };
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8ca44 and b23ed4e.

📒 Files selected for processing (8)
  • README.md (3 hunks)
  • instrumentation.ts (1 hunks)
  • next.config.mjs (2 hunks)
  • src/app/api/og-image/route.tsx (4 hunks)
  • src/features/changelog/index.tsx (2 hunks)
  • src/features/changelog/styles.ts (1 hunks)
  • src/lib/og-theme-colors.ts (1 hunks)
  • src/lib/services/changelog.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Files:

  • instrumentation.ts
  • src/app/api/og-image/route.tsx
  • src/lib/services/changelog.ts
  • src/features/changelog/index.tsx
  • src/lib/og-theme-colors.ts
  • src/features/changelog/styles.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Let Prettier (via ESLint) handle formatting; avoid manual style deviations.

Files:

  • instrumentation.ts
  • src/app/api/og-image/route.tsx
  • src/lib/services/changelog.ts
  • src/features/changelog/index.tsx
  • src/lib/og-theme-colors.ts
  • src/features/changelog/styles.ts
src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js App Router lives in src/app (routes, layouts, metadata, providers). Shared stores sit in src/app/store.

Files:

  • src/app/api/og-image/route.tsx
src/{lib,utils}/**

📄 CodeRabbit inference engine (AGENTS.md)

Cross-cutting helpers live in src/lib (config, analytics, query client) and src/utils (Supabase client, formatting).

Files:

  • src/lib/services/changelog.ts
  • src/lib/og-theme-colors.ts
src/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Feature flows are grouped in src/features/* (e.g., code-editor, snippets, share-code); keep new domain logic in its own feature folder.

Files:

  • src/features/changelog/index.tsx
  • src/features/changelog/styles.ts
🧠 Learnings (7)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Applied to files:

  • instrumentation.ts
  • next.config.mjs
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.

Applied to files:

  • next.config.mjs
  • README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Run locally with `pnpm dev` (Next dev server). Production build: `pnpm build`; serve build: `pnpm start`. Lint & format: `pnpm lint` (ESLint with Prettier rules); auto-fix common issues: `pnpm lint:fix`.

Applied to files:

  • next.config.mjs
  • README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/app/** : Next.js App Router lives in `src/app` (routes, layouts, metadata, providers). Shared stores sit in `src/app/store`.

Applied to files:

  • next.config.mjs
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to .env.local : Store secrets in `.env.local`; never commit keys. Required keys include Supabase (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`) and Sentry/PostHog credentials where applicable.

Applied to files:

  • README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Install dependencies using `pnpm install` (repo tracks `pnpm-lock.yaml`).

Applied to files:

  • README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use the `src/components/ui` primitives and `cn` helper for styling; align with existing Tailwind tokens and CSS variables in `src/app/globals.css`.

Applied to files:

  • src/features/changelog/styles.ts
🧬 Code graph analysis (3)
src/app/api/og-image/route.tsx (2)
src/lib/utils/og-syntax-highlight.ts (3)
  • truncateCodeForOG (131-141)
  • highlightCodeForOG (62-69)
  • renderStyledSegments (79-126)
src/lib/og-theme-colors.ts (1)
  • getThemeColors (64-66)
src/lib/services/changelog.ts (2)
scripts/check-env.mjs (1)
  • result (9-9)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/features/changelog/index.tsx (2)
src/components/ui/popover/index.tsx (1)
  • PopoverContent (42-42)
src/components/ui/separator/index.tsx (1)
  • Separator (29-29)
🔇 Additional comments (10)
src/features/changelog/index.tsx (1)

37-37: LGTM! Good UI improvements.

The addition of max-h-[500px] overflow-y-auto on the PopoverContent (line 37) prevents the changelog popover from growing excessively and provides smooth scrolling for long content. The mt-3 spacing on the Separator (line 108) improves visual separation between entries while correctly avoiding extra margin on the last item.

Also applies to: 108-108

src/features/changelog/styles.ts (1)

14-14: LGTM! Layout change to vertical stacking.

Changing the title from a horizontal row (items-center) to a vertical column (flex-col) stacks the badge above the entry title text. This aligns well with the vertical content flow established by the popover height constraints and improves readability.

src/lib/services/changelog.ts (1)

49-81: LGTM for client-side implementation.

The client-side path correctly uses a relative URL and delegates API key handling to the server-side route. The timeout and error handling are appropriate. Once the server-side return shape is fixed (see previous comment), both paths will be consistent.

src/app/api/og-image/route.tsx (1)

90-92: LGTM! Watermark rendering is now Satori-compatible.

The previous review flagged CSS filter and transform properties that were unsupported in Satori. The current implementation uses only basic positioning (position: absolute, bottom, right) and text styling, which are fully supported.

README.md (2)

13-16: Clear documentation of the new animations feature.

The description effectively communicates the multi-slide animation capability and export options.


44-46: Good addition of environment documentation.

Documenting the validation tooling and pointing users to .env.example provides a clear setup path for developers.

next.config.mjs (4)

1-7: Well-structured environment handling.

The side-effect import ensures validation runs early, and the fallback chain provides a sensible development default while allowing production override via CORS_ALLOWED_ORIGIN or NEXT_PUBLIC_APP_URL.


11-15: Appropriate use of bundle optimization.

Using optimizePackageImports for highlight.js is a good optimization choice for a code-focused application.


53-80: Excellent cache strategy for static assets.

The Cache-Control headers follow best practices: aggressive caching with immutable for fingerprinted assets, and shorter TTL with stale-while-revalidate for dynamic content.


81-130: Secure and well-structured CORS configuration.

The tiered approach is correct: public endpoints use wildcard CORS while authenticated APIs restrict to appOrigin. The addition of Authorization in allowed headers (Line 127) properly supports authenticated requests.

Comment on lines +42 to +60
export const ogThemeColors: Record<string, SyntaxColorMap> = {
// All themes use the same base colors to minimize bundle size
sublime: base,
hyper: base,
"github-dark": base,
monokai: base,
"night-owl": base,
nightOwl: base,
blue: base,
gotham: base,
candy: base,
peach: base,
teal: base,
flamingo: base,
creamy: base,
ice: base,
dracula: base,
nord: base,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Theme consolidation trade-off accepted, but consider documenting the limitation.

All themes now reference the same base palette to minimize Edge Runtime bundle size (as noted in the file header). This resolves the duplication concern from the previous review but means the theme parameter is effectively ignored throughout the application.

Consider adding a JSDoc comment above the export to make this behavior explicit:

/**
 * Theme color mappings for OG images.
 * Note: All themes currently use the same base palette to optimize Edge Runtime bundle size.
 * The theme parameter is accepted for API compatibility but does not affect colors.
 */
export const ogThemeColors: Record<string, SyntaxColorMap> = {
🤖 Prompt for AI Agents
In src/lib/og-theme-colors.ts around lines 42 to 60, add a concise JSDoc/TSDoc
comment immediately above the exported ogThemeColors to document that all listed
themes currently reference the same `base` palette to minimize Edge Runtime
bundle size and that the theme parameter is accepted for API compatibility but
does not change colors; include the suggested text (brief title, one-line note
about bundle-size optimization, and one-line note that theme is accepted but
ignored) so consumers and future maintainers understand the limitation without
changing the existing export.

…idation logic from the updateWatermarkPreferenceAction. Enhance OG image generation to support customizable background themes in editor options. Improve user experience in EditorOptionsMenu by handling loading states for user plans and watermark preferences, ensuring smoother interactions during updates.
…uthentication context. Refactor Home component by removing unused Supabase client and user query logic, simplifying state management for loading states in CodeEditor.
…eries upon subscription activation. Update plan configuration retrieval to normalize legacy plan IDs, ensuring compatibility during database migration.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/api/og-image/route.tsx (2)

28-34: Unused description variables are dead code.

descriptionOverride (line 29) and descriptionFromDb (lines 33, 52) are declared and assigned but never used in the rendered output. Either remove them or include the description in the OG image if intended.

     const slugParam = searchParams.get("slug");
     const titleOverride = searchParams.get("title");
-    const descriptionOverride = searchParams.get("description");

     let decodedPayload: Payload | null = null;
     let titleFromDb: string | undefined;
-    let descriptionFromDb: string | undefined;

And remove the assignment on line 52:

             titleFromDb = data.title ?? undefined;
-            descriptionFromDb = data.description ?? undefined;

Also applies to: 52-53


36-55: Empty catch block silently masks shared link fetch failures.

The catch {} on line 55 swallows all errors from the dynamic imports and getSharedLink call. This makes debugging OG image issues for shared links extremely difficult since failures leave no trace.

         }
-      } catch {}
+      } catch (error) {
+        console.error("Failed to fetch shared link for OG image:", error);
+      }
     } else if (payloadParam) {
♻️ Duplicate comments (5)
src/app/api/og-image/route.tsx (1)

78-101: Empty catch block masks ImageResponse rendering failures.

This was flagged in a previous review. The inner catch (lines 99-100) returns an empty <div/> when ImageResponse construction fails but provides no indication of what went wrong, making it difficult to diagnose rendering issues.

Add error logging before returning the fallback:

-    } catch {
-      return new ImageResponse(<div/>,size);
+    } catch (error) {
+      console.error("ImageResponse rendering failed:", error);
+      return new ImageResponse(<div />, size);
     }
src/actions/user/update-watermark-preference.ts (1)

37-43: Good improvement, but fragile string matching remains.

Improvement noted: The duplicate PRO plan check has been correctly removed from this action (as suggested in past review), properly delegating plan validation to the service layer.

⚠️ Fragile detection persists: Line 41 still uses result.error?.includes('PRO') to infer requiresUpgrade. This string matching is brittle—if the service error message changes (e.g., localization, rewording to "Pro plan required"), the detection silently breaks.

Recommended fix: Have the service return an explicit requiresUpgrade field:

Update the service return type in user-preferences.ts:

Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>

Then update this action to use the explicit flag:

     if (!result.success) {
       return {
         success: false,
         error: result.error || 'Failed to update preference',
-        requiresUpgrade: result.error?.includes('PRO'),
+        requiresUpgrade: result.requiresUpgrade,
       };
     }
src/lib/config/plans.ts (3)

17-22: Docstring still claims function throws, but it returns empty string.

The JSDoc says "Throws a clear error if the environment variable is missing" but the implementation logs a warning and returns an empty string (lines 26-33). This documentation mismatch was flagged in a previous review.

Apply this diff to align the documentation:

 /**
- * Validates and returns a Stripe price ID from environment variables.
- * Throws a clear error if the environment variable is missing or empty.
- * This ensures configuration errors are caught at startup rather than causing
- * silent failures during checkout.
+ * Reads a Stripe price ID from environment variables.
+ * Logs a warning and returns an empty string if the variable is missing or empty.
+ * Validation happens at checkout time in API routes.
  */

186-187: Remove duplicate comment line.

Line 187 duplicates the comment from line 186.

 // Helper to get upgrade target (next plan tier)
-// Helper to get upgrade target (next plan tier)
 export function getUpgradeTarget(currentPlan: PlanId): PlanId | null {

188-192: Consider deriving upgrade target from planOrder for maintainability.

As noted in a previous review, deriving the upgrade path from planOrder would keep the logic automatically in sync when tiers change.

 export function getUpgradeTarget(currentPlan: PlanId): PlanId | null {
-  if (currentPlan === 'free') return 'starter';
-  if (currentPlan === 'starter') return 'pro';
-  return null; // Pro has no upgrade target
+  const index = planOrder.indexOf(currentPlan);
+  if (index === -1 || index === planOrder.length - 1) return null;
+  return planOrder[index + 1];
 }
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b23ed4e and 79e82ba.

📒 Files selected for processing (10)
  • src/actions/user/update-watermark-preference.ts (1 hunks)
  • src/app/api/og-image/route.tsx (4 hunks)
  • src/app/checkout/success/checkout-success-client.tsx (1 hunks)
  • src/app/providers.tsx (2 hunks)
  • src/components/auth-provider/index.tsx (1 hunks)
  • src/features/animation/embed-view.tsx (2 hunks)
  • src/features/home/index.tsx (2 hunks)
  • src/features/user-tools/ui/editor-options-menu/index.tsx (6 hunks)
  • src/features/user/queries.ts (1 hunks)
  • src/lib/config/plans.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js App Router lives in src/app (routes, layouts, metadata, providers). Shared stores sit in src/app/store.

Files:

  • src/app/checkout/success/checkout-success-client.tsx
  • src/app/providers.tsx
  • src/app/api/og-image/route.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Files:

  • src/app/checkout/success/checkout-success-client.tsx
  • src/components/auth-provider/index.tsx
  • src/actions/user/update-watermark-preference.ts
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/app/providers.tsx
  • src/features/user/queries.ts
  • src/features/animation/embed-view.tsx
  • src/lib/config/plans.ts
  • src/app/api/og-image/route.tsx
  • src/features/home/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Let Prettier (via ESLint) handle formatting; avoid manual style deviations.

Files:

  • src/app/checkout/success/checkout-success-client.tsx
  • src/components/auth-provider/index.tsx
  • src/actions/user/update-watermark-preference.ts
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/app/providers.tsx
  • src/features/user/queries.ts
  • src/features/animation/embed-view.tsx
  • src/lib/config/plans.ts
  • src/app/api/og-image/route.tsx
  • src/features/home/index.tsx
src/components/**

📄 CodeRabbit inference engine (AGENTS.md)

Reusable building blocks are in src/components (ui design system, analytics, SEO helpers, session sync). Assets and marketing visuals sit in src/assets.

Files:

  • src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/components/**/*.{ts,tsx}: Use the src/components/ui primitives and cn helper for styling; align with existing Tailwind tokens and CSS variables in src/app/globals.css.
Surface upgrade prompts via UpgradeDialog and show current usage with UsageStatsWidget.

Files:

  • src/components/auth-provider/index.tsx
src/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Feature flows are grouped in src/features/* (e.g., code-editor, snippets, share-code); keep new domain logic in its own feature folder.

Files:

  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/user/queries.ts
  • src/features/animation/embed-view.tsx
  • src/features/home/index.tsx
src/{lib,utils}/**

📄 CodeRabbit inference engine (AGENTS.md)

Cross-cutting helpers live in src/lib (config, analytics, query client) and src/utils (Supabase client, formatting).

Files:

  • src/lib/config/plans.ts
🧠 Learnings (6)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).

Applied to files:

  • src/app/checkout/success/checkout-success-client.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.

Applied to files:

  • src/components/auth-provider/index.tsx
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/user/queries.ts
  • src/lib/config/plans.ts
  • src/features/home/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.

Applied to files:

  • src/actions/user/update-watermark-preference.ts
  • src/features/user-tools/ui/editor-options-menu/index.tsx
  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.

Applied to files:

  • src/features/user/queries.ts
  • src/lib/config/plans.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Applied to files:

  • src/features/user/queries.ts
  • src/lib/config/plans.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).

Applied to files:

  • src/lib/config/plans.ts
🧬 Code graph analysis (9)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (1)
  • USAGE_QUERY_KEY (12-12)
src/components/auth-provider/index.tsx (1)
src/app/store/index.ts (1)
  • useUserStore (46-53)
src/actions/user/update-watermark-preference.ts (1)
src/lib/services/user-preferences.ts (1)
  • updateWatermarkPreference (76-138)
src/features/user-tools/ui/editor-options-menu/index.tsx (2)
src/app/store/index.ts (1)
  • useUserStore (46-53)
src/features/user/queries.ts (3)
  • useWatermarkPreference (105-125)
  • useUserPlan (68-92)
  • useUpdateWatermarkPreference (127-206)
src/app/providers.tsx (2)
src/components/auth-provider/index.tsx (1)
  • AuthProvider (14-58)
src/features/billing/components/portal-return-handler.tsx (1)
  • PortalReturnHandler (9-65)
src/features/user/queries.ts (3)
src/lib/sentry-utils.ts (1)
  • reportQueryError (11-32)
src/lib/services/user-preferences.ts (2)
  • WatermarkPreference (4-8)
  • getWatermarkPreference (16-66)
src/actions/user/update-watermark-preference.ts (1)
  • updateWatermarkPreferenceAction (16-53)
src/features/animation/embed-view.tsx (6)
src/features/animation/share-utils.ts (1)
  • AnimationSharePayload (5-16)
src/features/animation/hooks/use-watermark-visibility.ts (1)
  • useWatermarkVisibility (19-46)
src/features/animation/analytics.ts (1)
  • trackAnimationEvent (7-16)
src/lib/themes-options.ts (1)
  • themes (1-74)
src/lib/fonts-options.ts (1)
  • fonts (1-58)
src/lib/utils/index.ts (1)
  • cn (10-12)
src/app/api/og-image/route.tsx (2)
src/lib/utils/og-syntax-highlight.ts (3)
  • truncateCodeForOG (131-141)
  • highlightCodeForOG (62-69)
  • renderStyledSegments (79-126)
src/lib/og-theme-colors.ts (1)
  • getThemeColors (64-66)
src/features/home/index.tsx (1)
src/features/code-editor/index.tsx (1)
  • CodeEditor (17-165)
🔇 Additional comments (16)
src/app/api/og-image/route.tsx (3)

1-11: LGTM - Types and imports are well-structured.

The Payload type now correctly includes backgroundTheme in the editor property, addressing a prior review concern. The imports are appropriately organized.


13-21: LGTM - Safe decoding with appropriate fallback.

The safeDecodePayload function correctly returns null on decode failures, which is the expected behavior for invalid input. The caller handles null gracefully.


60-76: LGTM - Theme extraction and code processing are correct.

The backgroundTheme is now properly extracted from the payload with a sensible default. The code truncation, highlighting, and segment rendering pipeline follows the utility APIs correctly.

src/actions/user/update-watermark-preference.ts (2)

19-32: LGTM! Clean authentication pattern.

The authentication flow follows the established server action pattern and provides clear error messaging for unauthenticated requests.


45-52: LGTM! Standard error handling.

The success path and catch block follow established conventions for server actions. Error logging aids debugging while keeping user-facing messages generic.

src/features/animation/embed-view.tsx (2)

46-49: LGTM! Safe default for embed watermark visibility.

The hook correctly defaults to showing the watermark when no userId is provided. The comment accurately documents the current limitation and potential future enhancement to fetch the owner's preference.


52-62: LGTM! Theme backward compatibility addressed.

Including both "night-owl" and "nightOwl" variants ensures the dark background detection works regardless of which theme identifier format is used.

src/features/user/queries.ts (2)

15-35: LGTM! Good extraction of user resolution logic.

The resolveUserId helper centralizes the authentication check and user ID resolution, reducing duplication across query functions. Good use of JSDoc documentation.


52-63: LGTM! Appropriate error handling pattern for React Query v5.

The useEffect-based error handling correctly adapts to React Query v5's removal of onError from useQuery. The pattern is consistently applied across all query hooks.

src/features/user-tools/ui/editor-options-menu/index.tsx (3)

61-68: LGTM! Plan loading race condition properly addressed.

The implementation correctly:

  1. Captures isPlanLoading from useUserPlan (line 63)
  2. Computes isPro only when plan data has loaded (line 66)
  3. Guards the handler against clicks during loading (lines 104-106)
  4. Disables the checkbox during all loading states (line 185)

This prevents the race condition where PRO users would see upgrade errors before their plan data loaded.


98-128: LGTM! Well-structured watermark preference handler.

The handler properly validates prerequisites (sign-in, plan loaded, PRO status) before mutation, provides clear toast feedback for success and error scenarios, and correctly extracts error messages.


177-195: LGTM! Clean conditional rendering with proper UX.

The watermark option is appropriately hidden for unauthenticated users, clearly indicates PRO requirement for non-PRO users, and handles loading/pending states correctly.

src/lib/config/plans.ts (3)

169-174: LGTM! Legacy plan ID normalization with clear documentation.

The inline comment on line 171 clearly explains the backward compatibility purpose of the 'started''starter' mapping.


194-212: LGTM! Usage utility functions are well-implemented.

The isLimitReached, getUsagePercentage, and getUsageColor helpers handle edge cases correctly, including the max === 0 scenario where current > 0 returns 100%.


65-167: LGTM! Comprehensive plan configuration.

The plan definitions are well-structured with clear limits, feature descriptions, and pricing tiers. The use of Infinity for Pro plan limits and null for Free pricing is appropriate.

src/app/providers.tsx (1)

19-23: LGTM! Provider composition is correct.

The nesting order ensures that AuthProvider and PortalReturnHandler both have access to QueryClientProvider, and Toaster is rendered within the authenticated context. This setup properly coordinates authentication state, billing portal returns, and UI feedback.

…user_id' for limit checks. Normalize plan values from database to ensure consistency across user plans. Enhance checkout success flow by integrating new query keys for user plan and billing info, improving cache invalidation logic. Refactor AuthProvider to handle auth state changes more robustly, ensuring UI consistency during user session updates.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (4)
src/actions/animations/create-animation.ts (1)

103-104: Redundant Infinity check remains.

At line 90, maxSlidesPerAnimation === Infinity is converted to null. Therefore, upgradeMaxSlides at line 103 can only be null or a number. The upgradeMaxSlides === Infinity check at line 104 is unreachable.

-          const upgradeMaxSlidesDisplay = upgradeMaxSlides === null || upgradeMaxSlides === Infinity ? 'unlimited' : upgradeMaxSlides.toString()
+          const upgradeMaxSlidesDisplay = upgradeMaxSlides === null ? 'unlimited' : upgradeMaxSlides.toString()
src/app/checkout/success/checkout-success-client.tsx (2)

38-46: Toast duration exceeds redirect delay.

The toast is configured for 5000ms, but the redirect happens after 1500ms. Users will be redirected before seeing the full toast message, potentially missing important feedback.

Align the durations by reducing toast duration to match redirect delay:

     toast.success(`Subscription activated!`, {
       description: `Your ${plan} subscription is now active.`,
-      duration: 5000,
+      duration: 1500,
     });

Or increase redirect delay if you want users to read the full message:

     const redirectTimer = setTimeout(() => {
       router.push("/");
-    }, 1500);
+    }, 5000);

15-15: Remove unused sessionId prop.

The sessionId prop is included in the effect dependency array but is never used in the effect body. With the executedRef guard in place, having it in the dependency array provides no functional benefit (the effect body won't re-execute anyway).

Remove sessionId from the component interface:

 type CheckoutSuccessClientProps = {
   plan: string;
-  sessionId: string;
 };

 export function CheckoutSuccessClient({
   plan,
-  sessionId,
 }: CheckoutSuccessClientProps) {
   const router = useRouter();
   const queryClient = useQueryClient();
   const executedRef = useRef(false);

   useEffect(() => {
     // ... effect body
-  }, [plan, sessionId, router, queryClient]);
+  }, [plan, router, queryClient]);

Note: If you implement the empty dependency array fix from the critical issue above, you can remove it from that array as well.

Also applies to: 20-20, 52-52

src/features/billing/components/portal-return-handler.tsx (1)

48-48: This issue has already been flagged in a previous review.

The async syncSubscription() call is not awaited, causing the success toast (lines 51-53) and URL cleanup (lines 56-63) to execute before the subscription sync completes. Please refer to the existing review comment for the recommended fix.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79e82ba and f8c2cd2.

📒 Files selected for processing (9)
  • src/actions/animations/create-animation.ts (1 hunks)
  • src/actions/snippets/create-snippet.ts (2 hunks)
  • src/app/checkout/success/checkout-success-client.tsx (1 hunks)
  • src/components/auth-provider/index.tsx (1 hunks)
  • src/features/billing/components/portal-return-handler.tsx (1 hunks)
  • src/features/billing/queries.ts (1 hunks)
  • src/features/home/index.tsx (3 hunks)
  • src/features/user/queries.ts (1 hunks)
  • src/lib/sentry-utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Feature flows are grouped in src/features/* (e.g., code-editor, snippets, share-code); keep new domain logic in its own feature folder.

Files:

  • src/features/billing/components/portal-return-handler.tsx
  • src/features/home/index.tsx
  • src/features/billing/queries.ts
  • src/features/user/queries.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Files:

  • src/features/billing/components/portal-return-handler.tsx
  • src/features/home/index.tsx
  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/billing/queries.ts
  • src/components/auth-provider/index.tsx
  • src/lib/sentry-utils.ts
  • src/actions/animations/create-animation.ts
  • src/actions/snippets/create-snippet.ts
  • src/features/user/queries.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Let Prettier (via ESLint) handle formatting; avoid manual style deviations.

Files:

  • src/features/billing/components/portal-return-handler.tsx
  • src/features/home/index.tsx
  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/billing/queries.ts
  • src/components/auth-provider/index.tsx
  • src/lib/sentry-utils.ts
  • src/actions/animations/create-animation.ts
  • src/actions/snippets/create-snippet.ts
  • src/features/user/queries.ts
src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js App Router lives in src/app (routes, layouts, metadata, providers). Shared stores sit in src/app/store.

Files:

  • src/app/checkout/success/checkout-success-client.tsx
src/components/**

📄 CodeRabbit inference engine (AGENTS.md)

Reusable building blocks are in src/components (ui design system, analytics, SEO helpers, session sync). Assets and marketing visuals sit in src/assets.

Files:

  • src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/components/**/*.{ts,tsx}: Use the src/components/ui primitives and cn helper for styling; align with existing Tailwind tokens and CSS variables in src/app/globals.css.
Surface upgrade prompts via UpgradeDialog and show current usage with UsageStatsWidget.

Files:

  • src/components/auth-provider/index.tsx
src/{lib,utils}/**

📄 CodeRabbit inference engine (AGENTS.md)

Cross-cutting helpers live in src/lib (config, analytics, query client) and src/utils (Supabase client, formatting).

Files:

  • src/lib/sentry-utils.ts
🧠 Learnings (6)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.

Applied to files:

  • src/features/home/index.tsx
  • src/components/auth-provider/index.tsx
  • src/lib/sentry-utils.ts
  • src/actions/animations/create-animation.ts
  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.

Applied to files:

  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/billing/queries.ts
  • src/components/auth-provider/index.tsx
  • src/actions/snippets/create-snippet.ts
  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).

Applied to files:

  • src/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Applied to files:

  • src/lib/sentry-utils.ts
  • src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).

Applied to files:

  • src/actions/animations/create-animation.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.

Applied to files:

  • src/features/user/queries.ts
🧬 Code graph analysis (7)
src/features/billing/components/portal-return-handler.tsx (3)
src/actions/stripe/checkout.ts (1)
  • syncSubscription (219-279)
src/features/user/queries.ts (2)
  • BILLING_INFO_QUERY_KEY (13-13)
  • USAGE_QUERY_KEY (11-11)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/features/home/index.tsx (1)
src/features/code-editor/index.tsx (1)
  • CodeEditor (17-165)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (3)
  • USAGE_QUERY_KEY (11-11)
  • USER_PLAN_QUERY_KEY (12-12)
  • BILLING_INFO_QUERY_KEY (13-13)
src/features/billing/queries.ts (2)
src/lib/services/billing.ts (4)
  • BillingInfo (129-138)
  • getBillingInfo (175-280)
  • PaymentMethodInfo (152-160)
  • InvoiceInfo (162-170)
src/features/user/queries.ts (1)
  • BILLING_INFO_QUERY_KEY (13-13)
src/components/auth-provider/index.tsx (2)
src/app/store/index.ts (1)
  • useUserStore (46-53)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/actions/animations/create-animation.ts (7)
src/features/animations/dtos.ts (1)
  • Animation (3-12)
src/lib/services/database/types.ts (1)
  • Animation (27-37)
src/actions/utils/validation.ts (2)
  • createAnimationInputSchema (91-97)
  • formatZodError (107-112)
src/actions/utils/action-result.ts (2)
  • error (18-20)
  • success (11-13)
src/actions/utils/with-auth.ts (1)
  • withAuthAction (5-11)
src/lib/services/usage-limits.ts (1)
  • UsageLimitCheck (21-28)
src/lib/config/plans.ts (2)
  • getPlanConfig (170-174)
  • getUpgradeTarget (188-192)
src/features/user/queries.ts (7)
src/lib/services/usage-limits.ts (2)
  • UsageSummary (30-55)
  • getUserUsage (426-632)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/lib/sentry-utils.ts (1)
  • reportQueryError (12-35)
src/lib/config/plans.ts (1)
  • PlanId (8-8)
src/lib/services/user-preferences.ts (2)
  • WatermarkPreference (4-8)
  • getWatermarkPreference (16-66)
scripts/check-env.mjs (1)
  • result (9-9)
src/actions/user/update-watermark-preference.ts (1)
  • updateWatermarkPreferenceAction (16-53)
🔇 Additional comments (28)
src/actions/animations/create-animation.ts (4)

22-30: LGTM: Clean validation pattern.

The early schema validation with proper error formatting follows best practices. The try-catch wrapping the entire function provides a safety net for unexpected errors.


34-48: LGTM: Robust RPC call with proper guards.

The withAuthAction wrapper correctly provides authenticated context. The null/object guard at lines 45-48 addresses the previous review concern about missing null checks for RPC responses.


50-71: LGTM: Comprehensive RPC response normalization.

The dual snake_case/camelCase mapping handles potential response format variations, and the plan normalization ("started" → "starter") ensures consistency with the plan configuration system.


112-129: LGTM: Clean insertion and revalidation flow.

The insertion uses proper defaults for optional fields, returns early on failure, and revalidates both relevant paths. Returning data[0] as Animation is acceptable given the type assertion aligns with the expected DTO.

src/actions/snippets/create-snippet.ts (4)

24-32: LGTM: Consistent validation pattern with animations.

The schema validation and error formatting follow the same pattern as create-animation.ts, maintaining consistency across the codebase.


36-51: LGTM: Robust RPC handling with null guards.

The RPC call pattern and null/object validation follow the same structure as the animations action, addressing previous review comments about missing null checks.


81-90: LGTM: Plan-specific error messaging is clear and actionable.

The error messages provide context (current count, max allowed, over-limit amount) and clear upgrade guidance, matching the pattern in create-animation.ts.


92-110: LGTM: Clean insertion and revalidation.

The insertion handles optional fields with sensible defaults, checks for empty results, and revalidates appropriate paths. The pattern is consistent with the animations action.

src/features/home/index.tsx (1)

138-138: Loading state now dynamic, but depends on flawed timer.

This change addresses the past review finding about hardcoded isLoading={false}. The loading state is now properly dynamic and will enable the Editor's skeleton UI.

However, the loading state depends on the arbitrary 100ms timeout flagged above, which undermines reliability. Once that race condition is fixed with proper auth state synchronization, this implementation will work correctly.

This comment is contingent on fixing the critical issue in lines 39, 44-51.

src/features/billing/queries.ts (6)

1-11: LGTM: Clean imports and appropriate stale time configuration.

The imports are well-organized, and the 5-minute stale time is appropriate for billing data. Good practice importing BILLING_INFO_QUERY_KEY from the centralized user queries module.


13-30: LGTM: Proper authentication and user resolution.

The function correctly handles both explicit userId and current user scenarios, with appropriate authentication checks before fetching billing data.


32-38: LGTM: Cache key issue properly resolved.

The query key now correctly uses the "current" sentinel when userId is undefined, ensuring cache key consistency with the resolved user ID in fetchBillingInfo. This addresses the previously identified cache mismatch issue.


40-58: LGTM: Proper URL encoding and error handling.

The function correctly encodes the customerId parameter and handles errors gracefully by returning null as a safe default. The logging aids debugging while maintaining stability.


60-69: LGTM: Well-configured React Query hook.

The hook properly gates execution with the enabled flag and uses conservative retry logic appropriate for external API calls. Configuration is consistent with the file's pattern.


71-88: LGTM: Consistent error handling with safe defaults.

The function now consistently returns an empty array for all failure scenarios without throwing, addressing the previously identified inconsistent error handling. The safe default ensures consuming components remain stable.

src/components/auth-provider/index.tsx (1)

1-17: Excellent fix for the Supabase client memoization issue.

The useMemo hook ensures createClient() is called only once, creating a stable client instance across renders. This resolves the previous critical issue of subscription leaks and prevents the effect from re-running continuously.

src/lib/sentry-utils.ts (2)

12-35: LGTM!

The production environment guard and browser check are properly in place. The error reporting flow with context, tags, and flush is well-structured. Per coding guidelines, Sentry changes are now correctly guarded behind environment checks.


45-83: LGTM!

The billing issue reporter correctly implements production guards, severity levels, and contextual tagging. The error creation with a custom BillingIssue name aids in Sentry filtering.

src/features/user/queries.ts (4)

16-36: LGTM!

Good extraction of the user resolution logic into a reusable helper. The JSDoc documentation clearly explains the behavior and error cases.


38-67: LGTM!

The useEffect-based error handling is the correct approach for React Query v5 since onError was removed from useQuery options. Error reporting is properly delegated to reportQueryError.


69-93: LGTM!

Consistent error handling pattern with useUserUsage. The hook efficiently reuses fetchUserUsage to derive the plan.


1-14: LGTM! Clean imports with reportQueryError properly abstracted. Query key constants are well-defined and properly exported for use across billing features.

Note: BILLING_INFO_QUERY_KEY is used in src/features/billing/queries.ts and billing-related components for cache invalidation during subscription flows—the export pattern is correct.

src/app/checkout/success/checkout-success-client.tsx (1)

7-11: Query key constants properly imported and used.

The previous review comment about using constants for all query keys has been addressed. All three query keys (USAGE_QUERY_KEY, USER_PLAN_QUERY_KEY, BILLING_INFO_QUERY_KEY) are now imported as constants and used consistently throughout the effect.

Also applies to: 33-35

src/features/billing/components/portal-return-handler.tsx (5)

1-11: LGTM: Clean imports and proper client component setup.

The imports are well-organized, and using the query key constants from the centralized location ensures consistency across the codebase.


12-18: LGTM: Proper ref pattern to prevent strict mode double-firing.

The handledRef correctly prevents the handler from executing twice during development strict mode while still allowing re-execution if the component remounts.


20-24: LGTM: Guard condition properly prevents duplicate handling.

The combination of URL parameter check and ref guard ensures the handler executes exactly once per portal return.


55-63: LGTM: URL cleanup logic is correct.

The parameter removal logic properly constructs the new URL and uses router.replace with scroll: false to update the browser URL without page navigation or scroll jumps.


65-67: LGTM: Dependencies are complete and return is appropriate.

The effect dependency array correctly includes all values referenced within the effect, and returning null is the right pattern for a handler component that manages side effects without rendering UI.

…billing feature, streamlining the UI and improving user experience by eliminating unnecessary actions.
… state handling from the code editor, simplifying the component and improving performance by eliminating unnecessary state and effects.
…. Introduce aggressive letter-spacing reset in animation components for improved visual consistency. Re-export proxy for future compatibility.
…codebase and ensuring compatibility with Next.js 16.
… billing management. Refactor AuthProvider to manage authentication state more effectively and streamline the CheckoutSuccessClient component by removing unnecessary execution guards. Update Home component to utilize new auth context for loading states in CodeEditor.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/features/home/index.tsx (2)

47-52: Use searchParams hook consistently instead of window.location.search.

The component imports and uses useSearchParams() at line 20, but this effect directly accesses window.location.search. This creates inconsistency and potential hydration issues in Next.js 16, especially with React 19's stricter hydration checks.

Apply this diff to use the hook consistently:

  useEffect(() => {
-   const queryParams = new URLSearchParams(location.search);
+   // Convert searchParams to URLSearchParams for compatibility with existing logic
+   const queryParams = new URLSearchParams(searchParams.toString());
    
    if (queryParams.size === 0) {
      return;
    }
    
    const codeParam = queryParams.get("code");
    // ... rest of the logic
-  }, []);
+  }, [searchParams]);

Note: Adding searchParams to the dependency array ensures the effect runs when URL parameters change via client-side navigation.


54-68: Consider clarifying the code parameter validation logic.

The validation logic with multiple early returns and regex patterns could be more readable. The intent appears to be filtering out authentication tokens (UUIDs) and invalid base64 strings, but this isn't immediately clear.

Consider adding comments or extracting validation to named functions:

  const codeParam = queryParams.get("code");
  const encodedPattern = new RegExp("^[0-9a-zA-Z+/=]*$", "i"); // pattern for base64 encoded string
  
  const loggedInPattern = new RegExp(
    "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", // pattern for signing in
    "i"
  );
  
+ // Skip processing if code param is missing, invalid base64, or is an auth token (UUID)
+ const isAuthToken = codeParam && loggedInPattern.test(codeParam);
+ const isInvalidBase64 = codeParam && !encodedPattern.test(codeParam);
+ 
+ if (!codeParam || isAuthToken || isInvalidBase64) {
+   return;
+ }
  
- if (codeParam && !encodedPattern.test(codeParam)) {
-   return;
- }
- 
- if (codeParam && loggedInPattern.test(codeParam)) {
-   return;
- }
♻️ Duplicate comments (7)
src/app/checkout/success/checkout-success-client.tsx (3)

18-21: Remove unused sessionId prop.

The sessionId prop is destructured but never used in the component body. This was flagged in a previous review and remains unaddressed.

Apply this diff to remove it:

 type CheckoutSuccessClientProps = {
   plan: string;
-  sessionId: string;
 };

 export function CheckoutSuccessClient({
   plan,
-  sessionId,
 }: CheckoutSuccessClientProps) {

32-40: Toast duration exceeds redirect delay.

The toast displays for 5000ms but the redirect executes after 1500ms, meaning users are redirected before seeing the full toast message. This was flagged in a previous review and remains unaddressed.

Apply this diff to align the durations:

     toast.success(`Subscription activated!`, {
       description: `Your ${plan} subscription is now active.`,
-      duration: 5000,
+      duration: 1500,
     });

43-43: Add eslint-disable comment for intentional empty dependency array.

The empty dependency array is correct for ensuring the effect runs only once on mount (avoiding the React StrictMode double-execution issue), but React's exhaustive-deps rule will warn about missing dependencies (plan, router, queryClient). Add a disable comment to document the intentional choice.

Apply this diff:

     return () => clearTimeout(redirectTimer);
+  // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
src/components/auth-provider/index.tsx (1)

69-75: Optional: Remove redundant ['user'] invalidation.

Line 71 invalidates ['user'] within the SIGNED_OUT branch, but line 60 already invalidates ['user'] for all auth events (including SIGNED_OUT). While React Query handles this gracefully, removing the duplicate improves code clarity.

Apply this diff to remove the redundancy:

  } else if (event === 'SIGNED_OUT') {
    // Invalidate user-specific queries on sign out, preserving public data
-   await queryClient.invalidateQueries({ queryKey: ['user'] })
    await queryClient.invalidateQueries({ queryKey: ['billing-info'] })
    await queryClient.invalidateQueries({ queryKey: ['user-plan'] })
    await queryClient.invalidateQueries({ queryKey: ['collections'] })
    router.refresh()
src/features/animation/embed-view.tsx (1)

144-169: Missing dependencies: handleReset and handlePlayPause not in dependency array.

The effect references handleReset and handlePlayPause but they're not included in the dependency array at line 169. This can cause stale closure issues if these callbacks change. The auto-play effect (lines 126-142) correctly includes them.

-  }, [progress, isPlaying, isReady]);
+  }, [progress, isPlaying, isReady, handleReset, handlePlayPause]);
src/features/billing/queries.ts (1)

74-91: Consider logging errors in fetchInvoices for observability.

Unlike fetchPaymentMethod (line 58), fetchInvoices silently swallows errors without logging. While the graceful fallback to an empty array is appropriate, adding a log would help with debugging and observability.

   } catch (error) {
+    console.error("Error fetching invoices:", error);
     return [];
   }
src/features/code-editor/editor.tsx (1)

284-288: Missing dependencies in useEffect.

The useEffect references refetchUsage and isUsageLoading but only includes user_id in the dependency array. While the comment indicates this is intentional, omitting stable function references violates the exhaustive-deps rule and could cause subtle bugs if React Query's implementation changes.

     useEffect(() => {
       if (user_id && !isUsageLoading) {
         refetchUsage();
       }
-    }, [user_id]); // Only depend on user_id to trigger on user change
+    }, [user_id, refetchUsage, isUsageLoading]);
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8c2cd2 and 4f52b3f.

📒 Files selected for processing (10)
  • src/actions/snippets/create-snippet.ts (2 hunks)
  • src/app/checkout/success/checkout-success-client.tsx (1 hunks)
  • src/components/auth-provider/index.tsx (1 hunks)
  • src/features/animation/embed-view.tsx (2 hunks)
  • src/features/animation/layout-components.tsx (1 hunks)
  • src/features/animation/shared-view.tsx (2 hunks)
  • src/features/billing/index.tsx (1 hunks)
  • src/features/billing/queries.ts (1 hunks)
  • src/features/code-editor/editor.tsx (8 hunks)
  • src/features/home/index.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/features/**

📄 CodeRabbit inference engine (AGENTS.md)

Feature flows are grouped in src/features/* (e.g., code-editor, snippets, share-code); keep new domain logic in its own feature folder.

Files:

  • src/features/animation/shared-view.tsx
  • src/features/home/index.tsx
  • src/features/animation/embed-view.tsx
  • src/features/billing/index.tsx
  • src/features/code-editor/editor.tsx
  • src/features/billing/queries.ts
  • src/features/animation/layout-components.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.

Files:

  • src/features/animation/shared-view.tsx
  • src/features/home/index.tsx
  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/animation/embed-view.tsx
  • src/features/billing/index.tsx
  • src/features/code-editor/editor.tsx
  • src/actions/snippets/create-snippet.ts
  • src/features/billing/queries.ts
  • src/features/animation/layout-components.tsx
  • src/components/auth-provider/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Let Prettier (via ESLint) handle formatting; avoid manual style deviations.

Files:

  • src/features/animation/shared-view.tsx
  • src/features/home/index.tsx
  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/animation/embed-view.tsx
  • src/features/billing/index.tsx
  • src/features/code-editor/editor.tsx
  • src/actions/snippets/create-snippet.ts
  • src/features/billing/queries.ts
  • src/features/animation/layout-components.tsx
  • src/components/auth-provider/index.tsx
src/app/**

📄 CodeRabbit inference engine (AGENTS.md)

Next.js App Router lives in src/app (routes, layouts, metadata, providers). Shared stores sit in src/app/store.

Files:

  • src/app/checkout/success/checkout-success-client.tsx
src/components/**

📄 CodeRabbit inference engine (AGENTS.md)

Reusable building blocks are in src/components (ui design system, analytics, SEO helpers, session sync). Assets and marketing visuals sit in src/assets.

Files:

  • src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/components/**/*.{ts,tsx}: Use the src/components/ui primitives and cn helper for styling; align with existing Tailwind tokens and CSS variables in src/app/globals.css.
Surface upgrade prompts via UpgradeDialog and show current usage with UsageStatsWidget.

Files:

  • src/components/auth-provider/index.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use the `src/components/ui` primitives and `cn` helper for styling; align with existing Tailwind tokens and CSS variables in `src/app/globals.css`.

Applied to files:

  • src/features/animation/shared-view.tsx
  • src/features/animation/layout-components.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.

Applied to files:

  • src/features/home/index.tsx
  • src/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).

Applied to files:

  • src/app/checkout/success/checkout-success-client.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.

Applied to files:

  • src/app/checkout/success/checkout-success-client.tsx
  • src/features/code-editor/editor.tsx
  • src/actions/snippets/create-snippet.ts
  • src/features/billing/queries.ts
  • src/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.

Applied to files:

  • src/features/billing/index.tsx
  • src/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).

Applied to files:

  • src/features/code-editor/editor.tsx
🧬 Code graph analysis (7)
src/features/home/index.tsx (2)
src/components/auth-provider/index.tsx (1)
  • useAuth (18-24)
src/features/code-editor/index.tsx (1)
  • CodeEditor (17-165)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (3)
  • USAGE_QUERY_KEY (11-11)
  • USER_PLAN_QUERY_KEY (12-12)
  • BILLING_INFO_QUERY_KEY (13-13)
src/features/animation/embed-view.tsx (6)
src/features/animation/share-utils.ts (1)
  • AnimationSharePayload (5-16)
src/features/animation/hooks/use-watermark-visibility.ts (1)
  • useWatermarkVisibility (19-46)
src/features/animation/analytics.ts (1)
  • trackAnimationEvent (7-16)
src/lib/themes-options.ts (1)
  • themes (1-74)
src/lib/fonts-options.ts (1)
  • fonts (1-58)
src/lib/utils/index.ts (1)
  • cn (10-12)
src/features/code-editor/editor.tsx (5)
src/actions/utils/action-result.ts (1)
  • error (18-20)
src/app/store/index.ts (2)
  • EditorState (14-23)
  • useEditorStore (68-162)
src/features/user/queries.ts (2)
  • useUserUsage (45-67)
  • USAGE_QUERY_KEY (11-11)
src/features/snippets/queries.ts (2)
  • CreateSnippetProps (44-52)
  • createSnippet (412-447)
src/lib/services/usage-limits-cache.ts (1)
  • getUsageLimitsCacheProvider (155-160)
src/actions/snippets/create-snippet.ts (6)
src/actions/utils/action-result.ts (3)
  • ActionResult (4-6)
  • error (18-20)
  • success (11-13)
src/features/snippets/dtos.ts (1)
  • Snippet (1-9)
src/actions/utils/validation.ts (2)
  • createSnippetInputSchema (75-81)
  • formatZodError (107-112)
src/actions/utils/with-auth.ts (1)
  • withAuthAction (5-11)
src/lib/services/usage-limits.ts (1)
  • UsageLimitCheck (21-28)
src/lib/services/database/snippets.ts (1)
  • insertSnippet (78-175)
src/features/billing/queries.ts (2)
src/lib/services/billing.ts (4)
  • BillingInfo (129-138)
  • getBillingInfo (175-280)
  • PaymentMethodInfo (152-160)
  • InvoiceInfo (162-170)
src/features/user/queries.ts (1)
  • BILLING_INFO_QUERY_KEY (13-13)
src/components/auth-provider/index.tsx (2)
src/app/store/index.ts (1)
  • useUserStore (46-53)
src/actions/utils/action-result.ts (1)
  • error (18-20)
🪛 ast-grep (0.40.0)
src/features/animation/shared-view.tsx

[warning] 159-159: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

src/features/animation/embed-view.tsx

[warning] 201-201: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
src/features/animation/shared-view.tsx

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

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

(lint/security/noDangerouslySetInnerHtml)

src/features/animation/embed-view.tsx

[error] 202-202: 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 (24)
src/features/animation/layout-components.tsx (1)

114-114: LGTM: Typography normalization consistent with broader PR changes.

The change from letterSpacing: "normal" to letterSpacing: 0 is syntactically correct and aligns with the typography normalization across other animation components (morphing-code-renderer.tsx, unified-canvas.tsx) and global styles mentioned in the AI summary. The numeric zero forces explicit zero spacing rather than relying on browser defaults.

Note: This is a visual change that tightens letter spacing. The spread of contentStyle allows consumer overrides if needed.

src/features/animation/shared-view.tsx (1)

184-184: LGTM!

The addition of the id attribute appropriately targets the container for the style reset.

src/app/checkout/success/checkout-success-client.tsx (1)

46-50: LGTM!

The minimal loading state is appropriate for this brief transition, and the styling is consistent with the application's design system.

src/components/auth-provider/index.tsx (3)

1-24: LGTM: Clean context and hook setup.

The imports, type definitions, and useAuth hook follow standard React patterns. The error message for using the hook outside the provider is helpful.


26-37: LGTM: Proper component initialization.

The memoized Supabase client (line 34) correctly addresses the previous critical issue about client recreation. The dual tracking of initialization (ref for immediate check, state for rendering) is a sound pattern.


38-109: Excellent implementation with robust error handling.

The auth state listener correctly addresses all previous critical and major concerns:

  • Stable client: Memoized Supabase client (line 34) prevents recreation on every render.
  • Error resilience: Try-catch blocks (lines 58-90) ensure query failures don't break the auth flow, with the user store updated synchronously before any async operations.
  • Selective invalidation: SIGNED_OUT invalidates only user-specific queries (lines 71-74), preserving public data like changelogs.
  • Proper cleanup: Subscription unsubscribes on unmount (line 95).

The initialization tracking with both ref and state is well-designed, and the nested error handling for the fallback router.refresh() demonstrates defensive coding.

src/features/animation/embed-view.tsx (4)

65-81: LGTM!

Good defensive handling with fallback defaults for slide properties and proper memoization of derived values.


83-113: LGTM!

Store initialization is well-structured with proper cleanup to reset the presentational mode. The spread pattern preserves existing state while applying payload overrides.


126-142: LGTM!

Auto-play logic is well-guarded with ref to prevent re-triggering, includes proper cleanup, and correctly lists all dependencies including the handler callbacks.


247-264: LGTM!

Watermark rendering correctly uses the shouldShowWatermark flag and applies appropriate text color based on background theme. The isDarkBackground array properly handles both naming conventions for backward compatibility.

src/features/home/index.tsx (2)

99-101: LGTM: Two-stage loading properly handles mount and auth initialization.

The component correctly implements a two-stage loading pattern:

  1. Wait for client-side mount (lines 99-101) to prevent hydration issues
  2. Wait for auth initialization (line 131) to prevent unauthorized content exposure

This approach properly addresses the previous race condition concerns while maintaining Next.js compatibility.

Also applies to: 131-131


19-19: isInitialized is properly exported from AuthContextValue.

Verification confirms that isInitialized is correctly defined as a boolean property in the AuthContextValue type and properly exported via the useAuth() hook. The previous critical issues regarding hardcoded loading state and race conditions have been successfully addressed by implementing auth initialization state management.

src/features/billing/index.tsx (4)

1-29: LGTM! Imports are well-organized.

The imports follow a clear structure: external libraries first, then UI components, features, services, and local modules. The "use client" directive is correctly placed for this interactive component.


31-75: LGTM! Well-structured data fetching with proper conditional loading.

The component correctly:

  • Conditionally fetches payment method and invoices only when shouldShowBillingDetails is true
  • Handles both active subscriptions and canceled subscriptions that still have access
  • Uses the billing hooks with appropriate parameter gating

198-234: LGTM! Restore subscription flow is properly implemented.

The dynamic import for syncSubscription avoids loading Stripe code until needed. The toast.promise pattern provides good user feedback during the async operation. The previous duplication concern appears to have been addressed.


103-159: LGTM! Consistent error handling with user-friendly messages.

The error alerts follow a consistent pattern and provide clear, helpful feedback to users. Each section handles its own error state independently.

src/features/billing/queries.ts (3)

1-14: LGTM! Query keys and constants are well-defined.

Good practice:

  • Re-using BILLING_INFO_QUERY_KEY from @/features/user/queries for consistency
  • Exporting local query key constants for payment method and invoices
  • Using a descriptive constant for stale time

43-72: LGTM! Robust payment method fetching with proper error handling.

The implementation correctly:

  • URL-encodes the customerId parameter (addressing previous feedback)
  • Returns null for missing parameters or errors (graceful degradation)
  • Guards the hook with enabled: Boolean(customerId)
  • Limits retries appropriately

93-102: LGTM! Consistent hook implementation.

The useInvoices hook follows the established pattern with proper enabled gating, retry configuration, and query key usage.

src/actions/snippets/create-snippet.ts (1)

92-110: LGTM!

The snippet creation flow is well-structured: proper null check on the result, path revalidation for cache invalidation, and returning the created snippet. The integration with insertSnippet and error handling is correct.

src/features/code-editor/editor.tsx (4)

68-115: Well-designed upgrade error detection utility.

The isUpgradeError function comprehensively handles multiple error shapes (objects with codes/types, string messages) and checks for relevant keywords. The implementation is defensive and correctly handles edge cases.


117-138: Good extraction of rollback logic.

This helper consolidates the previously duplicated rollback logic between onError and onSettled handlers. The implementation correctly handles the edge cases and uses the store's getState()/setState() pattern appropriately.


314-395: Well-structured mutation with proper typing and rollback.

The mutation now has proper generic types, eliminating the previous any cast. The flow correctly:

  • Stores previous state for rollback in onMutate
  • Uses the extracted revertEditorState helper in error paths
  • Clears the usage limits cache before query invalidation (lines 384-389) to prevent race conditions
  • Conditionally opens upgrade dialog only for upgrade-related errors

514-550: Robust save flow with proper validation.

The handleSaveSnippet function includes all necessary guards:

  • Session validation (lines 516-519)
  • Usage data loading check (lines 521-525) to prevent race conditions
  • Limit check with analytics tracking before showing upgrade dialog

This ensures a good user experience with clear messaging when limits are reached.

… and improve overLimit calculation. Refactor animation component to track views based on slug changes instead of a boolean state. Clean up unused state variables in embed view and code editor components for better performance and readability.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/features/code-editor/editor.tsx (2)

397-426: 🧹 Nitpick | 🔵 Trivial

removeSnippet mutation doesn't revert editor state on failure.

handleCreateSnippet stores previousEditorState in its context and reverts via revertEditorState on error, but handleRemoveSnippet only reverts the query cache. If the server call fails, isSnippetSaved stays false in the store while the snippet still exists on the server — the bookmark icon would incorrectly show "unsaved."

Consider extending the context to include the previous editor state and reverting it in onError, mirroring the create mutation pattern.

♻️ Suggested fix
   const { mutate: handleRemoveSnippet } = useMutation<
     void,
     Error,
     RemoveSnippetProps,
-    { previousSnippets: unknown }
+    { previousSnippets: unknown; previousEditorState: EditorState | null; editorId: string | undefined }
   >({
     mutationFn: removeSnippet,
     onMutate: async () => {
       await queryClient.cancelQueries({ queryKey: queryKey });

       const previousSnippets = queryClient.getQueryData(queryKey);
+      const editorId = currentEditor?.id;
+      const previousEditorState = currentEditor ? { ...currentEditor } : null;
+
       useEditorStore.setState({
         editors: editors.map((editor) => {
           if (editor.id === currentEditor?.id) {
             return {
               ...editor,
               isSnippetSaved: false,
             };
           }
           return editor;
         }),
       });
-      return { previousSnippets };
+      return { previousSnippets, previousEditorState, editorId };
     },

     onError: (err, variables, context) => {
       if (context) {
         queryClient.setQueryData(queryKey, context.previousSnippets);
+        revertEditorState(context.previousEditorState, context.editorId);
       }
     },

513-548: ⚠️ Potential issue | 🟡 Minor

Add an explicit null guard for currentEditor before invoking the mutation.

currentEditor is typed EditorState | null. The ?. + ! pattern on lines 541 and 547 only silences TypeScript — at runtime, id would be undefined and state would be null if currentEditor is ever null. Currently protected by the rendering guard (button hidden when code === ""), but this is fragile if handleSaveSnippet is later called from a hotkey or another code path.

🛡️ Proposed fix
   const handleSaveSnippet = () => {
     // Validate user session
     if (!user || !user_id) {
       toast.error("Please log in to save snippets.");
       return;
     }

+    if (!currentEditor) {
+      return;
+    }
+
     // Wait for usage data to load before allowing save
     if (isUsageLoading || !usage) {
       toast.error("Loading your account limits. Please try again in a moment.");
       return;
     }
     // ... (snippetLimitReached check unchanged)

     handleCreateSnippet({
-      id: currentEditor?.id!,
+      id: currentEditor.id,
       user_id,
       currentUrl: currentUrlOrigin,
-      title: currentEditor?.title || "Untitled",
+      title: currentEditor.title || "Untitled",
       code: code,
       language: language,
-      state: currentEditor!,
+      state: currentEditor,
     });
   };
🤖 Fix all issues with AI agents
In `@src/actions/snippets/create-snippet.ts`:
- Around line 53-74: Extract the inline mapping in create-snippet.ts into a
shared function parseUsageLimitCheck(raw: unknown): UsageLimitCheck in a new
module `@/lib/services/usage-limits`, implementing the same logic: accept
camelCase or snake_case keys (canSave/can_save, overLimit/over_limit), default
handling for current/max/plan, and the "started" -> "starter" plan
normalization; replace the mapping in create-snippet.ts (and duplicate code in
create-animation.ts) to call parseUsageLimitCheck(limitCheckRaw) and import the
helper, and ensure the helper is exported and typed to return UsageLimitCheck so
both files use the same normalized result.
- Around line 84-85: The message interpolation uses max which is typed number |
null and can render "null" to users; update the free-plan branch in
create-snippet.ts (the if (plan === 'free') block where current, max, overLimit
are used) to handle max === null safely—choose a human-friendly fallback (e.g.,
"no limit" or a numeric fallback) or conditionally format the message so it
never inserts null, and use that sanitized string in the error(...) call.
- Around line 92-110: After verifying insertSnippet returned data, call the
Postgres RPC increment_snippet_count via the supabase client (await
supabase.rpc('increment_snippet_count', { p_user_id: user.id })) before
returning success; place this call after the existing data check (the block that
returns error on empty data) and before the final return success(data[0]), await
the RPC and handle or log a non-fatal failure so snippet creation still returns
the new snippet; reference insertSnippet, supabase, and increment_snippet_count
to locate the correct spot.
- Around line 38-40: The generated DB type for the RPC is wrong: update the
generated types declaration for check_snippet_limit so its Args uses
target_user_id: string (replace the incorrect p_user_id key) and ensure Returns
is Json; locate the check_snippet_limit entry in the generated types file (the
one that currently lists p_user_id) and change it to: Args: { target_user_id:
string } Returns: Json.

In `@src/features/animation/embed-view.tsx`:
- Around line 247-264: The watermark is currently controlled by
shouldShowWatermark and uses a hardcoded URL; update the logic so
shouldShowWatermark reflects the embed owner's subscription by reading the embed
metadata/owner plan passed into the component (e.g., add a prop like
embedOwnerPlan or fetch embed info inside EmbedView) and use that to decide
rendering in the component where shouldShowWatermark is evaluated, and replace
the hardcoded "https://jollycode.dev" in the anchor (the JSX around the anchor
tag and span in embed-view.tsx) with a configurable site URL or passed-in origin
(e.g., SITE_URL or a prop) so the link is not hardcoded.
- Around line 46-49: The embed view currently calls useWatermarkVisibility()
without context so it always defaults to showing watermarks; update the embed
flow to provide the embed creator's user ID to the hook (e.g.,
useWatermarkVisibility(ownerUserId)) or fetch the owner's watermark preference
server-side and include it in the share payload. Concretely, extend the
AnimationSharePayload to include ownerUserId (or ownerWatermarkPreference) and
ensure the code in embed-view.tsx passes that owner field into
useWatermarkVisibility (or uses the pref directly) so the watermark respects the
embed creator's subscription settings.

In `@src/features/billing/dtos.ts`:
- Around line 1-2: Remove the unused PlanId import: locate the import statement
that brings in PlanId (import type { PlanId } from "@/lib/config/plans";) at the
top of the billing DTOs and delete PlanId from that line so only the used
imports remain (e.g., keep the BillingInfo, PaymentMethodInfo, InvoiceInfo
import); ensure there are no remaining references to PlanId in any types in this
file.

In `@src/features/billing/queries.ts`:
- Around line 16-42: The hook useBillingInfo currently disables the query when
userId is falsy (enabled: Boolean(userId)), which makes the auth-fallback inside
fetchBillingInfo unreachable; fix by choosing one of two clear contracts: 1) If
the hook must always receive an explicit id, make userId required for
useBillingInfo (remove optional typing) and remove the auth-resolution fallback
from fetchBillingInfo (or throw early) so fetchBillingInfo only accepts a
concrete userId; or 2) If the hook should support the "current user" path,
change useBillingInfo to resolve the current user before enabling the query
(e.g., read current user from your auth context or call supabase.auth.getUser()
in the hook) and set enabled accordingly (or remove enabled) so
fetchBillingInfo’s fallback path is reachable; update references to
useBillingInfo, fetchBillingInfo, and the enabled flag to reflect the chosen
contract.

In `@src/features/code-editor/editor.tsx`:
- Around line 88-114: The code currently reassigns the function parameter
`error` when extracting a string from `errorObj`; instead create a new local
variable (e.g., `message`) to hold the extracted string and use that for
downstream checks. Specifically, when checking `if (typeof errorObj.message ===
"string")` or `if (typeof errorObj.error === "string")`, assign to `message`
(not `error`), then ensure `message` is the value used to build `errorLower`
(rename to `messageLower` if you prefer) and to feed
`upgradeKeywords.some(...)`; leave the original `error` parameter untouched and
keep `upgradeKeywords`, `errorObj`, and the keyword-check logic unchanged
otherwise.

Comment on lines +38 to +40
const { data: limitCheckRaw, error: limitError } = await supabase.rpc('check_snippet_limit', {
target_user_id: user.id
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for check_snippet_limit function definition in SQL migrations
find . -type f \( -name "*.sql" -o -name "*migration*" \) 2>/dev/null | head -20

Repository: danarocha-br/jolly-code

Length of output: 1321


🏁 Script executed:

#!/bin/bash
# Search for check_snippet_limit in the entire codebase
rg "check_snippet_limit" -A 5 -B 2

Repository: danarocha-br/jolly-code

Length of output: 12932


🏁 Script executed:

#!/bin/bash
# Check the most recent definition in migrations by sorting timestamps
rg "CREATE OR REPLACE FUNCTION public.check_snippet_limit" supabase/migrations/ -A 2 | head -50

Repository: danarocha-br/jolly-code

Length of output: 964


🏁 Script executed:

#!/bin/bash
# Get the exact content of the database types file to confirm parameter name
cat src/types/database.ts | grep -A 10 "check_snippet_limit"

Repository: danarocha-br/jolly-code

Length of output: 295


Update the TypeScript type definition to match the deployed SQL function signature.

The RPC parameter is correctly named target_user_id in the code and in the deployed SQL function (check_snippet_limit from migration 20251210120000), but src/types/database.ts incorrectly declares it as p_user_id. Update the generated types file to reflect:

check_snippet_limit: {
  Args: {
    target_user_id: string
  }
  Returns: Json
}
🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 38 - 40, The generated
DB type for the RPC is wrong: update the generated types declaration for
check_snippet_limit so its Args uses target_user_id: string (replace the
incorrect p_user_id key) and ensure Returns is Json; locate the
check_snippet_limit entry in the generated types file (the one that currently
lists p_user_id) and change it to: Args: { target_user_id: string } Returns:
Json.

Comment on lines +53 to +74
// Map RPC response (can be camelCase or snake_case) to camelCase UsageLimitCheck type
const rpcResponse = limitCheckRaw as {
can_save?: boolean
canSave?: boolean
current?: number | null
max?: number | null
plan?: string | null
over_limit?: number | null
overLimit?: number | null
}

return success({ snippet: data[0], usage: updatedUsage })
} catch (err) {
console.error('Error creating snippet:', err)
// Normalize plan value from database ("started" -> "starter")
const rawPlan = rpcResponse.plan ?? 'free';
const normalizedPlan = rawPlan === 'started' ? 'starter' : rawPlan;

if (err instanceof Error && err.message.includes('authenticated')) {
return error('User must be authenticated')
const limitCheck: UsageLimitCheck = {
canSave: Boolean(rpcResponse.canSave ?? rpcResponse.can_save ?? false),
current: rpcResponse.current ?? 0,
max: rpcResponse.max ?? null,
plan: (normalizedPlan as UsageLimitCheck['plan']) ?? 'free',
overLimit: rpcResponse.overLimit ?? rpcResponse.over_limit ?? undefined,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting the RPC-response-to-UsageLimitCheck mapping into a shared helper.

This camelCase/snake_case normalization block (including plan renaming) is likely duplicated in create-animation.ts and possibly other actions. A shared parseUsageLimitCheck(raw: unknown): UsageLimitCheck helper in @/lib/services/usage-limits would reduce duplication and ensure consistent mapping.

🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 53 - 74, Extract the
inline mapping in create-snippet.ts into a shared function
parseUsageLimitCheck(raw: unknown): UsageLimitCheck in a new module
`@/lib/services/usage-limits`, implementing the same logic: accept camelCase or
snake_case keys (canSave/can_save, overLimit/over_limit), default handling for
current/max/plan, and the "started" -> "starter" plan normalization; replace the
mapping in create-snippet.ts (and duplicate code in create-animation.ts) to call
parseUsageLimitCheck(limitCheckRaw) and import the helper, and ensure the helper
is exported and typed to return UsageLimitCheck so both files use the same
normalized result.

Comment on lines +84 to +85
if (plan === 'free') {
return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

max can be null, producing a user-facing message like "allows null".

max is typed number | null (line 78). If the RPC ever returns null for a free-plan user, the interpolated message becomes "the Free plan allows null".

Suggested fix
-          return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
+          return error(`You have ${current} snippets but the Free plan allows ${max ?? 0}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
📝 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
if (plan === 'free') {
return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
if (plan === 'free') {
return error(`You have ${current} snippets but the Free plan allows ${max ?? 0}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
}
🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 84 - 85, The message
interpolation uses max which is typed number | null and can render "null" to
users; update the free-plan branch in create-snippet.ts (the if (plan ===
'free') block where current, max, overLimit are used) to handle max === null
safely—choose a human-friendly fallback (e.g., "no limit" or a numeric fallback)
or conditionally format the message so it never inserts null, and use that
sanitized string in the error(...) call.

Comment on lines +92 to +110
const data = await insertSnippet({
id,
user_id: user.id,
title: title || 'Untitled',
code,
language,
url: url || null,
supabase
})

if (!data || data.length === 0) {
return error('Failed to create snippet')
}

// Revalidate the snippets list
revalidatePath('/snippets')
revalidatePath('/')

return success(data[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a DB trigger or an explicit increment call for snippet count
echo "=== Searching for increment functions in SQL ==="
rg -n "increment" --type sql | head -30

echo ""
echo "=== Searching for triggers on snippets table ==="
rg -n "CREATE TRIGGER" --type sql | grep -i snippet

echo ""
echo "=== Searching for increment calls in TypeScript ==="
rg -n "increment_snippet\|increment_usage\|decrement_snippet" --type ts | head -20

Repository: danarocha-br/jolly-code

Length of output: 4101


🏁 Script executed:

#!/bin/bash
echo "=== All CREATE TRIGGER statements in SQL files ==="
rg -n "CREATE TRIGGER" --type sql -A 3 | head -50

echo ""
echo "=== Looking for triggers related to snippets (broader search) ==="
rg -n "snippets" --type sql | grep -i trigger

echo ""
echo "=== Finding insertSnippet function ==="
fd -e ts -e tsx -e js -e jsx | xargs grep -l "insertSnippet" | head -10

Repository: danarocha-br/jolly-code

Length of output: 1110


🏁 Script executed:

#!/bin/bash
echo "=== Examining insertSnippet function in snippets.ts ==="
cat -n src/lib/services/database/snippets.ts

Repository: danarocha-br/jolly-code

Length of output: 10878


Add call to increment_snippet_count after successful snippet creation.

The insertSnippet function does not increment the usage count—there are no database triggers on the snippets table, and no explicit call to increment_snippet_count exists. The increment_snippet_count function is defined in the database but is never invoked during snippet creation, causing usage limits to fall out of sync. Add await supabase.rpc('increment_snippet_count', { p_user_id: user_id }) after the snippet is successfully inserted and added to the collection.

🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 92 - 110, After
verifying insertSnippet returned data, call the Postgres RPC
increment_snippet_count via the supabase client (await
supabase.rpc('increment_snippet_count', { p_user_id: user.id })) before
returning success; place this call after the existing data check (the block that
returns error on empty data) and before the final return success(data[0]), await
the RPC and handle or log a non-fatal failure so snippet creation still returns
the new snippet; reference insertSnippet, supabase, and increment_snippet_count
to locate the correct spot.

Comment on lines +46 to +49
// Check if watermark should be shown
// Note: For embed view, we need to get the owner's user ID from the payload or slug
// For now, we'll show watermark by default for embeds (can be enhanced later to fetch owner preference)
const { shouldShowWatermark } = useWatermarkVisibility();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd "useWatermarkVisibility\|use-watermark" --type f -e ts -e tsx

Repository: danarocha-br/jolly-code

Length of output: 49


🏁 Script executed:

# Search for the hook definition
rg "useWatermarkVisibility" -t ts -t tsx -A 10 -B 2

Repository: danarocha-br/jolly-code

Length of output: 94


🏁 Script executed:

# Check the AnimationSharePayload structure to understand what owner data is available
rg "AnimationSharePayload" -t ts -t tsx -A 10

Repository: danarocha-br/jolly-code

Length of output: 94


🏁 Script executed:

# List relevant files first
git ls-files | grep -E "(watermark|embed-view)" | head -20

Repository: danarocha-br/jolly-code

Length of output: 278


🏁 Script executed:

# Search for useWatermarkVisibility without type restrictions
rg "useWatermarkVisibility" -A 10 -B 2

Repository: danarocha-br/jolly-code

Length of output: 5780


🏁 Script executed:

# Look at the embed-view file mentioned in the review
cat -n src/features/animation/embed-view.tsx | head -80

Repository: danarocha-br/jolly-code

Length of output: 3351


🏁 Script executed:

# Check AnimationSharePayload structure
rg "AnimationSharePayload" -A 20 src/features/animation/share-utils.ts

Repository: danarocha-br/jolly-code

Length of output: 1324


🏁 Script executed:

# Also check if there's owner info in the payload
cat -n src/features/animation/share-utils.ts | head -100

Repository: danarocha-br/jolly-code

Length of output: 3497


🏁 Script executed:

# Look at the full use-watermark-visibility hook implementation
cat -n src/features/animation/hooks/use-watermark-visibility.ts

Repository: danarocha-br/jolly-code

Length of output: 1767


useWatermarkVisibility() needs owner context to respect embed creator's preferences.

The hook accepts an optional userId parameter but is called without arguments, so it defaults to showing the watermark (shouldShowWatermark: true). For embeds, this should reflect the embed creator's subscription status, not the viewer's. A Pro user who paid to remove watermarks would still see them on their embedded animations.

The AnimationSharePayload structure (slides, settings, editor configuration) does not include the owner's user ID or watermark preference. Either:

  1. Extend AnimationSharePayload to include ownerUserId and pass it to the hook, or
  2. Fetch and include the owner's watermark preference server-side before encoding the payload
🤖 Prompt for AI Agents
In `@src/features/animation/embed-view.tsx` around lines 46 - 49, The embed view
currently calls useWatermarkVisibility() without context so it always defaults
to showing watermarks; update the embed flow to provide the embed creator's user
ID to the hook (e.g., useWatermarkVisibility(ownerUserId)) or fetch the owner's
watermark preference server-side and include it in the share payload.
Concretely, extend the AnimationSharePayload to include ownerUserId (or
ownerWatermarkPreference) and ensure the code in embed-view.tsx passes that
owner field into useWatermarkVisibility (or uses the pref directly) so the
watermark respects the embed creator's subscription settings.

Comment on lines +247 to +264
{/* Watermark */}
{shouldShowWatermark && (
<a
href="https://jollycode.dev"
target="_blank"
rel="noopener noreferrer"
className="absolute bottom-6 right-6 flex items-center gap-0 hover:opacity-80 transition-opacity cursor-pointer"
>
<span
className={cn(
"text-xs font-medium tracking-wide",
isDarkBackground ? "text-white/90" : "text-foreground/90"
)}
>
jollycode.dev
</span>
</a>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Watermark visibility depends on shouldShowWatermark — ensure it correctly reflects the embed owner's plan.

The watermark rendering is gated by shouldShowWatermark, which is good. However, as noted above, the hook may not have the context to determine the embed owner's subscription plan. If a Pro user embeds their animation, viewers (who are unauthenticated) would still see the watermark since the hook likely defaults to showing it.

Also, the watermark link at line 250 uses a hardcoded URL (https://jollycode.dev) — same concern as handleOpenOriginal.

🤖 Prompt for AI Agents
In `@src/features/animation/embed-view.tsx` around lines 247 - 264, The watermark
is currently controlled by shouldShowWatermark and uses a hardcoded URL; update
the logic so shouldShowWatermark reflects the embed owner's subscription by
reading the embed metadata/owner plan passed into the component (e.g., add a
prop like embedOwnerPlan or fetch embed info inside EmbedView) and use that to
decide rendering in the component where shouldShowWatermark is evaluated, and
replace the hardcoded "https://jollycode.dev" in the anchor (the JSX around the
anchor tag and span in embed-view.tsx) with a configurable site URL or passed-in
origin (e.g., SITE_URL or a prop) so the link is not hardcoded.

Comment on lines +1 to +2
import type { PlanId } from "@/lib/config/plans";
import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused import: PlanId is imported but never referenced.

PlanId from @/lib/config/plans is not used in any type definition in this file.

Proposed fix
-import type { PlanId } from "@/lib/config/plans";
-import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
+import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
📝 Committable suggestion

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

Suggested change
import type { PlanId } from "@/lib/config/plans";
import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
🤖 Prompt for AI Agents
In `@src/features/billing/dtos.ts` around lines 1 - 2, Remove the unused PlanId
import: locate the import statement that brings in PlanId (import type { PlanId
} from "@/lib/config/plans";) at the top of the billing DTOs and delete PlanId
from that line so only the used imports remain (e.g., keep the BillingInfo,
PaymentMethodInfo, InvoiceInfo import); ensure there are no remaining references
to PlanId in any types in this file.

Comment on lines +16 to +42
export const fetchBillingInfo = async (
userId?: string
): Promise<BillingInfo | null> => {
const supabase = createClient();
const resolvedUserId =
userId ??
(
await supabase.auth.getUser().then(({ data }) => {
return data.user?.id;
})
);

if (!resolvedUserId) {
throw new Error("User not authenticated");
}

return getBillingInfo(supabase, resolvedUserId);
};

export const useBillingInfo = (userId?: string) => {
return useQuery<BillingInfo | null>({
queryKey: [BILLING_INFO_QUERY_KEY, userId ?? "current"],
queryFn: () => fetchBillingInfo(userId),
staleTime: BILLING_QUERY_STALE_TIME_MS,
enabled: Boolean(userId),
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

useBillingInfo with enabled: Boolean(userId) makes the auth-fallback path in fetchBillingInfo unreachable from the hook.

fetchBillingInfo resolves the current user from Supabase auth when userId is undefined (lines 20–26), and the query key uses "current" for that case (line 37). However, enabled: Boolean(userId) (line 40) disables the query when userId is falsy, so the fallback path is dead code when invoked through the hook.

This isn't a bug (the enabled guard correctly prevents the "User not authenticated" throw flagged in a previous review), but the two intents are contradictory. Consider aligning them:

  • If the hook should always require an explicit userId, remove the auth-resolution fallback from fetchBillingInfo (or make userId required) so the contract is clear.
  • If the hook should support the "current user" path, replace the enabled guard with a pre-resolved userId (e.g., passed from an auth context) so the query can fire.
🤖 Prompt for AI Agents
In `@src/features/billing/queries.ts` around lines 16 - 42, The hook
useBillingInfo currently disables the query when userId is falsy (enabled:
Boolean(userId)), which makes the auth-fallback inside fetchBillingInfo
unreachable; fix by choosing one of two clear contracts: 1) If the hook must
always receive an explicit id, make userId required for useBillingInfo (remove
optional typing) and remove the auth-resolution fallback from fetchBillingInfo
(or throw early) so fetchBillingInfo only accepts a concrete userId; or 2) If
the hook should support the "current user" path, change useBillingInfo to
resolve the current user before enabling the query (e.g., read current user from
your auth context or call supabase.auth.getUser() in the hook) and set enabled
accordingly (or remove enabled) so fetchBillingInfo’s fallback path is
reachable; update references to useBillingInfo, fetchBillingInfo, and the
enabled flag to reflect the chosen contract.

Comment on lines +88 to +114

// Extract message from error object if available
if (typeof errorObj.message === "string") {
error = errorObj.message;
} else if (typeof errorObj.error === "string") {
error = errorObj.error;
}
}

// Handle string errors
if (typeof error !== "string") {
return false;
}

const errorLower = error.toLowerCase();

// Check for explicit upgrade-related keywords
const upgradeKeywords = [
"upgrade",
"limit reached",
"plan limit",
"reached your",
"exceeded",
"over limit",
];

return upgradeKeywords.some((keyword) => errorLower.includes(keyword));
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid reassigning the error parameter; use a local variable instead.

Reassigning the function parameter error on lines 91/93 to change its type from object to string makes the control flow harder to follow. Extract the string message into a separate variable.

♻️ Suggested refactor
-    // Extract message from error object if available
-    if (typeof errorObj.message === "string") {
-      error = errorObj.message;
-    } else if (typeof errorObj.error === "string") {
-      error = errorObj.error;
-    }
-  }
-
-  // Handle string errors
-  if (typeof error !== "string") {
-    return false;
-  }
-
-  const errorLower = error.toLowerCase();
+    // Extract message from error object if available
+    const msg = typeof errorObj.message === "string"
+      ? errorObj.message
+      : typeof errorObj.error === "string"
+        ? errorObj.error
+        : null;
+    if (typeof msg === "string") {
+      return upgradeKeywords.some((kw) => msg.toLowerCase().includes(kw));
+    }
+    return false;
+  }
+
+  // Handle string errors
+  if (typeof error !== "string") {
+    return false;
+  }
+
+  const errorLower = error.toLowerCase();
🤖 Prompt for AI Agents
In `@src/features/code-editor/editor.tsx` around lines 88 - 114, The code
currently reassigns the function parameter `error` when extracting a string from
`errorObj`; instead create a new local variable (e.g., `message`) to hold the
extracted string and use that for downstream checks. Specifically, when checking
`if (typeof errorObj.message === "string")` or `if (typeof errorObj.error ===
"string")`, assign to `message` (not `error`), then ensure `message` is the
value used to build `errorLower` (rename to `messageLower` if you prefer) and to
feed `upgradeKeywords.some(...)`; leave the original `error` parameter untouched
and keep `upgradeKeywords`, `errorObj`, and the keyword-check logic unchanged
otherwise.

@danarocha-br danarocha-br merged commit 424b6fb into main Feb 6, 2026
4 checks passed
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.

1 participant