Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds a contact feature (client form, POST API, DB schema and Drizzle wiring), a Pricing component/page and media-query hook, a Radix Switch UI, and migrates many animation imports from Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant CF as ContactForm (client)
participant API as /api/contact (Next.js)
participant DB as Drizzle / Postgres
U->>CF: Fill form & submit
CF->>API: POST /api/contact {name,email,company,message}
API->>API: Validate with Zod
alt Valid
API->>DB: insert into usersTable
DB-->>API: inserted record
API-->>CF: 201 {success:true, data}
CF-->>U: Show success toast
else Invalid
API-->>CF: 400 {success:false, errors}
CF-->>U: Show field errors
else Server error
API-->>CF: 500 {success:false}
CF-->>U: Show error toast
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Review by RecurseML
🔍 Review performed on 5acc05a..1a15309
✨ No bugs found, your code is sparkling clean
✅ Files analyzed, no issues (5)
• surfsense_web/components/contact/contact-form.tsx
• surfsense_web/components/pricing.tsx
• surfsense_web/components/pricing/pricing-section.tsx
• surfsense_web/app/api/contact/route.ts
• surfsense_web/hooks/use-media-query.ts
⏭️ Files skipped (56)
| Locations |
|---|
surfsense_web/.env.example |
surfsense_web/app/contact/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx |
surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx |
surfsense_web/app/dashboard/api-key/api-key-client.tsx |
surfsense_web/app/dashboard/page.tsx |
surfsense_web/app/dashboard/searchspaces/page.tsx |
surfsense_web/app/db/index.ts |
surfsense_web/app/db/schema.ts |
surfsense_web/app/login/GoogleLoginButton.tsx |
surfsense_web/app/login/LocalLoginForm.tsx |
surfsense_web/app/login/page.tsx |
surfsense_web/app/onboard/page.tsx |
surfsense_web/app/pricing/page.tsx |
surfsense_web/app/register/page.tsx |
surfsense_web/components/ModernHeroWithGradients.tsx |
surfsense_web/components/Navbar.tsx |
surfsense_web/components/chat/AnimatedEmptyState.tsx |
surfsense_web/components/onboard/add-provider-step.tsx |
surfsense_web/components/onboard/assign-roles-step.tsx |
surfsense_web/components/onboard/completion-step.tsx |
surfsense_web/components/search-space-form.tsx |
surfsense_web/components/settings/llm-role-manager.tsx |
surfsense_web/components/settings/model-config-manager.tsx |
surfsense_web/components/theme/theme-toggle.tsx |
surfsense_web/components/ui/spotlight.tsx |
surfsense_web/components/ui/switch.tsx |
surfsense_web/components/ui/tilt.tsx |
surfsense_web/drizzle.config.ts |
surfsense_web/package.json |
surfsense_web/pnpm-lock.yaml |
surfsense_web/public/contact/world.svg |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx (1)
443-480: Key uniqueness may be unstable for files with duplicate names.The key
key={${file.name}-${index}}on line 445 could be unstable if users upload multiple files with the same name (e.g., multipledocument.pdffiles from different folders). This could cause React reconciliation issues if a user removes one file and adds another with the same name.Consider using a more stable unique identifier. If files don't have a unique ID, you could generate one when files are added:
const onDrop = useCallback((acceptedFiles: File[]) => { - setFiles((prevFiles) => [...prevFiles, ...acceptedFiles]); + setFiles((prevFiles) => [ + ...prevFiles, + ...acceptedFiles.map((file, idx) => ({ + file, + id: `${Date.now()}-${idx}-${file.name}`, + })), + ]); }, []);Then update the file type and mapping:
const [files, setFiles] = useState<Array<{ file: File; id: string }>>([]); // In the map: {files.map((item) => ( <motion.div key={item.id} // ... use item.file for file properties >surfsense_web/components/chat/AnimatedEmptyState.tsx (1)
7-7: Remove unused import.The
useSidebarimport is no longer used in this component after the sidebar state was removed from the effect logic.Apply this diff:
-import { useSidebar } from "@/components/ui/sidebar"; import { cn } from "@/lib/utils";surfsense_web/components/ui/spotlight.tsx (1)
48-60: Fix event listener cleanup - memory leak.Lines 57-58 create new arrow functions when removing event listeners, so the original listeners from lines 52-53 are never removed, causing a memory leak. Event listeners must use the same function reference for both add and remove.
Apply this diff to fix the cleanup:
+ const handleMouseEnter = useCallback(() => setIsHovered(true), []); + const handleMouseLeave = useCallback(() => setIsHovered(false), []); + useEffect(() => { if (!parentElement) return; parentElement.addEventListener("mousemove", handleMouseMove); - parentElement.addEventListener("mouseenter", () => setIsHovered(true)); - parentElement.addEventListener("mouseleave", () => setIsHovered(false)); + parentElement.addEventListener("mouseenter", handleMouseEnter); + parentElement.addEventListener("mouseleave", handleMouseLeave); return () => { parentElement.removeEventListener("mousemove", handleMouseMove); - parentElement.removeEventListener("mouseenter", () => setIsHovered(true)); - parentElement.removeEventListener("mouseleave", () => setIsHovered(false)); + parentElement.removeEventListener("mouseenter", handleMouseEnter); + parentElement.removeEventListener("mouseleave", handleMouseLeave); }; - }, [parentElement, handleMouseMove]); + }, [parentElement, handleMouseMove, handleMouseEnter, handleMouseLeave]);
♻️ Duplicate comments (18)
surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx (1)
4-4: Verify the motion/react package configuration.The import has been updated from
framer-motiontomotion/react. This change follows the same migration pattern across multiple files in this PR.See the verification script in the review comment for
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsxto confirm the motion package configuration.surfsense_web/components/onboard/add-provider-step.tsx (1)
4-4: Verify the motion/react package configuration.The import has been updated from
framer-motiontomotion/react. This change follows the same migration pattern across multiple files in this PR.See the verification script in the review comment for
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsxto confirm the motion package configuration.surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx (1)
4-4: Verify the motion/react package configuration.The import has been updated from
framer-motiontomotion/react. This change follows the same migration pattern across multiple files in this PR.See the verification script in the review comment for
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsxto confirm the motion package configuration.surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx (1)
4-4: Verify the motion/react package configuration.The import has been updated from
framer-motiontomotion/react. This change follows the same migration pattern across multiple files in this PR.See the verification script in the review comment for
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsxto confirm the motion package configuration.surfsense_web/components/onboard/assign-roles-step.tsx (1)
4-4: Verify the motion/react import path is valid.Same concern as in
confluence-connector/page.tsx: ensuremotion/reactis a valid import path for your environment. See the verification script in the previous file's review.surfsense_web/components/theme/theme-toggle.tsx (1)
4-4: Verify the motion/react import path is valid.Same import migration concern. Refer to the verification script in the
confluence-connector/page.tsxreview.surfsense_web/components/search-space-form.tsx (1)
5-5: Verify the motion/react import path is valid.This file imports both
motionand theVariantstype frommotion/react. Ensure both exports are available. Refer to the verification script in theconfluence-connector/page.tsxreview.surfsense_web/app/login/LocalLoginForm.tsx (1)
2-2: Verify the motion/react import path is valid.This file imports both
AnimatePresenceandmotionfrommotion/react. Ensure both exports are available from this path, asAnimatePresenceis a core animation primitive. Refer to the verification script in theconfluence-connector/page.tsxreview.surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx (1)
9-9: Same import migration as other files - see verification request.This file has the identical import path change from
framer-motiontomotion/react. Please see the verification request insurfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsxat line 18 to confirm this is a valid migration.surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx (1)
4-4: Same import migration - verification pending.This file has the same import path change from
framer-motiontomotion/react. Refer to the verification request insurfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsxat line 18.surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx (1)
4-4: Same import migration - verification pending.This file has the same import path change. See verification request in
surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsxat line 18.surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx (1)
3-3: Same import migration - verification pending.This file has the same import path change. See verification request in
surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsxat line 18.surfsense_web/app/dashboard/page.tsx (1)
3-3: Verify the "motion/react" import path is correct.The import source for both
motionandVariantshas changed fromframer-motiontomotion/react. As noted in the learnings,motionas a standalone package is ambiguous and may not be the official package name.Ensure this import path is valid and that type exports like
Variantsare available frommotion/react. The verification script in the previous file will confirm the package configuration.surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx (1)
3-3: Verify the "motion/react" import path is correct.Same import path change as other files in this PR. Confirm this resolves correctly per the verification script provided earlier.
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx (1)
3-3: Verify the "motion/react" import path is correct.Same import path change. Ensure consistency across the codebase and verify this path resolves correctly.
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx (1)
3-3: Verify the "motion/react" import path is correct.Same import path change as the rest of this PR. Confirm the package resolution is correct per earlier verification script.
surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx (1)
3-3: Verify the "motion/react" import source.Same concern as in DocumentsFilters.tsx: the import source
motion/reactshould be verified. See the verification script and comments in DocumentsFilters.tsx review.Based on learnings.
surfsense_web/components/ui/spotlight.tsx (1)
2-2: Verify the "motion/react" import source.Same concern as in other files: the import source
motion/reactshould be verified. See the verification script and comments in DocumentsFilters.tsx review.Based on learnings.
🧹 Nitpick comments (7)
surfsense_web/app/pricing/page.tsx (2)
1-1: Remove unused React import.In React 19 and Next.js 15, explicit React imports are no longer required for JSX. This import can be removed.
-import React from 'react' import PricingBasic from '@/components/pricing/pricing-section'
4-10: Add metadata export for SEO.Next.js pages should export metadata for proper SEO, Open Graph tags, and page titles.
Add metadata export at the top of the file:
import type { Metadata } from 'next' export const metadata: Metadata = { title: 'Pricing - SurfSense', description: 'Choose the right plan for your needs', }surfsense_web/components/pricing/pricing-section.tsx (1)
64-72: Clean wrapper component.The PricingBasic component properly delegates to the shared Pricing component with appropriate props.
For improved type safety, consider defining an explicit type:
+import { Pricing, type PricingProps } from "@/components/pricing"; + +type Plan = PricingProps['plans'][number]; + -const demoPlans = [ +const demoPlans: Plan[] = [surfsense_web/components/pricing.tsx (2)
95-129: Use a stable plan key instead of the index
key={index}will reshuffle animations/state if the plans array order changes. Prefer a deterministic identifier such asplan.nameorplan.hrefto keep the motion cards stable between renders.As per coding guidelines
Apply this diff:- {plans.map((plan, index) => ( + {plans.map((plan, index) => ( <motion.div - key={index} + key={plan.name}
169-175: Switch feature list to a stable keySimilarly, using the array index for feature items risks React reusing DOM nodes incorrectly. If each feature string is unique, use it directly as the key for deterministic reconciliation.
As per coding guidelines
Apply this diff:- {plan.features.map((feature, idx) => ( - <li key={idx} className="flex items-start gap-2"> + {plan.features.map((feature) => ( + <li key={feature} className="flex items-start gap-2">surfsense_web/package.json (1)
55-55: Standardize on a single PostgreSQL client.schema.ts (drizzle-orm/pg-core) uses node-postgres, while index.ts (drizzle-orm/postgres-js + postgres) uses postgres.js. Consolidate on one client to reduce bundle size and eliminate confusion—either remove
pg/@types/pganddrizzle-orm/pg-core, or removepostgresanddrizzle-orm/postgres-jsand switch fully to pg-core.surfsense_web/components/settings/llm-role-manager.tsx (1)
400-404: Consider using stable content-based keys instead of array index.The code uses the array index (
idx) as the key for mappingrole.characteristics. While this works becausecharacteristicsis a static array from a constant, using the characteristic string itself as the key would be more robust and align better with React best practices.Apply this diff to use content-based keys:
-{role.characteristics.map((char, idx) => ( - <Badge key={idx} variant="outline" className="text-xs"> +{role.characteristics.map((char) => ( + <Badge key={char} variant="outline" className="text-xs"> {char} </Badge> ))}As per coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
surfsense_web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsurfsense_web/public/contact/world.svgis excluded by!**/*.svg
📒 Files selected for processing (59)
surfsense_web/.env.example(1 hunks)surfsense_web/app/api/contact/route.ts(1 hunks)surfsense_web/app/contact/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx(1 hunks)surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx(1 hunks)surfsense_web/app/dashboard/api-key/api-key-client.tsx(1 hunks)surfsense_web/app/dashboard/page.tsx(1 hunks)surfsense_web/app/dashboard/searchspaces/page.tsx(1 hunks)surfsense_web/app/db/index.ts(1 hunks)surfsense_web/app/db/schema.ts(1 hunks)surfsense_web/app/login/GoogleLoginButton.tsx(1 hunks)surfsense_web/app/login/LocalLoginForm.tsx(1 hunks)surfsense_web/app/login/page.tsx(1 hunks)surfsense_web/app/onboard/page.tsx(1 hunks)surfsense_web/app/pricing/page.tsx(1 hunks)surfsense_web/app/register/page.tsx(1 hunks)surfsense_web/components/ModernHeroWithGradients.tsx(2 hunks)surfsense_web/components/Navbar.tsx(3 hunks)surfsense_web/components/chat/AnimatedEmptyState.tsx(1 hunks)surfsense_web/components/contact/contact-form.tsx(1 hunks)surfsense_web/components/onboard/add-provider-step.tsx(1 hunks)surfsense_web/components/onboard/assign-roles-step.tsx(1 hunks)surfsense_web/components/onboard/completion-step.tsx(1 hunks)surfsense_web/components/pricing.tsx(1 hunks)surfsense_web/components/pricing/pricing-section.tsx(1 hunks)surfsense_web/components/search-space-form.tsx(1 hunks)surfsense_web/components/settings/llm-role-manager.tsx(1 hunks)surfsense_web/components/settings/model-config-manager.tsx(1 hunks)surfsense_web/components/theme/theme-toggle.tsx(1 hunks)surfsense_web/components/ui/spotlight.tsx(1 hunks)surfsense_web/components/ui/switch.tsx(1 hunks)surfsense_web/components/ui/tilt.tsx(1 hunks)surfsense_web/drizzle.config.ts(1 hunks)surfsense_web/hooks/use-media-query.ts(1 hunks)surfsense_web/package.json(4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.rules/require_unique_id_props.mdc)
**/*.{jsx,tsx}: When mapping arrays to React elements in JSX/TSX, each rendered element must include a unique key prop
Keys used for React list items should be stable, predictable, and unique among siblings
Files:
surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/(manage)/page.tsxsurfsense_web/components/theme/theme-toggle.tsxsurfsense_web/components/settings/model-config-manager.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/page.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsxsurfsense_web/app/register/page.tsxsurfsense_web/components/ui/spotlight.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/edit/page.tsxsurfsense_web/components/chat/AnimatedEmptyState.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/linear-connector/page.tsxsurfsense_web/app/dashboard/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsxsurfsense_web/components/onboard/completion-step.tsxsurfsense_web/app/dashboard/searchspaces/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/github-connector/page.tsxsurfsense_web/app/onboard/page.tsxsurfsense_web/app/pricing/page.tsxsurfsense_web/app/contact/page.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/(manage)/page.tsxsurfsense_web/components/onboard/assign-roles-step.tsxsurfsense_web/components/pricing/pricing-section.tsxsurfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsxsurfsense_web/components/contact/contact-form.tsxsurfsense_web/components/onboard/add-provider-step.tsxsurfsense_web/app/dashboard/api-key/api-key-client.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/discord-connector/page.tsxsurfsense_web/components/pricing.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/serper-api/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsxsurfsense_web/components/ModernHeroWithGradients.tsxsurfsense_web/components/ui/switch.tsxsurfsense_web/components/settings/llm-role-manager.tsxsurfsense_web/components/ui/tilt.tsxsurfsense_web/app/login/page.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/upload/page.tsxsurfsense_web/app/login/LocalLoginForm.tsxsurfsense_web/components/search-space-form.tsxsurfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/PaginationControls.tsxsurfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsxsurfsense_web/app/login/GoogleLoginButton.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/slack-connector/page.tsxsurfsense_web/app/dashboard/[search_space_id]/connectors/add/linkup-api/page.tsxsurfsense_web/components/Navbar.tsx
**/.env.*
📄 CodeRabbit inference engine (.rules/no_env_files_in_repo.mdc)
Do not commit variant environment files like .env.* (e.g., .env.local, .env.production)
Files:
surfsense_web/.env.example
**/.env.example
📄 CodeRabbit inference engine (.rules/no_env_files_in_repo.mdc)
Provide a .env.example file with placeholder values instead of real secrets
Files:
surfsense_web/.env.example
🧬 Code graph analysis (4)
surfsense_web/app/api/contact/route.ts (2)
surfsense_web/app/db/index.ts (1)
db(6-6)surfsense_web/app/db/schema.ts (1)
usersTable(3-9)
surfsense_web/app/contact/page.tsx (1)
surfsense_web/components/contact/contact-form.tsx (1)
ContactFormGridWithDetails(24-204)
surfsense_web/components/pricing/pricing-section.tsx (1)
surfsense_web/components/pricing.tsx (1)
Pricing(33-202)
surfsense_web/components/pricing.tsx (1)
surfsense_web/hooks/use-media-query.ts (1)
useMediaQuery(8-37)
🪛 dotenv-linter (3.3.0)
surfsense_web/.env.example
[warning] 3-3: [UnorderedKey] The NEXT_PUBLIC_ETL_SERVICE key should go before the NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE key
(UnorderedKey)
[warning] 3-3: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 5-5: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
[warning] 5-5: [UnorderedKey] The DATABASE_URL key should go before the NEXT_PUBLIC_ETL_SERVICE key
(UnorderedKey)
🪛 GitHub Actions: Code Quality Checks
surfsense_web/app/contact/page.tsx
[error] 1-1: Prettier formatting check failed. The formatter would rewrite content in this file. Run the formatter (e.g., 'prettier --write') to fix code style issues.
surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx
[error] 3-3: The imports and exports are not sorted. Organize imports (Biome) to fix order.
🔇 Additional comments (31)
surfsense_web/app/dashboard/[search_space_id]/connectors/add/tavily-api/page.tsx (1)
4-4: Verifiedmotion/reactimport. Themotionpackage (v12.23.22) defines a./reactexport, soimport { motion } from "motion/react"is valid.surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx (1)
4-4: Import path change is part of project-wide migration.This file follows the same
motion/reactimport migration pattern flagged insurfsense_web/app/login/page.tsx. Verification of the import path validity applies here as well.surfsense_web/components/onboard/completion-step.tsx (2)
4-4: Import path change is part of project-wide migration.This file follows the same
motion/reactimport migration pattern. See verification request insurfsense_web/app/login/page.tsx.
55-90: LGTM: React key prop correctly applied.The
.map()rendering of configuration entries correctly includes a stablekey={role}prop on line 66, following React best practices and coding guidelines.surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-calendar-connector/page.tsx (1)
4-4: Import path change is part of project-wide migration.This file follows the same
motion/reactimport migration pattern. See verification request insurfsense_web/app/login/page.tsx.surfsense_web/app/onboard/page.tsx (2)
3-3: Import path change is part of project-wide migration.This file follows the same
motion/reactimport migration pattern. See verification request insurfsense_web/app/login/page.tsx.
129-158: LGTM: React key prop correctly applied.The
Array.from().map()rendering of step indicators correctly includes a stablekey={stepNum}prop on line 135, following React best practices and coding guidelines.surfsense_web/app/login/page.tsx (1)
3-3: Import path and dependency correct. The"motion/react"import is the documented entry point for Motion for React, and the project’s package.json includes"motion": "^12.23.22", matching the official upgrade guide (motion.dev).surfsense_web/components/onboard/assign-roles-step.tsx (2)
132-209: LGTM: Proper key props on mapped elements.The mapped
motion.divelements correctly usekey={key}(line 139), providing stable and unique keys for React reconciliation.
228-237: LGTM: Proper key props on progress indicators.The progress indicator divs correctly use
key={key}(line 229), ensuring proper React list rendering.surfsense_web/app/dashboard/[search_space_id]/connectors/add/confluence-connector/page.tsx (1)
4-4: Accept motion/react import path
The standalone motion@^12.23.22 package exports "./react", so the import is valid.surfsense_web/components/chat/AnimatedEmptyState.tsx (1)
109-117: Effect behavior changed: no longer reacts to sidebar state.The effect now runs only once on mount (empty dependency array), dispatching
SIDEBAR_CHANGEDimmediately and stabilizing layout after a timeout. Previously, this effect likely reacted to sidebar state changes. Ensure this behavior change is intentional and that animations still work correctly when the sidebar is toggled.The simplified logic looks correct, but verify that the animation timing still works as expected when users interact with the sidebar.
surfsense_web/app/dashboard/[search_space_id]/connectors/add/luma-connector/page.tsx (1)
4-4: Approve motion/react import usageProject’s package.json includes a “motion” dependency and the Motion documentation confirms “motion/react” is the correct entry point for the new Motion package, so no changes are needed.
surfsense_web/app/register/page.tsx (1)
3-3: Import migration looks consistent.The change from
framer-motiontomotion/reactaligns with the project-wide migration. Ensure the verification fromtilt.tsxconfirms the package is properly configured.surfsense_web/components/settings/model-config-manager.tsx (1)
17-17: LGTM!Consistent with the project-wide migration to
motion/react.surfsense_web/app/dashboard/[search_space_id]/documents/youtube/page.tsx (1)
5-5: LGTM!The migration includes both runtime and type imports (
motion,Variants), consistent with the project-wide update.surfsense_web/app/login/GoogleLoginButton.tsx (1)
3-3: LGTM!Consistent with the project-wide migration to
motion/react.surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx (1)
18-18: Confirmmotion/reactis valid:surfsense_web/package.jsonlists"motion": "^12.23.22"alongsideframer-motion, and the lock file shows its sub-packages (motion-dom,motion-utils), so importing frommotion/reactresolves correctly.surfsense_web/app/dashboard/[search_space_id]/connectors/[connector_id]/page.tsx (1)
4-4: No changes needed for motion/react import
The motion@12.23.22 package defines a “./react” export, soimport { motion } from "motion/react"resolves correctly.surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx (3)
159-183: LGTM: Correct key usage in list rendering.The
key={value}prop uses the unique type string, which is stable and unique among siblings.
218-228: LGTM: Correct key usage in list rendering.The
key={key}prop uses the column identifier from the tuple, which is stable and unique.
3-3: Approvemotion/reactimport
motion/reactis a valid entrypoint inmotion@^12.23.22per its package exports.surfsense_web/components/ModernHeroWithGradients.tsx (2)
2-2: LGTM: IconMail import added.The IconMail icon is correctly imported and used in the new Contact Us button.
57-63: LGTM: Contact Us button implemented consistently.The new Contact Us button follows the same pattern and styling as the existing Discord and GitHub buttons, providing a cohesive user experience.
surfsense_web/.env.example (1)
4-5: LGTM! Proper placeholder pattern for optional database configuration.The DATABASE_URL is correctly marked as OPTIONAL with a clear placeholder format. The connection string template makes it easy for developers to understand what values to substitute.
surfsense_web/components/pricing/pricing-section.tsx (1)
5-62: Well-structured pricing data.The pricing tiers are clearly defined with consistent schema. The Enterprise tier appropriately links to the new contact page.
surfsense_web/package.json (2)
16-19: LGTM! Database tooling scripts properly configured.The Drizzle Kit workflow scripts are correctly set up for schema generation, migration, push, and studio access.
25-25: LGTM! Utility dependencies added appropriately.The new utility packages (
@number-flow/react,canvas-confetti,dotenv) are at reasonable versions for their intended use cases.Also applies to: 50-50, 54-54
surfsense_web/components/ui/switch.tsx (2)
1-11: LGTM! Component structure follows best practices.The component correctly uses the
forwardRefpattern with proper TypeScript typing, extracting the correct ref and props types from the Radix primitives.
12-29: LGTM! Switch component properly implemented with accessibility features.The implementation correctly:
- Composes Radix primitives with custom styling
- Includes focus-visible ring for keyboard navigation
- Handles disabled states appropriately
- Uses data-attributes for checked/unchecked styling
- Sets displayName for better debugging
- Applies smooth transitions for the thumb animation
Note: Accessible labels should be provided by consuming components using this switch.
surfsense_web/components/settings/llm-role-manager.tsx (1)
15-15: Import from “motion/react” is correct. Verified that the installed motion package (v12.23.x) provides React bindings viaimport { motion } from "motion/react"per the official Motion docs and npm registry (npmjs.com).
| import React from 'react' | ||
| import { ContactFormGridWithDetails } from '@/components/contact/contact-form' | ||
|
|
||
| const page = () => { | ||
| return ( | ||
| <div> | ||
| <ContactFormGridWithDetails /> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export default page No newline at end of file |
There was a problem hiding this comment.
Fix formatting and consider adding page metadata.
The pipeline indicates a Prettier formatting issue that must be resolved.
Run Prettier to fix:
npx prettier --write app/contact/page.tsxAdditionally, consider adding page metadata for SEO:
import React from 'react'
import { ContactFormGridWithDetails } from '@/components/contact/contact-form'
+import { Metadata } from 'next'
+
+export const metadata: Metadata = {
+ title: 'Contact Us | SurfSense',
+ description: 'Get in touch with the SurfSense team',
+}
const page = () => {
return (
- <div>
- <ContactFormGridWithDetails />
- </div>
+ <ContactFormGridWithDetails />
)
}
export default page🧰 Tools
🪛 GitHub Actions: Code Quality Checks
[error] 1-1: Prettier formatting check failed. The formatter would rewrite content in this file. Run the formatter (e.g., 'prettier --write') to fix code style issues.
🤖 Prompt for AI Agents
In surfsense_web/app/contact/page.tsx lines 1-12, Prettier formatting errors
must be fixed and page metadata should be added; run the formatter (npx prettier
--write app/contact/page.tsx) to correct whitespace/formatting, then export
Next.js page metadata (e.g., export const metadata = { title: 'Contact —
YourSite', description: 'Short description for SEO' }) above the component and
ensure the component is a default export; save and re-run lint/CI.
| import { format } from "date-fns"; | ||
| import { AnimatePresence, motion, type Variants } from "framer-motion"; | ||
| import { AnimatePresence, motion, type Variants } from "motion/react"; |
There was a problem hiding this comment.
Fix import order to resolve pipeline failure.
The pipeline reports an import/export sorting issue. The Biome linter requires imports to be organized in a specific order.
Please run your code formatter/linter to fix the import order:
# Fix import order with Biome
npx biome check --apply .🧰 Tools
🪛 GitHub Actions: Code Quality Checks
[error] 3-3: The imports and exports are not sorted. Organize imports (Biome) to fix order.
🤖 Prompt for AI Agents
In surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx around
lines 3 to 4, the import statements are not ordered according to the Biome
linter rules which causes the pipeline failure; run the Biome fixer and reorder
imports to match the project's import-sort convention (external packages,
grouped/aliased imports, then local imports), e.g. run `npx biome check --apply
.` to automatically fix the import order and then commit the formatted file.
| "use client"; | ||
|
|
||
| import { motion } from "framer-motion"; | ||
| import { motion } from "motion/react"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Verify the motion/react import path.
The import has been changed from framer-motion to motion/react. According to the retrieved learnings, framer-motion is the official animation library for React, and "motion" as a standalone package is not a widely-adopted separate package. This import path may be incorrect or may reference an unofficial fork.
Please verify this import path change by checking:
- Whether
motion/reactis a valid export from an installed package - If this is part of an intentional migration to a different package
🏁 Script executed:
#!/bin/bash
# Verify the motion/react import and package installation
echo "=== Checking package.json for motion-related dependencies ==="
cat package.json | jq '.dependencies, .devDependencies' | grep -i motion
echo -e "\n=== Checking if motion is a separate package or export from framer-motion ==="
# Check package-lock or yarn.lock for motion package
if [ -f package-lock.json ]; then
cat package-lock.json | jq '.packages | keys[]' | grep motion || echo "No motion package found in package-lock.json"
elif [ -f yarn.lock ]; then
grep '"motion@' yarn.lock || echo "No motion package found in yarn.lock"
fi
echo -e "\n=== Searching for other files using motion/react ==="
rg -l "from ['\"]motion/react['\"]" --type ts --type tsx || echo "No other files found"
echo -e "\n=== Searching for files still using framer-motion ==="
rg -l "from ['\"]framer-motion['\"]" --type ts --type tsx | head -5 || echo "No files using framer-motion found"Length of output: 371
🏁 Script executed:
#!/bin/bash
# Locate all package.json and lock files within 4 directory levels
echo "=== Found package.json files ==="
fd --max-depth 4 package.json -t f
echo -e "\n=== Found lock files ==="
fd --max-depth 4 package-lock.json -t f
fd --max-depth 4 yarn.lock -t f
fd --max-depth 4 pnpm-lock.yaml -t f
# For each package.json found, print the dependencies section
for f in $(fd --max-depth 4 package.json -t f); do
echo -e "\n=== Dependencies in $f ==="
jq '.dependencies, .devDependencies' "$f"
doneLength of output: 3875
🏁 Script executed:
#!/bin/bash
# Search for framer-motion imports in surfsense_web
rg -g '*.tsx' -n "from ['\"]framer-motion['\"]" surfsense_web || echo "No framer-motion imports found"
rg -g '*.ts' -n "from ['\"]framer-motion['\"]" surfsense_web || echo "No framer-motion imports found"
echo "------"
# Search for motion/react imports in surfsense_web
rg -g '*.tsx' -n "from ['\"]motion/react['\"]" surfsense_web || echo "No motion/react imports found"
rg -g '*.ts' -n "from ['\"]motion/react['\"]" surfsense_web || echo "No motion/react imports found"Length of output: 5516
Unify animation imports: Replace the lone import { motion } from "framer-motion" in surfsense_web/components/pricing.tsx with import { motion } from "motion/react", and remove the framer-motion dependency if it’s no longer used.
🤖 Prompt for AI Agents
In surfsense_web/app/dashboard/searchspaces/page.tsx around line 3, the project
is standardizing on the "motion" package; update any remaining imports of motion
from "framer-motion" (specifically in surfsense_web/components/pricing.tsx) to
use import { motion } from "motion/react" instead, then run a project-wide
search to confirm no other files import from "framer-motion" and if none remain
remove the framer-motion dependency from package.json and lockfile and run
npm/yarn install to update dependencies.
| const client = postgres(process.env.DATABASE_URL!); | ||
| export const db = drizzle({ client, schema }); |
There was a problem hiding this comment.
Add validation for DATABASE_URL to prevent runtime crashes.
The non-null assertion on process.env.DATABASE_URL! will cause a runtime error if the environment variable is missing. Since the contact form is marked as OPTIONAL in the .env.example, the database setup should gracefully handle missing configuration.
Apply this diff to add proper validation:
+if (!process.env.DATABASE_URL) {
+ throw new Error(
+ 'DATABASE_URL is not defined. Please set it in your .env file or disable the contact form feature.'
+ );
+}
+
const client = postgres(process.env.DATABASE_URL);
export const db = drizzle({ client, schema });Additionally, consider connection pool configuration for production:
-const client = postgres(process.env.DATABASE_URL);
+const client = postgres(process.env.DATABASE_URL, {
+ max: 10, // Maximum pool size
+ idle_timeout: 20, // Close idle connections after 20s
+ connect_timeout: 10, // Connection timeout in seconds
+});Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In surfsense_web/app/db/index.ts around lines 5-6, the code uses a non-null
assertion on process.env.DATABASE_URL which will throw at runtime if the
variable is missing; update the file to validate DATABASE_URL before creating
the client: check if process.env.DATABASE_URL is present and either (a) throw a
clear, descriptive error with guidance when the app requires a DB, or (b) export
a no-op/mock db or undefined so the rest of the app can run when the database is
optional; if creating a client, pass configuration options instead of the raw
string and include connection pool settings (max connections, idle timeout)
suitable for production, and ensure exported db is typed to reflect the possible
undefined/no-op state so callers handle it safely.
| export const usersTable = pgTable("users", { | ||
| id: integer().primaryKey().generatedAlwaysAsIdentity(), | ||
| name: varchar({ length: 255 }).notNull(), | ||
| email: varchar({ length: 255 }).notNull().unique(), |
There was a problem hiding this comment.
Remove the unique constraint on contact emails
Line [6]: Marking email as .unique() means a second submission from the same address will violate the constraint and your /api/contact handler will fail with a duplicate key error instead of accepting the new message. Contact forms usually allow repeat outreach, so please drop the uniqueness constraint (or model submissions in a separate table keyed by their own id). Apply this diff to unblock repeat submissions:
- email: varchar({ length: 255 }).notNull().unique(),
+ email: varchar({ length: 255 }).notNull(),📝 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.
| email: varchar({ length: 255 }).notNull().unique(), | |
| email: varchar({ length: 255 }).notNull(), |
🤖 Prompt for AI Agents
In surfsense_web/app/db/schema.ts around line 6, the email column is marked
.unique() which prevents repeat submissions; remove the .unique() call from the
email field definition (or alternatively model contact submissions in their own
table with a separate primary id) so multiple messages from the same address are
allowed and duplicate-key errors are avoided by the /api/contact handler.
| <div className="flex justify-center mb-10"> | ||
| <label className="relative inline-flex items-center cursor-pointer"> | ||
| <Label> | ||
| <Switch | ||
| ref={switchRef as any} | ||
| checked={!isMonthly} | ||
| onCheckedChange={handleToggle} | ||
| className="relative" | ||
| /> | ||
| </Label> | ||
| </label> | ||
| <span className="ml-2 font-semibold"> | ||
| Annual billing <span className="text-primary">(Save 20%)</span> | ||
| </span> | ||
| </div> |
There was a problem hiding this comment.
Fix nested labels so the Switch is properly announced
<label> wrapping another <Label> (which renders a <label>) creates invalid nested labels, so the Radix Switch loses its accessible name and screen readers won’t announce the control correctly. Please unwrap the Switch and point the single label at it via htmlFor (or add an aria-label) so the billing toggle keeps valid semantics.
Apply this diff:
- <div className="flex justify-center mb-10">
- <label className="relative inline-flex items-center cursor-pointer">
- <Label>
- <Switch
- ref={switchRef as any}
- checked={!isMonthly}
- onCheckedChange={handleToggle}
- className="relative"
- />
- </Label>
- </label>
- <span className="ml-2 font-semibold">
- Annual billing <span className="text-primary">(Save 20%)</span>
- </span>
- </div>
+ <div className="flex items-center justify-center mb-10 gap-2">
+ <Switch
+ id="billing-cycle"
+ ref={switchRef as any}
+ checked={!isMonthly}
+ onCheckedChange={handleToggle}
+ className="relative"
+ aria-label="Toggle annual billing"
+ />
+ <Label htmlFor="billing-cycle" className="font-semibold cursor-pointer">
+ Annual billing <span className="text-primary">(Save 20%)</span>
+ </Label>
+ </div>📝 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.
| <div className="flex justify-center mb-10"> | |
| <label className="relative inline-flex items-center cursor-pointer"> | |
| <Label> | |
| <Switch | |
| ref={switchRef as any} | |
| checked={!isMonthly} | |
| onCheckedChange={handleToggle} | |
| className="relative" | |
| /> | |
| </Label> | |
| </label> | |
| <span className="ml-2 font-semibold"> | |
| Annual billing <span className="text-primary">(Save 20%)</span> | |
| </span> | |
| </div> | |
| <div className="flex items-center justify-center mb-10 gap-2"> | |
| <Switch | |
| id="billing-cycle" | |
| ref={switchRef as any} | |
| checked={!isMonthly} | |
| onCheckedChange={handleToggle} | |
| className="relative" | |
| aria-label="Toggle annual billing" | |
| /> | |
| <Label htmlFor="billing-cycle" className="font-semibold cursor-pointer"> | |
| Annual billing <span className="text-primary">(Save 20%)</span> | |
| </Label> | |
| </div> |
🤖 Prompt for AI Agents
In surfsense_web/components/pricing.tsx around lines 78 to 92, there is a nested
<label> (a <label> wrapping the custom <Label> which itself renders a <label>),
which produces invalid HTML and breaks the Radix Switch accessibility; remove
the outer <label> wrapper so only a single label exists, give the Switch an id
and have the label reference it via htmlFor (or, alternatively, add a clear
aria-label to the Switch), and keep the existing ref, checked and
onCheckedChange props unchanged so the visual/behavioral logic is preserved.
| useSpring, | ||
| useTransform, | ||
| } from "framer-motion"; | ||
| } from "motion/react"; |
There was a problem hiding this comment.
🧩 Analysis chain
Verify the motion/react package is properly configured.
The import path has been updated from framer-motion to motion/react. Ensure this package is installed and that all exported APIs (MotionStyle, motion, SpringOptions, etc.) are available at this new path.
Run the following script to verify the package configuration:
🏁 Script executed:
#!/bin/bash
# Check if motion package exists and verify the export path
if [ -f "package.json" ]; then
echo "=== Checking package.json for motion-related dependencies ==="
cat package.json | jq '.dependencies, .devDependencies' | grep -E "(motion|framer)"
fi
echo -e "\n=== Checking if motion/react resolves correctly ==="
node -e "try { require.resolve('motion/react'); console.log('✓ motion/react resolves successfully'); } catch(e) { console.log('✗ motion/react does not resolve:', e.message); }"
echo -e "\n=== Searching for other files still using framer-motion ==="
rg -l "from ['\"]framer-motion['\"]" --type ts --type tsx 2>/dev/null || echo "No files found using framer-motion"Length of output: 258
Fix broken motion/react import
The import motion/react fails to resolve (module not found) and there’s no motion or framer-motion dependency in package.json. Revert to framer-motion or add/adjust the motion package so that motion/react resolves correctly.
🤖 Prompt for AI Agents
In surfsense_web/components/ui/tilt.tsx around line 11 the import from
"motion/react" fails to resolve; replace the import with the equivalent from
"framer-motion" (e.g. import { motion } from "framer-motion") or alternatively
add the correct "motion" package to package.json and install it so
"motion/react" is resolvable; after changing the import, run npm/yarn install
and update any TypeScript types/import paths accordingly.
| import 'dotenv/config'; | ||
| import { defineConfig } from 'drizzle-kit'; | ||
|
|
||
| export default defineConfig({ | ||
| out: './drizzle', | ||
| schema: './app/db/schema.ts', | ||
| dialect: 'postgresql', | ||
| dbCredentials: { | ||
| url: process.env.DATABASE_URL!, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
❓ Verification inconclusive
Add validation for DATABASE_URL to prevent CLI command failures.
Similar to the issue in app/db/index.ts, the non-null assertion on process.env.DATABASE_URL! will cause drizzle-kit commands (generate, push, migrate, studio) to crash if the environment variable is missing.
Apply this diff to add proper validation:
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
+if (!process.env.DATABASE_URL) {
+ throw new Error(
+ 'DATABASE_URL is not defined. Please set it in your .env file to use Drizzle Kit commands.'
+ );
+}
+
export default defineConfig({
out: './drizzle',
schema: './app/db/schema.ts',
dialect: 'postgresql',
dbCredentials: {
- url: process.env.DATABASE_URL!,
+ url: process.env.DATABASE_URL,
},
});This will provide a clear error message when developers try to run database commands without configuring DATABASE_URL.
Add a runtime check for DATABASE_URL in drizzle.config.ts
In surfsense_web/drizzle.config.ts, throw a clear error if process.env.DATABASE_URL is missing before calling defineConfig.
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
+if (!process.env.DATABASE_URL) {
+ throw new Error(
+ 'DATABASE_URL is not defined. Please set it in your .env file to use Drizzle Kit commands.'
+ );
+}
export default defineConfig({
out: './drizzle',
schema: './app/db/schema.ts',
dialect: 'postgresql',
dbCredentials: {
- url: process.env.DATABASE_URL!,
+ url: process.env.DATABASE_URL,
},
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import 'dotenv/config'; | |
| import { defineConfig } from 'drizzle-kit'; | |
| export default defineConfig({ | |
| out: './drizzle', | |
| schema: './app/db/schema.ts', | |
| dialect: 'postgresql', | |
| dbCredentials: { | |
| url: process.env.DATABASE_URL!, | |
| }, | |
| }); | |
| import 'dotenv/config'; | |
| import { defineConfig } from 'drizzle-kit'; | |
| if (!process.env.DATABASE_URL) { | |
| throw new Error( | |
| 'DATABASE_URL is not defined. Please set it in your .env file to use Drizzle Kit commands.' | |
| ); | |
| } | |
| export default defineConfig({ | |
| out: './drizzle', | |
| schema: './app/db/schema.ts', | |
| dialect: 'postgresql', | |
| dbCredentials: { | |
| url: process.env.DATABASE_URL, | |
| }, | |
| }); |
🤖 Prompt for AI Agents
In surfsense_web/drizzle.config.ts around lines 1 to 11, add a runtime check
that reads process.env.DATABASE_URL into a const and if it's falsy throw a clear
Error (e.g. "Missing DATABASE_URL environment variable") before calling
defineConfig; then pass that const as the dbCredentials.url value to ensure
defineConfig always receives a defined string.
surfsense_web/package.json
Outdated
| "drizzle-orm": "^0.44.5", | ||
| "emblor": "^1.4.8", | ||
| "framer-motion": "^12.23.9", | ||
| "framer-motion": "^12.23.22", |
There was a problem hiding this comment.
🧩 Analysis chain
Verify the "motion" package - likely incorrect dependency.
The learnings indicate that "motion" is not a standalone package but an export from framer-motion. The PR summary mentions migrating to motion/react imports, which suggests using import paths like import { motion } from "motion/react" from the framer-motion package itself, not a separate "motion" dependency.
Having both framer-motion and motion as separate dependencies at the same version is likely incorrect and may cause module resolution issues.
Based on learnings.
Run the following script to verify the "motion" package identity and check actual import usage:
Also applies to: 63-63
🏁 Script executed:
#!/bin/bash
# Description: Verify the motion package and check import patterns across the codebase
echo "=== Checking npm registry for standalone 'motion' package ==="
npm view motion versions --json 2>&1 | head -20
echo -e "\n=== Checking import patterns in codebase ==="
rg -n --type=tsx --type=jsx --type=ts --type=js 'from ["\x27](motion|framer-motion)' -A 1 | head -40
echo -e "\n=== Checking package.json in node_modules/motion if it exists ==="
if [ -d "node_modules/motion" ]; then
cat node_modules/motion/package.json | jq '.name, .version, .description' 2>/dev/null || echo "motion package exists but package.json unreadable"
else
echo "motion package not found in node_modules"
fiLength of output: 459
Remove the standalone motion dependency
The npm registry has no motion package at version ^12.23.22 (latest is 0.9.14); use the motion/react entrypoint from your existing framer-motion dependency instead.
🤖 Prompt for AI Agents
In surfsense_web/package.json around line 57, remove the invalid standalone
"motion" dependency entry (version ^12.23.22) and rely on the existing
"framer-motion" package; then update any import statements in the codebase that
reference "motion" to import from the framer-motion entrypoint (e.g., import {
motion } from 'framer-motion' or from 'framer-motion/whatever/entrypoint' as
appropriate), run npm install to refresh lockfile, and ensure
package-lock.json/Yarn lock is committed.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
surfsense_web/components/pricing.tsx (1)
78-92: Nested labels remain unresolved.The accessibility issue with nested
<label>elements flagged in the previous review has not been addressed. Please apply the fix suggested in the existing review comment to ensure the Switch maintains proper accessible semantics.
🧹 Nitpick comments (2)
surfsense_web/components/pricing.tsx (2)
99-128: Animation and styling logic assumes exactly 3 plans.The motion animation (lines 99-107) and className construction (lines 118-128) hardcode assumptions for a 3-plan layout using
index === 0,index === 2, etc. If theplansarray has a different length, the animations and styling will behave unexpectedly.Consider either:
- Adding a prop validation or comment documenting the 3-plan requirement
- Making the animation logic more generic to handle variable plan counts
For example, you could determine edge plans dynamically:
const isEdgePlan = index === 0 || index === plans.length - 1; const isLeftEdge = index === 0; const isRightEdge = index === plans.length - 1;
81-82: Avoidas anytype assertion.The
as anytype assertion on the Switch ref bypasses type safety. Check if the Switch component properly exposes its ref type, or use a more specific assertion if needed.If the Switch accepts
RefObject<HTMLButtonElement>, try:-ref={switchRef as any} +ref={switchRef}Or if it requires a callback ref:
ref={(node) => { if (node) (switchRef as React.MutableRefObject<HTMLButtonElement>).current = node; }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
surfsense_web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (2)
surfsense_web/components/pricing.tsx(1 hunks)surfsense_web/package.json(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.rules/require_unique_id_props.mdc)
**/*.{jsx,tsx}: When mapping arrays to React elements in JSX/TSX, each rendered element must include a unique key prop
Keys used for React list items should be stable, predictable, and unique among siblings
Files:
surfsense_web/components/pricing.tsx
🧬 Code graph analysis (1)
surfsense_web/components/pricing.tsx (1)
surfsense_web/hooks/use-media-query.ts (1)
useMediaQuery(8-37)
🔇 Additional comments (6)
surfsense_web/components/pricing.tsx (5)
1-13: LGTM! Clean imports.All imports are properly used throughout the component and align with the dependencies mentioned in the PR.
15-31: LGTM! Well-structured interfaces.The interfaces are clear, complete, and appropriate for the component's requirements.
33-40: LGTM! Proper state and hook usage.Component initialization is clean, with appropriate defaults and correct usage of
useMediaQueryhook.
71-76: LGTM! Clean header markup.The header structure is semantic and properly handles the multi-line description with
whitespace-pre-line.
178-196: LGTM! CTA button and link properly implemented.The Link component with
buttonVariantsand conditional styling based onisPopularis well-structured and follows Next.js best practices.surfsense_web/package.json (1)
62-62: Fix invalidmotionversion pin.
motion12.23.22 hasn’t been published yet, so installs will fail. The Motion changelog stops at 12.23.21 (Sep 24 2025), and the npm registry still serves 12.23.12 as the newest public tag. Drop this requirement back to the latest available patch before merging.- "motion": "^12.23.22", + "motion": "^12.23.21",Likely an incorrect or invalid review comment.
| const handleToggle = (checked: boolean) => { | ||
| setIsMonthly(!checked); | ||
| if (checked && switchRef.current) { | ||
| const rect = switchRef.current.getBoundingClientRect(); | ||
| const x = rect.left + rect.width / 2; | ||
| const y = rect.top + rect.height / 2; | ||
|
|
||
| confetti({ | ||
| particleCount: 50, | ||
| spread: 60, | ||
| origin: { | ||
| x: x / window.innerWidth, | ||
| y: y / window.innerHeight, | ||
| }, | ||
| colors: [ | ||
| "hsl(var(--primary))", | ||
| "hsl(var(--accent))", | ||
| "hsl(var(--secondary))", | ||
| "hsl(var(--muted))", | ||
| ], | ||
| ticks: 200, | ||
| gravity: 1.2, | ||
| decay: 0.94, | ||
| startVelocity: 30, | ||
| shapes: ["circle"], | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Confetti colors won't resolve CSS custom properties.
The confetti colors array uses hsl(var(--primary)) format, but canvas-confetti expects actual color values (e.g., "#3b82f6", "rgb(59, 130, 246)", "hsl(217, 91%, 60%)"). CSS custom properties aren't resolved at runtime by the confetti library, so these will be ignored or fall back to defaults.
To fix, either:
- Define the colors directly:
colors: [
- "hsl(var(--primary))",
- "hsl(var(--accent))",
- "hsl(var(--secondary))",
- "hsl(var(--muted))",
+ "#3b82f6",
+ "#8b5cf6",
+ "#ec4899",
+ "#f59e0b",
],- Or read computed styles at runtime:
+const primaryColor = getComputedStyle(document.documentElement)
+ .getPropertyValue('--primary').trim();
+// Convert HSL values to full hsl() string
colors: [
- "hsl(var(--primary))",
+ `hsl(${primaryColor})`,
// ... same for other colors
],📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleToggle = (checked: boolean) => { | |
| setIsMonthly(!checked); | |
| if (checked && switchRef.current) { | |
| const rect = switchRef.current.getBoundingClientRect(); | |
| const x = rect.left + rect.width / 2; | |
| const y = rect.top + rect.height / 2; | |
| confetti({ | |
| particleCount: 50, | |
| spread: 60, | |
| origin: { | |
| x: x / window.innerWidth, | |
| y: y / window.innerHeight, | |
| }, | |
| colors: [ | |
| "hsl(var(--primary))", | |
| "hsl(var(--accent))", | |
| "hsl(var(--secondary))", | |
| "hsl(var(--muted))", | |
| ], | |
| ticks: 200, | |
| gravity: 1.2, | |
| decay: 0.94, | |
| startVelocity: 30, | |
| shapes: ["circle"], | |
| }); | |
| } | |
| }; | |
| const handleToggle = (checked: boolean) => { | |
| setIsMonthly(!checked); | |
| if (checked && switchRef.current) { | |
| const rect = switchRef.current.getBoundingClientRect(); | |
| const x = rect.left + rect.width / 2; | |
| const y = rect.top + rect.height / 2; | |
| confetti({ | |
| particleCount: 50, | |
| spread: 60, | |
| origin: { | |
| x: x / window.innerWidth, | |
| y: y / window.innerHeight, | |
| }, | |
| colors: [ | |
| "#3b82f6", | |
| "#8b5cf6", | |
| "#ec4899", | |
| "#f59e0b", | |
| ], | |
| ticks: 200, | |
| gravity: 1.2, | |
| decay: 0.94, | |
| startVelocity: 30, | |
| shapes: ["circle"], | |
| }); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In surfsense_web/components/pricing.tsx around lines 42 to 69, the confetti
colors are passed as CSS custom property strings like "hsl(var(--primary))"
which canvas-confetti cannot resolve; change the implementation to compute
actual color values at runtime (or use hardcoded color strings). Use
getComputedStyle on the relevant element or document.documentElement to read
each --primary/--accent/--secondary/--muted value, convert/compose them into
valid CSS color strings (e.g., "hsl(...)" or "rgb(...)" or hex), trim and
provide sensible fallbacks if a variable is missing, then pass that array of
resolved color strings into confetti({ colors: [...] }). Ensure this resolution
happens inside handleToggle before calling confetti so the library receives real
color values.
| <div className="grid grid-cols-1 md:grid-cols-3 sm:2 gap-4"> | ||
| {plans.map((plan, index) => ( | ||
| <motion.div | ||
| key={index} |
There was a problem hiding this comment.
Use stable keys instead of array index.
Using index as the React key violates the coding guideline requiring stable, unique keys. If the plans array is ever filtered, sorted, or reordered, React's reconciliation will break, causing incorrect animations and potential state bugs.
Apply this diff to use a stable identifier:
-{plans.map((plan, index) => (
+{plans.map((plan) => (
<motion.div
- key={index}
+ key={plan.name}If plan names might not be unique, consider adding an id field to PricingPlan and using that instead.
As per coding guidelines.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In surfsense_web/components/pricing.tsx around lines 94 to 97, the component
uses the array index as the React key which is unstable; change the key to a
stable unique identifier such as plan.id (or plan.name if you guarantee
uniqueness), and if PricingPlan lacks an id add an id field to the type and
ensure each plan object includes a unique id before mapping so keys become
key={plan.id} (fallback to plan.name only if unique).
| willChange | ||
| className="font-variant-numeric: tabular-nums" | ||
| /> |
There was a problem hiding this comment.
Invalid className syntax breaks styling.
Line 155 uses CSS property syntax (font-variant-numeric: tabular-nums) as a className, which is invalid and won't apply any styling. Use the Tailwind utility class instead.
Apply this diff:
-className="font-variant-numeric: tabular-nums"
+className="tabular-nums"📝 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.
| willChange | |
| className="font-variant-numeric: tabular-nums" | |
| /> | |
| willChange | |
| className="tabular-nums" | |
| /> |
🤖 Prompt for AI Agents
In surfsense_web/components/pricing.tsx around lines 154 to 156, the className
is using CSS property syntax ("font-variant-numeric: tabular-nums") which is
invalid; replace that value with the Tailwind utility class "tabular-nums"
(e.g., className="tabular-nums" or append "tabular-nums" to any existing class
string), removing the colon and property name so the tabular-nums utility is
applied correctly.
| <ul className="mt-5 gap-2 flex flex-col"> | ||
| {plan.features.map((feature, idx) => ( | ||
| <li key={idx} className="flex items-start gap-2"> | ||
| <Check className="h-4 w-4 text-primary mt-1 flex-shrink-0" /> | ||
| <span className="text-left">{feature}</span> | ||
| </li> | ||
| ))} | ||
| </ul> |
There was a problem hiding this comment.
Use feature text as key instead of index.
The features map uses idx as the key, which violates the coding guideline. Since feature strings are likely unique within a plan, use the feature text itself as the key for better stability.
Apply this diff:
-{plan.features.map((feature, idx) => (
- <li key={idx} className="flex items-start gap-2">
+{plan.features.map((feature) => (
+ <li key={feature} className="flex items-start gap-2">If features might have duplicates, consider adding unique identifiers to the feature array in the data structure.
As per coding guidelines.
📝 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.
| <ul className="mt-5 gap-2 flex flex-col"> | |
| {plan.features.map((feature, idx) => ( | |
| <li key={idx} className="flex items-start gap-2"> | |
| <Check className="h-4 w-4 text-primary mt-1 flex-shrink-0" /> | |
| <span className="text-left">{feature}</span> | |
| </li> | |
| ))} | |
| </ul> | |
| <ul className="mt-5 gap-2 flex flex-col"> | |
| {plan.features.map((feature) => ( | |
| <li key={feature} className="flex items-start gap-2"> | |
| <Check className="h-4 w-4 text-primary mt-1 flex-shrink-0" /> | |
| <span className="text-left">{feature}</span> | |
| </li> | |
| ))} | |
| </ul> |
🤖 Prompt for AI Agents
In surfsense_web/components/pricing.tsx around lines 169 to 176, the map over
plan.features uses the array index (idx) as the React key which is unstable;
change the key to use the feature string itself (key={feature}) to provide a
stable identifier, and if features can be duplicated, update the feature data to
include unique ids and use that id as the key instead (e.g., key={feature.id}).
| "class-variance-authority": "^0.7.1", | ||
| "clsx": "^2.1.1", | ||
| "date-fns": "^4.1.0", | ||
| "dotenv": "^17.2.3", |
There was a problem hiding this comment.
Correct the dotenv version.
dotenv 17.2.3 isn’t on npm—latest published is 17.2.2. This typo will break npm install; please pin to the released patch.
- "dotenv": "^17.2.3",
+ "dotenv": "^17.2.2",📝 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.
| "dotenv": "^17.2.3", | |
| // In surfsense_web/package.json, update the dotenv dependency: | |
| "dotenv": "^17.2.2", |
🤖 Prompt for AI Agents
In surfsense_web/package.json around line 54, the dotenv dependency is
incorrectly set to "17.2.3" which does not exist on npm; change it to the
released patch "17.2.2" (pin exact version, not caret) in package.json and then
run npm install to update node_modules and regenerate the lockfile
(package-lock.json or yarn.lock) so the correct version is committed.
feat: added contact page
Description
feat: added contact page
Screenshots
API Changes
Types of changes
Testing
Checklist:
High-level PR Summary
This PR adds a new contact page functionality to the SurfSense web application. The implementation includes a contact form UI component, backend API route for handling form submissions, database schema and connection setup using Drizzle ORM, and UI integrations in the navbar and homepage. The contact form allows users to submit their name, email, company, and an optional message, which is then stored in a database. The PR also adds a pricing page with a tiered pricing display component, featuring animated transitions and yearly/monthly toggle options. Additionally, there's a dependency change from
framer-motiontomotion/reactacross multiple files, along with the addition of several new dependencies for database management.⏱️ Estimated Review Time: 30-90 minutes
💡 Review Order Suggestion
surfsense_web/app/contact/page.tsxsurfsense_web/components/contact/contact-form.tsxsurfsense_web/app/api/contact/route.tssurfsense_web/app/db/schema.tssurfsense_web/app/db/index.tssurfsense_web/.env.examplesurfsense_web/components/Navbar.tsxsurfsense_web/components/ModernHeroWithGradients.tsxsurfsense_web/app/pricing/page.tsxsurfsense_web/components/pricing/pricing-section.tsxsurfsense_web/components/pricing.tsxsurfsense_web/hooks/use-media-query.tssurfsense_web/drizzle.config.tssurfsense_web/package.jsonsurfsense_web/components/chat/AnimatedEmptyState.tsxsidebarStatewithout using an alternative, potentially causing functionality issuesSummary by CodeRabbit