Skip to content

Conversation

@ritesh301
Copy link
Contributor

@ritesh301 ritesh301 commented Nov 19, 2025

πŸš€ Pull Request

πŸ“‹ Description

Brief description of what this PR does.

🎯 Type of Change

  • πŸ› Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • πŸ’₯ Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • πŸ“š Documentation update
  • 🎨 Code style/formatting
  • ♻️ Code refactoring
  • ⚑ Performance improvements
  • πŸ§ͺ Tests

πŸ”— Related Issue

Fixes #(issue number)

πŸ§ͺ Testing

  • Tested locally
  • Added/updated tests
  • All tests pass
  • Manual testing completed

πŸ“Έ Screenshots/Videos

If applicable, add screenshots or videos demonstrating the changes.

πŸ“‹ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

🌍 Environment Tested

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Firefox, Safari] (if applicable)
  • Node Version: [e.g. 18.x, 20.x]

πŸ“ Additional Notes

Any additional information that reviewers should know.

Summary by CodeRabbit

New Features

  • Redesigned sign-in and sign-up pages with improved UI and clearer error messaging.
  • New dashboard redirect path for authenticated users post-login.
  • Added dedicated features page showcasing core capabilities and technical highlights.
  • Enhanced homepage with expanded feature cards, tech stack showcase, and call-to-action sections.
  • Implemented comprehensive UI component library for consistent design across the app.

Style

  • Introduced dark mode support with class-based theme switching.
  • Updated color system with semantic design tokens for improved visual consistency.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Introduces a comprehensive UI component library built on Radix UI and Tailwind CSS with 60+ components, redesigns authentication pages and homepage with Card-based layouts, adds theme provider support, configures shadcn/ui infrastructure, establishes Tailwind dark mode and design tokens, and initializes a Prisma database schema for user authentication.

Changes

