Skip to content

create react-vite example#12

Merged
agreea merged 16 commits into
mainfrom
react-vite
Jan 21, 2026
Merged

create react-vite example#12
agreea merged 16 commits into
mainfrom
react-vite

Conversation

@rebeccax06

@rebeccax06 rebeccax06 commented Jan 10, 2026

Copy link
Copy Markdown
Contributor
  • still need to switch to better auth
  • fix progress bar ui for when credits are used

Summary by CodeRabbit

  • New Features

    • Full React + Vite example app: auth flow, server API, per-customer integration, billing/pricing UI, checkout & subscription controls, usage tracking, protected routes, generation dashboard, and many UI primitives (cards, buttons, badges, carousel, tooltips, progress, skeletons).
  • Documentation

    • Added comprehensive README with setup, architecture, and getting-started steps.
  • Bug Fixes

    • Removed a noisy debug log from authentication middleware.
  • Chores

    • Added example env, .gitignore, DB schema/migrations & snapshots, pricing config, and project/tooling configs.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@lpmsh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds a new generation-based-subscription example: React + Vite frontend, Express backend, Drizzle/Postgres schema, Better Auth integration, Flowglad per-customer billing/usage wiring, pricing configuration, UI primitives, and supporting configs/tooling.

Changes

Cohort / File(s) Summary
Config & Env
react-vite/generation-based-subscription/.env.example, react-vite/generation-based-subscription/.gitignore, react-vite/generation-based-subscription/drizzle.config.ts, react-vite/generation-based-subscription/postcss.config.ts, react-vite/generation-based-subscription/package.json, react-vite/generation-based-subscription/server/package.json, react-vite/generation-based-subscription/tsconfig.json, react-vite/generation-based-subscription/vite.config.ts
New environment example and .gitignore; Drizzle/PostCSS/Vite/TS configs and package manifests added; dotenv validation and dev proxy/alias configured.
Docs & Pricing
react-vite/generation-based-subscription/README.md, react-vite/generation-based-subscription/pricing.yaml
New README and comprehensive pricing.yaml defining products, prices, features, and usage meters.
DB schema & migrations
react-vite/generation-based-subscription/drizzle/0000_safe_iron_fist.sql, react-vite/generation-based-subscription/drizzle/meta/*
New SQL migration and Drizzle snapshot/journal creating users, sessions, accounts, verifications, unique constraints and FKs.
DB client & schema exports
react-vite/generation-based-subscription/server/db/client.ts, react-vite/generation-based-subscription/server/db/schema.ts
Postgres Pool with pooled caching for dev and Drizzle instance; exported table definitions and aggregated schema.
Backend server & auth wiring
react-vite/generation-based-subscription/server/index.ts, react-vite/generation-based-subscription/server/lib/auth.ts
New Express server, Better Auth configuration, CORS, session extraction middleware, Flowglad per-customer factory/handler, health/test routes and error mapping.
Frontend entry & app shell
react-vite/generation-based-subscription/index.html, react-vite/generation-based-subscription/src/main.tsx, react-vite/generation-based-subscription/src/App.tsx, react-vite/generation-based-subscription/src/lib/auth-client.ts, react-vite/generation-based-subscription/src/components/providers.tsx, react-vite/generation-based-subscription/src/vite-env.d.ts
Vite entry, React root with ErrorBoundary, routing and ProtectedRoute, auth client, Flowglad provider wrapper, and Vite env typings.
Pages / UX flows
react-vite/generation-based-subscription/src/pages/home.tsx, react-vite/generation-based-subscription/src/pages/pricing.tsx, react-vite/generation-based-subscription/src/pages/sign-in.tsx, react-vite/generation-based-subscription/src/pages/sign-up.tsx
Home, Pricing, Sign-in, Sign-up pages with billing integration, generation/top-up actions, checkout reload handling and usage events.
Billing helpers & libs
react-vite/generation-based-subscription/src/lib/billing-helpers.ts, react-vite/generation-based-subscription/src/lib/utils.ts
New pricing/usage helper functions and cn utility for class composition.
UI primitives
react-vite/generation-based-subscription/src/components/ui/*
(e.g., badge.tsx, button.tsx, card.tsx, carousel.tsx, dropdown-menu.tsx, progress.tsx, skeleton.tsx, tooltip.tsx, ...)
New accessible UI primitives (forwardRef patterns, ARIA, composition) used across the app.
Feature components
react-vite/generation-based-subscription/src/components/*
(e.g., navbar.tsx, pricing-card.tsx, pricing-cards-grid.tsx, dashboard-skeleton.tsx, providers.tsx)
New components for navbar (cancel/uncancel flows), pricing cards/grid, skeleton and provider wrapper; contains billing actions and checkout handling.
Styling & tooling
react-vite/generation-based-subscription/src/index.css, react-vite/generation-based-subscription/tailwind.config.ts, react-vite/generation-based-subscription/eslint.config.ts
Tailwind base CSS, theme tokens, Tailwind config, ESLint config and PostCSS setup.
Misc
tanstack-start/generation-based-subscription/src/middleware/auth.ts
Removed a debug session log only.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Client as React App
    participant Auth as BetterAuth
    participant Server as Express
    participant DB as Postgres
    participant Flowglad

    User->>Client: Initiate generation or top-up (action, meterSlug)
    Client->>Client: show loading, build payload
    Client->>Server: POST /api/flowglad/... (with session headers)
    Server->>Auth: Validate/resolve session from headers
    Auth-->>Server: session / user info
    Server->>DB: lookup customer by user id / externalId
    DB-->>Server: customer record (or none)
    Server->>Flowglad: flowglad(customerExternalId).createUsageEvent(...) or requestHandler(...)
    Flowglad-->>Server: success / error
    Server-->>Client: 200 OK with data or error
    Client->>Client: billing.reload() and update UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • joeysabs

Poem

"I hopped through code with twitchy paws,
I wired auth and priced the laws,
carrots for plans, meters bright,
customers billed by moonlit night,
🥕 Example launched — what a cause!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'create react-vite example' directly matches the main purpose of the PR, which adds a complete React + Vite example project with all necessary files and configuration.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@lpmsh lpmsh linked an issue Jan 11, 2026 that may be closed by this pull request
@rebeccax06 rebeccax06 marked this pull request as ready for review January 12, 2026 21:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🤖 Fix all issues with AI agents
In @react-vite/generation-based-subscription/.env.example:
- Line 7: Remove the surrounding quotes from the VITE_APP_URL value so the .env
key is unquoted; change the entry VITE_APP_URL="http://localhost:5173" to
VITE_APP_URL=http://localhost:5173 (ensure no extra whitespace or quotes
remain).
- Line 17: Remove the spaces around the equals sign for the BETTER_AUTH_SECRET
entry so it follows .env conventions; change the line with the
BETTER_AUTH_SECRET variable to use BETTER_AUTH_SECRET="your-secret-key-here" (no
space before or after the =).

In @react-vite/generation-based-subscription/README.md:
- Line 251: The README references server/db/schema.ts but the actual schema file
is server/db/schema.js; update the README line that says "You can extend the
schema in `server/db/schema.ts`" to reference `server/db/schema.js` (or
alternatively rename the schema file to .ts and adjust imports), ensuring the
filename in README matches the real file (`server/db/schema.js`) so instructions
are accurate.

In @react-vite/generation-based-subscription/server/db/schema.js:
- Around line 17-28: The sessions and accounts table schemas define createdAt
and updatedAt with .notNull() but no default, which will cause insert failures;
update the sessions and accounts definitions (fields named createdAt and
updatedAt) to chain .defaultNow() after .notNull() so they behave like the users
table (i.e., createdAt.notNull().defaultNow() and
updatedAt.notNull().defaultNow()).
- Around line 52-59: The verifications table's timestamp columns (createdAt and
updatedAt in the pgTable 'verifications') are missing .notNull() unlike the
sessions and accounts pattern; update the column definitions to use
.notNull().defaultNow() for both createdAt and updatedAt so they match the other
tables and ensure non-null timestamps.

In @react-vite/generation-based-subscription/server/index.js:
- Around line 26-27: The in-memory Map userCache has no eviction/TTL and will
grow unbounded; replace or wrap userCache with a bounded/TTL cache (e.g., use an
LRU or TTL library such as lru-cache or node-cache) or implement max-size + LRU
eviction and optional TTL on entries, and ensure cache invalidation occurs on
user updates (refer to userCache usage locations to swap in the new cache
instance or add eviction logic).

In @react-vite/generation-based-subscription/src/components/navbar.jsx:
- Around line 174-191: The Tooltip wrapping the cancel action has a
TooltipTrigger but no TooltipContent, which is inconsistent with the uncancel
flow; either add a TooltipContent describing the action (e.g., explain
cancellation behavior or show expected date) or remove the Tooltip wrapper
entirely. Locate the block using Tooltip, TooltipTrigger, and DropdownMenuItem
(onSelect={handleCancelSubscription}) and either add a matching <TooltipContent>
with the message you want shown when hovering the cancel button or drop the
outer <Tooltip> and <TooltipTrigger> so the DropdownMenuItem is rendered
directly; ensure the disabled state logic (isCancelling, isUncancelling,
currentSubscription, billing.cancelSubscription) remains unchanged.

In @react-vite/generation-based-subscription/src/components/pricing-card.jsx:
- Around line 147-154: The button is being disabled when !billing.getPrice but
handleCheckout can fall back and proceed without billing.getPrice; update the
disabled prop to match handleCheckout's actual prerequisites. Replace the
current check that includes !billing.getPrice with a check that only disables
when billing.createCheckoutSession (and billing.loaded) are missing, e.g. keep
isCurrentPlan/isDefaultPlan/isLoading and !billing.loaded and
!billing.createCheckoutSession, so the button remains enabled when getPrice is
unavailable but createCheckoutSession can handle the checkout (see
handleCheckout).

In @react-vite/generation-based-subscription/src/components/ui/carousel.jsx:
- Around line 72-82: The effect currently registers both api.on('reInit',
onSelect) and api.on('select', onSelect) but only removes the 'select' listener
on cleanup; update the cleanup to remove both listeners so you call
api?.off('reInit', onSelect) and api?.off('select', onSelect) (mirroring the
subscriptions) to avoid leaks or stale callbacks in the component using api and
onSelect.

In
@react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx:
- Around line 30-50: DropdownMenuTrigger currently drops the forwarded ref when
rendering the cloned child (asChild path); update the React.cloneElement call to
forward the incoming ref to the child and preserve any existing child ref by
composing refs: create a combined ref setter that assigns the forwarded ref (ref
param from forwardRef) and also calls/assigns the child's original ref
(children.ref) if present, then pass that combined ref as the 'ref' prop in
React.cloneElement along with existing props (keep onClick and aria-expanded);
this ensures the forwardRef contract is honored while keeping handleClick and
other behavior intact.

In @react-vite/generation-based-subscription/src/components/ui/progress.jsx:
- Around line 7-21: The outer div that renders the progress bar (the element
with ref, cn and {...props}) lacks ARIA attributes; add role="progressbar" plus
aria-valuenow={clampedValue}, aria-valuemin="0", aria-valuemax="100" and
optionally aria-label or aria-valuetext (derived from clampedValue) to that div
so assistive tech can read the current value; keep the inner styling div as-is
and ensure clampedValue is a number between 0 and 100 when used for
aria-valuenow.

In @react-vite/generation-based-subscription/src/components/ui/tooltip.jsx:
- Around line 6-24: Tooltip is creating a new TooltipContext.Provider in the
Tooltip component which overwrites the values from TooltipProvider (so
delayDuration is lost); fix by reading the existing context inside Tooltip
(using React.useContext(TooltipContext) or similar) and then provide a merged
value object that includes both the existing values (e.g., delayDuration) and
the local state (open, setOpen) when returning TooltipContext.Provider from the
Tooltip component, or alternatively stop creating a provider in Tooltip and
consume the context instead; reference TooltipProvider, Tooltip, TooltipContext,
open, setOpen when applying the change.

In @react-vite/generation-based-subscription/src/lib/auth-client.js:
- Around line 3-5: The auth client fallback is pointed at the frontend dev
server; update the createAuthClient call (authClient) so its baseURL fallback
uses the backend URL http://localhost:3001 instead of http://localhost:5173, and
ensure the change is applied where authClient is exported; also add or update a
.env.example (or project docs) to clarify that VITE_APP_URL should be the
backend API server URL (e.g., http://localhost:3001) for local development.

In @react-vite/generation-based-subscription/src/main.jsx:
- Around line 40-46: The top-level JSX render call is indented incorrectly;
remove the unexpected leading whitespace so the createRoot(...).render(...)
expression and its JSX children (StrictMode, ErrorBoundary, App) are aligned at
top-level indentation. Locate the createRoot call and adjust its indentation to
match surrounding top-level code (no extra spaces before createRoot or its
closing parenthesis) to ensure consistent formatting.
🧹 Nitpick comments (30)
react-vite/generation-based-subscription/eslint.config.js (2)

16-24: Redundant and conflicting ecmaVersion settings.

ecmaVersion is set to 2020 at line 17 and to 'latest' in parserOptions at line 20. In ESLint flat config, only languageOptions.ecmaVersion is needed—the one inside parserOptions is redundant and potentially confusing.

Additionally, sourceType: 'module' should be moved to languageOptions level in flat config.

Suggested fix
     languageOptions: {
-      ecmaVersion: 2020,
+      ecmaVersion: 'latest',
       globals: globals.browser,
+      sourceType: 'module',
       parserOptions: {
-        ecmaVersion: 'latest',
         ecmaFeatures: { jsx: true },
-        sourceType: 'module',
       },
     },

26-26: Consider tightening the varsIgnorePattern.

The pattern ^[A-Z_] ignores all variables starting with an uppercase letter or underscore. This is very permissive and may mask genuinely unused imports (e.g., unused React components or constants).

A more common pattern is ^_ to mark intentionally unused variables explicitly. If the intent is to allow unused React components used only in JSX, note that ESLint typically detects JSX usage correctly.

react-vite/generation-based-subscription/README.md (2)

73-76: Add language specifier to fenced code block.

The static analysis tool flagged this code block as missing a language specifier. Consider adding a language identifier for consistency.

Suggested fix
-```
+```bash
 SERVER_PORT=3001
 VITE_APP_URL=http://localhost:5173
-```
+```

127-162: Add language specifier to fenced code block.

The project structure tree is missing a language specifier. Consider using text or plaintext to satisfy linting rules.

Suggested fix
-```
+```text
 ├── server/
 │   ├── db/
react-vite/generation-based-subscription/src/lib/billing-helpers.js (1)

72-91: Extract findUsagePriceByMeterSlug into a shared utility.

This function is duplicated in server/index.js (lines 284–298). Both the client and server implement the identical logic for building the meterIdBySlug Map and finding usage prices. Extract this into a shared utility that both can import.

react-vite/generation-based-subscription/src/components/ui/carousel.jsx (1)

54-65: Consider supporting ArrowUp/ArrowDown for vertical orientation.

Keyboard navigation currently uses ArrowLeft/ArrowRight regardless of orientation. For better accessibility in vertical carousels, consider mapping ArrowUp to scrollPrev and ArrowDown to scrollNext when orientation === 'vertical'.

react-vite/generation-based-subscription/.env.example (1)

10-12: Consider renaming NEXT_PUBLIC_BASE_URL for clarity.

NEXT_PUBLIC_ is a Next.js naming convention. Since this is a Vite project, consider using a more appropriate prefix like VITE_ or a generic name like API_BASE_URL or SERVER_URL to avoid confusion.

react-vite/generation-based-subscription/server/db/client.js (1)

12-25: Consider caching the db instance alongside the pool.

The pool is cached to prevent connection exhaustion during dev HMR, but the drizzle instance is recreated on each module reload. For consistency and to avoid potential overhead, consider caching db as well.

Proposed fix
 // Cache pool in global to avoid exhausting connections in dev
-const pool =
-  global.__db_pool__ ??
-  new Pool({
-    connectionString: process.env.DATABASE_URL,
-  });
+let pool = global.__db_pool__;
+let db = global.__db_instance__;

-if (process.env.NODE_ENV !== 'production') {
-  global.__db_pool__ = pool;
+if (!pool) {
+  pool = new Pool({
+    connectionString: process.env.DATABASE_URL,
+  });
+  if (process.env.NODE_ENV !== 'production') {
+    global.__db_pool__ = pool;
+  }
 }

-const db = drizzle(pool, { schema });
+if (!db) {
+  db = drizzle(pool, { schema });
+  if (process.env.NODE_ENV !== 'production') {
+    global.__db_instance__ = db;
+  }
+}

 module.exports = { db };
react-vite/generation-based-subscription/src/components/dashboard-skeleton.jsx (1)

4-68: LGTM! Well-structured skeleton component.

The skeleton accurately mirrors dashboard layout sections with appropriate sizing. The commented sections improve maintainability.

Minor nit: min-h-screen appears on both the outer div (line 6) and inner main (line 7) — only one is needed, likely on the outer container.

react-vite/generation-based-subscription/src/components/ui/button.jsx (1)

21-36: Consider defensive handling for invalid variant/size values.

If an invalid variant or size is passed, buttonVariants.variant[variant] returns undefined, which cn() handles gracefully (ignores it). However, this fails silently without warning developers of the typo.

This is a minor concern for an example project, but in production you might add runtime validation or use a library like class-variance-authority for type-safe variants.

react-vite/generation-based-subscription/server/lib/auth.js (1)

18-18: Consider renaming server-side environment variable.

VITE_APP_URL uses the VITE_ prefix which is conventionally reserved for client-side environment variables that Vite exposes to the browser. On the server side, consider using a different name like APP_URL or BASE_URL to avoid confusion.

This works functionally but may mislead developers about which variables are exposed to the client.

react-vite/generation-based-subscription/src/pages/sign-up.jsx (1)

103-105: Use React Router's Link component instead of anchor tag.

Using a native <a> tag causes a full page reload, losing React state and triggering unnecessary re-renders. Since you're already using react-router-dom, use Link for client-side navigation.

Suggested fix
+import { Link, useNavigate } from 'react-router-dom';
-import { useNavigate } from 'react-router-dom';
-            <a href="/sign-in" className="text-primary hover:underline">
-              Sign in
-            </a>
+            <Link to="/sign-in" className="text-primary hover:underline">
+              Sign in
+            </Link>
react-vite/generation-based-subscription/src/pages/sign-in.jsx (1)

90-92: Use React Router's Link component instead of anchor tag.

Same issue as in sign-up.jsx — using a native <a> tag causes a full page reload. Use Link from react-router-dom for client-side navigation.

Suggested fix
+import { Link, useNavigate, useLocation } from 'react-router-dom';
-import { useNavigate, useLocation } from 'react-router-dom';
-            <a href="/sign-up" className="text-primary hover:underline">
-              Sign up
-            </a>
+            <Link to="/sign-up" className="text-primary hover:underline">
+              Sign up
+            </Link>
react-vite/generation-based-subscription/drizzle.config.js (1)

7-9: Empty string fallback may cause confusing errors.

If DATABASE_URL is not set, the empty string fallback will cause drizzle-kit commands to fail with a potentially unclear connection error. Consider throwing an explicit error similar to the BETTER_AUTH_SECRET validation in auth.js.

Suggested fix
 require('dotenv').config();
 
+const databaseUrl = process.env.DATABASE_URL;
+if (!databaseUrl) {
+  throw new Error('DATABASE_URL environment variable is required');
+}
+
 module.exports = {
   out: './drizzle',
   schema: './server/db/schema.js',
   dialect: 'postgresql',
   dbCredentials: {
-    url: process.env.DATABASE_URL || '',
+    url: databaseUrl,
   },
 };
react-vite/generation-based-subscription/src/pages/pricing.jsx (1)

21-46: Consider simplifying the effect dependencies.

The effect includes isReloading in the dependency array but also sets it internally, which can cause unnecessary re-evaluations. Since the hasReloaded flag already prevents duplicate reloads, you could simplify by removing isReloading from dependencies and relying solely on hasReloaded.

Additionally, the URL cleanup in the finally block will execute even if billing.reload() fails, which seems intentional but worth confirming is the desired behavior.

♻️ Suggested simplification
  useEffect(() => {
    const reloadAfterCheckout = async () => {
      const urlParams = new URLSearchParams(window.location.search);
      const shouldReload = urlParams.has('checkout') || urlParams.has('session_id') || urlParams.has('success');
      
-     if (shouldReload && !hasReloaded) {
+     if (shouldReload && !hasReloaded && billing.reload && billing.loaded) {
        // User returned from checkout, reload billing data
-       if (billing.reload && billing.loaded && !isReloading) {
-         setIsReloading(true);
-         try {
-           await billing.reload();
-           setHasReloaded(true);
-         } catch (error) {
-           console.error('Error reloading billing data:', error);
-         } finally {
-           setIsReloading(false);
-           // Clean up URL params after reload completes
-           window.history.replaceState({}, '', window.location.pathname);
-         }
+       setHasReloaded(true); // Set early to prevent re-entry
+       try {
+         await billing.reload();
+       } catch (error) {
+         console.error('Error reloading billing data:', error);
+       } finally {
+         window.history.replaceState({}, '', window.location.pathname);
        }
      }
    };
    
    reloadAfterCheckout();
-   // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [billing.reload, billing.loaded, isReloading, hasReloaded]);
+  }, [billing.reload, billing.loaded, hasReloaded]);
react-vite/generation-based-subscription/src/components/navbar.jsx (1)

41-46: Extract repeated subscription resolution logic.

The same subscription extraction pattern appears three times (lines 43-46, 89-92, 137-139). Consider extracting this to a helper function or computing it once at the component level.

♻️ Suggested refactor
 export function Navbar() {
   const navigate = useNavigate();
   const { data: session } = authClient.useSession();
   const billing = useBilling();
   const [isCancelling, setIsCancelling] = useState(false);
   const [isUncancelling, setIsUncancelling] = useState(false);
   const [cancelError, setCancelError] = useState(null);
   const [uncancelError, setUncancelError] = useState(null);
+
+  // Extract current subscription once
+  const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
+  const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
+  const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
+
   if (!billing.loaded || !billing.loadBilling) {
     return null;
   }
   // ... rest of component

   async function handleCancelSubscription() {
-    // Get current subscription using the same pattern as other components
-    const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-    const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-    const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
     const subscriptionId = currentSubscription?.id;
     // ...
   }

   async function handleUncancelSubscription() {
-    // Get current subscription using the same pattern as other components
-    const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-    const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-    const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
     const subscriptionId = currentSubscription?.id;
     // ...
   }

-  // Get current subscription using the same pattern as other components
-  const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-  const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-  const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];

Also applies to: 87-92, 136-139

react-vite/generation-based-subscription/src/App.jsx (1)

41-62: Consider a shared layout for protected routes.

FlowgladProviderWrapper and Navbar are duplicated for both / and /pricing routes. Extracting a shared layout component would reduce repetition and make adding new protected routes easier.

♻️ Suggested approach
function ProtectedLayout({ children }) {
  return (
    <ProtectedRoute>
      <FlowgladProviderWrapper>
        <Navbar />
        {children}
      </FlowgladProviderWrapper>
    </ProtectedRoute>
  );
}

// Then in routes:
<Route path="/" element={<ProtectedLayout><HomePage /></ProtectedLayout>} />
<Route path="/pricing" element={<ProtectedLayout><PricingPage /></ProtectedLayout>} />
react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (2)

160-228: Extract skeleton loading component to reduce duplication.

The skeleton UI for loading state (lines 160-228) and empty plans state (lines 232-295) are nearly identical. Consider extracting a PricingCardsSkeleton component.

♻️ Suggested approach
function PricingCardsSkeleton({ isMobile, autoplayPlugin }) {
  if (isMobile) {
    return (
      <div className="px-4">
        <Carousel plugins={[autoplayPlugin.current]} /* ... */>
          <CarouselContent className="-ml-1">
            {[1, 2].map((i) => (
              <CarouselItem key={i} className="pl-1 basis-1/2">
                {/* skeleton card content */}
              </CarouselItem>
            ))}
          </CarouselContent>
          {/* ... */}
        </Carousel>
      </div>
    );
  }
  
  return (
    <div className="grid gap-6 auto-rows-fr md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-4">
      {[1, 2, 3, 4].map((i) => (
        /* skeleton card content */
      ))}
    </div>
  );
}

