Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR introduces a new articles feature to the application by generalizing the existing roadmap subscription system to support multiple update types ("roadmap" and "articles"). It adds article content sourcing via content collections, new marketing pages, refactored subscription components, and updates the backend routing and email infrastructure to handle both subscription types. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as UI<br/>(MarketingSubscription)
participant Hook as useSubscribeToUpdates
participant API as subscribeToUpdates<br/>(Endpoint)
participant Email as sendSubscriptionEmail
participant DB as Prisma<br/>(roadmapSubscription)
User->>UI: Submit subscription (email, type)
UI->>Hook: Call mutation
Hook->>API: Call subscribeToUpdates({email, type})
API->>DB: Upsert subscription by (email, type)
API->>Email: sendSubscriptionEmail({to, type})
Email->>Email: Render EmailSubscription<br/>with type-specific content
Email-->>API: Send confirmation email
API-->>Hook: Return success payload
Hook-->>UI: Show success message<br/>(type-specific)
UI-->>User: Display confirmation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
components/app/email-roadmap-subscription.tsx (1)
42-48: Make cover image conditional based on subscription type.The email template uses a hardcoded roadmap image (
/email/roadmap.png) for both roadmap and articles subscriptions. Articles subscribers will receive an email with an incorrect cover image.Apply this diff to make the image conditional:
+const imageUrl = isRoadmap ? "/email/roadmap.png" : "/email/articles.png" +const imageAlt = isRoadmap ? `${siteConfig.name} Roadmap` : `${siteConfig.name} Articles` + <Section className="mb-0"> <Img - src={`${siteConfig.url}/email/roadmap.png`} - alt={`${siteConfig.name} Roadmap`} + src={`${siteConfig.url}${imageUrl}`} + alt={imageAlt} className="w-full object-cover" /> </Section>Note: Ensure the
/email/articles.pngimage asset exists before deploying.orpc/hooks/use-users.ts (1)
104-111: Remove console.error for production code (existing issue).The error handler uses
console.error, which violates the coding guideline prohibiting console usage. This is an existing issue that should be addressed.Apply this diff:
export function useSubscribeToRoadmap() { return useMutation({ mutationFn: orpc.users.subscribeToRoadmap.call, - onError: (error) => { - console.error("Failed to subscribe to roadmap:", error) - }, }) }components/app/marketing-subscription.tsx (1)
13-18: Add accessible label for the email field (FormLabel).Inputs need an associated label. Use a visually hidden label to keep UI clean while satisfying a11y.
import { Form, FormControl, FormField, FormItem, + FormLabel, FormMessage, } from "@/components/ui/form" ... <FormField control={form.control} name="email" render={({ field }) => ( <FormItem className="w-full sm:flex-1"> + <FormLabel className="sr-only">Email</FormLabel> <FormControl> <Input type="email" placeholder="your@email.com" disabled={subscribeToUpdatesMutation.isPending} {...field} /> </FormControl> <FormMessage /> </FormItem> )} />As per coding guidelines.
Also applies to: 116-127
🧹 Nitpick comments (10)
schemas/user/roadmap/output.ts (1)
7-16: Consider consolidating duplicate schemas.Both
subscriptionOutputSchemaandroadmapSubscribeOutputSchemaare identical. While keeping both maintains backward compatibility, consider whether the roadmap-specific schema can simply re-export or extend the subscription schema to reduce duplication.Example consolidation:
export const subscriptionOutputSchema = z.object({ success: z.boolean(), error: z.string().optional(), }) // Backward compatibility - use the same schema export const roadmapSubscribeOutputSchema = subscriptionOutputSchemacomponents/shared/counter.tsx (2)
19-34: Consider adding ARIA labels for better accessibility.While the buttons have visible text, adding
aria-labelattributes would improve screen reader experience by providing more context about what's being incremented/decremented.<Button onClick={decrement} variant="outline" size="sm"> + aria-label={`Decrement by ${step}`} - </Button> <Button onClick={increment} variant="outline" size="sm"> + aria-label={`Increment by ${step}`} + </Button> <Button onClick={reset} variant="secondary" size="sm"> + aria-label={`Reset to ${initialValue}`} Reset </Button>
12-17: Consider optimizing with useCallback for stable handler references.The handler functions are recreated on every render. For better performance, especially if Counter is used frequently, consider wrapping them with
useCallback.+import { useState, useCallback } from "react" - const increment = () => setCount(count + step) - const decrement = () => setCount(count - step) - const reset = () => setCount(initialValue) + const increment = useCallback(() => setCount(c => c + step), [step]) + const decrement = useCallback(() => setCount(c => c - step), [step]) + const reset = useCallback(() => setCount(initialValue), [initialValue])components/app/marketing-articles-list.tsx (2)
28-29: Make the scroll listener passive.Marking the scroll handler as
{ passive: true }improves responsiveness and prevents main-thread blocking.- scrollContainer.addEventListener("scroll", handleScroll) - return () => scrollContainer.removeEventListener("scroll", handleScroll) + scrollContainer.addEventListener("scroll", handleScroll, { passive: true }) + return () => + scrollContainer.removeEventListener("scroll", handleScroll)As per best practices.
39-43: Consider using the shared UI Link for consistency.To centralize link behavior/styles, import and use
components/ui/link.tsxinstead ofnext/linkdirectly. Optional, for consistency with MDX links.components/app/marketing-subscription.tsx (5)
21-26: Reuse shared Zod schema/types; avoid duplication.Leverage the shared schema from
schemas/user/roadmap/input.tsto keep UI and server validation in sync. Pick only-import { z } from "zod" +import { z } from "zod" +import { subscriptionInputSchema } from "@/schemas/user/roadmap/input" +import type { SubscriptionInput as BaseSubscriptionInput } from "@/schemas/user/roadmap/input" -const subscriptionInputSchema = z.object({ - email: z.string().email("Please enter a valid email address"), -}) +const subscriptionEmailOnlySchema = subscriptionInputSchema.pick({ email: true }) -type SubscriptionInput = z.infer<typeof subscriptionInputSchema> +type SubscriptionInput = Pick<BaseSubscriptionInput, "email"> ... -const form = useForm<SubscriptionInput>({ - resolver: zodResolver(subscriptionInputSchema), +const form = useForm<SubscriptionInput>({ + resolver: zodResolver(subscriptionEmailOnlySchema),Based on guidelines.
Also applies to: 43-45
50-63: Clear both timers to avoid state updates after unmount.The inner
setTimeoutisn’t cleared, risking updates on unmounted components. Track and clear both timers.useEffect(() => { - if (showSuccess) { - const timer = setTimeout(() => { - setIsTransitioning(true) - setTimeout(() => { - setShowSuccess(false) - setIsTransitioning(false) - }, 300) // Wait for fade out animation - }, 3000) // Show success for 3 seconds - - return () => clearTimeout(timer) - } + if (!showSuccess) return + let fadeTimer: ReturnType<typeof setTimeout> | undefined + const timer = setTimeout(() => { + setIsTransitioning(true) + fadeTimer = setTimeout(() => { + setShowSuccess(false) + setIsTransitioning(false) + }, 300) + }, 3000) + + return () => { + clearTimeout(timer) + if (fadeTimer) clearTimeout(fadeTimer) + } }, [showSuccess])
129-138: Button disabled style per repo convention.Add
disabled:opacity-50for consistency with button states.<Button type="submit" - className="w-full sm:w-auto" + className="w-full sm:w-auto disabled:opacity-50" disabled={subscribeToUpdatesMutation.isPending} >
107-111: Autocomplete: prefer field-levelautoComplete="email"; avoid disabling at form level.Improves UX and meets validity guidance.
-<form - onSubmit={form.handleSubmit(onSubmit)} - className="flex flex-col gap-2 sm:flex-row" - autoComplete="off" -> +<form + onSubmit={form.handleSubmit(onSubmit)} + className="flex flex-col gap-2 sm:flex-row" +> ... <Input type="email" placeholder="your@email.com" + autoComplete="email" disabled={subscribeToUpdatesMutation.isPending} {...field} />Also applies to: 118-123
140-153: Announce success text to AT users.Mark the status region as live so announcements are read by screen readers.
-<div className="flex items-center gap-2"> +<div className="flex items-center gap-2" role="status" aria-live="polite">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (25)
.gitignore(1 hunks)app/(marketing)/articles/[slug]/page.tsx(1 hunks)app/(marketing)/articles/page.tsx(1 hunks)app/(marketing)/roadmap/page.tsx(2 hunks)components/app/email-roadmap-subscription.tsx(3 hunks)components/app/marketing-articles-list.tsx(1 hunks)components/app/marketing-header-desktop.tsx(1 hunks)components/app/marketing-subscription.tsx(5 hunks)components/shared/counter.tsx(1 hunks)components/ui/link.tsx(1 hunks)data/articles/advanced-security-features.mdx(1 hunks)data/articles/getting-started-with-zero-locker.mdx(1 hunks)lib/content-collections.ts(1 hunks)lib/email/send-roadmap-subscription-email.ts(1 hunks)next.config.ts(2 hunks)orpc/hooks/use-users.ts(1 hunks)orpc/routers/user.ts(5 hunks)package.json(2 hunks)prisma.config.ts(2 hunks)schemas/user/roadmap/input.ts(1 hunks)schemas/user/roadmap/output.ts(1 hunks)schemas/utils/article.ts(1 hunks)schemas/utils/index.ts(1 hunks)styles/globals.css(1 hunks)tsconfig.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (38)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{js,jsx,ts,tsx}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't write functions that exceed a given Cognitive Complexity score
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Use for...of statements instead of Array.forEach
Don't create classes that only have static members
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Don't use ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use arrow functions instead of function expressions
Use Date.now() to get milliseconds since the Unix Epoch
Use .flatMap() instead of map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work
Use concise optional chaining instead of chained logical expressions
Use regular expression literals instead of the RegExp constructor when possible
Don't use number literal object member names that aren't base 10 or use underscore separators
Remove redundant terms from logical expressions
Use while loops instead of for loops when you don't need initializer and update expressions
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min and Math.max to clamp value...
Files:
schemas/utils/index.tsschemas/user/roadmap/output.tsapp/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxschemas/utils/article.tslib/email/send-roadmap-subscription-email.tsnext.config.tscomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxschemas/user/roadmap/input.tslib/content-collections.tsapp/(marketing)/roadmap/page.tsxprisma.config.tsorpc/hooks/use-users.tsorpc/routers/user.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Make sure switch-case statements are exhaustive
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibility modifiers on class properties and methods
Put default function parameters and optional function parameters last
Initialize each enum member value explicitly
Use export type for types
Use import type for types
Make sure all enum members are literal values
Use function types instead of object types with call signatures
Don't use void type outside of generic or return types
Don't use TypeScript const enum
Don't declare empty interfaces
Don't let variables evolve into any type through reassignments
Don't use the any type
Don't misuse the non-null assertion operator (!) in TypeScript files
Don't use implicit any type on variable declarations
Don't merge interfaces and classes unsafely
Don't use overload signatures that aren't next to each other
Use the namespace keyword instead of the module keyword to declare TypeScript namespacesUse type-only imports when possible
**/*.{ts,tsx}: Use camelCase for function and method names
Use UPPER_SNAKE_CASE for constants
Use camelCase for variables
Use PascalCase for classes and types
Use absolute imports for top-level modules (e.g., @/components, @/lib, @/entities) instead of long relative paths
Follow import order: React imports, third-party, internal abso...
Files:
schemas/utils/index.tsschemas/user/roadmap/output.tsapp/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxschemas/utils/article.tslib/email/send-roadmap-subscription-email.tsnext.config.tscomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxschemas/user/roadmap/input.tslib/content-collections.tsapp/(marketing)/roadmap/page.tsxprisma.config.tsorpc/hooks/use-users.tsorpc/routers/user.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{js,ts}: Don't use __dirname and __filename in the global scope
Use with { type: "json" } for JSON module imports
Don't access namespace imports dynamically
Use node:assert/strict over node:assert
Use the node: protocol for Node.js builtin modules
Files:
schemas/utils/index.tsschemas/user/roadmap/output.tsschemas/utils/article.tslib/email/send-roadmap-subscription-email.tsnext.config.tsschemas/user/roadmap/input.tslib/content-collections.tsprisma.config.tsorpc/hooks/use-users.tsorpc/routers/user.ts
schemas/**/[a-z0-9-]*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Zod schemas should be placed in the schemas directory and named in kebab-case with .ts
Files:
schemas/utils/index.tsschemas/user/roadmap/output.tsschemas/utils/article.tsschemas/user/roadmap/input.ts
{components,entities,lib,orpc,schemas,hooks,config,types}/**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Use barrel exports: each folder should include an index.ts for clean imports
Files:
schemas/utils/index.ts
schemas/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Define Zod validation schemas for API and forms under schemas/
schemas/**/*.ts: Define all input/output schemas in the /schemas folder
Export both the Zod schema and its inferred TypeScript type
Provide clear, field-specific, contextual validation error messages in Zod schemas
List endpoint input schemas must include pagination (page, limit), search, and filtering fields with consistent defaults and boundsSchemas must be named in kebab-case.ts (e.g., credential-form-dto.ts)
schemas/**/*.ts: Within schemas, import from concrete files (e.g., .../input, .../output) and avoid barrel imports under /schemas to prevent circular dependencies
Do not import from '@/schemas/' or '@/schemas/utils' inside the schemas folder; use direct file imports like '@/schemas//input'
Use .optional() for optional fields, .nullable() for nullable fields, and .nullish() for values that can be null or undefined
Validate arrays with z.array(itemSchema); mark arrays optional with .optional() when not required
Use nested object validation for structured fields (e.g., z.object({...}) or referenced schemas)
Compose schemas via .extend(...) and use .partial() for update variants
Reuse common patterns by defining base schemas and extending them for specific cases
Files:
schemas/utils/index.tsschemas/user/roadmap/output.tsschemas/utils/article.tsschemas/user/roadmap/input.ts
schemas/**/output.ts
📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)
schemas/**/output.ts: Name output schemas as {entity}{Type}OutputSchema (e.g., credentialSimpleOutputSchema, userSimpleOutputSchema)
Output schema variants: {entity}SimpleOutputSchema, {entity}IncludeOutputSchema, list{Entity}OutputSchema
Files:
schemas/user/roadmap/output.ts
schemas/**/{input,output,enums}.ts
📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)
schemas/**/{input,output,enums}.ts: Name TypeScript types inferred from schemas as {Entity}{Type}Input / {Entity}{Type}Output / {Entity}Infer
Generate types via z.infer<> from schemas and export both schemas and their corresponding types
Files:
schemas/user/roadmap/output.tsschemas/user/roadmap/input.ts
**/*.{html,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{html,jsx,tsx}: Don't use the accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for the tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use an explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a title attribute for iframe elements
Include caption tracks for audio and video elements
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Use correct ISO language/country codes for the lang attribute
Don't use variables that haven't been declared in the document
Make sure void (self-closing) elements don't have children
Don't use event handlers on non-interactiv...Files:
app/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxapp/(marketing)/roadmap/page.tsx**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{jsx,tsx}: Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Use semantic elements instead of role attributes in JSX
Don't use unnecessary fragments
Don't pass children as props
Don't use the return value of React.render
Make sure all dependencies are correctly specified in React hooks
Make sure all React hooks are called from the top level of component functions
Don't forget key props in iterators and collection literals
Don't define React components inside other components
Don't assign to React component props
Don't use dangerous JSX props
Don't use both children and dangerouslySetInnerHTML props on the same element
Use <>...</> instead of ...
Don't add extra closing tags for components without children
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Watch out for possible "wrong" semicolons inside JSX elements
Don't put multiple components in one file; each file must have one componentFiles:
app/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxapp/(marketing)/roadmap/page.tsx**/*.{html,jsx,tsx,css}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Don't use TailwindCSS class names 'h-NUMBER w-NUMBER'; instead use 'size-NUMBR'
Files:
app/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxstyles/globals.csscomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxapp/(marketing)/roadmap/page.tsxapp/**/page.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
Page files must be named page.tsx and define the page component and metadata
Page components in the Next.js App Router must be named page.tsx
Pages must be named page.tsx
Files:
app/(marketing)/articles/page.tsxapp/(marketing)/articles/[slug]/page.tsxapp/(marketing)/roadmap/page.tsxapp/**/[(]*[)]/**
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
Use parentheses-named folders (group) to create route groups that don’t affect the URL
Files:
app/(marketing)/articles/page.tsxapp/(marketing)/articles/[slug]/page.tsxapp/(marketing)/roadmap/page.tsxapp/**/{page,layout}.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
app/**/{page,layout}.tsx: Export a Metadata object asexport const metadatain pages and layouts
Perform server-side data fetching in pages and layouts where possibleFiles:
app/(marketing)/articles/page.tsxapp/(marketing)/articles/[slug]/page.tsxapp/(marketing)/roadmap/page.tsxapp/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
app/**/*.tsx: Prefer Server Components by default in the app directory
Add "use client" only when necessary for Client ComponentsFiles:
app/(marketing)/articles/page.tsxapp/(marketing)/articles/[slug]/page.tsxapp/(marketing)/roadmap/page.tsxapp/(marketing)/**
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Place marketing and landing content within app/(marketing)/
Files:
app/(marketing)/articles/page.tsxapp/(marketing)/articles/[slug]/page.tsxapp/(marketing)/roadmap/page.tsx**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-patterns.mdc)
**/*.tsx: Use Component Composition Pattern: build complex UI by composing smaller, reusable components (e.g., Form, FormField, FormItem)
Handle form errors with React Hook Form and Zod resolver for client-side validation
Avoid direct database access in React components; use hooks/services instead
**/*.tsx: Use PascalCase for React component names
Use functional components only, typed with explicit props interfaces
Optimize components using React.memo for expensive components, useMemo for heavy computations, and useCallback for stable handlers
**/*.tsx: Never use useState for form state; use React Hook Form (useForm) instead
Always use Zod schemas with zodResolver for form validation
Always use shadcn Form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
Always use Icons.spinner with animate-spin for loading states
Never use text like "Deleting..." or "Loading..." for loading states
Use size-4 instead of h-4 w-4 for spinner sizing (Tailwind)
Disable buttons during loading: disabled={isLoading}
Show spinner + text inside buttons when loading
Use Button variant="destructive" for delete actions
Add disabled opacity class to buttons: className="disabled:opacity-50"
Avoid manual form validation; rely on Zod + resolver integrationFiles:
app/(marketing)/articles/page.tsxcomponents/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxapp/(marketing)/roadmap/page.tsxcomponents/shared/**
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Place reusable shared components under components/shared
Files:
components/shared/counter.tsxcomponents/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
components/**/*.{ts,tsx}: Always define a TypeScript interface for component props
Follow established naming patterns consistently across files
Use strict, explicit TypeScript types for safetyFiles:
components/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxcomponents/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
components/**/*.tsx: Add 'use client' directive for components that use client-side hooks/effects
Use cn() for conditional Tailwind classes
Use semantic Tailwind colors (e.g., text-foreground, bg-background) and consistent spacing (space-y-4, gap-4)
Use responsive Tailwind prefixes (md:, lg:, xl:) for adaptive layouts
Define component variants with cva and defaultVariants when variants are needed
Prefer useState for local state; use useReducer for complex state
Use TanStack Query hooks from /orpc/hooks for server state; prefer server-side data fetching when possible
Implement proper ARIA attributes and keyboard navigation for accessibility
Use React.memo for expensive components
Use useMemo and useCallback for expensive computations and stable callbacks
Lazy-load heavy components via dynamic import
Import only needed icons from lucide-react (no wildcard imports)
Wrap components that might fail in Error Boundaries
Provide loading and error states for async UI
Add JSDoc comments for complex components
components/**/*.tsx: Components must be named in kebab-case.tsx (e.g., dashboard-credential-form.tsx)
Use React Hook Form for form management in UI forms
Use React Hook Form error handling, display validation errors, and handle submission errors gracefully in formsFiles:
components/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxcomponents/**/[a-z0-9-]*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Name React components in kebab-case with .tsx under the components directory (e.g., dashboard-credential-form.tsx)
Files:
components/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxcomponents/{app,layout,shared,ui}/**
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Organize React components under components/app, components/layout, components/shared, and components/ui
Files:
components/shared/counter.tsxcomponents/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/ui/link.tsxcomponents/app/marketing-subscription.tsxcomponents/{layout,shared,ui}/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)
Organize layout, shared, and base UI components under components/layout, components/shared, and components/ui
Files:
components/shared/counter.tsxcomponents/ui/link.tsxcomponents/app/**
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Place application-specific React components under components/app
Files:
components/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/app/marketing-subscription.tsxcomponents/app/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Use naming pattern {feature}-{purpose}-{type}.tsx for app components
Files:
components/app/marketing-header-desktop.tsxcomponents/app/email-roadmap-subscription.tsxcomponents/app/marketing-articles-list.tsxcomponents/app/marketing-subscription.tsxcomponents/app/marketing-*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Marketing components in components/app must be prefixed with marketing-
Place marketing components under components/app/ with filenames starting marketing-*.tsx
Files:
components/app/marketing-header-desktop.tsxcomponents/app/marketing-articles-list.tsxcomponents/app/marketing-subscription.tsxtsconfig.json
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Enable and maintain TypeScript strict mode across the project
Enable TypeScript strict mode in tsconfig.json
Files:
tsconfig.jsoncomponents/ui/**
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Place base shadcn/ui components under components/ui
Files:
components/ui/link.tsxschemas/**/input.ts
📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)
schemas/**/input.ts: Name input schemas as {entity}{Type}InputSchema (e.g., credentialInputSchema, cardMetadataInputSchema)
CRUD input schema naming: create{Entity}InputSchema, get{Entity}InputSchema, update{Entity}InputSchema, delete{Entity}InputSchema, list{Entity}InputSchema
Provide explicit, user-friendly validation messages in input schemas (e.g., .min(..., 'message'), .email('message'))Files:
schemas/user/roadmap/input.tsorpc/hooks/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/orpc-folder.mdc)
orpc/hooks/**/*.ts: Create React Query hooks that call orpc client methods with stable queryKey factories
Use enabled flags for queries that depend on optional parameters (e.g., only fetch detail when id is truthy)
On successful mutations, invalidate relevant list queries and update detail cache via setQueryData when possible
Centralize query keys with a factory (e.g., entityKeys) to ensure consistent cache keys across hooksUse TanStack Query for server state in hooks
Files:
orpc/hooks/use-users.tsorpc/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Ensure consistent error handling and responses within oRPC routes and client code
orpc/**/*.ts: Use oRPC standard error codes (ORPCError) with meaningful, contextual messages
Handle specific error types (e.g., Prisma codes), log errors, and map to appropriate ORPC error codes
Use FORBIDDEN for permission errors; include feature and action in the message and upgrade info when applicableAlways encrypt passwords before storage in server-side handlers/services
Files:
orpc/hooks/use-users.tsorpc/routers/user.tsorpc/hooks/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Place React Query hooks for API calls under orpc/hooks/
Files:
orpc/hooks/use-users.ts**/use-*.ts
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)
Hooks files must be named use-kebab-case.ts (e.g., use-copy-to-clipboard.ts)
Files:
orpc/hooks/use-users.tsorpc/hooks/use-*.ts
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)
Place oRPC-related hooks under orpc/hooks with use-*.ts naming
Files:
orpc/hooks/use-users.tsorpc/routers/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/orpc-folder.mdc)
orpc/routers/**/*.ts: In oRPC routers, build procedures using os.$context(), then compose authProcedure and permissionProcedure via .use(...) middleware chaining
Define routes with .input(zodSchema).output(zodSchema).handler(async (...) => { ... }) to enforce schema-validated inputs/outputs
For list endpoints, implement pagination (page, limit), deterministic orderBy, and compute total/hasMore
Apply authMiddleware to protected routes and requirePermission(feature, action) where feature-gated access is needed
Use ORPCError with standard codes (e.g., UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BAD_REQUEST) for predictable error handling
Map known ORM errors (e.g., Prisma P2002) to meaningful ORPCError codes like CONFLICT and fall back to INTERNAL_SERVER_ERROR
Always validate inputs with Zod schemas and define explicit output schemas; handle validation errors gracefully
Never expose sensitive fields in responses; use client-safe entity transformations and selective include projections
Group related procedures in a single router file and use consistent naming for routes and files
Use include helpers (e.g., EntityQuery.getClientSafeInclude()) to fetch only client-safe relations
Use server-side caching or efficient queries where appropriate; prefer pagination over large result sets
Return deterministic ordering for list endpoints (e.g., orderBy createdAt desc)
Provide meaningful error messages when throwing BAD_REQUEST/CONFLICT while avoiding sensitive details
orpc/routers/**/*.ts: Group routes by feature (e.g., credentialRouter, cardRouter) in dedicated router files
Use descriptive, action-indicative procedure names within routers
Use action-based names: create, getById, list, update, delete
Keep procedure names consistent across all routers
Use camelCase for procedure names
Use Zod schemas for all procedure inputs via .input(schema)
List procedures must return metadata (total, hasMore, page, limit) alongside data
Apply authentication middleware to all protecte...Files:
orpc/routers/user.tsorpc/routers/{credential,card,secret,user}.ts
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Define oRPC routers in orpc/routers/ with files credential.ts, card.ts, secret.ts, and user.ts
Files:
orpc/routers/user.tsorpc/routers/**/!(*index).ts
📄 CodeRabbit inference engine (.cursor/rules/orpc-patterns.mdc)
Export routers individually from their own files
Files:
orpc/routers/user.tsorpc/routers/*.ts
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)
Define route handlers in orpc/routers with one file per domain (e.g., credential.ts, card.ts)
Files:
orpc/routers/user.ts🧠 Learnings (11)
📚 Learning: 2025-10-21T20:38:30.813Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/schemas-folder.mdc:0-0 Timestamp: 2025-10-21T20:38:30.813Z Learning: Applies to schemas/**/output.ts : Output schema variants: {entity}SimpleOutputSchema, {entity}IncludeOutputSchema, list{Entity}OutputSchemaApplied to files:
schemas/user/roadmap/output.ts📚 Learning: 2025-10-02T20:55:02.956Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/app-folder.mdc:0-0 Timestamp: 2025-10-02T20:55:02.956Z Learning: Applies to app/**/{page,layout}.tsx : Export a Metadata object as `export const metadata` in pages and layoutsApplied to files:
app/(marketing)/articles/page.tsx📚 Learning: 2025-10-02T22:16:27.157Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/architecture.mdc:0-0 Timestamp: 2025-10-02T22:16:27.157Z Learning: Applies to schemas/**/*.ts : Define Zod validation schemas for API and forms under schemas/Applied to files:
schemas/utils/article.ts📚 Learning: 2025-10-02T21:03:01.338Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/prisma-folder.mdc:0-0 Timestamp: 2025-10-02T21:03:01.338Z Learning: Applies to prisma/seed/index.ts : Use prisma/seed/index.ts as the seeding entrypoint; connect, run seeds in dependency order (users → platforms → credentials), handle errors, and disconnectApplied to files:
prisma.config.ts📚 Learning: 2025-10-02T22:16:27.157Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/architecture.mdc:0-0 Timestamp: 2025-10-02T22:16:27.157Z Learning: Applies to prisma/seed/** : Store database seeding scripts under prisma/seed/Applied to files:
prisma.config.ts📚 Learning: 2025-10-02T21:03:01.338Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/prisma-folder.mdc:0-0 Timestamp: 2025-10-02T21:03:01.338Z Learning: Applies to prisma/schema/schema.prisma : Keep generator and datasource configuration in prisma/schema/schema.prisma (main schema entry)Applied to files:
prisma.config.ts📚 Learning: 2025-10-02T22:17:55.478Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/database-patterns.mdc:0-0 Timestamp: 2025-10-02T22:17:55.478Z Learning: Applies to prisma/migrations/*_*/migrate.{ts,js} : Create migration scripts for complex data changesApplied to files:
prisma.config.ts📚 Learning: 2025-10-02T22:21:11.735Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/zero-locker-rules.mdc:0-0 Timestamp: 2025-10-02T22:21:11.735Z Learning: Applies to orpc/hooks/use-*.ts : Place oRPC-related hooks under orpc/hooks with use-*.ts namingApplied to files:
orpc/hooks/use-users.ts📚 Learning: 2025-10-02T22:19:00.164Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/orpc-patterns.mdc:0-0 Timestamp: 2025-10-02T22:19:00.164Z Learning: Applies to orpc/**/*.ts : Handle specific error types (e.g., Prisma codes), log errors, and map to appropriate ORPC error codesApplied to files:
orpc/routers/user.ts📚 Learning: 2025-10-02T21:01:44.094Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/orpc-folder.mdc:0-0 Timestamp: 2025-10-02T21:01:44.094Z Learning: Applies to orpc/routers/**/*.ts : Map known ORM errors (e.g., Prisma P2002) to meaningful ORPCError codes like CONFLICT and fall back to INTERNAL_SERVER_ERRORApplied to files:
orpc/routers/user.ts📚 Learning: 2025-10-02T22:19:00.164Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/orpc-patterns.mdc:0-0 Timestamp: 2025-10-02T22:19:00.164Z Learning: Applies to orpc/**/*.ts : Use oRPC standard error codes (ORPCError) with meaningful, contextual messagesApplied to files:
orpc/routers/user.ts🧬 Code graph analysis (11)
app/(marketing)/articles/page.tsx (3)
config/site.ts (1)
siteConfig(3-41)components/app/marketing-articles-list.tsx (1)
MarketingArticlesList(11-86)components/app/marketing-subscription.tsx (1)
MarketingSubscription(33-157)components/app/marketing-header-desktop.tsx (1)
components/ui/link.tsx (1)
Link(9-35)components/app/email-roadmap-subscription.tsx (2)
config/site.ts (1)
siteConfig(3-41)components/ui/link.tsx (1)
Link(9-35)app/(marketing)/articles/[slug]/page.tsx (3)
components/ui/link.tsx (1)
Link(9-35)components/shared/icons.tsx (1)
Icons(165-293)lib/date-utils.ts (1)
DateFormatter(11-106)components/app/marketing-articles-list.tsx (2)
lib/date-utils.ts (1)
DateFormatter(11-106)components/shared/icons.tsx (1)
Icons(165-293)lib/email/send-roadmap-subscription-email.ts (4)
config/site.ts (1)
siteConfig(3-41)lib/email/resend-client.ts (1)
resendClient(5-5)env.ts (1)
env(4-27)components/app/email-roadmap-subscription.tsx (1)
EmailSubscription(25-130)components/app/marketing-subscription.tsx (2)
schemas/user/roadmap/input.ts (2)
subscriptionInputSchema(7-14)SubscriptionInput(21-21)orpc/hooks/use-users.ts (1)
useSubscribeToUpdates(94-101)lib/content-collections.ts (1)
schemas/utils/article.ts (1)
articleSchema(3-10)app/(marketing)/roadmap/page.tsx (1)
components/app/marketing-subscription.tsx (1)
MarketingSubscription(33-157)orpc/hooks/use-users.ts (1)
orpc/client/utils.ts (1)
orpc(5-7)orpc/routers/user.ts (3)
lib/email/send-roadmap-subscription-email.ts (1)
sendSubscriptionEmail(13-40)schemas/user/roadmap/input.ts (1)
subscriptionInputSchema(7-14)schemas/user/roadmap/output.ts (2)
subscriptionOutputSchema(7-10)SubscriptionOutput(18-18)🔇 Additional comments (25)
styles/globals.css (1)
4-5: LGTM! Typography plugin properly configured.The Tailwind typography plugin is correctly imported to support MDX-rendered article content. The placement after the
@importstatements follows the correct order for Tailwind v4 configuration.prisma.config.ts (1)
15-15: LGTM! Improved string quoting.Switching to single quotes eliminates the need to escape inner double quotes, improving readability while maintaining functional equivalence.
.gitignore (1)
44-46: LGTM! Properly excluding generated content.The ignore patterns correctly exclude content-collections generated directories, which aligns with the new content-collections integration and the path alias in
tsconfig.json.components/app/marketing-header-desktop.tsx (1)
29-34: LGTM! Articles link properly integrated.The new Articles navigation link follows the established pattern and styling of the existing Roadmap link, with appropriate mobile-responsive conditional rendering.
package.json (1)
36-38: LGTM! Dependencies properly added.The new dependencies for content-collections (
@content-collections/*) and Tailwind typography (@tailwindcss/typography) correctly support the MDX article rendering and styling features introduced in this PR.Also applies to: 76-76
components/app/email-roadmap-subscription.tsx (2)
25-29: LGTM! Type-based dynamic content properly implemented.The refactoring to support both "roadmap" and "articles" subscription types with derived title, linkUrl, and linkText is well-structured and follows good practices for component generalization.
75-105: LGTM! Conditional content rendering correctly implemented.The conditional blocks properly differentiate between roadmap-specific content (product milestones, roadmap updates, early access) and articles-specific content (technical insights, business updates, feature announcements) based on the subscription type.
tsconfig.json (1)
23-24: LGTM! Content-collections path alias properly configured.The new
@/content-collectionspath alias correctly maps to the generated content-collections directory, enabling clean imports of generated types and content. The trailing comma addition maintains consistent formatting.schemas/user/roadmap/input.ts (2)
7-14: Well-structured subscription input schema.The schema properly validates email and subscription type with clear error messages. The use of
z.enumwith a customerrorMapprovides a good user experience.
21-22: Type exports follow naming conventions.The exported types
SubscriptionInputandRoadmapSubscribeInputfollow the required naming pattern for schema-inferred types.data/articles/advanced-security-features.mdx (3)
1-8: Front matter follows article schema structure.The metadata fields (id, title, description, publishedAt, image, href) align with the articleSchema defined for content-collections integration.
18-20: All external links are valid and accessible.Verification confirms all URLs in the article are reachable with proper HTTP status codes (200 for direct access, 301 for permanent redirects). No broken or unreachable links were detected.
26-28: Counter component is properly available in MDX context.The Counter component is correctly injected. It's imported in
app/(marketing)/articles/[slug]/page.tsxand explicitly passed to theMDXContentcomponents prop, making it available to theadvanced-security-features.mdxarticle.schemas/user/roadmap/output.ts (1)
7-10: Output schema is correctly structured.The schema defines a clear success/error response pattern with proper optional handling for the error field.
components/shared/counter.tsx (1)
1-35: Component implementation follows best practices.The Counter component correctly uses:
- Client directive for useState
- Explicit TypeScript interface for props
- Default prop values
- Functional component pattern
next.config.ts (2)
23-23: Export wrapped with content-collections plugin.The Next.js config is now wrapped with the content-collections plugin, enabling MDX content processing during build.
2-6: Content Collections plugin integration correctly configured.The configPath file exists and the plugin wrapper is properly initialized.
data/articles/getting-started-with-zero-locker.mdx (2)
1-8: Front matter structure is consistent with article schema.The metadata follows the same pattern as the other article, ensuring consistent processing by content-collections.
27-29: Counter component usage consistent with other articles.The Counter is used with
initialValue={0} step={1}, matching the expected props interface. Ensure it's available in the MDX components context (same verification as the other article applies here).schemas/utils/index.ts (1)
10-10: LGTM - Barrel export is consistent and safe.The new
export * from "./article"follows the existing pattern for utility schema exports. No circular dependencies are present because the codebase correctly uses concrete file imports (e.g.,@/schemas/utils/platform/output) rather than barrel imports from@/schemas/utilswithin the schemas folder.app/(marketing)/articles/page.tsx (1)
8-18: Page metadata and composition look solid.App Router conventions followed; metadata defined and server component usage is correct.
Also applies to: 20-35
app/(marketing)/roadmap/page.tsx (1)
8-8: Subscription component swap looks correct.Updated import and usage align with the new generalized
MarketingSubscription.Also applies to: 67-70
orpc/routers/user.ts (1)
248-253: Email dispatch update is correct.Passing
type: "roadmap"to the generalized sender is appropriate.lib/content-collections.ts (1)
7-8: Let me inspect the actual repository files to verify the configuration:Based on my verification:
The review comment's verification request is appropriate.
✅ Verified:
data/articlesexists at the project root.
⚠️ Unresolved: The critical question remains whether the content-collections plugin resolves thedirectorypath:
- Relative to the config file location (
lib/), where../data/articleswould be correct- Relative to the project root, where it should be
data/articlesThe non-standard config location (
lib/content-collections.tsinstead of the documented convention ofcontent-collections.tsat project root) combined with available web search results (which show industry-standard behavior of resolving paths from project root) suggests../data/articlesmay be incorrect. However, I cannot definitively confirm the plugin's actual behavior without access to its source code or explicit documentation on path resolution.app/(marketing)/articles/[slug]/page.tsx (1)
14-16: ****The current code is correct for Next.js 15. The official Next.js 15 documentation explicitly requires
params: Promise<{ slug: string }>withawait paramsbefore accessing properties—exactly as implemented in the file. The suggested changes to remove the Promise type and await calls would introduce runtime errors and contradict the framework's requirements.Likely an incorrect or invalid review comment.
| <Image | ||
| src={image} | ||
| alt={`${title} cover image`} | ||
| className="object-cover" | ||
| fill | ||
| priority | ||
| /> |
There was a problem hiding this comment.
Image alt text and responsive sizes.
- Alt text includes the word “image,” which violates the repo guideline. Use a meaningful alt without “image/picture/photo.”
- With
fill, provide asizesattribute for correct responsive behavior. As per coding guidelines.
<Image
src={image}
- alt={`${title} cover image`}
+ alt={`${title} cover`}
className="object-cover"
fill
+ sizes="(max-width: 640px) 100vw, (max-width: 1024px) 90vw, 896px"
priority
/>🤖 Prompt for AI Agents
In app/(marketing)/articles/[slug]/page.tsx around lines 91 to 97, the Image
component uses an alt that contains the word "image" and is missing a sizes prop
while using fill; update the alt to a concise descriptive phrase without the
words "image", "photo", or "picture" (for example use `${title} cover` or
another meaningful description) and add a responsive sizes attribute appropriate
for the layout (e.g. a mobile-first sizes string such as "(max-width: 640px)
100vw, 640px" or whatever fits the design breakpoints) so the Image has both an
accessible, guideline-compliant alt and correct responsive sizing when using
fill.
| // Subscribe to updates (roadmap or articles) | ||
| export function useSubscribeToUpdates() { | ||
| return useMutation({ | ||
| mutationFn: orpc.users.subscribeToUpdates.call, | ||
| onError: (error) => { | ||
| console.error("Failed to subscribe to updates:", error) | ||
| }, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Remove console.error for production code.
The error handler uses console.error, which violates the coding guideline prohibiting console usage. Consider using a proper error logging service or removing the error handler if errors are handled by the caller.
Apply this diff to remove the console statement:
export function useSubscribeToUpdates() {
return useMutation({
mutationFn: orpc.users.subscribeToUpdates.call,
- onError: (error) => {
- console.error("Failed to subscribe to updates:", error)
- },
})
}🤖 Prompt for AI Agents
In orpc/hooks/use-users.ts around lines 93 to 101, remove the console.error
usage by deleting the onError handler block entirely (i.e., remove the onError:
(error) => { console.error(...) },) so the mutation does not call console; if
you need error reporting instead, replace that block with a call to the
project's central logger or error-reporting service (e.g.,
processLogger.error(error) or sendToSentry) rather than using console.
| // Subscribe to updates (roadmap or articles) - unified endpoint | ||
| export const subscribeToUpdates = strictPublicProcedure | ||
| .input(subscriptionInputSchema) | ||
| .output(subscriptionOutputSchema) | ||
| .handler(async ({ input }): Promise<SubscriptionOutput> => { | ||
| try { | ||
| const { email, type } = input | ||
|
|
||
| // Check if email already exists in subscriptions for this type | ||
| const existingSubscription = | ||
| await database.roadmapSubscription.findUnique({ | ||
| where: { email }, | ||
| }) | ||
|
|
||
| if (existingSubscription) { | ||
| return { | ||
| success: false, | ||
| error: "Email is already subscribed", | ||
| } | ||
| } | ||
|
|
||
| // Add to roadmap subscriptions (we'll use the same table for now) | ||
| // TODO: Consider creating a unified subscriptions table with a type field | ||
| await database.roadmapSubscription.create({ | ||
| data: { | ||
| email, | ||
| }, | ||
| }) | ||
|
|
||
| // Send subscription confirmation email | ||
| try { | ||
| await sendSubscriptionEmail({ | ||
| to: email, | ||
| type, | ||
| }) | ||
| } catch (emailError) { | ||
| console.error(`Failed to send ${type} subscription email:`, emailError) | ||
| // Don't fail the request if email fails | ||
| } | ||
|
|
||
| return { success: true } | ||
| } catch (error) { | ||
| // Re-throw ORPC errors to let ORPC handle them | ||
| if (error instanceof ORPCError) { | ||
| throw error | ||
| } | ||
|
|
||
| // Handle Prisma-specific errors | ||
| if (error instanceof Prisma.PrismaClientKnownRequestError) { | ||
| console.error( | ||
| `Database constraint error subscribing to ${input.type}:`, | ||
| { | ||
| code: error.code, | ||
| message: error.message, | ||
| meta: error.meta, | ||
| } | ||
| ) | ||
|
|
||
| // Handle unique constraint violations | ||
| if (error.code === "P2002") { | ||
| return { | ||
| success: false, | ||
| error: "Email is already subscribed", | ||
| } | ||
| } | ||
|
|
||
| // Handle other known Prisma errors | ||
| return { | ||
| success: false, | ||
| error: "Database constraint violation occurred", | ||
| } | ||
| } | ||
|
|
||
| // Handle Prisma client errors (connection issues, etc.) | ||
| if (error instanceof Prisma.PrismaClientUnknownRequestError) { | ||
| console.error(`Unknown Prisma error subscribing to ${input.type}:`, { | ||
| message: error.message, | ||
| }) | ||
| return { | ||
| success: false, | ||
| error: "Database connection issue occurred", | ||
| } | ||
| } | ||
|
|
||
| // Handle Prisma validation errors | ||
| if (error instanceof Prisma.PrismaClientValidationError) { | ||
| console.error(`Prisma validation error subscribing to ${input.type}:`, { | ||
| message: error.message, | ||
| }) | ||
| return { | ||
| success: false, | ||
| error: "Invalid data provided", | ||
| } | ||
| } | ||
|
|
||
| // Handle unexpected errors | ||
| console.error(`Unexpected error subscribing to ${input.type}:`, error) | ||
| return { | ||
| success: false, | ||
| error: "An unexpected error occurred. Please try again later.", | ||
| } | ||
| } | ||
| }) | ||
|
|
There was a problem hiding this comment.
Functional bug: unified subscription uses a single-table/email-only key — can’t subscribe to both types.
subscribeToUpdates queries and inserts into roadmapSubscription keyed only by email, so an existing roadmap subscriber can’t subscribe to articles. This defeats the new “type-aware” flow. Fix by storing type alongside email with a composite unique key, and querying by both. Also prefer upsert to avoid TOCTOU. Replace console.error with a structured logger.
-// Subscribe to updates (roadmap or articles) - unified endpoint
-export const subscribeToUpdates = strictPublicProcedure
+// Subscribe to updates (roadmap or articles) - unified endpoint
+export const subscribeToUpdates = strictPublicProcedure
.input(subscriptionInputSchema)
.output(subscriptionOutputSchema)
.handler(async ({ input }): Promise<SubscriptionOutput> => {
try {
const { email, type } = input
- // Check if email already exists in subscriptions for this type
- const existingSubscription =
- await database.roadmapSubscription.findUnique({
- where: { email },
- })
-
- if (existingSubscription) {
- return {
- success: false,
- error: "Email is already subscribed",
- }
- }
-
- // Add to roadmap subscriptions (we'll use the same table for now)
- // TODO: Consider creating a unified subscriptions table with a type field
- await database.roadmapSubscription.create({
- data: {
- email,
- },
- })
+ // Persist subscription by (email, type)
+ // NOTE: Requires Prisma migration: a `marketingSubscription` model with fields { id, email, type, createdAt }
+ // and a unique index on (email, type).
+ await database.marketingSubscription.upsert({
+ where: { email_type: { email, type } },
+ update: {},
+ create: { email, type },
+ })
// Send subscription confirmation email
try {
await sendSubscriptionEmail({
to: email,
type,
})
} catch (emailError) {
- console.error(`Failed to send ${type} subscription email:`, emailError)
+ // logger.error(`Failed to send ${type} subscription email`, { error: emailError })
}
return { success: true }
} catch (error) {
// Re-throw ORPC errors to let ORPC handle them
if (error instanceof ORPCError) {
throw error
}
// Handle Prisma-specific errors
if (error instanceof Prisma.PrismaClientKnownRequestError) {
- console.error(
- `Database constraint error subscribing to ${input.type}:`,
- {
- code: error.code,
- message: error.message,
- meta: error.meta,
- }
- )
+ // logger.warn(`Constraint error subscribing to ${input.type}`, { code: error.code, meta: error.meta })
// Handle unique constraint violations
if (error.code === "P2002") {
return {
success: false,
error: "Email is already subscribed",
}
}
// Handle other known Prisma errors
return {
success: false,
error: "Database constraint violation occurred",
}
}
// Handle Prisma client errors (connection issues, etc.)
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
- console.error(`Unknown Prisma error subscribing to ${input.type}:`, {
- message: error.message,
- })
+ // logger.error(`Unknown Prisma error subscribing to ${input.type}`, { message: error.message })
return {
success: false,
error: "Database connection issue occurred",
}
}
// Handle Prisma validation errors
if (error instanceof Prisma.PrismaClientValidationError) {
- console.error(`Prisma validation error subscribing to ${input.type}:`, {
- message: error.message,
- })
+ // logger.error(`Prisma validation error subscribing to ${input.type}`, { message: error.message })
return {
success: false,
error: "Invalid data provided",
}
}
// Handle unexpected errors
- console.error(`Unexpected error subscribing to ${input.type}:`, error)
+ // logger.error(`Unexpected error subscribing to ${input.type}`, { error })
return {
success: false,
error: "An unexpected error occurred. Please try again later.",
}
}
})Migration note (required):
- Add a
marketingSubscriptionmodel withemail string,type enum('roadmap','articles'), and@@unique([email, type]). - Backfill existing
roadmapSubscriptionrows into the new table withtype='roadmap'.
As per coding guidelines (avoid index-based keys, avoid console, map DB constraints deterministically).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In orpc/routers/user.ts around lines 320–423 the unified subscription currently
queries and writes only by email into roadmapSubscription, preventing a user
from subscribing to multiple types; update the handler to store and check both
email and type (use a composite unique key [email,type]) and replace the
create-with-prior-find flow with an upsert (or create with unique composite
lookup) to avoid TOCTOU; also replace console.error calls with the project’s
structured logger (include error details), and update returned error messages to
reflect type-aware checks; separately add a migration to introduce a
marketingSubscription model with fields email:string and
type:enum('roadmap','articles') plus @@unique([email,type]) and backfill
existing roadmapSubscription rows as type='roadmap'.
| id: z.number(), | ||
| title: z.string(), | ||
| description: z.string(), | ||
| publishedAt: z.string(), |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Validate date format for publishedAt.
The publishedAt field accepts any string without date format validation. This could lead to invalid dates being stored and causing runtime errors during date parsing or formatting operations.
Apply this diff to add date string validation:
- publishedAt: z.string(),
+ publishedAt: z.string().datetime(),Or if you need date-only format (YYYY-MM-DD):
- publishedAt: z.string(),
+ publishedAt: z.string().date(),📝 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.
| publishedAt: z.string(), | |
| publishedAt: z.string().datetime(), |
🤖 Prompt for AI Agents
In schemas/utils/article.ts around line 7, the publishedAt field currently
allows any string; validate it to enforce a proper date format. Replace the
loose z.string() with either a Zod refinement that parses the string to a Date
and checks validity (e.g., use z.string().refine(s =>
!Number.isNaN(Date.parse(s)), { message: 'Invalid ISO date' })) to accept ISO
datetimes, or use a regex-based check for date-only YYYY-MM-DD (e.g.,
z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format')). Ensure the
validation message is descriptive and keep the field as a string type in the
schema.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/app/marketing-subscription.tsx (1)
38-43: Fix form type mismatch with schema.The form is typed as
useForm<SubscriptionInput>and usessubscriptionInputSchema(which requires bothtypefields), but the form only collects thetypecomes from props and is never part of the form values. This creates a type safety issue where the form's actual shape doesn't match its declared type.Consider one of these solutions:
Solution 1 (Recommended): Create an email-only schema and type the form correctly
+// At the top of the file, create a local schema +const emailOnlySchema = z.object({ + email: z.string().min(1, "Email is required").email("Invalid email address"), +}) + +type EmailFormData = z.infer<typeof emailOnlySchema> + - const form = useForm<SubscriptionInput>({ - resolver: zodResolver(subscriptionInputSchema), + const form = useForm<EmailFormData>({ + resolver: zodResolver(emailOnlySchema), defaultValues: { email: "", }, })Then update the
onSubmitsignature:- async function onSubmit(values: SubscriptionInput) { + async function onSubmit(values: EmailFormData) { subscribeToUpdatesMutation.mutate(Solution 2: Add type as a hidden form field
const form = useForm<SubscriptionInput>({ resolver: zodResolver(subscriptionInputSchema), defaultValues: { email: "", + type, }, })And add a hidden input in the form JSX after the email field, though this is less clean since the type is already available from props.
♻️ Duplicate comments (2)
app/(marketing)/articles/[slug]/page.tsx (1)
90-96: Image alt text and responsive sizes (previously flagged).This issue was already identified in a previous review:
- Alt text includes the word "image," which violates the coding guideline
- With
fill, provide asizesattribute for correct responsive behaviorAs per coding guidelines:
<Image src={image} - alt={`${title} cover image`} + alt={`${title} cover`} className="object-cover" fill + sizes="(max-width: 640px) 100vw, (max-width: 1024px) 90vw, 896px" priority />components/app/marketing-subscription.tsx (1)
81-86: Type-safe error handling needed.The
errorparameter is not typed and the code assumeserror.messageexists without type checking. This is the same issue flagged in a previous review.Apply the previously suggested fix:
- onError: (error) => { + onError: (error: unknown) => { + const message = + error instanceof Error + ? error.message + : "Something went wrong. Please try again." toast.error("Subscription failed", { - description: - error.message || "Something went wrong. Please try again.", + description: message, }) },As per coding guidelines.
🧹 Nitpick comments (2)
app/(marketing)/articles/[slug]/page.tsx (2)
17-21: Consider more robust slug extraction.Using
split("/").pop()assumes the href always follows a specific format and could be fragile. If the article schema includes aslugproperty, use it directly for clarity and reliability.If a
slugproperty exists:export async function generateStaticParams() { - return allArticles.map((article) => ({ - slug: article.href.split("/").pop(), - })) + return allArticles.map((article) => ({ + slug: article.slug, + })) }
23-42: Remove unnecessary template literal in title.The template literal on line 34 is redundant since
article.titleis already a string.return { - title: `${article.title}`, + title: article.title, description: article.description,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/(marketing)/articles/[slug]/page.tsx(1 hunks)components/app/marketing-subscription.tsx(5 hunks)next.config.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{js,jsx,ts,tsx}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't write functions that exceed a given Cognitive Complexity score
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Use for...of statements instead of Array.forEach
Don't create classes that only have static members
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Don't use ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use arrow functions instead of function expressions
Use Date.now() to get milliseconds since the Unix Epoch
Use .flatMap() instead of map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work
Use concise optional chaining instead of chained logical expressions
Use regular expression literals instead of the RegExp constructor when possible
Don't use number literal object member names that aren't base 10 or use underscore separators
Remove redundant terms from logical expressions
Use while loops instead of for loops when you don't need initializer and update expressions
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min and Math.max to clamp value...
Files:
next.config.tscomponents/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Make sure switch-case statements are exhaustive
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibility modifiers on class properties and methods
Put default function parameters and optional function parameters last
Initialize each enum member value explicitly
Use export type for types
Use import type for types
Make sure all enum members are literal values
Use function types instead of object types with call signatures
Don't use void type outside of generic or return types
Don't use TypeScript const enum
Don't declare empty interfaces
Don't let variables evolve into any type through reassignments
Don't use the any type
Don't misuse the non-null assertion operator (!) in TypeScript files
Don't use implicit any type on variable declarations
Don't merge interfaces and classes unsafely
Don't use overload signatures that aren't next to each other
Use the namespace keyword instead of the module keyword to declare TypeScript namespacesUse type-only imports when possible
**/*.{ts,tsx}: Use camelCase for function and method names
Use UPPER_SNAKE_CASE for constants
Use camelCase for variables
Use PascalCase for classes and types
Use absolute imports for top-level modules (e.g., @/components, @/lib, @/entities) instead of long relative paths
Follow import order: React imports, third-party, internal abso...
Files:
next.config.tscomponents/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsx
**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{js,ts}: Don't use __dirname and __filename in the global scope
Use with { type: "json" } for JSON module imports
Don't access namespace imports dynamically
Use node:assert/strict over node:assert
Use the node: protocol for Node.js builtin modules
Files:
next.config.ts
**/*.{html,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{html,jsx,tsx}: Don't use the accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for the tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use an explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a title attribute for iframe elements
Include caption tracks for audio and video elements
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Use correct ISO language/country codes for the lang attribute
Don't use variables that haven't been declared in the document
Make sure void (self-closing) elements don't have children
Don't use event handlers on non-interactiv...Files:
components/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsx**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
**/*.{jsx,tsx}: Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Use semantic elements instead of role attributes in JSX
Don't use unnecessary fragments
Don't pass children as props
Don't use the return value of React.render
Make sure all dependencies are correctly specified in React hooks
Make sure all React hooks are called from the top level of component functions
Don't forget key props in iterators and collection literals
Don't define React components inside other components
Don't assign to React component props
Don't use dangerous JSX props
Don't use both children and dangerouslySetInnerHTML props on the same element
Use <>...</> instead of ...
Don't add extra closing tags for components without children
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Watch out for possible "wrong" semicolons inside JSX elements
Don't put multiple components in one file; each file must have one componentFiles:
components/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsx**/*.{html,jsx,tsx,css}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Don't use TailwindCSS class names 'h-NUMBER w-NUMBER'; instead use 'size-NUMBR'
Files:
components/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxcomponents/app/**
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Place application-specific React components under components/app
Files:
components/app/marketing-subscription.tsxcomponents/app/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Use naming pattern {feature}-{purpose}-{type}.tsx for app components
Files:
components/app/marketing-subscription.tsxcomponents/app/marketing-*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
Marketing components in components/app must be prefixed with marketing-
Place marketing components under components/app/ with filenames starting marketing-*.tsx
Files:
components/app/marketing-subscription.tsxcomponents/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
components/**/*.{ts,tsx}: Always define a TypeScript interface for component props
Follow established naming patterns consistently across files
Use strict, explicit TypeScript types for safetyFiles:
components/app/marketing-subscription.tsxcomponents/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)
components/**/*.tsx: Add 'use client' directive for components that use client-side hooks/effects
Use cn() for conditional Tailwind classes
Use semantic Tailwind colors (e.g., text-foreground, bg-background) and consistent spacing (space-y-4, gap-4)
Use responsive Tailwind prefixes (md:, lg:, xl:) for adaptive layouts
Define component variants with cva and defaultVariants when variants are needed
Prefer useState for local state; use useReducer for complex state
Use TanStack Query hooks from /orpc/hooks for server state; prefer server-side data fetching when possible
Implement proper ARIA attributes and keyboard navigation for accessibility
Use React.memo for expensive components
Use useMemo and useCallback for expensive computations and stable callbacks
Lazy-load heavy components via dynamic import
Import only needed icons from lucide-react (no wildcard imports)
Wrap components that might fail in Error Boundaries
Provide loading and error states for async UI
Add JSDoc comments for complex components
components/**/*.tsx: Components must be named in kebab-case.tsx (e.g., dashboard-credential-form.tsx)
Use React Hook Form for form management in UI forms
Use React Hook Form error handling, display validation errors, and handle submission errors gracefully in formsFiles:
components/app/marketing-subscription.tsxcomponents/**/[a-z0-9-]*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)
Name React components in kebab-case with .tsx under the components directory (e.g., dashboard-credential-form.tsx)
Files:
components/app/marketing-subscription.tsxcomponents/{app,layout,shared,ui}/**
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Organize React components under components/app, components/layout, components/shared, and components/ui
Files:
components/app/marketing-subscription.tsx**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/zero-locker-patterns.mdc)
**/*.tsx: Use Component Composition Pattern: build complex UI by composing smaller, reusable components (e.g., Form, FormField, FormItem)
Handle form errors with React Hook Form and Zod resolver for client-side validation
Avoid direct database access in React components; use hooks/services instead
**/*.tsx: Use PascalCase for React component names
Use functional components only, typed with explicit props interfaces
Optimize components using React.memo for expensive components, useMemo for heavy computations, and useCallback for stable handlers
**/*.tsx: Never use useState for form state; use React Hook Form (useForm) instead
Always use Zod schemas with zodResolver for form validation
Always use shadcn Form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
Always use Icons.spinner with animate-spin for loading states
Never use text like "Deleting..." or "Loading..." for loading states
Use size-4 instead of h-4 w-4 for spinner sizing (Tailwind)
Disable buttons during loading: disabled={isLoading}
Show spinner + text inside buttons when loading
Use Button variant="destructive" for delete actions
Add disabled opacity class to buttons: className="disabled:opacity-50"
Avoid manual form validation; rely on Zod + resolver integrationFiles:
components/app/marketing-subscription.tsxapp/(marketing)/articles/[slug]/page.tsxapp/**/page.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
Page files must be named page.tsx and define the page component and metadata
Page components in the Next.js App Router must be named page.tsx
Pages must be named page.tsx
Files:
app/(marketing)/articles/[slug]/page.tsxapp/**/[(]*[)]/**
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
Use parentheses-named folders (group) to create route groups that don’t affect the URL
Files:
app/(marketing)/articles/[slug]/page.tsxapp/**/{page,layout}.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
app/**/{page,layout}.tsx: Export a Metadata object asexport const metadatain pages and layouts
Perform server-side data fetching in pages and layouts where possibleFiles:
app/(marketing)/articles/[slug]/page.tsxapp/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)
app/**/*.tsx: Prefer Server Components by default in the app directory
Add "use client" only when necessary for Client ComponentsFiles:
app/(marketing)/articles/[slug]/page.tsxapp/(marketing)/**
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Place marketing and landing content within app/(marketing)/
Files:
app/(marketing)/articles/[slug]/page.tsx🧠 Learnings (1)
📚 Learning: 2025-09-30T22:00:32.336Z
Learnt from: CR PR: FindMalek/zero-locker#0 File: .cursor/rules/general.mdc:0-0 Timestamp: 2025-09-30T22:00:32.336Z Learning: Applies to **/*.{html,jsx,tsx} : Give all elements requiring alt text meaningful information for screen readersApplied to files:
app/(marketing)/articles/[slug]/page.tsx🧬 Code graph analysis (2)
components/app/marketing-subscription.tsx (2)
orpc/hooks/use-users.ts (1)
useSubscribeToUpdates(94-101)schemas/user/roadmap/input.ts (2)
SubscriptionInput(21-21)subscriptionInputSchema(7-14)app/(marketing)/articles/[slug]/page.tsx (3)
components/ui/link.tsx (1)
Link(9-35)components/shared/icons.tsx (1)
Icons(165-293)lib/date-utils.ts (1)
DateFormatter(11-106)🔇 Additional comments (6)
next.config.ts (2)
2-6: LGTM! Content Collections plugin properly configured.The plugin integration follows the standard pattern and correctly references the configuration file at
./lib/content-collections.ts.
19-31: LGTM! Image patterns and plugin export configured correctly.The new remote patterns enable images from the specified domains, and the config is properly wrapped with the content-collections plugin.
app/(marketing)/articles/[slug]/page.tsx (3)
1-15: LGTM! Imports and type definitions are correct.The interface correctly types
paramsas a Promise, which aligns with Next.js 15's async params requirement.
44-85: LGTM! Article lookup and header rendering are well-structured.The component correctly handles async params, validates the article exists, and provides clear navigation. The article lookup duplication between
generateMetadataand the component is standard in Next.js.
101-110: LGTM! MDX rendering configured correctly.The prose styling and CustomLink component override ensure proper article rendering and link behavior.
components/app/marketing-subscription.tsx (1)
99-151: LGTM: Clean form implementation following guidelines.The JSX structure correctly follows all coding guidelines:
- Uses shadcn Form components properly
- Button includes
type="submit"attribute- Loading state uses
Icons.spinnerwithsize-4andanimate-spin- Buttons are disabled during pending state
- Shows spinner with text (not "Loading..." text)
- Proper accessibility with semantic HTML and form validation
Summary by CodeRabbit
New Features
Chores