Cohort / File(s) Summary
Authentication Pages Redesign
apps/web/app/(auth)/signin/page.tsx, apps/web/app/(auth)/signup/page.tsx
Refactored SignIn and SignUp components from inline forms to Card-based layouts with labeled inputs, OAuth integration, updated error messaging, and redirects changed from "/" to "/dashboard". Added branding headers and back-to-home navigation.
Core UI Component Library
apps/web/app/components/ui/accordion.tsx, accordion, alert.tsx, alert-dialog.tsx, aspect-ratio.tsx, avatar.tsx, badge.tsx, breadcrumb.tsx, button.tsx, calendar.tsx, card.tsx, carousel.tsx, chart.tsx, checkbox.tsx, collapsible.tsx, command.tsx
Radix UI primitive wrappers with consistent data-slot attributes, className composition via cn utility, and theme-aware styling. Accordion, Alert, AspectRatio, Avatar, Badge, Breadcrumb, Button, Calendar, Card, Carousel, Chart.
Additional UI Components (Context to Tooltip)
apps/web/app/components/ui/context-menu.tsx, dialog.tsx, drawer.tsx, dropdown-menu.tsx, form.tsx, hover-card.tsx, input-otp.tsx, input.tsx, label.tsx, menubar.tsx, navigation-menu.tsx, pagination.tsx, popover.tsx, progress.tsx, radio-group.tsx, resizable.tsx, scroll-area.tsx, select.tsx, separator.tsx, sheet.tsx, sidebar.tsx
Complex interactive components including ContextMenu, Dialog, Drawer, DropdownMenu, Form (with react-hook-form integration), HoverCard, InputOTP, Label, Menubar, NavigationMenu, Pagination, Popover, Progress, RadioGroup, Resizable, ScrollArea, Select, Separator, Sheet, and Sidebar (with context and keyboard support).
Utility UI Components (Skeleton to Tooltip)
apps/web/app/components/ui/skeleton.tsx, slider.tsx, sonner.tsx, switch.tsx, table.tsx, tabs.tsx, textarea.tsx, toast.tsx, toaster.tsx, toggle-group.tsx, toggle.tsx, tooltip.tsx, use-mobile.tsx, use-toast.ts
Skeleton placeholders, Slider, Sonner Toaster wrapper, Switch, Table, Tabs, Textarea, Toast system with state management and UI rendering. Includes useIsMobile hook and useToast hook with pub-sub pattern.
Theme & Configuration
apps/web/app/components/theme-provider.tsx, apps/web/tailwind.config.js, apps/web/globals.css, apps/web/components.json
Added ThemeProvider wrapper for next-themes, enabled class-based dark mode in Tailwind, defined comprehensive color token system via CSS variables, and configured shadcn/ui tooling with icon library (lucide), style preset (new-york), and path aliases.
Page Redesigns & New Features
apps/web/app/features/page.tsx, apps/web/app/page.tsx
New Features page showcasing core development features, technical capabilities, tech stack, and CTA. Redesigned HomePage with multi-section layout including header, hero, stats, feature grid, how-it-works, tech stack, use cases, and footer with programmatic routing.
Utility & Configuration
apps/web/app/lib/utils.ts, apps/web/lib/utils.ts
Added cn() utility function for composing Tailwind classes with clsx and tailwind-merge for conflict resolution. Duplicated across two lib directories.
Legacy UI Components
apps/web/components/ui/badge.tsx, button.tsx, card.tsx, input.tsx, label.tsx
Older versions of UI components (Badge, Button, Card, Input, Label) in legacy directory using forwardRef and direct styling.
Dependencies & Database
apps/web/package.json, apps/web/prisma/migrations/.../*
Added dependencies: @radix-ui/react-label, @radix-ui/react-slot, class-variance-authority, clsx, lucide-react, tailwind-merge, tailwindcss-animate. Initialized Prisma schema with User, Account, Session, VerificationToken, and Authenticator tables with cascading foreign keys and indexes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • Sidebar component (apps/web/app/components/ui/sidebar.tsx) β€” Complex context provider with cookie-based state persistence, keyboard shortcuts (Ctrl+B toggle), mobile responsiveness with Sheet, and extensive responsive class variants across 20+ sub-components
  • Chart component (apps/web/app/components/ui/chart.tsx) β€” Dynamic CSS custom property generation, Recharts integration, ChartConfig type system, and context validation; verify payload resolution logic and color mapping
  • Toast system (apps/web/app/components/ui/use-toast.ts) β€” Pub-sub pattern with reducer-based state management, TOAST_LIMIT enforcement, and removal timeout scheduling; trace edge cases in dismiss/remove flow
  • Form component (apps/web/app/components/ui/form.tsx) β€” Complex react-hook-form integration with FormField context, useFormField hook, ARIA attribute wiring, and ID generation via React.useId
  • Authentication page redirects β€” Verify redirect logic change from "/" to "/dashboard" propagates correctly through OAuth flows and post-login behavior
  • Database schema β€” Validate foreign key cascades, indexes on unique fields (email, sessionToken), and Authenticator credentialID handling for passkey support

Poem

🐰 A burrow of components, fine and bright,
With Radix stones and Tailwind light,
From badges to charts, each piece in place,
A UI garden finds its grace,
With toasts that pop and sidebars slide,
The design system spreads far and wide! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the template placeholder text with no actual implementation details, testing results, or meaningful content filled in. Replace template placeholders with concrete details: describe the design changes, specify which type of change applies, reference related issues, document testing performed, and provide relevant screenshots or implementation notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (1 passed)
Check name Status Explanation
Title check βœ… Passed The title 'design landing,features , signin, and signup pages' clearly summarizes the main changes: UI design work across multiple pages.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❀️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 31

🧹 Nitpick comments (40)
apps/web/app/components/ui/aspect-ratio.tsx (1)

5-7: Simplify the parameter syntax.

The destructuring syntax { ...props } is unnecessarily verbose and equivalent to simply using props as the parameter name. While technically valid, it adds cognitive overhead without benefit.

Apply this diff:

-function AspectRatio({
-  ...props
-}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
+function AspectRatio(
+  props: React.ComponentProps<typeof AspectRatioPrimitive.Root>
+) {
   return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
 }
apps/web/app/components/ui/alert.tsx (1)

22-35: Consider adding ref forwarding for flexibility.

While the current implementation follows common shadcn/ui patterns, consider using React.forwardRef to allow consumers to attach refs to the Alert component if needed for DOM manipulation or integration with other libraries.

Example implementation:

-function Alert({
+const Alert = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<'div'> & VariantProps<typeof alertVariants>
+>(({
   className,
   variant,
   ...props
-}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
+}, ref) => {
   return (
     <div
+      ref={ref}
       data-slot="alert"
       role="alert"
       className={cn(alertVariants({ variant }), className)}
       {...props}
     />
   )
-}
+})
+Alert.displayName = "Alert"
apps/web/prisma/migrations/20250911181640_init/migration.sql (1)

16-32: Column naming inconsistency: mix of snake_case and camelCase.

The Account table uses snake_case for OAuth-standard field names (refresh_token, access_token, token_type, id_token, session_state) but camelCase for domain fields (userId, providerAccountId, createdAt). This inconsistency can cause:

  • Confusion in ORM mapping (Prisma schema vs. database column names)
  • Difficulty in future migrations or refactoring
  • Inconsistency with other tables (User, Session, Authenticator) which use camelCase throughout

Consider standardizing to camelCase for consistency with the rest of the schema:

-    "refresh_token" TEXT,
-    "access_token" TEXT,
+    "refreshToken" TEXT,
+    "accessToken" TEXT,
     "expires_at" INTEGER,
-    "token_type" TEXT,
+    "tokenType" TEXT,
-    "id_token" TEXT,
-    "session_state" TEXT,
+    "idToken" TEXT,
+    "sessionState" TEXT,

If the snake_case naming is required for NextAuth.js or OAuth provider compatibility, document this in a migration comment or schema comment.

apps/web/app/components/ui/sheet.tsx (2)

9-29: Consider React.forwardRef wrappers for Radix primitives

Sheet, SheetTrigger, SheetClose, and SheetPortal are thin wrappers over Radix primitives but don’t forward refs, which can be limiting if callers want to imperatively focus, measure, or integrate with other libraries that rely on refs.

Refactoring these to React.forwardRef (and using React.ComponentRef<typeof SheetPrimitive.*> for the ref type) would make the API more flexible and consistent with common Radix wrapper patterns, without changing behavior for current call sites.

Please double‑check how other Radix-based primitives in this UI kit handle refs and align this file for consistency.


25-29: Clarify intent of SheetPortal and SheetOverlay (currently internal-only)

SheetPortal and SheetOverlay are defined with their own data-slot attributes but are not exported, so the only way they’re used is internally by SheetContent.

If the intent is to expose them as building blocks (similar to other primitives in the kit), consider exporting them from this file. If they’re meant to be purely internal implementation details, you could inline their JSX into SheetContent to reduce indirection and avoid suggesting a public surface that doesn’t actually exist.

Also applies to: 31-44, 130-139

apps/web/app/components/ui/menubar.tsx (2)

9-23: Consider using React.forwardRef to preserve ref support from primitives

All wrappers are plain function components typed with React.ComponentProps<...>. This drops ref support from the underlying primitives (e.g., calling code cannot attach a ref to MenubarTrigger or MenubarItem), which can be useful for focus management or integrations.

Consider adopting the usual forwardRef pattern for these components. For example, for MenubarTrigger:

-import function MenubarTrigger({
-  className,
-  ...props
-}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
-  return (
-    <MenubarPrimitive.Trigger
-      data-slot="menubar-trigger"
-      className={cn(
-        'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none',
-        className,
-      )}
-      {...props}
-    />
-  )
-}
+const MenubarTrigger = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Trigger
+    ref={ref}
+    data-slot="menubar-trigger"
+    className={cn(
+      'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none',
+      className,
+    )}
+    {...props}
+  />
+))
+
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName

You could apply the same pattern to other elements where consumers might reasonably need refs (e.g., Menubar, MenubarItem, MenubarCheckboxItem, MenubarRadioItem, MenubarSubTrigger, etc.).

Also applies to: 25-50, 51-66, 67-89, 91-112, 114-212, 213-241, 243-257


17-19: Verify custom Tailwind utilities (shadow-xs, rounded-xs, outline-hidden, complex variants) exist in your config

Several classes rely on non-default or advanced Tailwind utilities:

  • shadow-xs on the root menubar.
  • rounded-xs on checkbox and radio items.
  • outline-hidden on multiple focusable elements.
  • Complex selectors like data-[variant=destructive]:*:[svg]:!text-destructive and [&_svg:not([class*='text-'])]:text-muted-foreground.

If your Tailwind config (or plugins) intentionally defines these utilities and variants, this is fine. If not, they will silently no-op and you’ll lose the intended styling.

It’s worth double-checking that:

  • These custom tokens are registered in tailwind.config.*, and
  • Your Tailwind version supports the nested/child selector variants you’re using.

Also applies to: 82-84, 124-126, 149-151, 176-178, 191-192, 205-207, 232-233

apps/web/app/components/ui/use-mobile.tsx (1)

5-19: Consider initializing state with undefined to prevent hydration mismatches.

The hook currently returns false during SSR (since !!undefined === false), but the actual mobile state is only determined client-side after the effect runs. This could cause hydration mismatches if components render differently based on this value.

Additionally, the onChange handler checks window.innerWidth < MOBILE_BREAKPOINT instead of using mql.matches, which is inconsistent even though both evaluate the same condition.

Consider this refactor for consistency and clarity:

 export function useIsMobile() {
   const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)

   React.useEffect(() => {
     const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
     const onChange = () => {
-      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+      setIsMobile(mql.matches)
     }
     mql.addEventListener('change', onChange)
-    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+    setIsMobile(mql.matches)
     return () => mql.removeEventListener('change', onChange)
   }, [])

   return !!isMobile
 }

Alternatively, to properly handle SSR, consider returning boolean | undefined and letting consumers handle the undefined case explicitly.

apps/web/app/components/ui/input-otp.tsx (1)

9-75: Improve OTP slot robustness and composability

  • InputOTPSlot assumes it’s always rendered under an OTPInputContext; if someone accidentally uses it outside <InputOTP>, it will silently render an empty slot. Consider throwing a clear error when OTPInputContext is missing so misuse is obvious during development.
  • All four exports (InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator) are plain function components. If you expect consumers to programmatically focus or measure these elements, wrapping them in React.forwardRef (similar to your Card components) would make them more flexible without changing the external API.
apps/web/app/components/ui/tooltip.tsx (1)

8-61: Clarify Tooltip provider layering to avoid redundant/nondeterministic configuration

Right now each <Tooltip> instance wraps itself in a new TooltipProvider, while you also export and use TooltipProvider separately (e.g., around the sidebar). That means:

  • Any app-level TooltipProvider no longer controls delayDuration for <Tooltip> usages, because the inner provider created by Tooltip wins.
  • You end up with nested providers in places like SidebarProvider with no functional benefit.

I’d recommend picking a single pattern:

  • Either keep Tooltip as a thin alias over TooltipPrimitive.Root and rely on a single <TooltipProvider> high in the tree, or
  • Keep the per-instance provider inside <Tooltip> and drop external TooltipProvider usage (or make Tooltip accept provider props and forward them).

This will make delay behavior and configuration more predictable.

apps/web/app/components/ui/accordion.tsx (1)

15-63: Consider where className is applied in AccordionContent

AccordionContent hard-codes classes on AccordionPrimitive.Content and applies the incoming className only to the inner <div>. This works, but it means consumers can’t easily override/extend the animation/overflow behavior on the Radix Content itself.

If you want className to control the actual content panel, consider also (or instead) merging it into the AccordionPrimitive.Content className, and giving the inner wrapper a fixed style. That will make the API a bit more intuitive for consumers.

apps/web/app/components/ui/form.tsx (1)

21-66: Harden form context hooks to fail fast on misuse

A couple of small DX/a11y pitfalls in the form helpers:

  • FormFieldContext and FormItemContext are initialized with {} as ..., so useFormField’s if (!fieldContext) check will never trigger (an empty object is always truthy).
  • Because the guard comes after you read fieldContext.name and feed it into useFormState / getFieldState, using FormLabel / FormControl / FormMessage outside a <FormField> / <FormItem> will quietly compute state for name === undefined and generate IDs like "undefined-form-item" instead of throwing a clear error.

To make misuse obvious and avoid weird IDs, you can:

  • Initialize the contexts as nullable, and
  • Assert them before using any of their properties.

For example:

-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue,
-)
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined,
+)

const useFormField = () => {
-  const fieldContext = React.useContext(FormFieldContext)
-  const itemContext = React.useContext(FormItemContext)
+  const fieldContext = React.useContext(FormFieldContext)
+  const itemContext = React.useContext(FormItemContext)
+
+  if (!fieldContext) {
+    throw new Error('useFormField should be used within <FormField>')
+  }
+  if (!itemContext) {
+    throw new Error('useFormField should be used within <FormItem>')
+  }

(and similarly make FormItemContext nullable).

This keeps the existing API but gives much clearer feedback when the composition helpers are used incorrectly.

Also applies to: 68-75, 90-156

apps/web/app/components/ui/sidebar.tsx (2)

56-90: Revisit sidebar open-state helper and cookie persistence semantics

A couple of things in SidebarProvider worth tightening up:

  • setOpen writes SIDEBAR_COOKIE_NAME on every call, but this module never reads that cookie back. Unless some other layer is reading it and passing defaultOpen/open in, this cookie currently doesn’t actually persist anything and just adds side effects. Either wire up a useEffect to read it on mount or remove the cookie write until persistence is in place.
  • The setOpen signature allows a functional updater ((value) => !value), but it evaluates that updater against the captured open value, not the actual previous state from React. That means if setOpen((prev) => !prev) were ever called multiple times in quick succession, the behavior could diverge from React’s usual functional updates. A more robust pattern is to delegate to _setOpen’s functional form when uncontrolled, and to call setOpenProp directly with the computed value when controlled.

These are subtle and don’t break current usage (you only toggle once per interaction today), but tightening them up now will make the provider safer to reuse.


498-546: Minor ergonomics tweak for SidebarMenuButton tooltip handling

Inside SidebarMenuButton, when tooltip is a string you mutate the parameter:

if (typeof tooltip === 'string') {
  tooltip = { children: tooltip }
}

This works but can run against common no-param-reassign lint rules and makes the flow a bit harder to follow. Consider introducing a new local instead:

const tooltipProps =
  typeof tooltip === 'string' ? { children: tooltip } : tooltip

return (
  <Tooltip>
    <TooltipTrigger asChild>{button}</TooltipTrigger>
    <TooltipContent
      side="right"
      align="center"
      hidden={state !== 'collapsed' || isMobile}
      {...tooltipProps}
    />
  </Tooltip>
)

That keeps the types and behavior the same while avoiding parameter reassignment.

apps/web/app/components/ui/slider.tsx (1)

8-15: Consider adding ref forwarding for better composition.

While not critical for basic usage, forwarding refs allows consumers to access the underlying DOM element, which can be useful for form libraries, focus management, and programmatic control.

Apply this diff to add ref forwarding:

-function Slider({
+const Slider = React.forwardRef<
+  React.ElementRef<typeof SliderPrimitive.Root>,
+  React.ComponentProps<typeof SliderPrimitive.Root>
+>(function Slider({
   className,
   defaultValue,
   value,
   min = 0,
   max = 100,
   ...props
-}: React.ComponentProps<typeof SliderPrimitive.Root>) {
+}, ref) {
   const _values = React.useMemo(
     () =>
       Array.isArray(value)
         ? value
         : Array.isArray(defaultValue)
           ? defaultValue
           : [min, max],
     [value, defaultValue, min, max],
   )
 
   return (
     <SliderPrimitive.Root
+      ref={ref}
       data-slot="slider"
       // ... rest of props

Then update the export:

-export { Slider }
+export { Slider }
apps/web/app/components/ui/textarea.tsx (1)

5-16: Add ref forwarding for form library compatibility.

Form elements like textarea are commonly used with form libraries (React Hook Form, Formik) that require ref access for validation, focus management, and direct DOM manipulation.

Apply this diff:

-function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
+const Textarea = React.forwardRef<
+  HTMLTextAreaElement,
+  React.ComponentProps<'textarea'>
+>(function Textarea({ className, ...props }, ref) {
   return (
     <textarea
+      ref={ref}
       data-slot="textarea"
       className={cn(
         'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
         className,
       )}
       {...props}
     />
   )
-}
+})
+Textarea.displayName = 'Textarea'
apps/web/app/components/ui/switch.tsx (1)

8-29: LGTM! Clean Radix Switch wrapper.

The component properly wraps Radix UI Switch primitives with consistent styling and data-slot attributes. The thumb transition logic is correct.

Consider adding ref forwarding for form library compatibility:

-function Switch({
+const Switch = React.forwardRef<
+  React.ElementRef<typeof SwitchPrimitive.Root>,
+  React.ComponentProps<typeof SwitchPrimitive.Root>
+>(function Switch({
   className,
   ...props
-}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
+}, ref) {
   return (
     <SwitchPrimitive.Root
+      ref={ref}
       data-slot="switch"
       // ... rest
   )
-}
+})
+Switch.displayName = 'Switch'
apps/web/app/components/ui/avatar.tsx (1)

8-51: LGTM! Complete Avatar component set.

All three components (Avatar, AvatarImage, AvatarFallback) properly wrap Radix primitives with consistent styling. The composition pattern allows flexible usage.

For maximum flexibility, consider adding ref forwarding to all three components:

const Avatar = React.forwardRef<
  React.ElementRef<typeof AvatarPrimitive.Root>,
  React.ComponentProps<typeof AvatarPrimitive.Root>
>(function Avatar({ className, ...props }, ref) {
  return (
    <AvatarPrimitive.Root
      ref={ref}
      data-slot="avatar"
      className={cn(
        'relative flex size-8 shrink-0 overflow-hidden rounded-full',
        className,
      )}
      {...props}
    />
  )
})
Avatar.displayName = 'Avatar'

// Similar pattern for AvatarImage and AvatarFallback
apps/web/components/ui/button.tsx (1)

7-55: Button implementation looks solid; consider defaulting type="button" for safety.

The CVA setup and forwardRef/Slot pattern look correct and should work well across use cases. One thing to consider is explicitly defaulting type="button" when asChild is false and no type is provided, to avoid accidental form submissions when this component is used inside <form> elements.

You can leave behavior as-is if you intentionally rely on the native "submit" default, but it’s worth a quick pass over current usages to confirm there are no unintended submits.

apps/web/app/components/ui/input.tsx (1)

5-19: Input is well-structured; consider making it ref‑forwarding.

The styling and cn usage look good and should be reusable across auth pages. To make this more flexible for focus management and libraries like react-hook-form, consider converting it to a React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>> and passing ref through to the <input> element (plus optionally setting Input.displayName = 'Input').

Not urgent if you don’t yet need refs on this component, but it’s a common requirement for inputs.

apps/web/app/components/ui/label.tsx (1)

8-22: Label implementation is fine; consider consolidating the two Label components.

This Label wrapper is straightforward and the styling/data-slot setup looks good. However, you now have:

  • apps/web/components/ui/label.tsx (Radix + forwardRef + cva)
  • apps/web/app/components/ui/label.tsx (Radix + function component + data-slot)

To avoid confusion over which Label to import and keep behavior consistent (especially around refs), consider centralizing on one implementation and re-exporting it where needed, or clearly separating their intended scopes (e.g., β€œdesign system” vs. β€œapp-only”) with naming or path conventions.

apps/web/app/components/ui/radio-group.tsx (1)

9-43: RadioGroup wrappers look correct; consider ref forwarding for flexibility

The wrappers correctly pass props to Radix primitives and compose classes via cn, so they should behave as expected. One improvement is to wrap both RadioGroup and RadioGroupItem in React.forwardRef so consumers can still get DOM refs (for focus management, form libs, etc.) while using these abstractions over Radix.

apps/web/app/globals.css (1)

43-118: Color token setup looks good; align color-scheme with .dark for class-based theming

The HSL token system under :root/.dark plus the @apply bg-background text-foreground on body is solid. If you plan to use Tailwind’s dark class strategy for theme toggling, consider also setting color-scheme: dark; inside the .dark block (or mirroring it there) so native UI (scrollbars, form controls, autofill, etc.) matches the active theme even when it differs from prefers-color-scheme.

apps/web/app/components/ui/button.tsx (1)

7-36: Button implementation is solid; guard against accidental form submits

buttonVariants and the asChild + Slot pattern look good and are consistent with typical CVA/Radix setups.

Two non-blocking improvements:

  • Set a default type="button" when rendering a native <button> (i.e., when asChild is false) to avoid unintended form submissions in forms where type isn’t explicitly passed.
  • Optionally convert Button to React.forwardRef so refs continue to work when wrapping it around Radix primitives or other focus-sensitive elements.

Also applies to: 38-57

apps/web/app/components/ui/toggle.tsx (1)

9-29: Toggle wrapper is correct; consider forwardRef for consistency with Radix

The Toggle variant system and wrapper over TogglePrimitive.Root look good and should behave correctly.

For better interoperability with Radix patterns and focus management, you might want to wrap Toggle in React.forwardRef (using React.ElementRef<typeof TogglePrimitive.Root> / React.ComponentPropsWithoutRef for typing), similar to how many Radix-based UI kits expose these primitives.

Also applies to: 31-45

apps/web/app/components/ui/collapsible.tsx (1)

5-31: Consider using forwardRef to preserve ref support on wrappers

These wrappers currently drop any ref that consumers might pass (it’s treated as a normal prop). For Radix primitives it’s common to keep ref behavior aligned with the original components.

You could refactor like:

- function CollapsibleTrigger({
-  ...props
-}: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {
-  return (
-    <CollapsiblePrimitive.Trigger
-      data-slot="collapsible-trigger"
-      {...props}
-    />
-  )
-}
+ const CollapsibleTrigger = React.forwardRef<
+   React.ElementRef<typeof CollapsiblePrimitive.Trigger>,
+   React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.Trigger>
+ >((props, ref) => (
+   <CollapsiblePrimitive.Trigger
+     ref={ref}
+     data-slot="collapsible-trigger"
+     {...props}
+   />
+ ))

(Same idea for CollapsibleContent if you expect consumers to attach refs there.)

apps/web/app/page.tsx (3)

12-14: Prefer link semantics over buttons for pure navigation items

In the header and footer, the β€œFeatures” and β€œGet Started” entries are rendered as <button> elements that only trigger navigation. For navigation (as opposed to in-place actions), anchors/links are more semantically correct and give users expected behaviors (open in new tab, copy link, etc.).

Consider switching these to <a> / framework <Link> components with href and styling them as buttons rather than using plain <button> + programmatic navigation.

Also applies to: 35-45, 446-465


70-76: Deduplicate scrollIntoView logic for the features section

The document.getElementById("features")?.scrollIntoView snippet is duplicated in both the hero and CTA buttons. Small, but easy to DRY and centralize:

-  const handleNavigation = (path: string) => {
-    router.push(path)
-  }
+  const handleNavigation = (path: string) => {
+    router.push(path)
+  }
+
+  const scrollToFeatures = () => {
+    document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' })
+  }
@@
-              <Button
-                variant="outline"
-                size="lg"
-                className="h-12 px-8 bg-transparent"
-                onClick={() => {
-                  document.getElementById("features")?.scrollIntoView({ behavior: "smooth" })
-                }}
-              >
+              <Button
+                variant="outline"
+                size="lg"
+                className="h-12 px-8 bg-transparent"
+                onClick={scrollToFeatures}
+              >
@@
-              <Button
-                variant="outline"
-                size="lg"
-                className="h-12 px-8 bg-transparent"
-                onClick={() => {
-                  document.getElementById("features")?.scrollIntoView({ behavior: "smooth" })
-                }}
-              >
+              <Button
+                variant="outline"
+                size="lg"
+                className="h-12 px-8 bg-transparent"
+                onClick={scrollToFeatures}
+              >

Also applies to: 419-421


495-497: Make the copyright year dynamic

The footer hardcodes Β© 2024, which will go stale quickly. You can keep it in sync with a simple expression:

-            <p className="text-xs text-muted-foreground">Β© 2024 Dev8.dev. All rights reserved.</p>
+            <p className="text-xs text-muted-foreground">
+              Β© {new Date().getFullYear()} Dev8.dev. All rights reserved.
+            </p>
apps/web/app/components/ui/scroll-area.tsx (1)

8-28: Consider forwarding refs through ScrollArea and ScrollBar

As with other Radix wrappers, preserving ref behavior is useful (e.g., for programmatic focus/measurement). Currently these are plain function components, so ref is treated as a normal prop and not forwarded to the underlying primitive.

You could follow a forwardRef pattern similar to:

- function ScrollArea({
-  className,
-  children,
-  ...props
-}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
-  return (
-    <ScrollAreaPrimitive.Root
+ const ScrollArea = React.forwardRef<
+   React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+   React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
+ >(({ className, children, ...props }, ref) => (
+   <ScrollAreaPrimitive.Root
+      ref={ref}
       data-slot="scroll-area"
       className={cn('relative', className)}
       {...props}
     >
@@
-      <ScrollBar />
+      <ScrollBar />
       <ScrollAreaPrimitive.Corner />
-    </ScrollAreaPrimitive.Root>
-  )
-}
+    </ScrollAreaPrimitive.Root>
+ ))

(Similarly for ScrollBar if you need ref access there.)

Also applies to: 31-55

apps/web/tailwind.config.js (1)

1-4: Verify Tailwind config module style vs require("tailwindcss-animate")

This file uses an ESM-style export default but also calls require("tailwindcss-animate"). In a pure ESM environment, require isn’t defined, so depending on how Tailwind/Next load this config, you might hit a runtime error.

It may be safer to either:

  • Make the config clearly CommonJS:
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ['class'],
  // ...
  plugins: [require('tailwindcss-animate')],
}

or

  • Keep ESM and import the plugin:
import animate from 'tailwindcss-animate'

/** @type {import('tailwindcss').Config} */
export default {
  darkMode: ['class'],
  // ...
  plugins: [animate],
}