// Then use:
if (!billing?.loaded || !billing?.loadBilling) {
  return <div className="w-full space-y-8"><PricingCardsSkeleton isMobile={isMobile} autoplayPlugin={autoplayPlugin} /></div>;
}

// And for empty plans:
{plans.length === 0 ? (
  <PricingCardsSkeleton isMobile={isMobile} autoplayPlugin={autoplayPlugin} />
) : /* actual plans */}

Also applies to: 232-295


112-125: Duplicate getPrice helper exists in pricing-card.jsx.

This getPrice function duplicates the same logic found in pricing-card.jsx (lines 48-60 in the relevant snippets). Consider extracting this to a shared utility in billing-helpers.js or similar.

react-vite/generation-based-subscription/server/index.js (3)

124-137: Fallback email with placeholder domain may cause issues downstream.

The fallback email user_${externalId}@example.com could cause problems if Flowglad validates email formats or attempts to send notifications. Consider logging a warning when this fallback is used.

Suggested improvement
        // Fallback if user not found in database
+       console.warn(`[Flowglad] User not found in database for externalId: ${externalId}, using fallback`);
        const fallback = {
          email: `user_${externalId}@example.com`,
          name: 'User',
        };
        return fallback;
      } catch (error) {
        console.error('[Flowglad] Error fetching user from database:', error);
        // Fallback on error
+       console.warn(`[Flowglad] Using fallback for externalId: ${externalId}`);
        const fallback = {
          email: `user_${externalId}@example.com`,
          name: 'User',
        };
        return fallback;

282-297: Duplicate implementation of findUsagePriceByMeterSlug.

This function is duplicated from src/lib/billing-helpers.js. Consider sharing this utility to avoid maintenance burden and potential drift between implementations.

Since the server uses CommonJS and the client uses ESM, you could:

  1. Create a shared utilities package
  2. Use a build step to compile shared code
  3. For this example, document that both implementations must stay in sync

233-238: Consider validating amount as an integer and using stronger randomness for transactionId.

  1. amount allows floats - if usage meters expect integers, consider Number.isInteger(amount)
  2. Math.random().toString(36) provides weak uniqueness for transaction IDs
Suggested improvements
-    if (!amount || typeof amount !== 'number' || amount <= 0) {
-      return res.status(400).json({ error: 'amount must be a positive number' });
+    if (!amount || typeof amount !== 'number' || !Number.isInteger(amount) || amount <= 0) {
+      return res.status(400).json({ error: 'amount must be a positive integer' });
     }

-    const finalTransactionId = transactionId || 
-      `usage_${Date.now()}_${Math.random().toString(36).substring(7)}`;
+    const crypto = require('crypto');
+    const finalTransactionId = transactionId || 
+      `usage_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`;
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx (1)

78-106: DropdownMenuItem doesn't close menu when onSelect is not provided.

The current logic only closes the menu if onSelect is provided. If a menu item is clicked without an onSelect handler, the menu stays open. This may be intentional, but typical dropdown behavior closes on any item click.

Suggested change if close-on-click is desired
   const handleClick = () => {
-    if (!disabled && onSelect) {
+    if (!disabled) {
-      onSelect();
+      onSelect?.();
       setOpen(false);
     }
   };
react-vite/generation-based-subscription/src/components/ui/tooltip.jsx (1)

54-71: sideOffset prop is accepted but not applied.

The sideOffset parameter is destructured but the positioning uses hardcoded mb-2 instead. Either remove the unused prop or apply it dynamically.

Option 1: Remove unused prop
-const TooltipContent = React.forwardRef(({ className, sideOffset = 4, children, ...props }, ref) => {
+const TooltipContent = React.forwardRef(({ className, children, ...props }, ref) => {
Option 2: Apply sideOffset dynamically
       className={cn(
-        'absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95',
+        'absolute z-50 bottom-full left-1/2 -translate-x-1/2 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95',
         className
       )}
+      style={{ marginBottom: `${sideOffset}px` }}
react-vite/generation-based-subscription/src/pages/home.jsx (3)

358-394: handleGenerateRelaxImage and handleGenerateRelaxSDVideo are marked async but contain no await.

These functions are declared async but don't perform any asynchronous operations. The async keyword is unnecessary and may be misleading.

Suggested fix
-  const handleGenerateRelaxImage = async () => {
+  const handleGenerateRelaxImage = () => {
     if (!hasRelaxMode) {
       return;
     }

     setIsGeneratingRelaxImage(true);

-    try {
-      const nextIndex = (currentImageIndex + 1) % mockImages.length;
-      setCurrentImageIndex(nextIndex);
-      const nextImage = mockImages[nextIndex];
-      if (nextImage) {
-        setDisplayedContent(nextImage);
-      }
-    } finally {
-      setIsGeneratingRelaxImage(false);
+    const nextIndex = (currentImageIndex + 1) % mockImages.length;
+    setCurrentImageIndex(nextIndex);
+    const nextImage = mockImages[nextIndex];
+    if (nextImage) {
+      setDisplayedContent(nextImage);
     }
+    setIsGeneratingRelaxImage(false);
   };

396-466: getPrice helper is duplicated in both top-up handlers.

The identical getPrice function is defined twice (lines 406-415 and 457-466). Extract it to a shared helper outside the handlers.

Suggested refactor
+  // Helper to get price by slug
+  const getPrice = (slug) => {
+    if (billing.getPrice) return billing.getPrice(slug);
+    const model = billing.pricingModel || billing.catalog;
+    if (!model?.products) return null;
+    for (const product of model.products) {
+      const price = product.prices?.find((p) => p.slug === slug);
+      if (price) return price;
+    }
+    return null;
+  };

   const handlePurchaseFastGenerationTopUp = async () => {
     setTopUpError(null);

     if (!billing.loaded || !billing.loadBilling) {
       setTopUpError('Billing data not loaded. Please refresh the page.');
       return;
     }

-    // Get price using SDK or fallback (same pattern as pricing page)
-    const getPrice = (slug) => {
-      if (billing.getPrice) return billing.getPrice(slug);
-      const pricingModel = billing.pricingModel || billing.catalog;
-      if (!pricingModel?.products) return null;
-      for (const product of pricingModel.products) {
-        const price = product.prices?.find((p) => p.slug === slug);
-        if (price) return price;
-      }
-      return null;
-    };

     const priceObj = getPrice('fast_generation_top_up');

33-59: Consider extracting custom hooks to reduce component complexity.

This component manages 15+ state variables and multiple concerns (auth, billing, generation, checkout). For an example app this demonstrates all features, but extracting hooks like useCheckoutReload, useUsageGeneration, or useTopUp would improve maintainability.

This is optional for an example project, but would be beneficial if this codebase grows.

react-vite/generation-based-subscription/drizzle/0000_safe_iron_fist.sql (2)

40-47: Consider adding an index on verifications.identifier for lookup performance.

The identifier column is likely used to look up verification tokens by email or other identifiers. Without an index, these lookups will be full table scans.

Suggested index
CREATE INDEX "verifications_identifier_idx" ON "verifications" ("identifier");

24-38: Add indexes on foreign key columns for improved query performance.

The sessions.user_id and accounts.user_id columns reference the users table but lack indexes. This will impact performance for common auth queries (e.g., looking up sessions or accounts by user). Best practice for Drizzle/Better Auth is to index all foreign key columns on the "many" side.

Suggested indexes
CREATE INDEX "sessions_user_id_idx" ON "sessions" ("user_id");
CREATE INDEX "accounts_user_id_idx" ON "accounts" ("user_id");
react-vite/generation-based-subscription/src/components/pricing-card.jsx (1)

48-57: getPrice helper is duplicated across components.

This same fallback pattern exists in home.jsx (lines 406-415, 457-466). Consider extracting to a shared utility in billing-helpers.js.

Suggested shared utility

Add to src/lib/billing-helpers.js:

export function getPrice(billing, slug) {
  if (billing.getPrice) return billing.getPrice(slug);
  const pricingModel = billing.pricingModel || billing.catalog;
  if (!pricingModel?.products) return null;
  for (const product of pricingModel.products) {
    const price = product.prices?.find((p) => p.slug === slug);
    if (price) return price;
  }
  return null;
}

Then import and use: const priceObj = getPrice(billing, priceSlug);

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6cd5a9 and ba86877.

⛔ Files ignored due to path filters (4)
  • react-vite/generation-based-subscription/bun.lock is excluded by !**/*.lock
  • react-vite/generation-based-subscription/package-lock.json is excluded by !**/package-lock.json
  • react-vite/generation-based-subscription/public/vite.svg is excluded by !**/*.svg
  • react-vite/generation-based-subscription/src/assets/react.svg is excluded by !**/*.svg
📒 Files selected for processing (44)
  • react-vite/generation-based-subscription/.env.example
  • react-vite/generation-based-subscription/.gitignore
  • react-vite/generation-based-subscription/README.md
  • react-vite/generation-based-subscription/drizzle.config.js
  • react-vite/generation-based-subscription/drizzle/0000_safe_iron_fist.sql
  • react-vite/generation-based-subscription/drizzle/meta/0000_snapshot.json
  • react-vite/generation-based-subscription/drizzle/meta/_journal.json
  • react-vite/generation-based-subscription/eslint.config.js
  • react-vite/generation-based-subscription/index.html
  • react-vite/generation-based-subscription/package.json
  • react-vite/generation-based-subscription/postcss.config.js
  • react-vite/generation-based-subscription/pricing.yaml
  • react-vite/generation-based-subscription/server/db/client.js
  • react-vite/generation-based-subscription/server/db/schema.js
  • react-vite/generation-based-subscription/server/index.js
  • react-vite/generation-based-subscription/server/lib/auth.js
  • react-vite/generation-based-subscription/server/package.json
  • react-vite/generation-based-subscription/src/App.jsx
  • react-vite/generation-based-subscription/src/components/dashboard-skeleton.jsx
  • react-vite/generation-based-subscription/src/components/navbar.jsx
  • react-vite/generation-based-subscription/src/components/pricing-card.jsx
  • react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx
  • react-vite/generation-based-subscription/src/components/providers.jsx
  • react-vite/generation-based-subscription/src/components/ui/badge.jsx
  • react-vite/generation-based-subscription/src/components/ui/button.jsx
  • react-vite/generation-based-subscription/src/components/ui/card.jsx
  • react-vite/generation-based-subscription/src/components/ui/carousel.jsx
  • react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx
  • react-vite/generation-based-subscription/src/components/ui/progress.jsx
  • react-vite/generation-based-subscription/src/components/ui/skeleton.jsx
  • react-vite/generation-based-subscription/src/components/ui/tooltip.jsx
  • react-vite/generation-based-subscription/src/hooks/use-mobile.js
  • react-vite/generation-based-subscription/src/index.css
  • react-vite/generation-based-subscription/src/lib/auth-client.js
  • react-vite/generation-based-subscription/src/lib/billing-helpers.js
  • react-vite/generation-based-subscription/src/lib/utils.js
  • react-vite/generation-based-subscription/src/main.jsx
  • react-vite/generation-based-subscription/src/pages/home.jsx
  • react-vite/generation-based-subscription/src/pages/pricing.jsx
  • react-vite/generation-based-subscription/src/pages/sign-in.jsx
  • react-vite/generation-based-subscription/src/pages/sign-up.jsx
  • react-vite/generation-based-subscription/tailwind.config.js
  • react-vite/generation-based-subscription/vite.config.js
  • tanstack-start/generation-based-subscription/src/middleware/auth.ts
💤 Files with no reviewable changes (1)
  • tanstack-start/generation-based-subscription/src/middleware/auth.ts
🧰 Additional context used
🧬 Code graph analysis (15)
react-vite/generation-based-subscription/src/components/navbar.jsx (4)
react-vite/generation-based-subscription/src/components/providers.jsx (1)
  • session (5-5)
react-vite/generation-based-subscription/src/lib/auth-client.js (2)
  • authClient (3-5)
  • authClient (3-5)
react-vite/generation-based-subscription/src/pages/pricing.jsx (1)
  • billing (6-6)
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx (5)
  • DropdownMenuTrigger (30-50)
  • DropdownMenuContent (53-75)
  • DropdownMenuLabel (109-115)
  • DropdownMenuSeparator (118-124)
  • DropdownMenuItem (78-106)
react-vite/generation-based-subscription/src/components/ui/button.jsx (1)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/badge.jsx (1)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/pages/home.jsx (4)
react-vite/generation-based-subscription/src/pages/pricing.jsx (1)
  • billing (6-6)
react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (5)
  • billing (25-25)
  • subscriptions (29-29)
  • currentSubscriptions (30-30)
  • pricingModel (40-40)
  • pricingModel (44-44)
react-vite/generation-based-subscription/src/lib/billing-helpers.js (1)
  • computeUsageTotal (9-41)
react-vite/generation-based-subscription/src/components/ui/progress.jsx (1)
  • Progress (4-22)
react-vite/generation-based-subscription/server/lib/auth.js (3)
react-vite/generation-based-subscription/server/db/client.js (3)
  • require (4-4)
  • require (5-5)
  • db (23-23)
react-vite/generation-based-subscription/server/db/schema.js (2)
  • require (1-1)
  • betterAuthSchema (62-67)
react-vite/generation-based-subscription/server/index.js (8)
  • require (4-4)
  • require (5-5)
  • require (6-6)
  • require (7-7)
  • require (8-8)
  • require (9-9)
  • auth (160-162)
  • auth (219-221)
react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (3)
react-vite/generation-based-subscription/src/components/pricing-card.jsx (4)
  • billing (21-21)
  • pricingModel (41-41)
  • displayPrice (38-38)
  • PricingCard (20-171)
react-vite/generation-based-subscription/src/hooks/use-mobile.js (2)
  • isMobile (6-10)
  • useMobile (5-26)
react-vite/generation-based-subscription/src/components/ui/card.jsx (4)
  • Card (4-13)
  • CardHeader (16-22)
  • CardFooter (51-57)
  • CardContent (46-48)
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx (3)
react-vite/generation-based-subscription/src/components/ui/tooltip.jsx (3)
  • React (27-27)
  • React (55-55)
  • open (15-15)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/carousel.jsx (1)
  • onSelect (40-44)
react-vite/generation-based-subscription/src/pages/pricing.jsx (5)
react-vite/generation-based-subscription/server/index.js (1)
  • billing (241-241)
react-vite/generation-based-subscription/src/components/navbar.jsx (1)
  • billing (23-23)
react-vite/generation-based-subscription/src/pages/home.jsx (1)
  • billing (36-36)
react-vite/generation-based-subscription/src/components/pricing-card.jsx (2)
  • billing (21-21)
  • error (23-23)
react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (1)
  • billing (25-25)
react-vite/generation-based-subscription/server/index.js (5)
react-vite/generation-based-subscription/server/db/client.js (3)
  • require (4-4)
  • require (5-5)
  • db (23-23)
react-vite/generation-based-subscription/server/db/schema.js (2)
  • require (1-1)
  • users (3-15)
react-vite/generation-based-subscription/server/lib/auth.js (6)
  • require (4-4)
  • require (5-5)
  • require (6-6)
  • require (7-7)
  • require (8-8)
  • auth (15-38)
react-vite/generation-based-subscription/src/pages/pricing.jsx (1)
  • billing (6-6)
react-vite/generation-based-subscription/src/lib/billing-helpers.js (5)
  • usagePrice (84-88)
  • findUsagePriceByMeterSlug (72-91)
  • meterIdBySlug (76-78)
  • usageMeterId (80-80)
  • price (104-104)
react-vite/generation-based-subscription/src/pages/sign-in.jsx (4)
react-vite/generation-based-subscription/src/pages/sign-up.jsx (6)
  • navigate (8-8)
  • email (9-9)
  • password (10-10)
  • error (12-12)
  • isLoading (13-13)
  • handleSubmit (15-39)
react-vite/generation-based-subscription/src/lib/auth-client.js (2)
  • authClient (3-5)
  • authClient (3-5)
react-vite/generation-based-subscription/src/components/ui/card.jsx (4)
  • Card (4-13)
  • CardHeader (16-22)
  • CardTitle (25-34)
  • CardDescription (37-43)
react-vite/generation-based-subscription/src/components/ui/button.jsx (1)
  • Button (21-36)
react-vite/generation-based-subscription/server/db/client.js (3)
react-vite/generation-based-subscription/server/db/schema.js (1)
  • require (1-1)
react-vite/generation-based-subscription/server/index.js (6)
  • require (4-4)
  • require (5-5)
  • require (6-6)
  • require (7-7)
  • require (8-8)
  • require (9-9)
react-vite/generation-based-subscription/server/lib/auth.js (5)
  • require (4-4)
  • require (5-5)
  • require (6-6)
  • require (7-7)
  • require (8-8)
react-vite/generation-based-subscription/src/components/ui/skeleton.jsx (1)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/hooks/use-mobile.js (1)
react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (1)
  • isMobile (26-26)
react-vite/generation-based-subscription/src/lib/billing-helpers.js (1)
react-vite/generation-based-subscription/server/index.js (6)
  • currentSubscription (247-247)
  • meterIdBySlug (285-287)
  • usageMeterId (289-289)
  • usagePrice (252-252)
  • usagePrice (292-294)
  • priceSlug (259-259)
react-vite/generation-based-subscription/src/components/ui/card.jsx (1)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
🪛 dotenv-linter (4.0.0)
react-vite/generation-based-subscription/.env.example

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

(QuoteCharacter)


[warning] 17-17: [SpaceCharacter] The line has spaces around equal sign

(SpaceCharacter)

🪛 markdownlint-cli2 (0.18.1)
react-vite/generation-based-subscription/README.md

73-73: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


127-127: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (39)
react-vite/generation-based-subscription/eslint.config.js (1)

5-5: The eslint/config import path is correct for the project's ESLint version (v9.39.1). Both defineConfig and globalIgnores are valid exports from this path in ESLint v9+. No changes needed.

react-vite/generation-based-subscription/server/package.json (1)

1-3: LGTM!

The minimal package.json correctly establishes CommonJS module resolution for the server directory, allowing the server-side code to use module.exports syntax while the frontend can use ESM.

react-vite/generation-based-subscription/src/lib/utils.js (1)

1-6: LGTM!

This is the standard shadcn/ui utility pattern for composing Tailwind classes. The combination of clsx for conditional class handling and twMerge for resolving Tailwind class conflicts is a well-established best practice.

react-vite/generation-based-subscription/drizzle/meta/_journal.json (1)

1-13: LGTM!

This is an auto-generated Drizzle ORM migration journal file that correctly tracks the initial migration. The structure and format are appropriate for the Drizzle Kit migration system.

react-vite/generation-based-subscription/src/components/ui/skeleton.jsx (1)

1-12: LGTM!

Clean implementation of the shadcn/ui Skeleton component. The use of cn for class composition allows consumers to customize the styling while maintaining the base animation and appearance.

react-vite/generation-based-subscription/.gitignore (1)

1-96: LGTM!

Comprehensive .gitignore with good coverage of Node.js/React/Vite artifacts. The explicit enforcement of Bun as the package manager (Lines 93-96) by ignoring other lock files is a good practice for consistency, and correctly leaves bun.lockb to be tracked.

react-vite/generation-based-subscription/src/hooks/use-mobile.js (1)

1-26: LGTM!

The hook is well-implemented with proper SSR safety checks, correct use of matchMedia for responsive detection, and appropriate cleanup of the event listener. The lazy initialization in useState ensures the initial value is computed correctly on first render.

react-vite/generation-based-subscription/package.json (2)

6-16: LGTM!

Scripts are well-organized with concurrent dev server setup, Drizzle CLI commands, and standard build/lint scripts. The dependency versions are appropriate for a React 19 + Vite project.


17-52: Dependencies look appropriate for the stack.

The dependency selection is solid for this example project. React 19.2, Better Auth, Flowglad, and Drizzle ORM are correctly paired with compatible dev tooling including eslint-plugin-react-hooks v7 which is required for React 19.

react-vite/generation-based-subscription/vite.config.js (1)

1-22: LGTM!

The Vite configuration is clean and appropriate for the project. The API proxy correctly forwards /api/* requests to the Express backend, and the @ alias provides convenient imports from the src directory.

react-vite/generation-based-subscription/drizzle/meta/0000_snapshot.json (1)

1-324: LGTM!

This is an auto-generated Drizzle migration snapshot. The schema correctly defines the Better Auth tables with appropriate constraints, foreign keys with cascading deletes, and unique constraints on email and session tokens.

react-vite/generation-based-subscription/pricing.yaml (1)

1-306: LGTM!

The pricing configuration is comprehensive and well-structured. The tiered subscription model with usage credit grants, toggle features, and usage-based pricing for top-ups is cleanly organized. Slug references between products and features are consistent.

react-vite/generation-based-subscription/src/lib/billing-helpers.js (3)

9-41: LGTM!

The computeUsageTotal function is well-implemented with defensive programming. It properly handles missing data, builds an efficient lookup map, and gracefully returns 0 on any errors.


50-63: LGTM!

Clean implementation with appropriate null safety checks and explicit return type coercion via String().


100-110: LGTM!

The function correctly iterates through products to find a matching price by slug and returns the product's default status. The early returns provide good guard clause patterns.

react-vite/generation-based-subscription/src/components/ui/carousel.jsx (1)

113-149: LGTM!

CarouselContent and CarouselItem are well-implemented with proper ref forwarding, orientation-aware styling, and appropriate ARIA attributes (role="group", aria-roledescription="slide").

react-vite/generation-based-subscription/index.html (1)

1-13: LGTM!

Standard Vite HTML entry point with proper meta tags, viewport configuration, and module script loading.

react-vite/generation-based-subscription/src/main.jsx (1)

17-38: LGTM!

Well-structured ErrorBoundary with proper lifecycle methods. The implementation correctly captures errors via getDerivedStateFromError and logs them in componentDidCatch.

react-vite/generation-based-subscription/postcss.config.js (1)

1-6: LGTM!

Standard PostCSS configuration for Tailwind CSS with autoprefixer.

react-vite/generation-based-subscription/tailwind.config.js (1)

1-52: LGTM!

Well-structured Tailwind configuration following the shadcn/ui theming pattern with CSS variable-based color tokens. The content paths correctly target the index.html and all source files for JIT compilation.

react-vite/generation-based-subscription/src/index.css (1)

1-60: LGTM! Standard shadcn/ui theming setup.

The CSS variables follow the shadcn/ui convention of using raw HSL values, and both light/dark themes are properly defined. The Tailwind directives and base layer styles are correctly configured.

Minor note: The two @layer base blocks (lines 5-50 and 52-60) could be consolidated into one, but this is purely cosmetic.

react-vite/generation-based-subscription/src/components/providers.jsx (1)

4-22: LGTM! Clean session-aware billing integration.

The logic correctly gates billing data loading on authenticated sessions, and the withCredentials: true config ensures auth cookies are sent with requests. The !isPending check prevents premature loading decisions during session hydration.

react-vite/generation-based-subscription/src/components/ui/button.jsx (1)

37-39: LGTM!

The Button component is well-structured with proper forwardRef usage, displayName assignment, and clean exports. The base styles provide good accessibility defaults with focus-visible states and disabled styling.

react-vite/generation-based-subscription/server/lib/auth.js (1)

15-38: LGTM!

The Better Auth configuration is well-structured with:

  • Proper secret validation with early failure
  • Drizzle adapter with explicit schema mapping
  • Flowglad plugin integration for billing
  • Trusted origins for secure cookie handling

The requireEmailVerification: false setting is appropriate for an example/demo project.

react-vite/generation-based-subscription/src/pages/sign-up.jsx (1)

15-39: LGTM!

The form submission handler is well-implemented:

  • Prevents default form behavior
  • Clears previous errors before submission
  • Handles both API-level errors (result.error) and exceptions
  • Uses finally to ensure loading state resets
  • Navigates with replace: true to prevent back-navigation to the form
react-vite/generation-based-subscription/src/pages/sign-in.jsx (1)

7-40: LGTM!

The sign-in implementation is solid:

  • Good use of location.state?.from?.pathname for redirect-after-login pattern
  • Consistent error handling matching sign-up page
  • Proper loading state management
react-vite/generation-based-subscription/drizzle.config.js (1)

3-10: LGTM!

The Drizzle configuration is properly structured with correct schema path pointing to the Better Auth schema definitions.

react-vite/generation-based-subscription/src/pages/pricing.jsx (1)

48-64: LGTM!

The JSX structure is clean with proper responsive styling and semantic hierarchy.

react-vite/generation-based-subscription/src/components/navbar.jsx (1)

158-237: LGTM on the overall component structure.

The dropdown menu implementation with conditional cancel/uncancel actions and proper loading/error states is well structured.

react-vite/generation-based-subscription/src/components/ui/badge.jsx (1)

11-22: LGTM!

Clean implementation following the shadcn/ui pattern. The cn utility properly merges base styles with variant-specific and custom classes.

react-vite/generation-based-subscription/src/App.jsx (1)

12-31: LGTM on ProtectedRoute implementation.

The component properly handles the loading state, redirects unauthenticated users, and prevents rendering children until authentication is confirmed.

react-vite/generation-based-subscription/src/components/pricing-cards-grid.jsx (1)

24-110: LGTM on the plan transformation logic.

The filtering, transformation, and sorting of pricing plans from the catalog is well-structured with appropriate null checks and fallbacks.

react-vite/generation-based-subscription/server/db/schema.js (1)

3-15: LGTM on table structure and foreign key definitions.

The schema correctly defines primary keys, unique constraints, and cascading deletes for referential integrity.

Also applies to: 30-50

react-vite/generation-based-subscription/src/components/ui/card.jsx (1)

1-60: LGTM!

Clean implementation of the Card component family following the shadcn/ui pattern. Proper use of forwardRef, displayName assignments, and the cn utility for className composition.

react-vite/generation-based-subscription/server/index.js (1)

143-150: Health endpoint exposes userId - verify this is intentional.

Exposing user IDs in health checks may be acceptable for debugging but could be a minor information disclosure in production. For an example app this is fine, but consider noting this in documentation.

react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx (1)

109-134: LGTM!

DropdownMenuLabel and DropdownMenuSeparator are clean, simple presentational components with proper forwardRef usage and displayName assignments.

react-vite/generation-based-subscription/src/pages/home.jsx (1)

498-810: LGTM!

The JSX structure is well-organized with:

  • Proper conditional rendering for loading/error states
  • Accessible tooltips explaining disabled button states
  • Semantic grouping of action buttons by category
  • Progress bars with appropriate keys for re-rendering
react-vite/generation-based-subscription/drizzle/0000_safe_iron_fist.sql (1)

1-10: LGTM!

The users table schema is well-designed with appropriate constraints:

  • Primary key on id
  • Unique constraint on email
  • Sensible defaults for email_verified, created_at, updated_at
react-vite/generation-based-subscription/src/components/pricing-card.jsx (1)

88-121: LGTM!

The card header rendering is clean with:

  • Proper conditional rendering for description
  • Responsive typography classes
  • Good use of the cn utility for dynamic styling based on isPopular and isCurrentPlan

Comment thread react-vite/generation-based-subscription/.env.example Outdated
Comment thread react-vite/generation-based-subscription/.env.example Outdated
Comment thread react-vite/generation-based-subscription/README.md
Comment thread react-vite/generation-based-subscription/server/db/schema.js Outdated
Comment thread react-vite/generation-based-subscription/server/db/schema.js Outdated
Comment on lines +30 to +50
const DropdownMenuTrigger = React.forwardRef(({ asChild, children, ...props }, ref) => {
const { open, setOpen } = React.useContext(DropdownMenuContext);

const handleClick = () => setOpen(!open);

if (asChild && React.isValidElement(children)) {
// Clone element without passing ref to avoid ref access during render
// The child component should handle its own ref if needed
return React.cloneElement(children, {
...props,
onClick: handleClick,
'aria-expanded': open,
});
}

return (
<button ref={ref} onClick={handleClick} aria-expanded={open} {...props}>
{children}
</button>
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ref is not forwarded when asChild is true.

When asChild is true, the ref prop passed to DropdownMenuTrigger is ignored. The comment on line 36-37 acknowledges this, but it breaks the forwardRef contract and may cause issues for consumers expecting ref forwarding.

Suggested fix
   if (asChild && React.isValidElement(children)) {
-    // Clone element without passing ref to avoid ref access during render
-    // The child component should handle its own ref if needed
     return React.cloneElement(children, {
       ...props,
+      ref,
       onClick: handleClick,
       'aria-expanded': open,
     });
   }
📝 Committable suggestion

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

Suggested change
const DropdownMenuTrigger = React.forwardRef(({ asChild, children, ...props }, ref) => {
const { open, setOpen } = React.useContext(DropdownMenuContext);
const handleClick = () => setOpen(!open);
if (asChild && React.isValidElement(children)) {
// Clone element without passing ref to avoid ref access during render
// The child component should handle its own ref if needed
return React.cloneElement(children, {
...props,
onClick: handleClick,
'aria-expanded': open,
});
}
return (
<button ref={ref} onClick={handleClick} aria-expanded={open} {...props}>
{children}
</button>
);
});
const DropdownMenuTrigger = React.forwardRef(({ asChild, children, ...props }, ref) => {
const { open, setOpen } = React.useContext(DropdownMenuContext);
const handleClick = () => setOpen(!open);
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
...props,
ref,
onClick: handleClick,
'aria-expanded': open,
});
}
return (
<button ref={ref} onClick={handleClick} aria-expanded={open} {...props}>
{children}
</button>
);
});
🤖 Prompt for AI Agents
In @react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx
around lines 30 - 50, DropdownMenuTrigger currently drops the forwarded ref when
rendering the cloned child (asChild path); update the React.cloneElement call to
forward the incoming ref to the child and preserve any existing child ref by
composing refs: create a combined ref setter that assigns the forwarded ref (ref
param from forwardRef) and also calls/assigns the child's original ref
(children.ref) if present, then pass that combined ref as the 'ref' prop in
React.cloneElement along with existing props (keep onClick and aria-expanded);
this ensures the forwardRef contract is honored while keeping handleClick and
other behavior intact.

Comment thread react-vite/generation-based-subscription/src/components/ui/tooltip.jsx Outdated
Comment on lines +3 to +5
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_APP_URL || 'http://localhost:5173',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what port the backend server uses and what VITE_APP_URL is documented as
echo "=== Checking .env.example for VITE_APP_URL ===" 
fd -t f ".env.example" --exec cat {}

echo ""
echo "=== Checking for backend/server port configuration ===" 
rg -n "listen|PORT" --type js -g '**/server/**' -A 2

echo ""
echo "=== Checking for any API configuration or server setup in root ===" 
find . -maxdepth 2 -name "*.js" -o -name "*.ts" | grep -E "(server|api|backend)" | head -20

echo ""
echo "=== Checking package.json for dev/server scripts ===" 
fd -t f "package.json" --exec jq '.scripts' {}

Repository: flowglad/examples

Length of output: 3481


Fix the baseURL fallback to point to the backend server, not the frontend.

The fallback http://localhost:5173 points to the Vite frontend dev server, but the Better Auth backend runs on port 3001 (see server/index.js:30). During local development without VITE_APP_URL set, auth requests will fail. Update the fallback to http://localhost:3001.

Additionally, create .env.example or update any environment documentation to clarify that VITE_APP_URL should point to the backend API server URL.

🤖 Prompt for AI Agents
In @react-vite/generation-based-subscription/src/lib/auth-client.js around lines
3 - 5, The auth client fallback is pointed at the frontend dev server; update
the createAuthClient call (authClient) so its baseURL fallback uses the backend
URL http://localhost:3001 instead of http://localhost:5173, and ensure the
change is applied where authClient is exported; also add or update a
.env.example (or project docs) to clarify that VITE_APP_URL should be the
backend API server URL (e.g., http://localhost:3001) for local development.

Comment thread react-vite/generation-based-subscription/src/main.jsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/drizzle.config.cjs`:
- Line 2: The console.log call exposing process.env.DATABASE_URL should be
removed or replaced so credentials aren't printed; locate the
console.log('process.env.DATABASE_URL', process.env.DATABASE_URL) statement and
either delete it or change it to log only presence (e.g., log a boolean like
!!process.env.DATABASE_URL) or a non-sensitive masked message, ensuring no raw
DATABASE_URL value is emitted to logs.

In `@react-vite/generation-based-subscription/src/components/pricing-card.jsx`:
- Around line 126-137: The code accesses plan.features.length which will throw
if plan.features is undefined; update the rendering to guard or default features
(e.g., ensure features is an array before using .length or map). Replace uses of
plan.features with a safe value (for example destructure const { features = [] }
= plan at the top of the component, or use (plan.features || []) and optional
chaining like (plan.features ?? []).length and (plan.features ?? []).map) so the
conditional and map in the PricingCard rendering (the plan.features checks and
map) operate on an array and cannot raise a TypeError.

In `@react-vite/generation-based-subscription/src/pages/sign-up.jsx`:
- Around line 100-105: Replace the plain anchor with react-router-dom's Link to
enable client-side navigation: add Link to the existing react-router-dom import
(alongside useNavigate if present) and change the <a href="/sign-in"
className="text-primary hover:underline">Sign in</a> element to a <Link> that
uses to="/sign-in" and preserves the same className and content.
♻️ Duplicate comments (2)
react-vite/generation-based-subscription/src/main.jsx (1)

40-46: Indentation is inconsistent.

The createRoot call and its JSX children have misaligned indentation. This was flagged in a previous review.

react-vite/generation-based-subscription/.env.example (1)

1-22: Environment configuration looks good overall.

The file provides clear documentation with helpful comments for each variable. The configuration covers database, authentication, and billing integration requirements.

One remaining issue on line 17: BETTER_AUTH_SECRET still has quotes around the value, which can cause parsing issues with some .env parsers.

Suggested fix
-BETTER_AUTH_SECRET="your-secret-key-here"
+BETTER_AUTH_SECRET=your-secret-key-here
🧹 Nitpick comments (8)
react-vite/generation-based-subscription/src/components/ui/progress.jsx (1)

31-32: Minor: Extra blank line at end of file.

There are two trailing blank lines. Consider removing one for consistency with typical linting rules (single trailing newline).

react-vite/generation-based-subscription/src/main.jsx (2)

7-15: Solid fallback UI implementation.

The component handles missing error gracefully with the nullish coalescing fallback. One optional enhancement: consider adding aria-live="assertive" to the container so screen readers announce the error immediately.


18-38: Standard error boundary pattern — well implemented.

The class component correctly implements getDerivedStateFromError and componentDidCatch. For a more robust solution in production, consider adding a reset mechanism:

♻️ Optional: Add reset capability
 class ErrorBoundary extends React.Component {
   constructor(props) {
     super(props);
     this.state = { hasError: false, error: null };
+    this.resetError = this.resetError.bind(this);
   }

+  resetError() {
+    this.setState({ hasError: false, error: null });
+  }

   static getDerivedStateFromError(error) {
     return { hasError: true, error };
   }

   componentDidCatch(error, errorInfo) {
     console.error('Error caught by boundary:', error, errorInfo);
   }

   render() {
     if (this.state.hasError) {
-      return <ErrorFallback error={this.state.error} />;
+      return <ErrorFallback error={this.state.error} onReset={this.resetError} />;
     }
     return this.props.children;
   }
 }

Then update ErrorFallback to accept an onReset prop alongside the reload option.

react-vite/generation-based-subscription/src/components/ui/carousel.jsx (1)

54-65: Consider adding ArrowUp/ArrowDown for vertical orientation.

Keyboard navigation currently uses ArrowLeft/ArrowRight regardless of orientation. For vertical carousels, users would intuitively expect ArrowUp/ArrowDown to navigate slides.

♻️ Suggested enhancement
     const handleKeyDown = React.useCallback(
       (event) => {
-        if (event.key === 'ArrowLeft') {
+        if (event.key === 'ArrowLeft' || (orientation === 'vertical' && event.key === 'ArrowUp')) {
           event.preventDefault();
           scrollPrev();
-        } else if (event.key === 'ArrowRight') {
+        } else if (event.key === 'ArrowRight' || (orientation === 'vertical' && event.key === 'ArrowDown')) {
           event.preventDefault();
           scrollNext();
         }
       },
-      [scrollPrev, scrollNext]
+      [scrollPrev, scrollNext, orientation]
     );
react-vite/generation-based-subscription/README.md (2)

73-76: Add language specifier to fenced code block.

This code block is missing a language specifier, which affects syntax highlighting and accessibility tools.

Suggested fix
-```
+```bash
 SERVER_PORT=3001
 VITE_APP_URL=http://localhost:5173
-```
+```

127-162: Add language specifier to project structure block.

The directory tree code block should have a language specifier for consistency.

Suggested fix
-```
+```text
 ├── server/
 │   ├── db/
react-vite/generation-based-subscription/drizzle.config.cjs (1)

7-9: Consider failing explicitly when DATABASE_URL is missing.

The empty string fallback will cause Drizzle commands to fail with a confusing connection error rather than a clear configuration error.

Suggested improvement
   dbCredentials: {
-    url: process.env.DATABASE_URL || '',
+    url: process.env.DATABASE_URL,
   },

Alternatively, add explicit validation:

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL environment variable is required');
}
react-vite/generation-based-subscription/src/components/pricing-card.jsx (1)

116-119: Consider deriving the billing interval from plan data.

The hardcoded /month text assumes all plans use monthly billing. If the pricing model supports annual or other intervals, consider deriving this from the plan object (e.g., plan.interval or similar) for accuracy.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba86877 and 82b8ce5.

📒 Files selected for processing (12)
  • react-vite/generation-based-subscription/.env.example
  • react-vite/generation-based-subscription/README.md
  • react-vite/generation-based-subscription/drizzle.config.cjs
  • react-vite/generation-based-subscription/package.json
  • react-vite/generation-based-subscription/server/index.js
  • react-vite/generation-based-subscription/src/components/navbar.jsx
  • react-vite/generation-based-subscription/src/components/pricing-card.jsx
  • react-vite/generation-based-subscription/src/components/ui/carousel.jsx
  • react-vite/generation-based-subscription/src/components/ui/progress.jsx
  • react-vite/generation-based-subscription/src/components/ui/tooltip.jsx
  • react-vite/generation-based-subscription/src/main.jsx
  • react-vite/generation-based-subscription/src/pages/sign-up.jsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • react-vite/generation-based-subscription/server/index.js
  • react-vite/generation-based-subscription/src/components/ui/tooltip.jsx
  • react-vite/generation-based-subscription/src/components/navbar.jsx
🧰 Additional context used
🧬 Code graph analysis (3)
react-vite/generation-based-subscription/src/components/ui/progress.jsx (3)
react-vite/generation-based-subscription/src/components/ui/tooltip.jsx (2)
  • React (28-28)
  • React (56-56)
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsx (3)
  • React (31-31)
  • React (55-55)
  • React (80-80)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/carousel.jsx (2)
react-vite/generation-based-subscription/src/lib/utils.js (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/button.jsx (1)
  • Button (21-36)
react-vite/generation-based-subscription/src/pages/sign-up.jsx (4)
react-vite/generation-based-subscription/src/pages/sign-in.jsx (6)
  • navigate (8-8)
  • email (10-10)
  • password (11-11)
  • error (12-12)
  • isLoading (13-13)
  • handleSubmit (17-40)
react-vite/generation-based-subscription/src/lib/auth-client.js (2)
  • authClient (3-5)
  • authClient (3-5)
react-vite/generation-based-subscription/src/components/ui/card.jsx (5)
  • Card (4-13)
  • CardHeader (16-22)
  • CardTitle (25-34)
  • CardDescription (37-43)
  • CardContent (46-48)
react-vite/generation-based-subscription/src/components/ui/button.jsx (1)
  • Button (21-36)
🪛 dotenv-linter (4.0.0)
react-vite/generation-based-subscription/.env.example

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

(QuoteCharacter)

🪛 markdownlint-cli2 (0.18.1)
react-vite/generation-based-subscription/README.md

73-73: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


127-127: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (16)
react-vite/generation-based-subscription/src/components/ui/progress.jsx (1)

1-30: Well-implemented Progress component with proper accessibility.

The implementation is clean and handles edge cases correctly:

  • Value clamping protects against out-of-range inputs
  • ARIA attributes (role, aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext) provide full screen reader support
  • forwardRef allows parent components to access the underlying DOM element
  • The cn utility usage is consistent with other UI components in the codebase

The accessibility concerns from the previous review have been fully addressed.

react-vite/generation-based-subscription/src/main.jsx (1)

1-4: LGTM!

Imports are correct for React 19 — using createRoot from react-dom/client is the proper modern API.

react-vite/generation-based-subscription/src/pages/sign-up.jsx (3)

1-5: LGTM!

Imports are appropriate for the component's needs. The auth-client and UI components are correctly imported.


15-38: LGTM!

The handleSubmit function properly handles the async sign-up flow with correct error handling, loading state management via finally, and appropriate use of replace: true for post-auth navigation. The pattern is consistent with sign-in.jsx.


40-99: LGTM!

The form structure is well-organized with proper accessibility (labels correctly associated via htmlFor/id), consistent Card-based UI, appropriate HTML5 validation, and clear loading state feedback on the button.

react-vite/generation-based-subscription/src/components/ui/carousel.jsx (7)

1-15: LGTM!

The imports are appropriate, and the context pattern with the useCarousel hook guard is well-implemented for sharing carousel state with child components.


72-83: LGTM - Event listener cleanup is now complete.

The effect properly removes both reInit and select listeners in the cleanup function, addressing the previously identified memory leak concern.


114-131: LGTM!

The CarouselContent component correctly wraps the Embla viewport and applies appropriate flex styling with negative margins that pair with the slide padding for proper spacing.


133-150: LGTM!

The CarouselItem component has proper accessibility attributes and correct styling that complements the negative margins in CarouselContent.


180-206: LGTM!

The CarouselNext component correctly mirrors CarouselPrevious with appropriate positioning adjustments and disabled state binding.


208-214: LGTM!

All carousel components are properly exported for external use.


152-178: The variant="outline" and size="icon" defaults are properly defined in the Button component's buttonVariants object. No action needed.

react-vite/generation-based-subscription/README.md (1)

1-252: Well-structured and comprehensive documentation.

The README provides excellent coverage of:

  • Tech stack and features
  • Step-by-step setup instructions
  • Project structure overview
  • Authentication and billing flow explanations
  • Available scripts reference

This will help users get started quickly with the example.

react-vite/generation-based-subscription/package.json (1)

1-53: Package configuration is well-structured.

The dependencies are appropriate for the project scope:

  • React 19.2.0 with compatible eslint-plugin-react-hooks v7.0.1 (per React 19 requirements)
  • Proper separation of runtime and dev dependencies
  • Concurrent script setup enables efficient local development

The ESM module type ("type": "module") aligns with modern JavaScript practices and Vite's expectations.

react-vite/generation-based-subscription/src/components/pricing-card.jsx (2)

44-86: LGTM!

The checkout handler has good error handling with proper loading state management. The getPrice fallback logic elegantly handles both the SDK method and manual catalog lookup.


147-153: Previous review concern addressed.

The disabled condition no longer includes !billing.getPrice, which aligns correctly with the fallback logic in handleCheckout.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread react-vite/generation-based-subscription/drizzle.config.cjs Outdated
Comment thread react-vite/generation-based-subscription/src/components/pricing-card.jsx Outdated
Comment thread react-vite/generation-based-subscription/src/pages/sign-up.jsx Outdated
Comment on lines +9 to +12
# Application Configuration (Optional)
# Base URL for the application (used for auth callbacks)
# Defaults to http://localhost:3001 if not set
NEXT_PUBLIC_BASE_URL=http://localhost:3001

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i think SERVER_PORT is used instead?

Comment on lines +107 to +110
This starts:
- Frontend: [http://localhost:5173](http://localhost:5173)
- Backend API: [http://localhost:3001](http://localhost:3001)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe changes ports to be 3000, 3001? just a thought up to you

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

had flowglad running on 3000 so that's why i made it 3001!

@lpmsh

lpmsh commented Jan 15, 2026

Copy link
Copy Markdown
Contributor

@rebeccax06 could you also convert this to typescript instead of js/jsx ?

@lpmsh lpmsh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just some nitpick things and consistency with other example projects functionality is good though otherwise!

Comment on lines +15 to +24
if (!process.env.FLOWGLAD_SECRET_KEY) {
console.error('ERROR: FLOWGLAD_SECRET_KEY is not set in .env file');
process.exit(1);
}

// Check if BETTER_AUTH_SECRET is set
if (!process.env.BETTER_AUTH_SECRET) {
console.error('ERROR: BETTER_AUTH_SECRET is not set in .env file');
process.exit(1);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To be consistent with other examples change .env to .env.local

like this case in server/index.js

Suggested change
if (!process.env.FLOWGLAD_SECRET_KEY) {
console.error('ERROR: FLOWGLAD_SECRET_KEY is not set in .env file');
process.exit(1);
}
// Check if BETTER_AUTH_SECRET is set
if (!process.env.BETTER_AUTH_SECRET) {
console.error('ERROR: BETTER_AUTH_SECRET is not set in .env file');
process.exit(1);
}
if (!process.env.FLOWGLAD_SECRET_KEY) {
console.error('ERROR: FLOWGLAD_SECRET_KEY is not set in .env.local file');
process.exit(1);
}
// Check if BETTER_AUTH_SECRET is set
if (!process.env.BETTER_AUTH_SECRET) {
console.error('ERROR: BETTER_AUTH_SECRET is not set in .env.local file');
process.exit(1);
}

Comment thread react-vite/generation-based-subscription/server/index.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The pricing page shows the topups ($4 fast and $10 HD) where it should just show the subscriptions like the nextjs example

see the nextjs example's nextjs/generation-based-subscription/src/components/pricing-cards-grid.tsx

Image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

upon a successful purchase of a subscription the pricing page should redirect to the main page /

Comment thread react-vite/generation-based-subscription/src/pages/home.jsx Outdated
<DropdownMenuContent align="end">
<DropdownMenuLabel>Account Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={handleSignOut}>Log out</DropdownMenuItem>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Image

The dropdown/tootip for cancelling is kinda squished. Fix the width to show the entire tooltip

Comment thread react-vite/generation-based-subscription/src/pages/sign-in.jsx Outdated
Comment thread react-vite/generation-based-subscription/src/pages/sign-up.jsx Outdated
@lpmsh

lpmsh commented Jan 15, 2026

Copy link
Copy Markdown
Contributor

@rebeccax06 let me know when you make changes and feel like it's in a good spot and i can get a docs page setup for this example

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/.env.example`:
- Around line 9-12: The SERVER_PORT entry in .env.example is incorrect:
SERVER_PORT should be a numeric port, not a URL, because the server reads it as
const PORT = process.env.SERVER_PORT || 3001; update the example line to
SERVER_PORT=3001 and adjust the surrounding comment to state that this value is
the port number used by the server (default 3001) rather than a base URL.

In `@react-vite/generation-based-subscription/drizzle.config.ts`:
- Around line 7-13: The config currently falls back to an empty string for
process.env.DATABASE_URL causing runtime errors; before calling defineConfig,
check process.env.DATABASE_URL and if it's missing or empty throw a clear error
(e.g., "DATABASE_URL environment variable is required for drizzle config") so
that dbCredentials.url is never set to ''. Update the file to validate the env
var prior to using it in defineConfig and reference the dbCredentials/url and
defineConfig symbols when making the change.

In `@react-vite/generation-based-subscription/package.json`:
- Around line 35-54: Add "typescript" to devDependencies in package.json so
TypeScript is explicitly installed for local and CI builds; update the existing
"devDependencies" object (the block containing entries like "@types/react",
"vite", etc.) to include a "typescript": "<latest-compatible-version>" entry
(choose a version compatible with the project's `@types` and tooling) and run
npm/yarn/pnpm install to verify build/type-checking works.

In `@react-vite/generation-based-subscription/server/db/schema.ts`:
- Around line 21-22: In schema.ts update the sessions and accounts table
timestamp definitions so their createdAt and updatedAt columns use the same
defaultNow() pattern as users and verifications; locate the timestamp(...) calls
for createdAt and updatedAt in the sessions and accounts table builders and
append .defaultNow() to each timestamp('created_at', { withTimezone: true }) and
timestamp('updated_at', { withTimezone: true }) expression so inserts no longer
require manually provided timestamps.

In `@react-vite/generation-based-subscription/src/components/navbar.tsx`:
- Around line 172-173: The cancel-section renders even when there is no
subscription because the condition uses !currentSubscription?.isFreePlan (which
is true for undefined); update the guard in the Navbar component where the
cancellation UI is rendered to require a present subscription before checking
plan type (e.g., change the condition to ensure currentSubscription is truthy
and not a free plan using the existing currentSubscription symbol and the
isFreePlan property) so the cancel UI is not rendered when currentSubscription
is undefined.

In
`@react-vite/generation-based-subscription/src/components/pricing-cards-grid.tsx`:
- Around line 117-124: The early returns checking billing.loaded and
billing.loadBilling currently return null and prevent the later loading skeleton
branch from rendering; change these early returns so that when billing is not
loaded or loadBilling is false you render the component's existing loading
skeleton UI instead of returning null, and when billing.errors is present render
the component's error UI/message rather than null; update the conditional logic
in the PricingCardsGrid component (the checks for billing.loaded,
billing.loadBilling, and billing.errors) to return the appropriate skeleton or
error JSX used elsewhere in the component instead of null.
- Around line 71-78: The formatPrice function currently rounds cents away by
using maximumFractionDigits: 0; update formatPrice (used to compute
displayPrice) to preserve cents: format as USD with up to 2 decimal places and
show decimals only when needed (e.g., use maximumFractionDigits: 2 and set
minimumFractionDigits to 0 for whole-dollar amounts or 2 when cents are
non-zero) so values like $9.99 remain $9.99 while $10.00 can render as $10.
- Around line 216-279: The current conditional uses plans.length === 0 to show
skeletons, conflating "loading" with an empty loaded state; update the render
logic in the PricingCardsGrid component (the block using plans.length === 0 and
isMobile) to check an explicit loading flag (e.g., isLoading or plans == null)
for showing skeletons and instead render a user-friendly "no plans available"
message when loading is false and plans.length === 0; locate the conditional
around plans.length === 0 and replace it so skeletons are shown only when
isLoading is true (or plans === null/undefined) and when !isLoading &&
plans.length === 0 render a centered fallback card/message.

In `@react-vite/generation-based-subscription/src/components/ui/carousel.tsx`:
- Around line 180-233: Extract onClick from the props in both CarouselPrevious
and CarouselNext (e.g. ({ className, variant = 'outline', size = 'icon',
onClick: onClickProp, ...props }, ref)) and replace the direct
onClick={scrollPrev} / onClick={scrollNext} with a composed handler that first
calls the consumer onClickProp(e) and then, if !e.defaultPrevented, calls
scrollPrev()/scrollNext(); pass that composed handler as onClick and keep
spreading the rest of props (...props) as before so consumer props still apply.

In
`@react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx`:
- Around line 102-137: Replace the non-interactive <div> in the DropdownMenuItem
component with a semantic <button> (still forwarded via React.forwardRef for ref
handling) so items are keyboard-focusable; keep using the existing handleClick
and setOpen from DropdownMenuContext, map the disabled prop to the button's
disabled attribute (and add aria-disabled when needed), and add role="menuitem"
to the button while preserving className, variant logic and {...props}; remove
custom tabIndex/keyboard handlers (native button handles Enter/Space) unless
special behavior is required.
- Around line 44-66: DropdownMenuTrigger currently overrides existing onClick
handlers, omits aria-haspopup, and renders a default submit button; change
handleClick to toggle via setOpen(!open) (matching DropdownMenuContextValue),
and compose handlers so existing props.onClick and a child's onClick are
preserved (call them before/after toggling as appropriate) instead of
overwriting; when cloning the child pass a composed onClick that calls
child.props.onClick and props.onClick then handleClick, and when rendering the
fallback <button> include type="button", aria-haspopup="menu",
aria-expanded={open}, and compose props.onClick with handleClick so custom
handlers still run.

In `@react-vite/generation-based-subscription/src/components/ui/tooltip.tsx`:
- Around line 47-73: TooltipTrigger currently overwrites consumer handlers,
ignores delayDuration and lacks keyboard support; update the component to read
delayDuration from TooltipContext and implement composed handlers that merge
consumer onMouseEnter/onMouseLeave/onFocus/onBlur with internal behavior:
schedule setOpen(true) using a timeout based on delayDuration (store timer id in
a ref), clear the timer and call setOpen(false) on mouseLeave/blur (and clear on
unmount in a useEffect cleanup), and ensure when asChild is true you clone the
child but compose its existing event props rather than replacing them; for the
non-asChild branch attach the composed handlers directly to the span and spread
{...props} after to avoid consumer overrides. Use identifiers TooltipContext,
setOpen, delayDuration, and TooltipTrigger to locate and modify the code.

In `@react-vite/generation-based-subscription/src/hooks/use-mobile.ts`:
- Around line 12-23: The onChange callback used in the useEffect must have an
explicit TypeScript parameter type to satisfy strict mode; change the handler in
the useEffect (the onChange passed to mql.addEventListener) to accept event:
MediaQueryListEvent (e.g., const onChange = (event: MediaQueryListEvent) =>
setIsMobile(event.matches);) and ensure any local mql variable is treated as the
result of window.matchMedia so the typed event matches the listener signature.

In `@react-vite/generation-based-subscription/src/lib/billing-helpers.ts`:
- Around line 72-91: In findUsagePriceByMeterSlug, normalize IDs and slugs to
strings to avoid mismatches: when building meterIdBySlug from
pricingModel.usageMeters convert meter.id and meter.slug to String(...) (or
.toString()), convert the incoming usageMeterSlug to a string before using
meterIdBySlug.get(...), and when finding the usage price compare
String(price.usageMeterId) === String(usageMeterId) (or convert
price.usageMeterId to string before the equality check). Update references:
pricingModel.usageMeters, meter.id, meter.slug, meterIdBySlug, usageMeterSlug,
pricingModel.products, and price.usageMeterId accordingly.

In `@react-vite/generation-based-subscription/src/lib/utils.ts`:
- Around line 1-6: The cn helper currently uses an untyped rest parameter
causing strict-mode errors; import ClassValue from 'clsx' and change the
signature of the cn function to accept ...inputs: ClassValue[] (or readonly
ClassValue[]) and return string, then call clsx(inputs) and twMerge as before
(update the import to include ClassValue and add the type annotation to the cn
function/method).

In `@react-vite/generation-based-subscription/src/pages/home.tsx`:
- Around line 179-183: fastGenerationsTotal is computed using
billing.pricingModel directly which is inconsistent with the earlier fallback
const pricingModel = billing.pricingModel || billing.catalog; — update the call
that sets fastGenerationsTotal to pass the fallback pricingModel (not
billing.pricingModel) into computeUsageTotal (the same pattern used for other
totals) so the function receives pricingModel, currentSubscription, and
continues to use computeUsageTotal consistently.
- Around line 71-72: The empty catch block that follows the billing-data reload
try in home.tsx should not swallow errors; change it to catch the error (e) and
at minimum log it (e.g., console.error or a telemetry call) with a descriptive
message like "Error reloading billing data" so you can debug failures in the
billing reload code path in this component.

In `@react-vite/generation-based-subscription/src/vite-env.d.ts`:
- Around line 3-8: The current file wraps the ImportMetaEnv interface in a
"declare global" module and likely contains an "export {}" which turns the file
into a module and prevents Vite's ambient augmentation; remove the "declare
global" wrapper and any export statements so the file is an ambient declaration
and simply declares the ImportMetaEnv interface (refer to the ImportMetaEnv
identifier) with your VITE_ variables to allow Vite to augment import.meta.env
correctly.

In `@react-vite/generation-based-subscription/vite.config.ts`:
- Around line 1-21: The '@' alias in the Vite config is incorrect: update the
resolve.alias entry in the export of defineConfig to map '@' to the project src
directory using a relative path (e.g., '@/': '/src' replacement with the correct
'./src' or path resolved via new URL or path.resolve) so imports using '@'
resolve to the project's src; then add matching TypeScript paths in
tsconfig.json by adding a "paths" mapping under "compilerOptions" mapping "@/*"
to ["src/*"] (and ensure "baseUrl" is set, e.g., "."), so IDEs and the
TypeScript compiler honor the same alias.
♻️ Duplicate comments (2)
react-vite/generation-based-subscription/.env.example (1)

14-18: Remove quotes around BETTER_AUTH_SECRET.
Some .env parsers treat quotes literally; dotenv-linter already flags this.

✅ Proposed fix
-BETTER_AUTH_SECRET="your-secret-key-here"
+BETTER_AUTH_SECRET=your-secret-key-here
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)

3-6: Verify that the Vite proxy configuration is in place for auth requests.

The comment states Vite proxies API requests, but this requires proper proxy configuration in vite.config.ts. The previous review flagged that the fallback http://localhost:5173 points to the frontend, not the backend on port 3001.

If the Vite proxy is correctly configured, this is fine. Otherwise, update the fallback to point to the backend server.

#!/bin/bash
# Verify Vite proxy configuration exists for auth/API requests
echo "=== Checking vite.config.ts for proxy configuration ==="
fd -t f "vite.config.ts" --exec cat {}
🧹 Nitpick comments (17)
react-vite/generation-based-subscription/eslint.config.ts (2)

16-24: Redundant ecmaVersion specification.

ecmaVersion is defined twice: 2020 at line 17 and 'latest' within parserOptions at line 20. The parserOptions.ecmaVersion typically takes precedence, making the outer one redundant.

Suggested fix
     languageOptions: {
-      ecmaVersion: 2020,
       globals: globals.browser,
       parserOptions: {
         ecmaVersion: 'latest',
         ecmaFeatures: { jsx: true },
         sourceType: 'module',
       },
     },

25-27: Use @typescript-eslint/no-unused-vars for TypeScript files.

The project uses TypeScript with strict type checking enabled (noUnusedLocals and noUnusedParameters in tsconfig.json). The base ESLint no-unused-vars rule does not have proper TypeScript support without @typescript-eslint/parser and will miss or mishandle TypeScript-specific constructs. The recommended setup with ESLint 9 flat config is to use the typescript-eslint package, which provides both the parser and the TypeScript-aware no-unused-vars rule.

Additionally, the pattern ^[A-Z_] ignores all uppercase-prefixed variables and underscore-prefixed identifiers, which may unintentionally suppress legitimate unused variable warnings for React components and constants.

react-vite/generation-based-subscription/package.json (1)

7-9: Scripts assume bun runtime is installed.

The dev scripts use bun run which requires bun to be installed. This is fine if documented, but ensure the README clearly states bun as a prerequisite, or consider providing npm/yarn alternatives for broader compatibility.

react-vite/generation-based-subscription/src/components/ui/skeleton.tsx (2)

4-4: Empty interface can be simplified to a type alias.

The empty interface extending HTMLAttributes adds no additional properties. A type alias is more concise:

Suggested simplification
-interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {}
+type SkeletonProps = React.HTMLAttributes<HTMLDivElement>;

6-13: Consider adding forwardRef for consistency with other UI primitives.

The Button component uses forwardRef, but Skeleton does not. For a cohesive UI library, consistent ref forwarding across components helps consumers who need DOM access.

Optional: Add forwardRef
-function Skeleton({ className, ...props }: SkeletonProps) {
-  return (
-    <div
-      className={cn('animate-pulse rounded-md bg-muted', className)}
-      {...props}
-    />
-  );
-}
+const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
+  ({ className, ...props }, ref) => {
+    return (
+      <div
+        ref={ref}
+        className={cn('animate-pulse rounded-md bg-muted', className)}
+        {...props}
+      />
+    );
+  }
+);
+Skeleton.displayName = 'Skeleton';
react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx (1)

18-40: Consider reducing repetition in skeleton grid sections.

The three action button skeleton blocks (lines 19-25, 26-32, 33-39) are nearly identical. While explicit structure is acceptable for skeletons, extracting a helper could improve maintainability:

Optional: Extract repeated pattern
const ActionGroupSkeleton = ({ labelWidth }: { labelWidth: string }) => (
  <div>
    <Skeleton className={`h-4 ${labelWidth} mb-3`} />
    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
      <Skeleton className="h-11 w-full" />
      <Skeleton className="h-11 w-full" />
    </div>
  </div>
);

// Usage:
<div className="space-y-6">
  <ActionGroupSkeleton labelWidth="w-32" />
  <ActionGroupSkeleton labelWidth="w-40" />
  <ActionGroupSkeleton labelWidth="w-48" />
</div>
react-vite/generation-based-subscription/src/components/providers.tsx (1)

4-4: Add TypeScript types for the component props.

Since this project was converted to TypeScript, the children prop should be explicitly typed. This improves type safety and editor intellisense.

🛠️ Suggested fix
+interface FlowgladProviderWrapperProps {
+  children: React.ReactNode;
+}
+
-export function FlowgladProviderWrapper({ children }) {
+export function FlowgladProviderWrapper({ children }: FlowgladProviderWrapperProps) {
react-vite/generation-based-subscription/src/components/ui/carousel.tsx (1)

76-87: Respect orientation in keyboard navigation.

For vertical carousels, ArrowUp/ArrowDown are expected; currently only ArrowLeft/ArrowRight work. Consider switching based on orientation.

♻️ Suggested update
     const handleKeyDown = React.useCallback(
       (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === 'ArrowLeft') {
+        const isHorizontal = orientation === 'horizontal';
+        if (
+          (isHorizontal && event.key === 'ArrowLeft') ||
+          (!isHorizontal && event.key === 'ArrowUp')
+        ) {
           event.preventDefault();
           scrollPrev();
-        } else if (event.key === 'ArrowRight') {
+        } else if (
+          (isHorizontal && event.key === 'ArrowRight') ||
+          (!isHorizontal && event.key === 'ArrowDown')
+        ) {
           event.preventDefault();
           scrollNext();
         }
       },
-      [scrollPrev, scrollNext]
+      [orientation, scrollPrev, scrollNext]
     );
react-vite/generation-based-subscription/src/App.tsx (2)

12-12: Add TypeScript type for children prop.

Since this file was converted to TypeScript, the ProtectedRoute component should have proper typing for its props.

Suggested fix
-function ProtectedRoute({ children }) {
+function ProtectedRoute({ children }: { children: React.ReactNode }) {

41-62: Consider extracting a shared layout to reduce duplication.

The FlowgladProviderWrapper and Navbar pattern is repeated for both protected routes. Extracting a layout component would improve maintainability.

Suggested refactor
function ProtectedLayout({ children }: { children: React.ReactNode }) {
  return (
    <ProtectedRoute>
      <FlowgladProviderWrapper>
        <Navbar />
        {children}
      </FlowgladProviderWrapper>
    </ProtectedRoute>
  );
}

// Then in routes:
<Route path="/" element={<ProtectedLayout><HomePage /></ProtectedLayout>} />
<Route path="/pricing" element={<ProtectedLayout><PricingPage /></ProtectedLayout>} />
react-vite/generation-based-subscription/src/main.tsx (1)

4-4: Remove file extension from import.

While Vite handles .tsx extensions in imports, the convention is to omit them. This also improves portability if the build setup changes.

Suggested fix
-import App from './App.tsx';
+import App from './App';
react-vite/generation-based-subscription/src/pages/sign-up.tsx (3)

2-3: Consolidate imports from same module.

Both useNavigate and Link are imported from react-router-dom in separate statements.

Suggested fix
-import { useNavigate } from 'react-router-dom';
-import { Link } from 'react-router-dom';
+import { useNavigate, Link } from 'react-router-dom';

15-28: Use try/finally to ensure loading state is always reset.

If authClient.signUp.email throws an unexpected error (e.g., network failure not handled by onError), the loading state will remain true. Using try/finally ensures consistent cleanup.

Suggested fix
   async function onSubmit(e: React.FormEvent) {
     e.preventDefault();
     setLoading(true);
     setError(null);
-    await authClient.signUp.email(
-      { name, email, password, callbackURL: '/' },
-      {
-        onError: (ctx) => setError(ctx.error.message),
-        onSuccess: () => navigate('/'),
-        onRequest: () => {},
-      }
-    );
-    setLoading(false);
+    try {
+      await authClient.signUp.email(
+        { name, email, password, callbackURL: '/' },
+        {
+          onError: (ctx) => setError(ctx.error.message),
+          onSuccess: () => navigate('/'),
+        }
+      );
+    } finally {
+      setLoading(false);
+    }
   }

34-39: Consider adding required attribute to name field.

The email and password fields have required, but the name field does not. If the backend requires a name, the form should enforce it client-side for better UX.

         <input
           value={name}
           onChange={(e) => setName(e.target.value)}
           placeholder="Name"
           className="w-full rounded border px-3 py-2"
+          required
         />
react-vite/generation-based-subscription/server/index.ts (1)

302-317: Consider extracting shared helper to avoid duplication.

This findUsagePriceByMeterSlug function duplicates the implementation in src/lib/billing-helpers.ts (lines 71-90). Consider moving this to a shared location (e.g., a common lib/ directory accessible by both server and client) to avoid drift between the two implementations.

Suggested approach

Create a shared module, for example shared/billing-utils.ts, and import from both the server and client code:

// shared/billing-utils.ts
export function findUsagePriceByMeterSlug(usageMeterSlug: string, pricingModel: any) {
  // ... single implementation
}

Then update imports in both files to use the shared module.

react-vite/generation-based-subscription/src/pages/home.tsx (2)

59-84: Consider extracting checkout reload logic into a shared hook.

This reload-after-checkout pattern is duplicated in pricing.tsx. Extracting it into a custom hook (e.g., useCheckoutReload) would improve maintainability and ensure consistent behavior across pages.

Example hook extraction
// src/hooks/useCheckoutReload.ts
export function useCheckoutReload(billing: ReturnType<typeof useBilling>) {
  const [isReloading, setIsReloading] = useState(false);
  const [hasReloaded, setHasReloaded] = useState(false);

  useEffect(() => {
    const reloadAfterCheckout = async () => {
      const urlParams = new URLSearchParams(window.location.search);
      const shouldReload = urlParams.has('checkout') || urlParams.has('session_id') || urlParams.has('success');
      
      if (shouldReload && !hasReloaded && typeof billing.reload === 'function' && billing.loaded && !isReloading) {
        setIsReloading(true);
        try {
          await billing.reload();
          setHasReloaded(true);
        } catch (error) {
          console.error('Error reloading billing data:', error);
        } finally {
          setIsReloading(false);
          window.history.replaceState({}, '', window.location.pathname);
        }
      }
    };
    reloadAfterCheckout();
  }, [billing.reload, billing.loaded, isReloading, hasReloaded]);

  return { isReloading, hasReloaded };
}

335-371: The async keyword is unnecessary.

handleGenerateRelaxImage and handleGenerateRelaxSDVideo are marked async but contain no await expressions. While harmless, removing async clarifies that these are synchronous operations.

Suggested fix
-  const handleGenerateRelaxImage = async () => {
+  const handleGenerateRelaxImage = () => {
     if (!hasRelaxMode) {
       return;
     }
     // ...
   };

-  const handleGenerateRelaxSDVideo = async () => {
+  const handleGenerateRelaxSDVideo = () => {
     if (!hasUnlimitedRelaxedSDVideo) {
       return;
     }
     // ...
   };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6b2fd and 5e4a93d.

⛔ Files ignored due to path filters (1)
  • react-vite/generation-based-subscription/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • react-vite/generation-based-subscription/.env.example
  • react-vite/generation-based-subscription/drizzle.config.ts
  • react-vite/generation-based-subscription/eslint.config.ts
  • react-vite/generation-based-subscription/package.json
  • react-vite/generation-based-subscription/postcss.config.ts
  • react-vite/generation-based-subscription/server/db/client.ts
  • react-vite/generation-based-subscription/server/db/schema.ts
  • react-vite/generation-based-subscription/server/index.ts
  • react-vite/generation-based-subscription/server/lib/auth.ts
  • react-vite/generation-based-subscription/src/App.tsx
  • react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx
  • react-vite/generation-based-subscription/src/components/navbar.tsx
  • react-vite/generation-based-subscription/src/components/pricing-card.tsx
  • react-vite/generation-based-subscription/src/components/pricing-cards-grid.tsx
  • react-vite/generation-based-subscription/src/components/providers.tsx
  • react-vite/generation-based-subscription/src/components/ui/badge.tsx
  • react-vite/generation-based-subscription/src/components/ui/button.tsx
  • react-vite/generation-based-subscription/src/components/ui/card.tsx
  • react-vite/generation-based-subscription/src/components/ui/carousel.tsx
  • react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx
  • react-vite/generation-based-subscription/src/components/ui/progress.tsx
  • react-vite/generation-based-subscription/src/components/ui/skeleton.tsx
  • react-vite/generation-based-subscription/src/components/ui/tooltip.tsx
  • react-vite/generation-based-subscription/src/hooks/use-mobile.ts
  • react-vite/generation-based-subscription/src/lib/auth-client.ts
  • react-vite/generation-based-subscription/src/lib/billing-helpers.ts
  • react-vite/generation-based-subscription/src/lib/utils.ts
  • react-vite/generation-based-subscription/src/main.tsx
  • react-vite/generation-based-subscription/src/pages/home.tsx
  • react-vite/generation-based-subscription/src/pages/pricing.tsx
  • react-vite/generation-based-subscription/src/pages/sign-in.tsx
  • react-vite/generation-based-subscription/src/pages/sign-up.tsx
  • react-vite/generation-based-subscription/src/vite-env.d.ts
  • react-vite/generation-based-subscription/tailwind.config.ts
  • react-vite/generation-based-subscription/tsconfig.json
  • react-vite/generation-based-subscription/vite.config.ts
✅ Files skipped from review due to trivial changes (2)
  • react-vite/generation-based-subscription/postcss.config.ts
  • react-vite/generation-based-subscription/tsconfig.json
🧰 Additional context used
🧬 Code graph analysis (16)
react-vite/generation-based-subscription/server/lib/auth.ts (2)
react-vite/generation-based-subscription/server/db/client.ts (1)
  • db (20-20)
react-vite/generation-based-subscription/server/db/schema.ts (1)
  • betterAuthSchema (62-67)
react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx (2)
react-vite/generation-based-subscription/src/components/ui/card.tsx (3)
  • Card (72-72)
  • CardHeader (72-72)
  • CardContent (72-72)
react-vite/generation-based-subscription/src/components/ui/skeleton.tsx (1)
  • Skeleton (15-15)
react-vite/generation-based-subscription/src/components/ui/badge.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/pages/pricing.tsx (1)
react-vite/generation-based-subscription/src/components/pricing-cards-grid.tsx (1)
  • PricingCardsGrid (25-320)
react-vite/generation-based-subscription/src/components/ui/skeleton.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/progress.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/pages/sign-up.tsx (1)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/ui/card.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/pages/sign-in.tsx (2)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/ui/button.tsx (1)
  • Button (44-44)
react-vite/generation-based-subscription/src/pages/home.tsx (3)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx (1)
  • DashboardSkeleton (4-68)
react-vite/generation-based-subscription/src/lib/billing-helpers.ts (1)
  • computeUsageTotal (9-41)
react-vite/generation-based-subscription/server/index.ts (3)
react-vite/generation-based-subscription/server/db/client.ts (1)
  • db (20-20)
react-vite/generation-based-subscription/server/db/schema.ts (1)
  • users (3-15)
react-vite/generation-based-subscription/src/lib/billing-helpers.ts (1)
  • findUsagePriceByMeterSlug (72-91)
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/App.tsx (8)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/ui/tooltip.tsx (1)
  • TooltipProvider (102-102)
react-vite/generation-based-subscription/src/pages/sign-in.tsx (1)
  • SignInPage (7-66)
react-vite/generation-based-subscription/src/pages/sign-up.tsx (1)
  • SignUpPage (7-73)
react-vite/generation-based-subscription/src/components/providers.tsx (1)
  • FlowgladProviderWrapper (4-22)
react-vite/generation-based-subscription/src/components/navbar.tsx (1)
  • Navbar (20-247)
react-vite/generation-based-subscription/src/pages/home.tsx (1)
  • HomePage (32-765)
react-vite/generation-based-subscription/src/pages/pricing.tsx (1)
  • PricingPage (5-65)
react-vite/generation-based-subscription/src/components/providers.tsx (1)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/ui/tooltip.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/button.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
🪛 dotenv-linter (4.0.0)
react-vite/generation-based-subscription/.env.example

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

(QuoteCharacter)

🔇 Additional comments (45)
react-vite/generation-based-subscription/eslint.config.ts (1)

7-14: Configuration structure looks good for a Vite + React project.

The flat config setup with globalIgnores, appropriate file targeting, and extension of js.configs.recommended, reactHooks.configs.flat.recommended, and reactRefresh.configs.vite is well-structured for this stack.

react-vite/generation-based-subscription/server/lib/auth.ts (1)

16-33: The Better Auth configuration is correct for this architecture. The backend's baseURL and trustedOrigins properly point to the frontend origin because the Vite development proxy transparently forwards /api/* requests from localhost:5173 to localhost:3001. From the backend's perspective, the "public URL where Better Auth is served" is the frontend URL (what the browser sees), making baseURL: VITE_APP_URL the correct setting. Callbacks and cookies will work properly since they're scoped to the origin the browser actually requests (localhost:5173), which is also correctly listed in trustedOrigins. This architecture is documented in the auth-client comment and matches Better Auth's expectations.

Likely an incorrect or invalid review comment.

react-vite/generation-based-subscription/src/components/ui/button.tsx (1)

1-44: Well-structured Button component with proper React patterns.

Good use of forwardRef for ref forwarding, displayName for debugging, and type-safe variant/size props via keyof typeof. The component follows common UI library patterns (similar to shadcn/ui).

react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx (1)

4-68: Clean skeleton implementation that mirrors the dashboard structure.

The skeleton properly reflects the expected dashboard layout with appropriate placeholder dimensions. The component provides good UX during loading states.

react-vite/generation-based-subscription/src/components/providers.tsx (1)

5-21: LGTM!

The session-aware billing loading logic is well-implemented. Only enabling loadBilling when authentication is confirmed (!isPending && !!session?.user) prevents unnecessary billing API calls for unauthenticated users, and withCredentials: true correctly enables cookie-based authentication.

react-vite/generation-based-subscription/tailwind.config.ts (1)

1-52: LGTM!

This is a well-structured Tailwind configuration following the shadcn/ui convention with CSS variable-based theming. The content paths are comprehensive, and the color/radius extensions properly reference CSS custom properties for consistent theming.

react-vite/generation-based-subscription/src/components/ui/badge.tsx (1)

1-28: LGTM!

The Badge component is well-implemented with proper TypeScript typing, variant support via keyof typeof badgeVariants, and includes appropriate focus states for accessibility. The className composition with cn() follows the established pattern.

react-vite/generation-based-subscription/src/components/ui/progress.tsx (1)

8-36: LGTM!

The Progress component is well-implemented with proper accessibility support including role="progressbar" and all relevant ARIA attributes. The value clamping to [0, 100] ensures valid percentage calculations. Good use of forwardRef for ref forwarding.

react-vite/generation-based-subscription/src/components/ui/tooltip.tsx (2)

29-38: Clean state scoping for tooltip instances.

Local open state is correctly isolated per Tooltip while still inheriting provider context.


81-98: Tooltip content rendering looks good.

Conditional rendering and positioning styles are clear and consistent with the expected overlay behavior.

react-vite/generation-based-subscription/src/components/ui/carousel.tsx (2)

22-28: Good guard for Carousel context usage.

The hook enforces correct usage and keeps the consumer API clean.


136-176: Carousel layout primitives are clear and flexible.

Content and item wrappers are cleanly composed and adapt well to orientation.

react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx (3)

15-35: Solid click-outside handling and context setup.

State wiring and cleanup are straightforward and correct.


75-97: Content rendering and alignment look good.

The alignment handling and class composition are consistent and clear.


142-159: Label and separator primitives look good.

Simple, reusable, and consistent with the styling approach.

react-vite/generation-based-subscription/src/components/navbar.tsx (4)

36-39: Sign-out flow is clear and direct.

The async sign-out and navigation sequence is straightforward.


41-85: Cancellation flow is robust.

The confirmation prompt, loading state, error handling, and reload path are well composed.


87-128: Uncancel flow mirrors cancel logic well.

Good consistency with cancellation handling and state resets.


32-34: No action needed. The existing if (billing.errors) check is correct—it properly handles the case where billing.errors is either null (no error, navbar renders) or an error value (error exists, navbar returns null). The concern about array defaults doesn't apply to this codebase.

react-vite/generation-based-subscription/src/lib/billing-helpers.ts (3)

1-41: Usage total computation is straightforward and defensive.

Early returns and error handling keep it resilient to partial data.


50-63: Meter lookup helper is clean and predictable.

Returning normalized id/slug strings is a good touch.


100-109: Default plan lookup is clear and efficient.

The early-return pattern keeps the intent easy to follow.

react-vite/generation-based-subscription/src/components/ui/card.tsx (6)

4-16: LGTM — base Card primitive is clean and consistent.


18-27: LGTM — header wrapper matches the design system conventions.


29-41: LGTM — semantic title + typography classes look good.


43-52: LGTM — description styling is consistent and clear.


54-59: LGTM — content wrapper is concise and consistent.


61-70: LGTM — footer layout is aligned with the rest of the card.

react-vite/generation-based-subscription/src/components/pricing-card.tsx (2)

17-24: LGTM — plan contract is clear and minimal.


35-174: LGTM — checkout flow and UI composition are solid.

react-vite/generation-based-subscription/src/pages/sign-in.tsx (1)

14-27: The current implementation is correct. authClient.signIn.email from better-auth does not throw or reject — it handles errors via the onError callback (already in place). Since await will always complete, setLoading(false) is guaranteed to run regardless of whether authentication succeeds or fails. No changes are needed.

react-vite/generation-based-subscription/src/App.tsx (1)

33-69: Overall routing structure looks good.

The App component correctly sets up:

  • Public routes for authentication (/sign-in, /sign-up)
  • Protected routes for authenticated content
  • A catch-all redirect to home
  • Proper provider hierarchy with TooltipProvider at the top
react-vite/generation-based-subscription/server/db/client.ts (2)

5-7: Good practice: fail-fast validation.

Validating DATABASE_URL at module load prevents cryptic runtime errors later.


9-18: Connection pool caching pattern is correct for dev environments.

The global caching prevents connection exhaustion during hot module reloads. For production use, consider adding pool configuration options (e.g., max, idleTimeoutMillis) to tune connection behavior.

Optional: Add pool configuration for production
 const pool =
   (global as any).__db_pool__ ??
   new Pool({
     connectionString: process.env.DATABASE_URL,
+    max: 20, // adjust based on expected load
+    idleTimeoutMillis: 30000,
   });
react-vite/generation-based-subscription/src/main.tsx (2)

31-51: ErrorBoundary implementation is solid.

The class-based error boundary correctly implements getDerivedStateFromError for state derivation and componentDidCatch for logging. The fallback UI provides a clear error message and recovery option.


53-64: Root element validation and render setup look good.

Failing fast when the root element is missing prevents confusing errors, and the render hierarchy (StrictMode → ErrorBoundary → App) is correctly ordered.

react-vite/generation-based-subscription/server/db/schema.ts (3)

48-49: Same observation: accounts timestamps lack defaultNow().

Consistent with the sessions table, but different from users/verifications. Ensure this matches BetterAuth's expected schema behavior.


3-15: Users table schema is well-structured.

Good practices observed:

  • Unique constraint on email
  • Proper default for emailVerified
  • Timestamps with timezone and defaultNow()

61-67: Clean schema export for BetterAuth integration.

Aggregating tables into betterAuthSchema provides a convenient single export for the auth library configuration.

react-vite/generation-based-subscription/src/pages/pricing.tsx (1)

1-65: LGTM!

The component properly handles the post-checkout billing reload flow with appropriate guards against duplicate reloads, error handling, and URL cleanup. The separation of concerns with PricingCardsGrid keeps this component focused on the reload orchestration.

react-vite/generation-based-subscription/server/index.ts (3)

11-25: LGTM!

Environment variable loading and validation are correctly configured with .env.local preference and clear error messages on missing required secrets.


66-140: LGTM!

The session extraction middleware and Flowglad factory are well-structured with appropriate error handling and fallbacks. The factory pattern cleanly creates per-customer Flowglad instances.


233-300: LGTM!

The /api/usage-events endpoint has proper authentication checks, input validation, and comprehensive error handling with appropriate HTTP status codes.

react-vite/generation-based-subscription/src/pages/home.tsx (2)

240-258: Good optimistic UI pattern.

The manual usage adjustment approach with reset-on-successful-reload provides responsive feedback while maintaining eventual consistency with server state.


453-765: LGTM!

The JSX structure is well-organized with clear sections for generation actions, relax mode, top-ups, and usage meters. Tooltip feedback for disabled states provides good UX, and the loading/generating states are properly handled.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread react-vite/generation-based-subscription/.env.example Outdated
Comment thread react-vite/generation-based-subscription/drizzle.config.ts
Comment thread react-vite/generation-based-subscription/package.json
Comment on lines +21 to +22
createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "schema.ts" -o -name "*schema*" | head -20

Repository: flowglad/examples

Length of output: 420


🏁 Script executed:

cat -n react-vite/generation-based-subscription/server/db/schema.ts

Repository: flowglad/examples

Length of output: 2824


🌐 Web query:

BetterAuth Drizzle ORM schema sessions table timestamp configuration

💡 Result:

Short answer and recommended Drizzle schema + notes:

  • Use Drizzle's timestamp columns with defaultNow() (and timestamptz if you prefer timezone-aware) and map BetterAuth session fields to your column names in the BetterAuth config. Example fields BetterAuth expects: id, token (optional in some versions), userId, expiresAt, createdAt, updatedAt. [1][2]

Example Drizzle (pg-core) sessions table:

import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const sessions = pgTable("sessions", {
  id: text("id").primaryKey(),
  token: text("token").notNull().unique(), // if your BetterAuth version expects token
  userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
  expiresAt: timestamp("expires_at").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
  ipAddress: text("ip_address"),
  userAgent: text("user_agent"),
});
  • If you want timezone-aware timestamps, use timestamptz in Postgres (Drizzle's timestamp maps to timestamptz for pg by default in many setups) and defaultNow() for auto-set createdAt/updatedAt. [2]

  • Configure BetterAuth mapping to match your column names. Example:

    session: {
    modelName: "sessions",
    fields: {
    id: "id",
    token: "token",
    userId: "user_id",
    expiresAt: "expires_at",
    createdAt: "created_at",
    updatedAt: "updated_at",
    }
    }

  • Caveat: recent BetterAuth releases have had issues around the sessions token/id mapping and generation (session creation can fail if adapter/version expects a token field or different id behavior). If you see session creation errors, check the adapter version and the GitHub issues for workarounds (some users needed to include token or toggle generateId). [3]

Sources:

  • BetterAuth database schema docs. [1]
  • Drizzle schema examples showing timestamp + defaultNow() usage. [2]
  • BetterAuth GitHub issue about sessions token/id mapping and generateId problems. [3]

Add defaultNow() to sessions and accounts timestamps.

The sessions and accounts tables are missing .defaultNow() on createdAt and updatedAt fields (lines 21–22 and 48–49). This inconsistency forces the application to manually provide these values on every insert. BetterAuth's recommended schema includes .defaultNow() for both fields, matching the pattern already used in the users and verifications tables. Add the defaults to both tables for consistency and reduced boilerplate.

🤖 Prompt for AI Agents
In `@react-vite/generation-based-subscription/server/db/schema.ts` around lines 21
- 22, In schema.ts update the sessions and accounts table timestamp definitions
so their createdAt and updatedAt columns use the same defaultNow() pattern as
users and verifications; locate the timestamp(...) calls for createdAt and
updatedAt in the sessions and accounts table builders and append .defaultNow()
to each timestamp('created_at', { withTimezone: true }) and
timestamp('updated_at', { withTimezone: true }) expression so inserts no longer
require manually provided timestamps.

Comment thread react-vite/generation-based-subscription/src/components/navbar.tsx Outdated
Comment thread react-vite/generation-based-subscription/src/lib/utils.ts Outdated
Comment thread react-vite/generation-based-subscription/src/pages/home.tsx Outdated
Comment thread react-vite/generation-based-subscription/src/pages/home.tsx
Comment on lines +3 to +8
declare global {
interface ImportMetaEnv {
readonly VITE_APP_URL?: string;
// Add other VITE_ prefixed env variables here as needed
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Vite ImportMetaEnv TypeScript type declaration pattern

💡 Result:

Use a global declaration file that augments Vite's ImportMetaEnv and ImportMeta types (no imports in that file). Put it in src/ (e.g. src/vite-env.d.ts) and reference vite/client before your overrides. Example:

src/vite-env.d.ts

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_FEATURE_FLAG?: 'on' | 'off'
  // add more VITE_* keys here
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Notes

  • Vite already provides base types in vite/client; your file augments them. [1][2]
  • Do not include import/export statements in this .d.ts (that will turn it into a module and prevent augmentation). If you use tsconfig.compilerOptions.types, ensure the file is included. [2][3]

References

  • Vite — Env Variables and Modes (IntelliSense for TypeScript). [1]
  • Vite — Features / Client Types (triple-slash reference and overrides). [2]
  • Vite v3 docs (same guidance + warning about imports). [3]

Remove declare global wrapper and export {} statement — they break Vite's type augmentation.

According to official Vite documentation, environment type declarations must be ambient (not module declarations). The export {} statement converts this file into a module, which prevents global augmentation from working. Use the standard pattern instead:

Correct pattern
 /// <reference types="vite/client" />
 
-declare global {
-  interface ImportMetaEnv {
-    readonly VITE_APP_URL?: string;
-    // Add other VITE_ prefixed env variables here as needed
-  }
+interface ImportMetaEnv {
+  readonly VITE_APP_URL?: string;
+  // Add other VITE_ prefixed env variables here as needed
 }
 
-export {};
+interface ImportMeta {
+  readonly env: ImportMetaEnv;
+}

Remove all import/export statements to keep this as an ambient declaration file.

🤖 Prompt for AI Agents
In `@react-vite/generation-based-subscription/src/vite-env.d.ts` around lines 3 -
8, The current file wraps the ImportMetaEnv interface in a "declare global"
module and likely contains an "export {}" which turns the file into a module and
prevents Vite's ambient augmentation; remove the "declare global" wrapper and
any export statements so the file is an ambient declaration and simply declares
the ImportMetaEnv interface (refer to the ImportMetaEnv identifier) with your
VITE_ variables to allow Vite to augment import.meta.env correctly.

Comment on lines +1 to +21
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
// Proxy API requests to Express backend
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
},
},
},
resolve: {
alias: {
'@': '/src',
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Vite alias configuration best practices fileURLToPath import.meta.url

💡 Result:

Here are concise best practices for configuring Vite path aliases when using import.meta.url / fileURLToPath, plus a minimal working example.

Best practices

  • Define aliases as absolute filesystem paths (not relative strings) so Vite + Rollup resolve the same path in dev and build. Use fileURLToPath(new URL(..., import.meta.url)) to get a stable absolute path in an ESM vite.config.ts. [1][2]
  • Keep TS/IDE aware of the same aliases: add matching paths in tsconfig.json (compilerOptions.paths) or use vite-tsconfig-paths so type checking and editors match runtime imports. [2]
  • Prefer a single source-of-truth pattern in vite.config.ts (compute root once, reuse for aliases) to avoid mismatch between environments. [2]
  • Avoid mixing alias names with node/third-party package names (use prefix like @ or #) to prevent resolver conflicts. [1]
  • If using import.meta.url-based URL imports for assets, ensure esbuild/build target supports import.meta.url (ES2020+); Vite will transform static string URLs but dynamic values may break. Use new URL('./asset.png', import.meta.url) only for static strings. [3]

Minimal example (vite.config.ts)

  • Use ESM imports from node:url and convert to filesystem path:

    import { defineConfig } from 'vite'
    import { fileURLToPath, URL } from 'node:url'
    import vue from '@vitejs/plugin-vue'

    const srcPath = fileURLToPath(new URL('./src', import.meta.url))

    export default defineConfig({
    plugins: [vue()],
    resolve: {
    alias: {
    '@': srcPath,
    '@components': fileURLToPath(new URL('./src/components', import.meta.url))
    }
    }
    })

Notes

  • Add matching tsconfig.json paths (or use vite-tsconfig-paths) to keep TypeScript/editor tooling in sync. [2]
  • If you must support CommonJS config files, use path.resolve(__dirname, ...) in that case, but prefer ESM config and import.meta.url for consistency. [1][2]

Sources

  • How to configure Vite aliases with fileURLToPath + import.meta.url (examples & explanation). [1]
  • Vite discussion / examples showing resolve.alias with fileURLToPath(new URL(..., import.meta.url)) and the need to sync tsconfig paths. [2]
  • MDN docs for import.meta.url / new URL(..., import.meta.url) and caveats for static vs dynamic URLs. [3]

🏁 Script executed:

# Check the project structure to verify if the alias path is correct
find react-vite/generation-based-subscription -type d -name src | head -20
ls -la react-vite/generation-based-subscription/ 2>/dev/null | head -20

Repository: flowglad/examples

Length of output: 1299


🏁 Script executed:

# Check tsconfig.json for path configuration
cat react-vite/generation-based-subscription/tsconfig.json

Repository: flowglad/examples

Length of output: 621


🏁 Script executed:

# Check if there are any imports using the @ alias in the codebase
rg '@/' react-vite/generation-based-subscription/src --type ts --type tsx -c

Repository: flowglad/examples

Length of output: 88


🏁 Script executed:

# Check if there are any imports using the @ alias
rg '@/' react-vite/generation-based-subscription/src -A 2 | head -50

Repository: flowglad/examples

Length of output: 43


🏁 Script executed:

# Check vite.config.ts more closely and see if tsconfig has path mappings
cat react-vite/generation-based-subscription/vite.config.ts

Repository: flowglad/examples

Length of output: 479


🏁 Script executed:

# Check if the src directory structure would work with the alias
find react-vite/generation-based-subscription/src -type f \( -name "*.ts" -o -name "*.jsx" -o -name "*.js" \) | head -20

Repository: flowglad/examples

Length of output: 374


Fix @ alias path and add TypeScript configuration.

The current alias '@': '/src' is broken—it resolves to the filesystem root /src, not your project's src directory. Additionally, TypeScript needs matching path configuration in tsconfig.json for IDE and type checking to work correctly.

Update vite.config.ts:

 import { defineConfig } from 'vite'
+import { fileURLToPath, URL } from 'node:url'
 import react from '@vitejs/plugin-react'

 export default defineConfig({
   plugins: [react()],
   server: {
     proxy: {
       '/api': {
         target: 'http://localhost:3001',
         changeOrigin: true,
         secure: false,
       },
     },
   },
   resolve: {
     alias: {
-      '@': '/src',
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
     },
   },
 })

Update tsconfig.json to add path mappings:

   "compilerOptions": {
     "target": "ES2020",
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    },
📝 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 { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
// Proxy API requests to Express backend
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
},
},
},
resolve: {
alias: {
'@': '/src',
},
},
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
// Proxy API requests to Express backend
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false,
},
},
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
🤖 Prompt for AI Agents
In `@react-vite/generation-based-subscription/vite.config.ts` around lines 1 - 21,
The '@' alias in the Vite config is incorrect: update the resolve.alias entry in
the export of defineConfig to map '@' to the project src directory using a
relative path (e.g., '@/': '/src' replacement with the correct './src' or path
resolved via new URL or path.resolve) so imports using '@' resolve to the
project's src; then add matching TypeScript paths in tsconfig.json by adding a
"paths" mapping under "compilerOptions" mapping "@/*" to ["src/*"] (and ensure
"baseUrl" is set, e.g., "."), so IDEs and the TypeScript compiler honor the same
alias.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/.env.example`:
- Line 17: The BETTER_AUTH_SECRET entry in the .env example uses quotes which is
inconsistent and may break some env parsers; update the BETTER_AUTH_SECRET key
(BETTER_AUTH_SECRET) to remove the surrounding double quotes so its value is
unquoted, matching other entries like DATABASE_URL and VITE_APP_URL.

In `@react-vite/generation-based-subscription/src/lib/billing-helpers.ts`:
- Around line 69-76: The loop computing total from featureItems can produce NaN
when item.amount is undefined; in the code around the total variable and the
loop over featureItems (using usageMeterById, meterSlug, and usageMeterSlug),
guard the addition by validating item.amount is a number or defaulting to 0
before adding (e.g., compute const amt = typeof item.amount === 'number' ?
item.amount : 0 and use total += amt); ensure you reference the same identifiers
(featureItems, usageMeterById, meterSlug, usageMeterSlug) when updating the
logic so undefined amounts no longer corrupt total.
♻️ Duplicate comments (1)
react-vite/generation-based-subscription/src/lib/billing-helpers.ts (1)

123-136: Normalize meter IDs/slugs to strings during lookup.

This still compares raw IDs/slugs and can miss matches when values are numbers vs strings. Normalize all sides to string for consistent comparisons.

🛠️ Proposed fix
-  const meterIdBySlug = new Map(
-    pricingModel.usageMeters.map((meter: UsageMeter) => [meter.slug, meter.id])
-  );
-
-  const usageMeterId = meterIdBySlug.get(usageMeterSlug);
+  const meterIdBySlug = new Map(
+    pricingModel.usageMeters.map((meter: UsageMeter) => [
+      String(meter.slug),
+      String(meter.id),
+    ])
+  );
+
+  const usageMeterId = meterIdBySlug.get(String(usageMeterSlug));
   if (!usageMeterId) return null;

   // Find price by meter ID
   const usagePrice = pricingModel.products
     .flatMap((product: Product) => product.prices ?? [])
     .find(
-      (price: Price) => price.type === 'usage' && price.usageMeterId === usageMeterId
+      (price: Price) =>
+        price.type === 'usage' && String(price.usageMeterId) === usageMeterId
     );
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e4a93d and 718f84d.

⛔ Files ignored due to path filters (1)
  • react-vite/generation-based-subscription/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • react-vite/generation-based-subscription/.env.example
  • react-vite/generation-based-subscription/drizzle.config.ts
  • react-vite/generation-based-subscription/package.json
  • react-vite/generation-based-subscription/src/App.tsx
  • react-vite/generation-based-subscription/src/components/providers.tsx
  • react-vite/generation-based-subscription/src/hooks/use-mobile.ts
  • react-vite/generation-based-subscription/src/lib/billing-helpers.ts
  • react-vite/generation-based-subscription/src/lib/utils.ts
  • react-vite/generation-based-subscription/src/main.tsx
  • react-vite/generation-based-subscription/src/pages/sign-up.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • react-vite/generation-based-subscription/src/main.tsx
  • react-vite/generation-based-subscription/src/pages/sign-up.tsx
  • react-vite/generation-based-subscription/src/hooks/use-mobile.ts
  • react-vite/generation-based-subscription/package.json
  • react-vite/generation-based-subscription/src/components/providers.tsx
  • react-vite/generation-based-subscription/src/App.tsx
  • react-vite/generation-based-subscription/drizzle.config.ts
  • react-vite/generation-based-subscription/src/lib/utils.ts
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
react-vite/generation-based-subscription/.env.example

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

(QuoteCharacter)

🔇 Additional comments (4)
react-vite/generation-based-subscription/.env.example (4)

1-4: LGTM!

The database configuration is well-documented with a clear example connection string format.


6-7: LGTM!

The frontend URL is correctly configured without quotes.


9-12: LGTM!

The server port is correctly configured as a numeric value.


19-22: LGTM!

The Flowglad configuration is well-documented with a helpful link to obtain the secret key.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread react-vite/generation-based-subscription/.env.example Outdated
Comment thread react-vite/generation-based-subscription/src/lib/billing-helpers.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/src/components/ui/carousel.tsx`:
- Around line 39-132: The keyboard handler handleKeyDown only handles
ArrowLeft/ArrowRight and misses vertical behavior and the orientation
dependency; update handleKeyDown (used in the Carousel React.forwardRef) to
check orientation === 'vertical' and map ArrowUp/ArrowDown to
scrollPrev/scrollNext for vertical carousels (keep ArrowLeft/ArrowRight for
horizontal), call event.preventDefault() before scrolling, and add orientation
to the useCallback dependency array so the handler updates when orientation
changes.

In `@react-vite/generation-based-subscription/src/pages/home.tsx`:
- Around line 87-102: The useEffect block refetching billing data calls
billing.reload() without handling its returned Promise; update the effect so the
promise is handled (either make the effect callback call an async IIFE and await
billing.reload(), or call billing.reload().catch(err => processLogger/error
handler)), referencing previousUserIdRef and billing.reload to locate the code;
ensure you preserve the branch that sets previousUserIdRef.current and mirror
the error handling pattern used in the checkout reload effect (log or handle the
error) to avoid unhandled promise rejections.
♻️ Duplicate comments (4)
react-vite/generation-based-subscription/server/db/schema.ts (1)

48-49: Add defaults for account timestamps.

Lines 48-49 are notNull() without defaultNow(), so inserts must always provide values. This is inconsistent with users/sessions and can break if the adapter doesn’t supply timestamps.

🛠️ Proposed fix
-  createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
-  updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),
+  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
BetterAuth Drizzle ORM accounts table createdAt updatedAt defaultNow recommended
react-vite/generation-based-subscription/src/pages/home.tsx (2)

256-258: Avoid silently swallowing errors.

Empty catch block here matches the pattern previously flagged at lines 71-73. For consistency and debugging, add a log message.

Suggested fix
         } catch {
-          // Keep manual adjustments if reload fails
+          // Keep manual adjustments if reload fails
+          console.error('Error reloading billing data after fast image generation');
         }

321-323: Avoid silently swallowing errors.

Same pattern as noted above. Consider adding a log message for consistency.

Suggested fix
         } catch {
-          // Keep manual adjustments if reload fails
+          // Keep manual adjustments if reload fails
+          console.error('Error reloading billing data after HD video generation');
         }
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx (1)

127-165: Make menu items keyboard-accessible (use <button>).
Items are <div>s, which are not focusable or keyboard-activatable by default. This blocks keyboard navigation.

✅ Accessible fix
-interface DropdownMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
+interface DropdownMenuItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
   onSelect?: () => void;
   disabled?: boolean;
   variant?: 'default' | 'destructive';
   children: React.ReactNode;
 }
 
-const DropdownMenuItem = React.forwardRef<HTMLDivElement, DropdownMenuItemProps>(
+const DropdownMenuItem = React.forwardRef<HTMLButtonElement, DropdownMenuItemProps>(
   ({ className, onSelect, disabled, variant, children, ...props }, ref) => {
     const context = React.useContext(DropdownMenuContext);
     if (!context) throw new Error('DropdownMenuItem must be used within DropdownMenu');
     const { setOpen } = context;
@@
     return (
-      <div
+      <button
         ref={ref}
+        type="button"
+        role="menuitem"
         className={cn(
           'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
           'focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground',
           disabled && 'pointer-events-none opacity-50',
           variant === 'destructive' && 'text-destructive focus:text-destructive',
           className
         )}
+        disabled={disabled}
         onClick={handleClick}
         {...props}
       >
         {children}
-      </div>
+      </button>
     );
   }
 );
🧹 Nitpick comments (3)
react-vite/generation-based-subscription/server/db/schema.ts (1)

57-58: Make verification table timestamps non-null for consistency with other tables.

Lines 57-58 in the verifications table lack .notNull(), while all other tables (users, sessions, accounts) enforce this constraint on their createdAt and updatedAt fields. Add .notNull() to maintain consistent data integrity across the schema.

♻️ Suggested change
-  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
-  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
+  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
+  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
react-vite/generation-based-subscription/src/pages/home.tsx (2)

336-353: Consider removing unnecessary async keyword.

This function contains no await statements. The async keyword is unnecessary and could mislead readers into expecting asynchronous operations.

Suggested fix
-  const handleGenerateRelaxImage = async () => {
+  const handleGenerateRelaxImage = () => {
     if (!hasRelaxMode) {
       return;
     }

     setIsGeneratingRelaxImage(true);

-    try {
-      const nextIndex = (currentImageIndex + 1) % mockImages.length;
-      setCurrentImageIndex(nextIndex);
-      const nextImage = mockImages[nextIndex];
-      if (nextImage) {
-        setDisplayedContent(nextImage);
-      }
-    } finally {
-      setIsGeneratingRelaxImage(false);
+    const nextIndex = (currentImageIndex + 1) % mockImages.length;
+    setCurrentImageIndex(nextIndex);
+    const nextImage = mockImages[nextIndex];
+    if (nextImage) {
+      setDisplayedContent(nextImage);
     }
+    setIsGeneratingRelaxImage(false);
   };

355-372: Consider removing unnecessary async keyword.

Same as handleGenerateRelaxImage above—no await is used.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 718f84d and d898f4c.

📒 Files selected for processing (11)
  • react-vite/generation-based-subscription/.env.example
  • react-vite/generation-based-subscription/server/db/schema.ts
  • react-vite/generation-based-subscription/src/components/navbar.tsx
  • react-vite/generation-based-subscription/src/components/ui/carousel.tsx
  • react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx
  • react-vite/generation-based-subscription/src/lib/billing-helpers.ts
  • react-vite/generation-based-subscription/src/lib/utils.ts
  • react-vite/generation-based-subscription/src/pages/home.tsx
  • react-vite/generation-based-subscription/src/vite-env.d.ts
  • react-vite/generation-based-subscription/tsconfig.json
  • react-vite/generation-based-subscription/vite.config.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • react-vite/generation-based-subscription/src/vite-env.d.ts
  • react-vite/generation-based-subscription/src/components/navbar.tsx
  • react-vite/generation-based-subscription/tsconfig.json
  • react-vite/generation-based-subscription/vite.config.ts
  • react-vite/generation-based-subscription/src/lib/billing-helpers.ts
  • react-vite/generation-based-subscription/.env.example
🧰 Additional context used
🧬 Code graph analysis (3)
react-vite/generation-based-subscription/src/pages/home.tsx (7)
react-vite/generation-based-subscription/src/lib/auth-client.ts (1)
  • authClient (3-6)
react-vite/generation-based-subscription/src/components/dashboard-skeleton.tsx (1)
  • DashboardSkeleton (4-68)
react-vite/generation-based-subscription/src/lib/billing-helpers.ts (1)
  • computeUsageTotal (47-84)
react-vite/generation-based-subscription/src/components/ui/card.tsx (4)
  • Card (72-72)
  • CardHeader (72-72)
  • CardTitle (72-72)
  • CardContent (72-72)
react-vite/generation-based-subscription/src/components/ui/tooltip.tsx (3)
  • Tooltip (102-102)
  • TooltipTrigger (102-102)
  • TooltipContent (102-102)
react-vite/generation-based-subscription/src/components/ui/button.tsx (1)
  • Button (44-44)
react-vite/generation-based-subscription/src/components/ui/progress.tsx (1)
  • Progress (36-36)
react-vite/generation-based-subscription/src/components/ui/carousel.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx (1)
react-vite/generation-based-subscription/src/lib/utils.ts (1)
  • cn (4-6)
🔇 Additional comments (16)
react-vite/generation-based-subscription/server/db/schema.ts (3)

3-15: Solid user table schema.

Defaults and constraints look consistent and aligned with common auth schemas.


17-28: Sessions table looks good.

Defaults and FK cascade align with typical session modeling.


61-67: Schema export is clean and clear.

react-vite/generation-based-subscription/src/pages/home.tsx (2)

374-452: LGTM!

The top-up purchase handlers have proper validation, error handling, and loading state management.


454-766: LGTM!

The JSX structure is well-organized with proper loading states, tooltips for accessibility, and clear visual hierarchy. The key prop pattern on Progress components ensures proper re-rendering when usage values change.

react-vite/generation-based-subscription/src/lib/utils.ts (1)

1-5: LGTM — cn helper is clean and correctly typed.

react-vite/generation-based-subscription/src/components/ui/carousel.tsx (5)

22-28: Guarded context usage looks good.


136-154: CarouselContent structure and orientation handling look solid.


158-176: CarouselItem layout and semantics look good.


180-213: CarouselPrevious wiring and composition look good.


217-250: CarouselNext wiring and composition look good.

react-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsx (5)

15-37: DropdownMenu state + outside click handling looks fine.


44-92: Trigger composition and ARIA setup look good.


100-125: Content rendering and alignment handling look good.


167-175: Label component looks good.


177-185: Separator component looks good.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread react-vite/generation-based-subscription/src/pages/home.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/src/pages/home.tsx`:
- Around line 60-79: When a billing reload succeeds you must clear the fallback
manualUsageAdjustments so usage isn't double-counted; in reloadAfterCheckout,
after await billing.reload() and after calling
setHasReloadedAfterCheckout(true), also call the state reset for
manualUsageAdjustments (e.g. setManualUsageAdjustments(...) or
resetManualUsageAdjustments) and ensure the same reset is applied in the other
reload path referenced (the similar block around the 88-103 range that handles
user changes/reloads) so adjustments are cleared both on successful checkout
reloads and on user switches.
♻️ Duplicate comments (1)
react-vite/generation-based-subscription/src/pages/home.tsx (1)

250-262: Don’t swallow reload failures in generation flows.

These empty catch blocks hide reload errors and make failures hard to diagnose. Mirror the existing logging approach used elsewhere.

🛠️ Proposed fix
       if (billing.reload) {
         try {
           await billing.reload();
           // Reset adjustments after reload to sync with server data
           setManualUsageAdjustments(prev => ({
             ...prev,
             fast_generations: 0
           }));
-        } catch {
-          // Keep manual adjustments if reload fails
-        }
+        } catch (error) {
+          // Keep manual adjustments if reload fails
+          console.error('Error reloading billing data after usage event', error);
+        }
       }
       if (billing.reload) {
         try {
           await billing.reload();
           // Reset adjustments after reload to sync with server data
           setManualUsageAdjustments(prev => ({
             ...prev,
             hd_video_minutes: 0
           }));
-        } catch {
-          // Keep manual adjustments if reload fails
-        }
+        } catch (error) {
+          // Keep manual adjustments if reload fails
+          console.error('Error reloading billing data after usage event', error);
+        }
       }

Also applies to: 315-326

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d898f4c and f6277dd.

📒 Files selected for processing (2)
  • react-vite/generation-based-subscription/src/components/ui/carousel.tsx
  • react-vite/generation-based-subscription/src/pages/home.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • react-vite/generation-based-subscription/src/components/ui/carousel.tsx

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread react-vite/generation-based-subscription/src/pages/home.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/src/components/navbar.tsx`:
- Around line 28-34: The component currently returns early using a truthiness
test for billing.errors; change that to an explicit null comparison to match the
project pattern and the BillingWithChecks type (i.e., replace the `if
(billing.errors)` check with `if (billing.errors !== null)`), keeping the
existing early-return behavior and leaving the preceding `if (!billing.loaded ||
!billing.loadBilling)` check unchanged.
🧹 Nitpick comments (1)
react-vite/generation-based-subscription/src/components/navbar.tsx (1)

41-46: Deduplicate “current subscription” lookup logic.
The same lookup logic appears in three places. A small helper avoids drift and makes future changes safer.

♻️ Suggested refactor
+  const getCurrentSubscription = () => {
+    const subscriptions = Array.isArray(billing?.subscriptions)
+      ? billing.subscriptions
+      : [];
+    const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
+    return currentSubscriptions[0] || billing.currentSubscriptions?.[0];
+  };
+
   async function handleCancelSubscription() {
-    const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-    const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-    const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
+    const currentSubscription = getCurrentSubscription();
     const subscriptionId = currentSubscription?.id;
     ...
   }

   async function handleUncancelSubscription() {
-    const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-    const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-    const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
+    const currentSubscription = getCurrentSubscription();
     const subscriptionId = currentSubscription?.id;
     ...
   }

-  const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
-  const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
-  const currentSubscription = currentSubscriptions[0] || billing.currentSubscriptions?.[0];
+  const currentSubscription = getCurrentSubscription();

Also applies to: 88-92, 136-140

Comment thread react-vite/generation-based-subscription/src/components/navbar.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/README.md`:
- Around line 72-76: Update the fenced code blocks under the "### Server
Configuration" section (the block showing SERVER_PORT/VITE_APP_URL) and the
later project tree block to include language identifiers so markdownlint MD040
is satisfied; change the environment variables block to use ```env and the
project tree block to use an appropriate identifier like ```text (or ```bash if
you prefer shell styling). Locate the sections by the "### Server Configuration"
heading and the project tree snippet (the block starting with ├── server/) and
add the language tags to the opening backticks for each fenced code block.

In `@react-vite/generation-based-subscription/src/pages/home.tsx`:
- Around line 136-165: The code uses undocumented experimental fields via the
fallback helpers getUsageBalance and getFeatureAccess; remove these helpers and
their usage so the component relies only on the documented billing API
(billing.checkUsageBalance and billing.checkFeatureAccess). Concretely, delete
getUsageBalance and getFeatureAccess, stop assigning
checkUsageBalance/checkFeatureAccess to fallbacks, and update the skeleton
condition to only check for currentSubscription and presence of
billing.checkUsageBalance/checkFeatureAccess before rendering <DashboardSkeleton
/>; if you must keep experimental access, instead add a clear TODO comment and a
feature-guarded runtime check around currentSubscription.experimental to make
the usage explicit and flagged for removal.
🧹 Nitpick comments (4)
react-vite/generation-based-subscription/server/index.ts (1)

154-168: Reuse req.user to avoid a second session lookup.

You already populate req.user in middleware, so you can short-circuit here and reduce auth calls.

♻️ Suggested tweak
   getCustomerExternalId: async (req: Request) => {
     // Get session from Better Auth and extract user ID
     // Use fromNodeHeaders to convert Express headers to Better Auth format
     try {
+      if (req.user?.id) {
+        return req.user.id;
+      }
       const session = await auth.api.getSession({
         headers: fromNodeHeaders(req.headers),
       });
react-vite/generation-based-subscription/src/components/navbar.tsx (2)

28-34: Minor inconsistency: Use strict null check for consistency with home.tsx.

Line 32 uses loose equality (!= null) while home.tsx uses strict equality (!== null). While functionally similar (loose catches both null and undefined), using a consistent pattern across the codebase improves readability.

♻️ Suggested fix
-  if (billing.errors != null) {
+  if (billing.errors !== null) {
     return null;
   }

41-128: Consider extracting repeated subscription derivation logic.

The pattern to derive currentSubscription is duplicated three times (lines 43-46, 89-92, and 137-139). For an example app this is acceptable, but extracting it to a useMemo or helper would improve maintainability.

♻️ Suggested refactor using useMemo

Add this near the top of the component (after useBilling()):

const currentSubscription = useMemo(() => {
  const subscriptions = Array.isArray(billing?.subscriptions) ? billing.subscriptions : [];
  const currentSubscriptions = subscriptions.filter((s) => s?.current === true);
  return currentSubscriptions[0] || billing.currentSubscriptions?.[0];
}, [billing?.subscriptions, billing?.currentSubscriptions]);

Then use currentSubscription directly in the handlers instead of re-deriving it.

react-vite/generation-based-subscription/src/pages/home.tsx (1)

340-376: Minor: Async functions without await.

handleGenerateRelaxImage and handleGenerateRelaxSDVideo are declared async but don't contain any await expressions. While harmless, they could be synchronous functions.

♻️ Suggested simplification
-  const handleGenerateRelaxImage = async () => {
+  const handleGenerateRelaxImage = () => {
     if (!hasRelaxMode) {
       return;
     }

     setIsGeneratingRelaxImage(true);

-    try {
       const nextIndex = (currentImageIndex + 1) % mockImages.length;
       setCurrentImageIndex(nextIndex);
       const nextImage = mockImages[nextIndex];
       if (nextImage) {
         setDisplayedContent(nextImage);
       }
-    } finally {
       setIsGeneratingRelaxImage(false);
-    }
   };

Same applies to handleGenerateRelaxSDVideo.

Comment thread react-vite/generation-based-subscription/README.md
Comment thread react-vite/generation-based-subscription/src/pages/home.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@react-vite/generation-based-subscription/src/pages/sign-up.tsx`:
- Around line 31-33: The code is calling navigate('/') directly during render
when session is truthy; move that navigation into a useEffect to avoid side
effects during render: import useEffect, then replace the inline if (session)
navigate('/') with a useEffect that depends on session (and optionally navigate)
and calls navigate('/') inside the effect when session is truthy; update the
imports at the top to include useEffect and ensure the effect uses the same
navigate and session identifiers.
🧹 Nitpick comments (4)
react-vite/generation-based-subscription/src/pages/home.tsx (2)

41-57: Consider grouping related state to reduce complexity.

The component has many individual state variables. For an example project this is acceptable, but in production code, consider grouping related states (e.g., all generation loading states, all error states) into objects or using a reducer.


314-350: Relax mode handlers are synchronous but marked async.

These handlers don't perform any async operations (no API calls, no usage tracking). The async keyword is unnecessary but harmless. Could simplify by removing async if desired.

react-vite/generation-based-subscription/src/pages/sign-up.tsx (1)

39-61: Consider adding autocomplete attributes and accessible labels.

For better UX and accessibility, consider adding autocomplete attributes and proper <label> elements or aria-label attributes.

Example improvement
         <input
           value={name}
           onChange={(e) => setName(e.target.value)}
           placeholder="Name"
           className="w-full rounded border px-3 py-2"
+          autoComplete="name"
+          aria-label="Name"
           required
         />
         <input
           type="email"
           value={email}
           onChange={(e) => setEmail(e.target.value)}
           placeholder="Email"
           className="w-full rounded border px-3 py-2"
+          autoComplete="email"
+          aria-label="Email"
           required
         />
         <input
           type="password"
           value={password}
           onChange={(e) => setPassword(e.target.value)}
           placeholder="Password"
           className="w-full rounded border px-3 py-2"
+          autoComplete="new-password"
+          aria-label="Password"
           required
         />
react-vite/generation-based-subscription/src/App.tsx (1)

41-62: Consider extracting a shared layout for protected routes.

The FlowgladProviderWrapper and Navbar are duplicated for each protected route. Extracting a ProtectedLayout component would reduce duplication and make adding new protected routes easier.

Example refactor
function ProtectedLayout({ children }: { children: React.ReactNode }) {
  return (
    <ProtectedRoute>
      <FlowgladProviderWrapper>
        <Navbar />
        {children}
      </FlowgladProviderWrapper>
    </ProtectedRoute>
  );
}

// Then in App:
<Route path="/" element={<ProtectedLayout><HomePage /></ProtectedLayout>} />
<Route path="/pricing" element={<ProtectedLayout><PricingPage /></ProtectedLayout>} />

Comment on lines +31 to +33
if (session) {
navigate('/');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move navigation side effect into useEffect.

Calling navigate() directly during render is a side effect that violates React's render purity principle. This can cause issues in Strict Mode and may trigger multiple navigations.

Suggested fix
+  useEffect(() => {
+    if (session) {
+      navigate('/');
+    }
+  }, [session, navigate]);
+
-  if (session) {
-    navigate('/');
-  }
-
   return (

Also add useEffect to the imports on line 1:

-import { useState } from 'react';
+import { useState, useEffect } from 'react';
📝 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 (session) {
navigate('/');
}
useEffect(() => {
if (session) {
navigate('/');
}
}, [session, navigate]);
return (
Suggested change
if (session) {
navigate('/');
}
import { useState, useEffect } from 'react';
🤖 Prompt for AI Agents
In `@react-vite/generation-based-subscription/src/pages/sign-up.tsx` around lines
31 - 33, The code is calling navigate('/') directly during render when session
is truthy; move that navigation into a useEffect to avoid side effects during
render: import useEffect, then replace the inline if (session) navigate('/')
with a useEffect that depends on session (and optionally navigate) and calls
navigate('/') inside the effect when session is truthy; update the imports at
the top to include useEffect and ensure the effect uses the same navigate and
session identifiers.

@agreea agreea merged commit dc07bef into main Jan 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

react + vite example

3 participants