create react-vite example#12
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
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 conflictingecmaVersionsettings.
ecmaVersionis set to2020at line 17 and to'latest'inparserOptionsat line 20. In ESLint flat config, onlylanguageOptions.ecmaVersionis needed—the one insideparserOptionsis redundant and potentially confusing.Additionally,
sourceType: 'module'should be moved tolanguageOptionslevel 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 thevarsIgnorePattern.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
textorplaintextto satisfy linting rules.Suggested fix
-``` +```text ├── server/ │ ├── db/react-vite/generation-based-subscription/src/lib/billing-helpers.js (1)
72-91: ExtractfindUsagePriceByMeterSluginto 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 themeterIdBySlugMap 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
scrollPrevand ArrowDown toscrollNextwhenorientation === 'vertical'.react-vite/generation-based-subscription/.env.example (1)
10-12: Consider renamingNEXT_PUBLIC_BASE_URLfor clarity.
NEXT_PUBLIC_is a Next.js naming convention. Since this is a Vite project, consider using a more appropriate prefix likeVITE_or a generic name likeAPI_BASE_URLorSERVER_URLto avoid confusion.react-vite/generation-based-subscription/server/db/client.js (1)
12-25: Consider caching thedbinstance alongside the pool.The pool is cached to prevent connection exhaustion during dev HMR, but the
drizzleinstance is recreated on each module reload. For consistency and to avoid potential overhead, consider cachingdbas 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-screenappears on both the outerdiv(line 6) and innermain(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
variantorsizeis passed,buttonVariants.variant[variant]returnsundefined, whichcn()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-authorityfor type-safe variants.react-vite/generation-based-subscription/server/lib/auth.js (1)
18-18: Consider renaming server-side environment variable.
VITE_APP_URLuses theVITE_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 likeAPP_URLorBASE_URLto 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'sLinkcomponent 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 usingreact-router-dom, useLinkfor 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'sLinkcomponent instead of anchor tag.Same issue as in
sign-up.jsx— using a native<a>tag causes a full page reload. UseLinkfromreact-router-domfor 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_URLis not set, the empty string fallback will causedrizzle-kitcommands to fail with a potentially unclear connection error. Consider throwing an explicit error similar to theBETTER_AUTH_SECRETvalidation inauth.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
isReloadingin the dependency array but also sets it internally, which can cause unnecessary re-evaluations. Since thehasReloadedflag already prevents duplicate reloads, you could simplify by removingisReloadingfrom dependencies and relying solely onhasReloaded.Additionally, the URL cleanup in the
finallyblock will execute even ifbilling.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.
FlowgladProviderWrapperandNavbarare duplicated for both/and/pricingroutes. 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
PricingCardsSkeletoncomponent.♻️ 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: DuplicategetPricehelper exists in pricing-card.jsx.This
getPricefunction duplicates the same logic found inpricing-card.jsx(lines 48-60 in the relevant snippets). Consider extracting this to a shared utility inbilling-helpers.jsor 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.comcould 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 offindUsagePriceByMeterSlug.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:
- Create a shared utilities package
- Use a build step to compile shared code
- For this example, document that both implementations must stay in sync
233-238: Consider validatingamountas an integer and using stronger randomness fortransactionId.
amountallows floats - if usage meters expect integers, considerNumber.isInteger(amount)Math.random().toString(36)provides weak uniqueness for transaction IDsSuggested 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:DropdownMenuItemdoesn't close menu whenonSelectis not provided.The current logic only closes the menu if
onSelectis provided. If a menu item is clicked without anonSelecthandler, 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:sideOffsetprop is accepted but not applied.The
sideOffsetparameter is destructured but the positioning uses hardcodedmb-2instead. 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:handleGenerateRelaxImageandhandleGenerateRelaxSDVideoare markedasyncbut contain noawait.These functions are declared
asyncbut don't perform any asynchronous operations. Theasynckeyword 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:getPricehelper is duplicated in both top-up handlers.The identical
getPricefunction 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, oruseTopUpwould 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 onverifications.identifierfor lookup performance.The
identifiercolumn 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_idandaccounts.user_idcolumns reference theuserstable 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:getPricehelper is duplicated across components.This same fallback pattern exists in
home.jsx(lines 406-415, 457-466). Consider extracting to a shared utility inbilling-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
⛔ Files ignored due to path filters (4)
react-vite/generation-based-subscription/bun.lockis excluded by!**/*.lockreact-vite/generation-based-subscription/package-lock.jsonis excluded by!**/package-lock.jsonreact-vite/generation-based-subscription/public/vite.svgis excluded by!**/*.svgreact-vite/generation-based-subscription/src/assets/react.svgis excluded by!**/*.svg
📒 Files selected for processing (44)
react-vite/generation-based-subscription/.env.examplereact-vite/generation-based-subscription/.gitignorereact-vite/generation-based-subscription/README.mdreact-vite/generation-based-subscription/drizzle.config.jsreact-vite/generation-based-subscription/drizzle/0000_safe_iron_fist.sqlreact-vite/generation-based-subscription/drizzle/meta/0000_snapshot.jsonreact-vite/generation-based-subscription/drizzle/meta/_journal.jsonreact-vite/generation-based-subscription/eslint.config.jsreact-vite/generation-based-subscription/index.htmlreact-vite/generation-based-subscription/package.jsonreact-vite/generation-based-subscription/postcss.config.jsreact-vite/generation-based-subscription/pricing.yamlreact-vite/generation-based-subscription/server/db/client.jsreact-vite/generation-based-subscription/server/db/schema.jsreact-vite/generation-based-subscription/server/index.jsreact-vite/generation-based-subscription/server/lib/auth.jsreact-vite/generation-based-subscription/server/package.jsonreact-vite/generation-based-subscription/src/App.jsxreact-vite/generation-based-subscription/src/components/dashboard-skeleton.jsxreact-vite/generation-based-subscription/src/components/navbar.jsxreact-vite/generation-based-subscription/src/components/pricing-card.jsxreact-vite/generation-based-subscription/src/components/pricing-cards-grid.jsxreact-vite/generation-based-subscription/src/components/providers.jsxreact-vite/generation-based-subscription/src/components/ui/badge.jsxreact-vite/generation-based-subscription/src/components/ui/button.jsxreact-vite/generation-based-subscription/src/components/ui/card.jsxreact-vite/generation-based-subscription/src/components/ui/carousel.jsxreact-vite/generation-based-subscription/src/components/ui/dropdown-menu.jsxreact-vite/generation-based-subscription/src/components/ui/progress.jsxreact-vite/generation-based-subscription/src/components/ui/skeleton.jsxreact-vite/generation-based-subscription/src/components/ui/tooltip.jsxreact-vite/generation-based-subscription/src/hooks/use-mobile.jsreact-vite/generation-based-subscription/src/index.cssreact-vite/generation-based-subscription/src/lib/auth-client.jsreact-vite/generation-based-subscription/src/lib/billing-helpers.jsreact-vite/generation-based-subscription/src/lib/utils.jsreact-vite/generation-based-subscription/src/main.jsxreact-vite/generation-based-subscription/src/pages/home.jsxreact-vite/generation-based-subscription/src/pages/pricing.jsxreact-vite/generation-based-subscription/src/pages/sign-in.jsxreact-vite/generation-based-subscription/src/pages/sign-up.jsxreact-vite/generation-based-subscription/tailwind.config.jsreact-vite/generation-based-subscription/vite.config.jstanstack-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)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: Theeslint/configimport path is correct for the project's ESLint version (v9.39.1). BothdefineConfigandglobalIgnoresare 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.jsoncorrectly establishes CommonJS module resolution for the server directory, allowing the server-side code to usemodule.exportssyntax 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
clsxfor conditional class handling andtwMergefor 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
cnfor 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
.gitignorewith 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 leavesbun.lockbto 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
matchMediafor responsive detection, and appropriate cleanup of the event listener. The lazy initialization inuseStateensures 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-hooksv7 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 thesrcdirectory.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
computeUsageTotalfunction 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
getDerivedStateFromErrorand logs them incomponentDidCatch.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 baseblocks (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: trueconfig ensures auth cookies are sent with requests. The!isPendingcheck 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
forwardRefusage,displayNameassignment, 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: falsesetting 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
finallyto ensure loading state resets- Navigates with
replace: trueto prevent back-navigation to the formreact-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?.pathnamefor 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
cnutility 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,displayNameassignments, and thecnutility for className composition.react-vite/generation-based-subscription/server/index.js (1)
143-150: Health endpoint exposesuserId- 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!
DropdownMenuLabelandDropdownMenuSeparatorare clean, simple presentational components with properforwardRefusage anddisplayNameassignments.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
userstable schema is well-designed with appropriate constraints:
- Primary key on
id- Unique constraint on
- Sensible defaults for
email_verified,created_at,updated_atreact-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
cnutility for dynamic styling based onisPopularandisCurrentPlan
| 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> | ||
| ); | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| export const authClient = createAuthClient({ | ||
| baseURL: import.meta.env.VITE_APP_URL || 'http://localhost:5173', | ||
| }); |
There was a problem hiding this comment.
🧩 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.
There was a problem hiding this comment.
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
createRootcall 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_SECRETstill has quotes around the value, which can cause parsing issues with some.envparsers.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
getDerivedStateFromErrorandcomponentDidCatch. 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
ErrorFallbackto accept anonResetprop 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
/monthtext 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.intervalor similar) for accuracy.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
react-vite/generation-based-subscription/.env.examplereact-vite/generation-based-subscription/README.mdreact-vite/generation-based-subscription/drizzle.config.cjsreact-vite/generation-based-subscription/package.jsonreact-vite/generation-based-subscription/server/index.jsreact-vite/generation-based-subscription/src/components/navbar.jsxreact-vite/generation-based-subscription/src/components/pricing-card.jsxreact-vite/generation-based-subscription/src/components/ui/carousel.jsxreact-vite/generation-based-subscription/src/components/ui/progress.jsxreact-vite/generation-based-subscription/src/components/ui/tooltip.jsxreact-vite/generation-based-subscription/src/main.jsxreact-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)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 supportforwardRefallows parent components to access the underlying DOM element- The
cnutility usage is consistent with other UI components in the codebaseThe 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
createRootfromreact-dom/clientis 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
handleSubmitfunction properly handles the async sign-up flow with correct error handling, loading state management viafinally, and appropriate use ofreplace: truefor post-auth navigation. The pattern is consistent withsign-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
useCarouselhook 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
reInitandselectlisteners in the cleanup function, addressing the previously identified memory leak concern.
114-131: LGTM!The
CarouselContentcomponent 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
CarouselItemcomponent has proper accessibility attributes and correct styling that complements the negative margins inCarouselContent.
180-206: LGTM!The
CarouselNextcomponent correctly mirrorsCarouselPreviouswith appropriate positioning adjustments and disabled state binding.
208-214: LGTM!All carousel components are properly exported for external use.
152-178: Thevariant="outline"andsize="icon"defaults are properly defined in the Button component'sbuttonVariantsobject. 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
getPricefallback 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 inhandleCheckout.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| # 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 |
There was a problem hiding this comment.
i think SERVER_PORT is used instead?
| This starts: | ||
| - Frontend: [http://localhost:5173](http://localhost:5173) | ||
| - Backend API: [http://localhost:3001](http://localhost:3001) | ||
|
|
There was a problem hiding this comment.
maybe changes ports to be 3000, 3001? just a thought up to you
There was a problem hiding this comment.
had flowglad running on 3000 so that's why i made it 3001!
|
@rebeccax06 could you also convert this to typescript instead of js/jsx ? |
lpmsh
left a comment
There was a problem hiding this comment.
just some nitpick things and consistency with other example projects functionality is good though otherwise!
| 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); | ||
| } |
There was a problem hiding this comment.
To be consistent with other examples change .env to .env.local
like this case in server/index.js
| 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); | |
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
upon a successful purchase of a subscription the pricing page should redirect to the main page /
| <DropdownMenuContent align="end"> | ||
| <DropdownMenuLabel>Account Actions</DropdownMenuLabel> | ||
| <DropdownMenuSeparator /> | ||
| <DropdownMenuItem onSelect={handleSignOut}>Log out</DropdownMenuItem> |
|
@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 |
There was a problem hiding this comment.
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 aroundBETTER_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-herereact-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 fallbackhttp://localhost:5173points 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: RedundantecmaVersionspecification.
ecmaVersionis defined twice:2020at line 17 and'latest'withinparserOptionsat line 20. TheparserOptions.ecmaVersiontypically 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-varsfor TypeScript files.The project uses TypeScript with strict type checking enabled (
noUnusedLocalsandnoUnusedParametersintsconfig.json). The base ESLintno-unused-varsrule does not have proper TypeScript support without@typescript-eslint/parserand will miss or mishandle TypeScript-specific constructs. The recommended setup with ESLint 9 flat config is to use thetypescript-eslintpackage, which provides both the parser and the TypeScript-awareno-unused-varsrule.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 assumebunruntime is installed.The dev scripts use
bun runwhich 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
HTMLAttributesadds 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 addingforwardReffor 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
childrenprop 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
ProtectedRoutecomponent 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
FlowgladProviderWrapperandNavbarpattern 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
.tsxextensions 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
useNavigateandLinkare imported fromreact-router-domin 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.emailthrows an unexpected error (e.g., network failure not handled byonError), the loading state will remaintrue. Usingtry/finallyensures 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 addingrequiredattribute 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
findUsagePriceByMeterSlugfunction duplicates the implementation insrc/lib/billing-helpers.ts(lines 71-90). Consider moving this to a shared location (e.g., a commonlib/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: Theasynckeyword is unnecessary.
handleGenerateRelaxImageandhandleGenerateRelaxSDVideoare markedasyncbut contain noawaitexpressions. While harmless, removingasyncclarifies 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
⛔ Files ignored due to path filters (1)
react-vite/generation-based-subscription/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
react-vite/generation-based-subscription/.env.examplereact-vite/generation-based-subscription/drizzle.config.tsreact-vite/generation-based-subscription/eslint.config.tsreact-vite/generation-based-subscription/package.jsonreact-vite/generation-based-subscription/postcss.config.tsreact-vite/generation-based-subscription/server/db/client.tsreact-vite/generation-based-subscription/server/db/schema.tsreact-vite/generation-based-subscription/server/index.tsreact-vite/generation-based-subscription/server/lib/auth.tsreact-vite/generation-based-subscription/src/App.tsxreact-vite/generation-based-subscription/src/components/dashboard-skeleton.tsxreact-vite/generation-based-subscription/src/components/navbar.tsxreact-vite/generation-based-subscription/src/components/pricing-card.tsxreact-vite/generation-based-subscription/src/components/pricing-cards-grid.tsxreact-vite/generation-based-subscription/src/components/providers.tsxreact-vite/generation-based-subscription/src/components/ui/badge.tsxreact-vite/generation-based-subscription/src/components/ui/button.tsxreact-vite/generation-based-subscription/src/components/ui/card.tsxreact-vite/generation-based-subscription/src/components/ui/carousel.tsxreact-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsxreact-vite/generation-based-subscription/src/components/ui/progress.tsxreact-vite/generation-based-subscription/src/components/ui/skeleton.tsxreact-vite/generation-based-subscription/src/components/ui/tooltip.tsxreact-vite/generation-based-subscription/src/hooks/use-mobile.tsreact-vite/generation-based-subscription/src/lib/auth-client.tsreact-vite/generation-based-subscription/src/lib/billing-helpers.tsreact-vite/generation-based-subscription/src/lib/utils.tsreact-vite/generation-based-subscription/src/main.tsxreact-vite/generation-based-subscription/src/pages/home.tsxreact-vite/generation-based-subscription/src/pages/pricing.tsxreact-vite/generation-based-subscription/src/pages/sign-in.tsxreact-vite/generation-based-subscription/src/pages/sign-up.tsxreact-vite/generation-based-subscription/src/vite-env.d.tsreact-vite/generation-based-subscription/tailwind.config.tsreact-vite/generation-based-subscription/tsconfig.jsonreact-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 ofjs.configs.recommended,reactHooks.configs.flat.recommended, andreactRefresh.configs.viteis 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'sbaseURLandtrustedOriginsproperly 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), makingbaseURL: VITE_APP_URLthe 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 intrustedOrigins. 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
forwardReffor ref forwarding,displayNamefor debugging, and type-safe variant/size props viakeyof 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
loadBillingwhen authentication is confirmed (!isPending && !!session?.user) prevents unnecessary billing API calls for unauthenticated users, andwithCredentials: truecorrectly 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 withcn()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 offorwardReffor 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 existingif (billing.errors)check is correct—it properly handles the case wherebilling.errorsis eithernull(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.emailfrom better-auth does not throw or reject — it handles errors via theonErrorcallback (already in place). Sinceawaitwill 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
TooltipProviderat the topreact-vite/generation-based-subscription/server/db/client.ts (2)
5-7: Good practice: fail-fast validation.Validating
DATABASE_URLat 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
getDerivedStateFromErrorfor state derivation andcomponentDidCatchfor 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 lackdefaultNow().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
betterAuthSchemaprovides 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
PricingCardsGridkeeps 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.localpreference 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-eventsendpoint 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.
| createdAt: timestamp('created_at', { withTimezone: true }).notNull(), | ||
| updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "schema.ts" -o -name "*schema*" | head -20Repository: flowglad/examples
Length of output: 420
🏁 Script executed:
cat -n react-vite/generation-based-subscription/server/db/schema.tsRepository: 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.
| declare global { | ||
| interface ImportMetaEnv { | ||
| readonly VITE_APP_URL?: string; | ||
| // Add other VITE_ prefixed env variables here as needed | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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.
| 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', | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🧩 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 -20Repository: flowglad/examples
Length of output: 1299
🏁 Script executed:
# Check tsconfig.json for path configuration
cat react-vite/generation-based-subscription/tsconfig.jsonRepository: 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 -cRepository: 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 -50Repository: 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.tsRepository: 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 -20Repository: 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.
| 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.
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
react-vite/generation-based-subscription/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
react-vite/generation-based-subscription/.env.examplereact-vite/generation-based-subscription/drizzle.config.tsreact-vite/generation-based-subscription/package.jsonreact-vite/generation-based-subscription/src/App.tsxreact-vite/generation-based-subscription/src/components/providers.tsxreact-vite/generation-based-subscription/src/hooks/use-mobile.tsreact-vite/generation-based-subscription/src/lib/billing-helpers.tsreact-vite/generation-based-subscription/src/lib/utils.tsreact-vite/generation-based-subscription/src/main.tsxreact-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.
There was a problem hiding this comment.
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()withoutdefaultNow(), so inserts must always provide values. This is inconsistent withusers/sessionsand 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 recommendedreact-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
verificationstable lack.notNull(), while all other tables (users,sessions,accounts) enforce this constraint on theircreatedAtandupdatedAtfields. 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 unnecessaryasynckeyword.This function contains no
awaitstatements. Theasynckeyword 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 unnecessaryasynckeyword.Same as
handleGenerateRelaxImageabove—noawaitis used.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
react-vite/generation-based-subscription/.env.examplereact-vite/generation-based-subscription/server/db/schema.tsreact-vite/generation-based-subscription/src/components/navbar.tsxreact-vite/generation-based-subscription/src/components/ui/carousel.tsxreact-vite/generation-based-subscription/src/components/ui/dropdown-menu.tsxreact-vite/generation-based-subscription/src/lib/billing-helpers.tsreact-vite/generation-based-subscription/src/lib/utils.tsreact-vite/generation-based-subscription/src/pages/home.tsxreact-vite/generation-based-subscription/src/vite-env.d.tsreact-vite/generation-based-subscription/tsconfig.jsonreact-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
keyprop 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.
There was a problem hiding this comment.
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
catchblocks 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
📒 Files selected for processing (2)
react-vite/generation-based-subscription/src/components/ui/carousel.tsxreact-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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: Reusereq.userto avoid a second session lookup.You already populate
req.userin 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 withhome.tsx.Line 32 uses loose equality (
!= null) whilehome.tsxuses strict equality (!== null). While functionally similar (loose catches bothnullandundefined), 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
currentSubscriptionis duplicated three times (lines 43-46, 89-92, and 137-139). For an example app this is acceptable, but extracting it to auseMemoor 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
currentSubscriptiondirectly in the handlers instead of re-deriving it.react-vite/generation-based-subscription/src/pages/home.tsx (1)
340-376: Minor: Async functions withoutawait.
handleGenerateRelaxImageandhandleGenerateRelaxSDVideoare declaredasyncbut don't contain anyawaitexpressions. 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.
There was a problem hiding this comment.
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
asynckeyword is unnecessary but harmless. Could simplify by removingasyncif 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
autocompleteattributes and proper<label>elements oraria-labelattributes.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
FlowgladProviderWrapperandNavbarare duplicated for each protected route. Extracting aProtectedLayoutcomponent 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>} />
| if (session) { | ||
| navigate('/'); | ||
| } |
There was a problem hiding this comment.
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.
| if (session) { | |
| navigate('/'); | |
| } | |
| useEffect(() => { | |
| if (session) { | |
| navigate('/'); | |
| } | |
| }, [session, navigate]); | |
| return ( |
| 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.

Summary by CodeRabbit
New Features
Documentation
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.