Please double-check that your current build/dev setup loads this config without errors.

Also applies to: 50-61

apps/web/app/components/ui/toggle-group.tsx (1)

10-71: Context default prevents item-level overrides outside a ToggleGroup

The wrapper looks solid overall. One subtle behavioral detail: because ToggleGroupContext has a non-empty default and ToggleGroupItem does context.variant || variant / context.size || size, item-level variant/size props will never be honored when used outside a <ToggleGroup> (the context default always wins). Inside a group, group-level props always override item-level ones as well.

If you want items to be individually overridable while still inheriting from the group, consider using nullish coalescing with an explicit precedence order, e.g.:

-      data-variant={context.variant || variant}
-      data-size={context.size || size}
+      data-variant={variant ?? context.variant}
+      data-size={size ?? context.size}
...
-        toggleVariants({
-          variant: context.variant || variant,
-          size: context.size || size,
-        }),
+        toggleVariants({
+          variant: variant ?? context.variant,
+          size: size ?? context.size,
+        }),

This keeps sensible defaults but gives callers more control when needed.

apps/web/app/components/ui/resizable.tsx (1)

9-54: Wrappers look correct; consider extracting handle styles for readability

The ResizablePanelGroup, ResizablePanel, and ResizableHandle wrappers correctly forward props and strip withHandle from the DOM, and the slot-based styling is consistent with the rest of the UI.

The only nit is the very long className on ResizableHandle, which is a bit hard to scan and maintain. If this grows further, consider extracting it into a constant or splitting vertical vs horizontal modifiers to improve readability, e.g.:

const baseHandleClasses = 'bg-border relative flex w-px items-center ...';
const verticalHandleClasses =
  'data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full ...';

Then compose them with cn in the component.

apps/web/app/(auth)/signup/page.tsx (2)

24-68: Submit and error handling are sound; minor improvement for robustness

The submit flow (password match guard, try/catch/finally, using response.ok and server error message fallback) is solid and should behave predictably. One small robustness tweak would be to guard against non‑JSON responses from /api/auth/register (e.g., 500 HTML error pages) by wrapping the response.json() in a try/catch or checking response.headers.get("content-type") before parsing, to avoid a secondary exception masking the original error.

Otherwise this looks good.


70-73: Handle OAuth loading state in case signIn resolves without redirect and consider reusing OAuth UI

handleOAuthSignIn sets isLoading to true but never clears it. In the common OAuth redirect flow this is fine because the page unloads, but if signIn ever resolves without navigating (config error, popup being blocked, etc.), the page will stay in a permanently disabled state.

You could defensively wrap it with try/finally:

const handleOAuthSignIn = async (provider: string) => {
  setIsLoading(true);
  try {
    await signIn(provider, { callbackUrl: "/dashboard" });
  } finally {
    // Only safe if you know this path may stay on the same page
    setIsLoading(false);
  }
};

Also, the Google/GitHub button markup here is nearly identical to the sign-in page. Extracting a small <OAuthButtons onClick={handleOAuthSignIn} disabled={isLoading} /> component would reduce duplication and keep auth flows visually consistent.

Please double-check this behavior against your next-auth version’s signIn redirect semantics to decide whether you want the defensive finally block or whether you always expect a full-page navigation here.

Also applies to: 191-223

apps/web/app/(auth)/signin/page.tsx (1)

47-51: Align OAuth loading behavior with signup and DRY up shared OAuth button markup

Same note as on the signup page: handleOAuthSignIn sets isLoading to true and assumes a redirect. If signIn ever completes without navigation, the UI will stay disabled. Consider a defensive try/finally around signIn if that scenario is possible in your setup.

Also, the Google/GitHub OAuth button grid is duplicated between signin and signup. Extracting it into a shared component (e.g., <OAuthProviderButtons onClick={handleOAuthSignIn} disabled={isLoading} />) would reduce duplication and keep visual/behavioral changes in sync across both pages.

Please confirm against your next-auth configuration whether OAuth flows always navigate away; that should guide whether you need the defensive finally and how you want to handle potential errors on the current page.

Also applies to: 135-167

apps/web/app/components/ui/dropdown-menu.tsx (1)

9-32: Consider using React.forwardRef for Radix wrappers to preserve ref support

All dropdown primitives here (DropdownMenu, Trigger, Content, Item, etc.) are plain function components typed with React.ComponentProps<typeof …>. This drops ref support compared to the underlying Radix primitives (e.g., you can’t attach a ref to DropdownMenuTrigger to imperatively focus it).

For a UI kit built on Radix, it’s typical to wrap with React.forwardRef so consumers retain full ref ergonomics.

Example pattern for one component (and similarly for others):

-import function DropdownMenuTrigger({
-  ...props
-}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
-  return (
-    <DropdownMenuPrimitive.Trigger
-      data-slot="dropdown-menu-trigger"
-      {...props}
-    />
-  )
-}
+const DropdownMenuTrigger = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
+>(function DropdownMenuTrigger(props, ref) {
+  return (
+    <DropdownMenuPrimitive.Trigger
+      ref={ref}
+      data-slot="dropdown-menu-trigger"
+      {...props}
+    />
+  )
+})

If you align one component this way, I’d recommend applying the same pattern across the rest of the UI wrappers in this PR (dialog, alert-dialog, drawer, command, etc.). Please confirm your React/Radix versions support this typing pattern.

Also applies to: 62-83

apps/web/app/components/ui/dialog.tsx (1)

9-31: Forward refs through dialog wrappers to keep Radix ergonomics

Similar to the dropdown menu, all Dialog wrappers (Dialog, DialogTrigger, DialogPortal, DialogClose, DialogContent, etc.) are simple function components, so they don’t accept or forward ref to the underlying Radix primitives. That’s a regression vs. Radix’s native components, which are ref‑forwarding and often used with refs for focus management.

Consider updating them to use React.forwardRef. Example for DialogContent:

-function DialogContent({
-  className,
-  children,
-  showCloseButton = true,
-  ...props
-}: React.ComponentProps<typeof DialogPrimitive.Content> & {
-  showCloseButton?: boolean
-}) {
-  return (
-    <DialogPortal data-slot="dialog-portal">
-      <DialogOverlay />
-      <DialogPrimitive.Content
+const DialogContent = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
+    showCloseButton?: boolean
+  }
+>(function DialogContent(
+  { className, children, showCloseButton = true, ...props },
+  ref,
+) {
+  return (
+    <DialogPortal data-slot="dialog-portal">
+      <DialogOverlay />
+      <DialogPrimitive.Content
+        ref={ref}
         data-slot="dialog-content"
         className={cn(
           'bg-background data-[state=open]:animate-in ...',
           className,
         )}
         {...props}
       >
         {children}
         {showCloseButton && (
           {/* close button */}
         )}
       </DialogPrimitive.Content>
     </DialogPortal>
-  )
-}
+  )
+})

If you adopt this pattern here, I’d recommend doing the same for the other dialog primitives and the related components in this PR so the whole design system remains ref-friendly. Please verify types and behavior against your current React/Radix versions.

Also applies to: 49-81

apps/web/app/components/ui/alert-dialog.tsx (1)

121-143: Add data-slot for AlertDialogAction/Cancel to keep slot API consistent

Every other AlertDialog primitive here exposes a data-slot (root, trigger, overlay, content, header, footer, title, description), but AlertDialogAction and AlertDialogCancel do not. This isn’t a functional issue, but it breaks the otherwise consistent slot-based styling and test hooks.

You can align them with the rest of the API like:

function AlertDialogAction({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
  return (
    <AlertDialogPrimitive.Action
-      className={cn(buttonVariants(), className)}
+      data-slot="alert-dialog-action"
+      className={cn(buttonVariants(), className)}
      {...props}
    />
  )
}

function AlertDialogCancel({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
  return (
    <AlertDialogPrimitive.Cancel
-      className={cn(buttonVariants({ variant: 'outline' }), className)}
+      data-slot="alert-dialog-cancel"
+      className={cn(buttonVariants({ variant: 'outline' }), className)}
      {...props}
    />
  )
}

This keeps the design system’s slots predictable across all alert dialog subcomponents.

apps/web/app/components/ui/select.tsx (1)

9-51: Consider ref‑forwarding for Select wrappers

All Select wrappers (Select, SelectTrigger, SelectContent, SelectItem, etc.) are plain function components typed with React.ComponentProps<typeof SelectPrimitive.X>. This is fine functionally, but it means consumers cannot reliably attach refs to the underlying DOM/Radix elements (unlike typical Radix/shadcn patterns that use React.forwardRef).

If you expect callers to control focus, measure elements, or integrate with other libraries via refs, consider refactoring key components (e.g. SelectTrigger, SelectContent, SelectItem) to use React.forwardRef and ComponentPropsWithoutRef / ElementRef to match Radix’s types.

Also applies to: 101-185

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 6c208e8 and c30071b.

β›” Files ignored due to path filters (6)
  • apps/web/app/public/placeholder-logo.png is excluded by !**/*.png
  • apps/web/app/public/placeholder-logo.svg is excluded by !**/*.svg
  • apps/web/app/public/placeholder-user.jpg is excluded by !**/*.jpg
  • apps/web/app/public/placeholder.jpg is excluded by !**/*.jpg
  • apps/web/app/public/placeholder.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
πŸ“’ Files selected for processing (68)
  • apps/web/app/(auth)/signin/page.tsx (2 hunks)
  • apps/web/app/(auth)/signup/page.tsx (2 hunks)
  • apps/web/app/components/theme-provider.tsx (1 hunks)
  • apps/web/app/components/ui/accordion.tsx (1 hunks)
  • apps/web/app/components/ui/alert-dialog.tsx (1 hunks)
  • apps/web/app/components/ui/alert.tsx (1 hunks)
  • apps/web/app/components/ui/aspect-ratio.tsx (1 hunks)
  • apps/web/app/components/ui/avatar.tsx (1 hunks)
  • apps/web/app/components/ui/badge.tsx (1 hunks)
  • apps/web/app/components/ui/breadcrumb.tsx (1 hunks)
  • apps/web/app/components/ui/button.tsx (1 hunks)
  • apps/web/app/components/ui/calendar.tsx (1 hunks)
  • apps/web/app/components/ui/card.tsx (1 hunks)
  • apps/web/app/components/ui/carousel.tsx (1 hunks)
  • apps/web/app/components/ui/chart.tsx (1 hunks)
  • apps/web/app/components/ui/checkbox.tsx (1 hunks)
  • apps/web/app/components/ui/collapsible.tsx (1 hunks)
  • apps/web/app/components/ui/command.tsx (1 hunks)
  • apps/web/app/components/ui/context-menu.tsx (1 hunks)
  • apps/web/app/components/ui/dialog.tsx (1 hunks)
  • apps/web/app/components/ui/drawer.tsx (1 hunks)
  • apps/web/app/components/ui/dropdown-menu.tsx (1 hunks)
  • apps/web/app/components/ui/form.tsx (1 hunks)
  • apps/web/app/components/ui/hover-card.tsx (1 hunks)
  • apps/web/app/components/ui/input-otp.tsx (1 hunks)
  • apps/web/app/components/ui/input.tsx (1 hunks)
  • apps/web/app/components/ui/label.tsx (1 hunks)
  • apps/web/app/components/ui/menubar.tsx (1 hunks)
  • apps/web/app/components/ui/navigation-menu.tsx (1 hunks)
  • apps/web/app/components/ui/pagination.tsx (1 hunks)
  • apps/web/app/components/ui/popover.tsx (1 hunks)
  • apps/web/app/components/ui/progress.tsx (1 hunks)
  • apps/web/app/components/ui/radio-group.tsx (1 hunks)
  • apps/web/app/components/ui/resizable.tsx (1 hunks)
  • apps/web/app/components/ui/scroll-area.tsx (1 hunks)
  • apps/web/app/components/ui/select.tsx (1 hunks)
  • apps/web/app/components/ui/separator.tsx (1 hunks)
  • apps/web/app/components/ui/sheet.tsx (1 hunks)
  • apps/web/app/components/ui/sidebar.tsx (1 hunks)
  • apps/web/app/components/ui/skeleton.tsx (1 hunks)
  • apps/web/app/components/ui/slider.tsx (1 hunks)
  • apps/web/app/components/ui/sonner.tsx (1 hunks)
  • apps/web/app/components/ui/switch.tsx (1 hunks)
  • apps/web/app/components/ui/table.tsx (1 hunks)
  • apps/web/app/components/ui/tabs.tsx (1 hunks)
  • apps/web/app/components/ui/textarea.tsx (1 hunks)
  • apps/web/app/components/ui/toast.tsx (1 hunks)
  • apps/web/app/components/ui/toaster.tsx (1 hunks)
  • apps/web/app/components/ui/toggle-group.tsx (1 hunks)
  • apps/web/app/components/ui/toggle.tsx (1 hunks)
  • apps/web/app/components/ui/tooltip.tsx (1 hunks)
  • apps/web/app/components/ui/use-mobile.tsx (1 hunks)
  • apps/web/app/components/ui/use-toast.ts (1 hunks)
  • apps/web/app/features/page.tsx (1 hunks)
  • apps/web/app/globals.css (1 hunks)
  • apps/web/app/lib/utils.ts (1 hunks)
  • apps/web/app/page.tsx (1 hunks)
  • apps/web/components.json (1 hunks)
  • apps/web/components/ui/badge.tsx (1 hunks)
  • apps/web/components/ui/button.tsx (1 hunks)
  • apps/web/components/ui/card.tsx (1 hunks)
  • apps/web/components/ui/input.tsx (1 hunks)
  • apps/web/components/ui/label.tsx (1 hunks)
  • apps/web/lib/utils.ts (1 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/prisma/migrations/20250911181640_init/migration.sql (1 hunks)
  • apps/web/prisma/migrations/migration_lock.toml (1 hunks)
  • apps/web/tailwind.config.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (54)
apps/web/app/components/ui/progress.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/input.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/sonner.tsx (1)
apps/web/app/components/ui/toaster.tsx (1)
  • Toaster (13-35)
apps/web/components/ui/input.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/toggle-group.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/button.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/tooltip.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/scroll-area.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/label.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/input-otp.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/skeleton.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/hover-card.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/carousel.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/toaster.tsx (3)
apps/web/app/components/ui/sonner.tsx (1)
  • Toaster (25-25)
apps/web/app/components/ui/use-toast.ts (1)
  • useToast (191-191)
apps/web/app/components/ui/toast.tsx (6)
  • ToastProvider (122-122)
  • Toast (124-124)
  • ToastTitle (125-125)
  • ToastDescription (126-126)
  • ToastClose (127-127)
  • ToastViewport (123-123)
apps/web/app/components/ui/dialog.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/components/ui/badge.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/drawer.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/toast.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/slider.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/tabs.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/accordion.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/badge.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/sheet.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/radio-group.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/separator.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/breadcrumb.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/toggle.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/pagination.tsx (2)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/button.tsx (2)
  • Button (59-59)
  • buttonVariants (59-59)
apps/web/app/components/ui/resizable.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/card.tsx (2)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/navigation-menu.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/calendar.tsx (3)
apps/web/app/components/ui/button.tsx (2)
  • Button (59-59)
  • buttonVariants (59-59)
apps/web/components/ui/button.tsx (2)
  • Button (57-57)
  • buttonVariants (57-57)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/alert-dialog.tsx (2)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/button.tsx (1)
  • buttonVariants (59-59)
apps/web/components/ui/button.tsx (3)
apps/web/app/components/ui/button.tsx (2)
  • buttonVariants (59-59)
  • Button (59-59)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/avatar.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/chart.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/dropdown-menu.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/(auth)/signin/page.tsx (2)
apps/web/lib/auth-config.ts (1)
  • signIn (103-119)
packages/ui/src/code.tsx (1)
  • Code (3-11)
apps/web/app/components/ui/alert.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/checkbox.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/select.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/components/ui/card.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/popover.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/sidebar.tsx (2)
apps/web/app/components/ui/use-mobile.tsx (1)
  • useIsMobile (5-19)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/page.tsx (3)
packages/ui/src/code.tsx (1)
  • Code (3-11)
packages/ui/src/button.tsx (1)
  • Button (11-20)
packages/ui/src/card.tsx (1)
  • Card (3-27)
apps/web/app/components/ui/table.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/switch.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/textarea.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/form.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/components/ui/label.tsx (1)
apps/web/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/context-menu.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/command.tsx (2)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/components/ui/dialog.tsx (5)
  • Dialog (133-133)
  • DialogHeader (138-138)
  • DialogTitle (141-141)
  • DialogDescription (136-136)
  • DialogContent (135-135)
apps/web/app/components/ui/menubar.tsx (1)
apps/web/app/lib/utils.ts (1)
  • cn (4-6)
apps/web/app/(auth)/signup/page.tsx (1)
apps/web/lib/auth-config.ts (1)
  • signIn (103-119)
πŸͺ› ast-grep (0.40.0)
apps/web/app/components/ui/chart.tsx

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

(react-unsafe-html-injection)

πŸͺ› Biome (2.1.2)
apps/web/app/components/ui/chart.tsx

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

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

(lint/security/noDangerouslySetInnerHtml)

Comment on lines +6 to +20
const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
{
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive:
'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
},
},
defaultVariants: {
variant: 'default',
},
},
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Verify browser compatibility and CSS variable definition.

The grid layout uses the :has() CSS selector and references a --spacing CSS variable. Please ensure:

  1. The :has() selector is compatible with your target browsers (widely supported since 2023, but not in older browsers).
  2. The --spacing CSS variable is defined in your Tailwind configuration or global styles.

Run the following script to verify the --spacing CSS variable is defined:


🏁 Script executed:

#!/bin/bash
# Description: Search for --spacing CSS variable definition in theme/config files

# Search in Tailwind config files
fd -e js -e ts -e json 'tailwind.config' --exec cat {}

# Search for --spacing in CSS files
rg -n --type=css --type=scss -C2 '--spacing'

# Search in global style files
fd -e css -e scss 'global' --exec cat {}

Length of output: 4945



---

🏁 Script executed:

```shell
#!/bin/bash
# Corrected search for --spacing CSS variable definition (with proper escaping)
rg -n 'spacing' --type=css --type=scss --type=js --type=ts -C2

Length of output: 28


🏁 Script executed:

#!/bin/bash
# Also check if --spacing might be used elsewhere
rg --type=ts --type=tsx -n 'spacing'

Length of output: 27



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for --spacing with broader approach
rg -n '\-\-spacing' .

# Also search for spacing in TypeScript/JavaScript files more broadly
rg -n 'spacing' . -g '*.ts' -g '*.tsx' -g '*.js' -g '*.css'

Length of output: 1676



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any plugin or config that might define spacing
rg -n 'plugin' . -g '*.ts' -g '*.js' -C3

# Search for package.json to understand dependencies
fd -name 'package.json' -exec cat {} \;

# Search for any CSS-in-JS or theme files
fd -name '*.config.*' -exec cat {} \;

Length of output: 7492


Add missing --spacing CSS variable or use defined alternatives.

The calc(var(--spacing)*4) in alert.tsx:7 references a --spacing CSS variable that is not defined anywhere in your codebase. This will cause the CSS calculation to fail.

Either:

  1. Add --spacing to your CSS variables (e.g., in globals.css or Tailwind config)
  2. Use an existing variable like --radius or a Tailwind spacing value instead

Note: The :has() selector has broad browser support since 2023, so it's compatible with modern browsers.

πŸ€– Prompt for AI Agents
In apps/web/app/components/ui/alert.tsx around lines 6 to 20, the cva string
uses calc(var(--spacing)*4) but --spacing is not defined; fix by either defining
--spacing (e.g., add :root { --spacing: 0.25rem; } or another appropriate value
in globals.css or your main CSS variables file) or replace the calc(...) with a
known variable or value (for example a Tailwind spacing equivalent like 1rem or
a CSS variable you already use such as --radius) so the calc resolves correctly
and the grid template column computation works.

Comment on lines +1 to +7
'use client'

import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'

function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

Missing React import causes compilation error.

The code references React.ComponentProps at line 7 but doesn't import React. This will cause a TypeScript compilation error.

Apply this diff to fix the missing import:

 'use client'
 
+import React from 'react'
 import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'

Alternatively, you can use a type-only import:

 'use client'
 
+import type { ComponentProps } from 'react'
 import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
 
 function AspectRatio({
   ...props
-}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
+}: ComponentProps<typeof AspectRatioPrimitive.Root>) {
   return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
 }
πŸ“ Committable suggestion

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

Suggested change
'use client'
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
'use client'
import React from 'react'
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio }
Suggested change
'use client'
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
function AspectRatio({
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
'use client'
import type { ComponentProps } from 'react'
import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
function AspectRatio({
...props
}: ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio }
πŸ€– Prompt for AI Agents
In apps/web/app/components/ui/aspect-ratio.tsx around lines 1 to 7, the file
uses React.ComponentProps but does not import React, causing a TypeScript
compile error; fix by adding a React import at the top (either a runtime import
like `import React from 'react'` or a type-only import such as `import type
React from 'react'`), or alternatively replace `React.ComponentProps<typeof
AspectRatioPrimitive.Root>` with a direct type import like
`ComponentProps<typeof AspectRatioPrimitive.Root>` after adding `import type {
ComponentProps } from 'react'`.

Comment on lines +1 to +6
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Add 'use client' for Radix Slot usage in a Next.js app-router component

This file imports @radix-ui/react-slot, which is a client-side React primitive. In a Next.js app directory, this component must be marked as a client component or it may fail to compile or behave incorrectly at runtime.

Consider adding the directive at the very top:

+'use client'
+
 import * as React from 'react'
 import { Slot } from '@radix-ui/react-slot'
 import { cva, type VariantProps } from 'class-variance-authority'

 import { cn } from '@/lib/utils'

🏁 Script executed:

#!/bin/bash
# Check if file exists and show full content
fd "badge.tsx" --type f

Length of output: 69


🌐 Web query:

Next.js app router 'use client' directive Radix UI components required

πŸ’‘ Result:

Short answer
Most Radix primitives (Dialog, Popover, Tooltip, DropdownMenu, Select, Checkbox, Radio, Slider, Toggle, Tabs, Toast, Menubar, ContextMenu, etc.) rely on React state, context, portals or event handlers, so they must be rendered from a Client Component (i.e., a file with "use client"). Non‑interactive/simple presentational pieces (basic Badge, Card, Skeleton, plain Input/textarea when used purely for display) can be server components. Use a small client wrapper (top of file: "use client") that imports the Radix primitives and export the interactive component. (nextjs.org)

Why and how

  • Why: Next.js App Router uses React Server Components by default; anything that uses hooks, React.Context, portals or DOM APIs must be in a Client Component. Radix primitives commonly use those features. (nextjs.org)
  • How: mark the wrapper file with "use client" and import Radix there, or keep a thin client wrapper around the interactive primitive so the rest of your tree can remain server. Example pattern shown in Radix/Next.js guidance and community examples. (github.com)

If you want, tell me which Radix components you use and I’ll point exactly which need "use client" and show a minimal wrapper example.

Citations:


🏁 Script executed:

#!/bin/bash
# Show full content of the badge.tsx in app directory
cat apps/web/app/components/ui/badge.tsx

Length of output: 1631


🌐 Web query:

@radix-ui/react-slot client component required Next.js

πŸ’‘ Result:

Short answer: Yes β€” @radix-ui/react-slot (Radix Slot) must run inside a client component in Next.js. Mark the file that imports/uses Slot with "use client" (or wrap only the interactive part in a small client component) so the Slot primitives (event merging / context behavior) run on the client. [1][2]

Minimal example:

  • file: components/ButtonWithSlot.tsx
    "use client"
    import { Slot } from "@radix-ui/react-slot";
    export default function ButtonWithSlot({ children, ...props }) {
    const Comp = props.asChild ? Slot : "button";
    return <Comp {...props}>{children};
    }

Tips:

  • Prefer scoping "use client" to the smallest component that needs Slot to keep most of your tree as Server Components. [1][3]

Sources:
[1] Radix Slot docs β€” Radix Primitives.
[2] Examples from community component libraries showing "use client" with Slot (e.g., DiceUI / shadcn patterns).


Add 'use client' for Radix Slot usage in a Next.js app-router component

This file imports @radix-ui/react-slot, which requires a client component context in Next.js. The Badge component conditionally uses Slot when the asChild prop is true, which mandates the 'use client' directive at the top of the file.

Add the directive before imports:

+'use client'
+
 import * as React from 'react'
 import { Slot } from '@radix-ui/react-slot'
 import { cva, type VariantProps } from 'class-variance-authority'

 import { cn } from '@/lib/utils'
πŸ“ Committable suggestion

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

Suggested change
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
'use client'
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
πŸ€– Prompt for AI Agents
In apps/web/app/components/ui/badge.tsx around lines 1 to 6, the file uses
@radix-ui/react-slot (Slot) which requires a client component context in Next.js
app-router; add the 'use client' directive as the very first line of the file
(before any imports) so the component runs on the client; keep the directive
exactly as 'use client' (no extra characters) and ensure it precedes all imports
to satisfy Next.js requirements.

Comment on lines +52 to +63
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn('text-foreground font-normal', className)}
{...props}
/>
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

Tighten breadcrumb accessibility for current page and ellipsis

Two small a11y issues here:

  1. BreadcrumbPage (current page):

    • It’s rendered as a <span> but given role="link" and aria-disabled="true". That suggests an interactive control that’s then disabled, but it isn’t keyboard-focusable or clickable.
    • For current breadcrumb items, a common pattern is a non-interactive element with aria-current="page" only.

    Suggestion:

    • Drop role="link" and aria-disabled, keep aria-current="page".
  2. BreadcrumbEllipsis:

    • The outer span has aria-hidden="true" but also contains an sr-only β€œMore” label. aria-hidden hides the label from assistive tech, so it will never be read.
    • Either remove aria-hidden and use aria-label="More" or remove the sr-only span if this is purely decorative.

Also applies to: 83-98

πŸ€– Prompt for AI Agents
In apps/web/app/components/ui/breadcrumb.tsx around lines 52 to 63 (and
similarly at 83 to 98), the current page breadcrumb should not be exposed as an
interactive control: remove role="link" and aria-disabled="true" from
BreadcrumbPage and leave aria-current="page" only; for BreadcrumbEllipsis,
remove aria-hidden="true" (which hides the screen-reader text) and either add
aria-label="More" to the outer element or keep the sr-only text but not
aria-hidden β€” implement by removing aria-hidden and adding aria-label="More"
while removing the redundant sr-only span so assistive tech can announce the
ellipsis as β€œMore.”

Comment on lines +78 to +89
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'ArrowLeft') {
event.preventDefault()
scrollPrev()
} else if (event.key === 'ArrowRight') {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext],
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keyboard navigation doesn't respect orientation.

The keyboard handler only responds to ArrowLeft/ArrowRight regardless of orientation. When the carousel is vertical, users would expect ArrowUp/ArrowDown to navigate slides.

Apply this diff to support both orientations:

 const handleKeyDown = React.useCallback(
   (event: React.KeyboardEvent<HTMLDivElement>) => {
-    if (event.key === 'ArrowLeft') {
+    const isHorizontal = orientation === 'horizontal'
+    const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp'
+    const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown'
+    
+    if (event.key === prevKey) {
       event.preventDefault()
       scrollPrev()
-    } else if (event.key === 'ArrowRight') {
+    } else if (event.key === nextKey) {
       event.preventDefault()
       scrollNext()
     }
   },
-  [scrollPrev, scrollNext],
+  [orientation, scrollPrev, scrollNext],
 )

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In apps/web/app/components/ui/carousel.tsx around lines 78 to 89, the keyboard
handler only reacts to ArrowLeft/ArrowRight and ignores the carousel
orientation; update handleKeyDown to branch on the component's orientation prop
(e.g., 'horizontal' vs 'vertical') and map ArrowLeft/ArrowRight to prev/next for
horizontal and ArrowUp/ArrowDown to prev/next for vertical, ensuring
event.preventDefault() is still called and scrollPrev/scrollNext are invoked
accordingly; also include orientation in the useCallback dependency array.

Comment on lines 19 to 38
"dependencies": {
"@auth/prisma-adapter": "^2.10.0",
"@prisma/client": "^6.14.0",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@repo/ui": "workspace:*",
"@types/bcryptjs": "^3.0.0",
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.544.0",
"next": "^15.5.0",
"next-auth": "^4.24.11",
"prisma": "^6.14.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^4.1.1"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Verify that all added dependencies are actively used in the codebase.

The AI summary mentions 60+ components and extensive UI library expansion in apps/web/app/components/ui. Confirm that:

  1. All seven newly added dependencies are imported and used (no dead dependencies)
  2. If a full Radix UI component library is being built (60+ components), verify that all required Radix primitives are included (e.g., @radix-ui/react-dialog, @radix-ui/react-dropdown-menu, @radix-ui/react-select, etc.). The current additions only include react-label and react-slot

This will help prevent dependency bloat and ensure the components mentioned in the PR summary actually function as intended.


🏁 Script executed:

#!/bin/bash
# Description: Verify that newly added dependencies are actually imported and used

# Check for imports of the 7 new packages in the web app
for package in "radix-ui/react-label" "radix-ui/react-slot" "class-variance-authority" "clsx" "lucide-react" "tailwind-merge" "tailwindcss-animate"; do
  echo "=== Searching for imports of '$package' ==="
  rg "from ['\"]@?$package|import.*from ['\"]@?$package" apps/web --type ts --type tsx --type js --type jsx -C 2 || echo "  (No imports found)"
done

# List all files in components/ui directory to see what components exist
echo "=== Components in apps/web/app/components/ui ==="
fd . apps/web/app/components/ui --type f 2>/dev/null || echo "Directory not found or empty"

Length of output: 2744


I need to fix the search approach since the file type filter isn't recognized. Let me run corrected verification scripts:


🏁 Script executed:

#!/bin/bash
# Description: Search for imports of newly added dependencies in tsx/ts files without type filter

echo "=== Searching for dependency imports in apps/web ==="

# Search for imports of the 7 new packages (without file type filter)
for package in "radix-ui/react-label" "radix-ui/react-slot" "class-variance-authority" "clsx" "lucide-react" "tailwind-merge" "tailwindcss-animate"; do
  echo ""
  echo "### Imports of $package ###"
  rg "from ['\"](@)?$package" apps/web -C 2 | head -20 || true
done

# Sample check: Look at a few component files to understand their structure
echo ""
echo "=== Sample: button.tsx imports ==="
head -20 apps/web/app/components/ui/button.tsx 2>/dev/null || echo "File not found"

echo ""
echo "=== Sample: label.tsx imports ==="
head -20 apps/web/app/components/ui/label.tsx 2>/dev/null || echo "File not found"

echo ""
echo "=== Sample: card.tsx imports ==="
head -20 apps/web/app/components/ui/card.tsx 2>/dev/null || echo "File not found"

Length of output: 8123



🏁 Script executed:

#!/bin/bash
# Verify full package.json dependencies and check for missing Radix UI packages

echo "=== Current dependencies in package.json ==="
grep -A 50 '"dependencies"' apps/web/package.json

echo ""
echo "=== Check if tailwindcss-animate is used in CSS or config ==="
rg "tailwindcss-animate|animate-" apps/web/tailwind.config.ts 2>/dev/null || echo "(Not found in config)"

echo ""
echo "=== Check what Radix UI packages are imported in key components ==="
for component in "dialog.tsx" "select.tsx" "dropdown-menu.tsx"; do
  echo "### $component ###"
  head -15 "apps/web/app/components/ui/$component" 2>/dev/null | grep -E "import.*@radix-ui" || echo "(No Radix imports found)"
done

Length of output: 1376



🏁 Script executed:

#!/bin/bash
# Find all @radix-ui imports across all UI components

echo "=== All @radix-ui imports in UI components ==="
rg "@radix-ui" apps/web/app/components/ui --no-filename | sort | uniq

echo ""
echo "=== Compare against package.json Radix UI packages ==="
echo "In package.json:"
grep "@radix-ui" apps/web/package.json | sort

Length of output: 1933


Remove tailwindcss-animate (dead dependency) and add 26 missing Radix UI primitives required by existing UI components.

The codebase has 50+ UI components in apps/web/app/components/ui/ but is missing critical dependencies:

Confirmed issues:

  1. tailwindcss-animate@^1.0.7 is unusedβ€”not imported anywhere and absent from tailwind.config.ts. Remove it.

  2. 26 required Radix UI packages are missing (only 2 of 28 are in package.json):

    • Present: @radix-ui/react-label, @radix-ui/react-slot βœ“
    • Missing: @radix-ui/react-accordion, @radix-ui/react-alert-dialog, @radix-ui/react-aspect-ratio, @radix-ui/react-avatar, @radix-ui/react-checkbox, @radix-ui/react-collapsible, @radix-ui/react-context-menu, @radix-ui/react-dialog, @radix-ui/react-dropdown-menu, @radix-ui/react-hover-card, @radix-ui/react-menubar, @radix-ui/react-navigation-menu, @radix-ui/react-popover, @radix-ui/react-progress, @radix-ui/react-radio-group, @radix-ui/react-scroll-area, @radix-ui/react-select, @radix-ui/react-separator, @radix-ui/react-slider, @radix-ui/react-switch, @radix-ui/react-tabs, @radix-ui/react-toast, @radix-ui/react-toggle, @radix-ui/react-toggle-group, @radix-ui/react-tooltip

Verified as actively used: class-variance-authority, clsx, lucide-react, tailwind-merge are all properly imported throughout the components and should remain.

Without these Radix UI packages, the components will fail at runtime.

πŸ€– Prompt for AI Agents
In apps/web/package.json around lines 19 to 38, remove the unused
"tailwindcss-animate" dependency and add the 26 missing @radix-ui/react packages
used by the UI components: @radix-ui/react-accordion,
@radix-ui/react-alert-dialog, @radix-ui/react-aspect-ratio,
@radix-ui/react-avatar, @radix-ui/react-checkbox, @radix-ui/react-collapsible,
@radix-ui/react-context-menu, @radix-ui/react-dialog,
@radix-ui/react-dropdown-menu, @radix-ui/react-hover-card,
@radix-ui/react-menubar, @radix-ui/react-navigation-menu,
@radix-ui/react-popover, @radix-ui/react-progress, @radix-ui/react-radio-group,
@radix-ui/react-scroll-area, @radix-ui/react-select, @radix-ui/react-separator,
@radix-ui/react-slider, @radix-ui/react-switch, @radix-ui/react-tabs,
@radix-ui/react-toast, @radix-ui/react-toggle, @radix-ui/react-toggle-group, and
@radix-ui/react-tooltip; add them to "dependencies" using the same Radix version
family as the existing Radix packages in this file so the app’s UI components
resolve at runtime.

Comment on lines +35 to +41
CREATE TABLE "public"."Session" (
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

Session table is missing an explicit PRIMARY KEY constraint.

The Session table defines a UNIQUE constraint on sessionToken but does not have a PRIMARY KEY. This violates standard database schema design principles and can cause issues with:

  • Referential integrity and foreign key operations
  • Query optimization and execution plans
  • Database replication and backup/restore procedures

Add an explicit PRIMARY KEY column (typically an auto-incrementing id) or make sessionToken the PRIMARY KEY instead of just UNIQUE:

 CREATE TABLE "public"."Session" (
+    "id" TEXT NOT NULL,
     "sessionToken" TEXT NOT NULL,
     "userId" TEXT NOT NULL,
     "expires" TIMESTAMP(3) NOT NULL,
     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
     "updatedAt" TIMESTAMP(3) NOT NULL,
 
-    CONSTRAINT "Session_pkey" PRIMARY KEY ("sessionToken")
+    CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
 );
 
 -- CreateIndex
-CREATE UNIQUE INDEX "Session_sessionToken_key" ON "public"."Session"("sessionToken");
+CREATE UNIQUE INDEX "Session_sessionToken_key" ON "public"."Session"("sessionToken");

Alternatively, if sessionToken is guaranteed to be globally unique and immutable, it can serve as the PRIMARY KEY directly.

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In apps/web/prisma/migrations/20250911181640_init/migration.sql around lines 35
to 41, the Session table lacks an explicit PRIMARY KEY (only a UNIQUE on
sessionToken); update the CREATE TABLE to include a primary key: either add an
auto-incrementing id column (e.g., "id" BIGINT/INTEGER PRIMARY KEY GENERATED
ALWAYS AS IDENTITY) or make "sessionToken" the PRIMARY KEY instead of UNIQUE.
Ensure the chosen PK is reflected in any foreign key relationships or
application code that references Session, and run/commit the migration change so
the table is created with the explicit PRIMARY KEY constraint.

Comment on lines +44 to +50
CREATE TABLE "public"."VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,

CONSTRAINT "VerificationToken_pkey" PRIMARY KEY ("identifier","token")
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

VerificationToken table lacks cleanup mechanism and timestamps.

The VerificationToken table stores tokens with an expires field but has no:

  • createdAt timestamp for audit trails or cleanup queries
  • Index on the expires column for efficient cleanup of stale tokens
  • Automatic or documented cleanup strategy for expired tokens

Add createdAt and an index on expires:

 CREATE TABLE "public"."VerificationToken" (
     "identifier" TEXT NOT NULL,
     "token" TEXT NOT NULL,
     "expires" TIMESTAMP(3) NOT NULL,
+    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 
     CONSTRAINT "VerificationToken_pkey" PRIMARY KEY ("identifier","token")
 );
+
+-- CreateIndex
+CREATE INDEX "VerificationToken_expires_idx" ON "public"."VerificationToken"("expires");

I can generate a cleanup migration or a cron job template if needed.

πŸ€– Prompt for AI Agents
In apps/web/prisma/migrations/20250911181640_init/migration.sql around lines 44
to 50, the VerificationToken table is missing a createdAt column and an index on
expires; add a NOT NULL createdAt TIMESTAMP(3) DEFAULT now() column to the table
definition and create an index on the expires column (e.g., CREATE INDEX
CONCURRENTLY IF NOT EXISTS idx_verificationtoken_expires ON
"public"."VerificationToken" ("expires")) so cleanup queries are efficient; also
add a follow-up migration or note to implement a scheduled cleanup job (cron/DB
job) that deletes rows WHERE expires < now() to enforce token expiry.

Comment on lines +53 to +64
CREATE TABLE "public"."Authenticator" (
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,

CONSTRAINT "Authenticator_pkey" PRIMARY KEY ("userId","credentialID")
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Clarify the schema design for the transports column in Authenticator.

The transports column is stored as TEXT, but WebAuthn authenticator transports are typically an array (e.g., ["usb", "ble", "nfc"]). The current TEXT storage makes it unclear:

  • How the array is serialized (JSON string, comma-separated, etc.)
  • How queries filter by transport type
  • Whether the ORM (Prisma) handles serialization/deserialization

Recommend changing to JSON type for clarity and database-native querying:

 CREATE TABLE "public"."Authenticator" (
     "credentialID" TEXT NOT NULL,
     "userId" TEXT NOT NULL,
     "providerAccountId" TEXT NOT NULL,
     "credentialPublicKey" TEXT NOT NULL,
     "counter" INTEGER NOT NULL,
     "credentialDeviceType" TEXT NOT NULL,
     "credentialBackedUp" BOOLEAN NOT NULL,
-    "transports" TEXT,
+    "transports" JSON,

Alternatively, document the serialization format (e.g., "comma-separated") in a comment if TEXT is intentional for compatibility.


I'll verify the review comment by examining the migration file, Prisma schema, and how the transports column is actually used in the codebase.


🏁 Script executed:

#!/bin/bash
# Check the migration file content
echo "=== Migration file ==="
cat "apps/web/prisma/migrations/20250911181640_init/migration.sql" | sed -n '53,64p'

# Find Prisma schema file
echo -e "\n=== Prisma schema file location ==="
find . -name "schema.prisma" -o -name "*.prisma" | head -5

# Search for Authenticator model definition in Prisma schema
echo -e "\n=== Authenticator model in Prisma ==="
fd -e prisma | xargs grep -A 15 "model Authenticator" 2>/dev/null || echo "Not found with fd"

# Search for transports usage in the codebase
echo -e "\n=== Transports column usage ==="
rg -n "transports" --type=ts --type=js -C 2 | head -40

Length of output: 945


🏁 Script executed:

#!/bin/bash
# Search for Authenticator model creation/usage more broadly
echo "=== Authenticator usage in code ==="
rg -n "Authenticator" --type=ts --type=js -B 2 -A 2 | head -60

# Look for any JSON serialization patterns
echo -e "\n=== JSON stringify/parse related to authenticator ==="
rg -n "JSON\.(stringify|parse)" --type=ts --type=js -C 3 | grep -i "authenticator\|transport" -C 3 | head -40

# Search for WebAuthn library imports
echo -e "\n=== WebAuthn related imports ==="
rg -n "webauthn|@simplewebauthn" --type=ts --type=js | head -20

# Look for how authenticators are saved to database
echo -e "\n=== Database save/create patterns ==="
rg -n "\.create\(|\.upsert\(|prisma.*Authenticator" --type=ts --type=js -A 5 | head -50

Length of output: 551


🏁 Script executed:

#!/bin/bash
# Search without file type restrictions
echo "=== Authenticator references (all files) ==="
rg -n "Authenticator" | head -30

# Check the auth directory structure
echo -e "\n=== Auth directory structure ==="
find apps/web/app/api/auth -type f | head -20

# Look for any comments or TODOs related to authenticator/transports
echo -e "\n=== Comments about authenticator/transports ==="
rg -n "authenticator|transports" --type=ts --type=js -i | head -40

# Check package.json for WebAuthn dependencies
echo -e "\n=== WebAuthn dependencies in package.json ==="
find . -name "package.json" -exec grep -l "webauthn\|passkey" {} \;

# Inspect actual package.json files
echo -e "\n=== Root and apps packages ==="
cat package.json | grep -A 2 "webauthn\|passkey" 2>/dev/null || echo "Not in root"
cat apps/web/package.json | grep -A 2 "webauthn\|passkey" 2>/dev/null || echo "Not found in apps/web"

Length of output: 1148


🌐 Web query:

WebAuthn authenticator transports format specification array

πŸ’‘ Result:

Short answer

  • The transports field is an array (sequence) of DOMString values: unique, lexicographically ordered, and SHOULD be members of the AuthenticatorTransport set (client MUST ignore unknown values). [1][2]

Common values

  • WebAuthn Level 2 (stable): "usb", "nfc", "ble", "internal". [1]
  • WebAuthn Level 3 / newer drafts add additional tokens used by some implementations (e.g., "hybrid", "smart-card"). [2][3]

Where this appears

  • Returned by AuthenticatorAttestationResponse.getTransports() and stored in attestationObject / PublicKeyCredentialDescriptor.transports as a hint for allowCredentials. [1][4]

References
[1] W3C WebAuthn Level 2 β€” Authenticator Transport & transports fields.
[2] W3C WebAuthn Level 3 (editor’s draft) β€” expanded AuthenticatorTransport.
[3] MDN: AuthenticatorAttestationResponse.getTransports().


Change transports column from TEXT to JSON to match WebAuthn specification.

The W3C WebAuthn specification defines authenticator transports as an array of strings (e.g., ["usb", "ble", "nfc"]), not a single TEXT value. Storing as TEXT requires undocumented serialization/deserialization, creating confusion and maintenance issues.

Update the migration and schema:

 CREATE TABLE "public"."Authenticator" (
     "credentialID" TEXT NOT NULL,
     "userId" TEXT NOT NULL,
     "providerAccountId" TEXT NOT NULL,
     "credentialPublicKey" TEXT NOT NULL,
     "counter" INTEGER NOT NULL,
     "credentialDeviceType" TEXT NOT NULL,
     "credentialBackedUp" BOOLEAN NOT NULL,
-    "transports" TEXT,
+    "transports" JSON,

Prisma schema (line 77):

-  transports           String?
+  transports           Json?

This ensures schema alignment with WebAuthn standards and enables proper array handling in queries.

πŸ“ Committable suggestion

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

Suggested change
CREATE TABLE "public"."Authenticator" (
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
CONSTRAINT "Authenticator_pkey" PRIMARY KEY ("userId","credentialID")
);
CREATE TABLE "public"."Authenticator" (
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" JSON,
CONSTRAINT "Authenticator_pkey" PRIMARY KEY ("userId","credentialID")
);
πŸ€– Prompt for AI Agents
In apps/web/prisma/migrations/20250911181640_init/migration.sql around lines 53
to 64, change the "transports" column from TEXT to a JSON type to match WebAuthn
(store an array of strings). Update the CREATE TABLE to use JSONB (or JSON) for
"transports" and keep its nullability; if altering an existing DB migration for
applied databases, include an ALTER TABLE ... ALTER COLUMN ... TYPE jsonb USING
transports::jsonb conversion statement. Also update the Prisma schema (around
line 77) to use the Json field type for the corresponding model property so the
generated client and migrations remain consistent.

Comment on lines +76 to +82
ALTER TABLE "public"."Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."Authenticator" ADD CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add indexes on foreign key columns for query performance.

Foreign key columns (Account.userId, Session.userId, Authenticator.userId) lack indexes, which can lead to slow lookups and JOIN operations when:

  • Deleting a user and cascading deletes across related tables
  • Querying sessions or authenticators by user
  • Database query planner cannot optimize FK-based lookups efficiently

Add indexes on all foreign key columns:

 -- AddForeignKey
 ALTER TABLE "public"."Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
 
+-- CreateIndex
+CREATE INDEX "Account_userId_idx" ON "public"."Account"("userId");
+
 -- AddForeignKey
 ALTER TABLE "public"."Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
 
+-- CreateIndex
+CREATE INDEX "Session_userId_idx" ON "public"."Session"("userId");
+
 -- AddForeignKey
 ALTER TABLE "public"."Authenticator" ADD CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
 
+-- CreateIndex
+CREATE INDEX "Authenticator_userId_idx" ON "public"."Authenticator"("userId");

I can generate a separate migration file to add these indexes if you'd like.

πŸ“ Committable suggestion

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

Suggested change
ALTER TABLE "public"."Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Authenticator" ADD CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "public"."Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateIndex
CREATE INDEX "Account_userId_idx" ON "public"."Account"("userId");
-- AddForeignKey
ALTER TABLE "public"."Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateIndex
CREATE INDEX "Session_userId_idx" ON "public"."Session"("userId");
-- AddForeignKey
ALTER TABLE "public"."Authenticator" ADD CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateIndex
CREATE INDEX "Authenticator_userId_idx" ON "public"."Authenticator"("userId");
πŸ€– Prompt for AI Agents
In apps/web/prisma/migrations/20250911181640_init/migration.sql around lines 76
to 82 the foreign key columns Account.userId, Session.userId and
Authenticator.userId are not indexed which will slow joins, deletes and lookups;
add separate indexes on each FK (e.g. CREATE INDEX idx_account_user_id ON
"public"."Account"("userId"); CREATE INDEX idx_session_user_id ON
"public"."Session"("userId"); CREATE INDEX idx_authenticator_user_id ON
"public"."Authenticator"("userId");), or create a new migration file that runs
those CREATE INDEX statements (use CONCURRENTLY if needed for zero-downtime in
Postgres and choose index names that follow your naming convention).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant