Skip to content

feat: Implement a comprehensive project and campaign creation flow, i…#502

Open
0xdevcollins wants to merge 3 commits intomainfrom
feat/project-creation-page
Open

feat: Implement a comprehensive project and campaign creation flow, i…#502
0xdevcollins wants to merge 3 commits intomainfrom
feat/project-creation-page

Conversation

@0xdevcollins
Copy link
Copy Markdown
Collaborator

@0xdevcollins 0xdevcollins commented Mar 18, 2026

…ncluding dedicated public campaign pages.

Summary by CodeRabbit

  • New Features

    • Campaigns hub for discovering campaigns and an Explore Campaigns hero
    • Multi-step project/campaign creation flow with autosave drafts, draft management, and publish flow
    • Project pages with contribution lists and milestone detail pages (tabbed views)
  • UI/UX

    • Navbar adds direct links for creating projects and campaigns (no modal)
    • Updated project / projects landing UI and hero components
  • Tools

    • Sentry example pages to test error reporting and connectivity

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
boundless-kd16 Error Error Mar 20, 2026 9:16am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This pull request adds a multi-step project/campaign creation workflow, new campaign and project pages (contributions, milestones, project details), reusable creation UI components and hooks, project-related API/query utilities and types, navbar/navigation updates for campaigns, and Sentry diagnostic/example pages and config changes.

Changes

Cohort / File(s) Summary
Campaign & Project Pages
app/(landing)/campaigns/[slug]/contributions/page.tsx, app/(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx, app/(landing)/campaigns/[slug]/page.tsx, app/(landing)/campaigns/layout.tsx, app/(landing)/campaigns/page.tsx, app/(landing)/projects/page.tsx
Add campaign-specific pages (contributions, milestone detail, campaign view), campaign layout and projects listing updates; data fetching, conditional rendering, and tabbed milestone UI.
Project Creation Routes
app/projects/create/page.tsx, app/me/projects/create/page.tsx
New CreateProjectPage wrapping CreationLayout in Suspense; adjusted redirect target for me/projects/create to /projects/create.
Creation Flow Core
features/projects/hooks/use-project-creation.ts, features/projects/components/CreationFlow/CreationLayout.tsx, features/projects/components/CreationFlow/CreationNavigation.tsx, features/projects/components/CreationFlow/CreationSidebar.tsx
New hook + layout/navigation/sidebar orchestrating multi-step creation flow, autosave, step navigation, drafts listing, and publish flow.
Creation UI Primitives
features/projects/components/CreationFlow/CreationUI.tsx
New reusable controls: CreationInput, CreationTextarea, CreationButton, CreationToggle, CreationImageUpload (drag/drop, upload, validation).
Creation Flow Steps
features/projects/components/CreationFlow/Steps/*
BasicInfo.tsx, ProjectDetails.tsx, TeamInfo.tsx, SocialLinks.tsx, CampaignDetails.tsx, ReviewStep.tsx
Six step components implementing form sections (basic info, details, team, contacts, funding/milestones with DnD, and review) wired to form state.
Projects API / Queries / Types
features/projects/api/index.ts, features/projects/hooks/use-project-queries.ts, features/projects/types/index.ts
New project API client functions, React Query hooks with cache keys and mutations, and a full set of TypeScript types for drafts, projects, and publish payloads.
Landing UI & Nav
components/landing-page/navbar.tsx, components/landing-page/Explore.tsx, features/projects/components/CampaignPageHero.tsx, features/projects/components/GenericProjectHero.tsx, features/projects/components/GenericProjectsClient.tsx, features/projects/components/ProjectsPage.tsx
Navbar adds Campaigns menu and replaces modal-based creation with direct links; Explore styling tweaks; new hero and projects placeholder components; ProjectsClient accepts optional id prop.
Creation Flow Misc
features/projects/components/CreationFlow/Steps/CampaignDetails.tsx, features/projects/components/CreationFlow/Steps/*
Milestone drag/drop, inline date validation, milestone management, and review navigation wiring.
Sentry & Diagnostics
app/api/sentry-example-api/route.ts, app/sentry-example-page/page.tsx, sentry.edge.config.ts, sentry.server.config.ts, next.config.ts, package.json, .env.example, .gitignore
Add Sentry example API and page for diagnostics; adjust Sentry configs and version bump; sanitize .env.example and ignore .env.sentry-build-plugin.

Sequence Diagram

sequenceDiagram
    participant User
    participant Page as Creation Page
    participant Layout as CreationLayout
    participant Hook as useProjectCreation
    participant Sidebar as CreationSidebar
    participant Steps as Step Component
    participant API as Project API

    User->>Page: Open /projects/create
    Page->>Layout: render
    Layout->>Hook: initialize (formData, steps, draftId)
    Hook-->>Layout: return state & handlers
    Layout->>Sidebar: render recent drafts
    User->>Steps: edit fields
    Steps->>Layout: updateFormData(updates)
    Layout->>Hook: updateFormData(updates)
    Hook->>Hook: debounce autosave -> call API (create/update draft)
    Hook->>API: create/update draft
    API-->>Hook: draft saved (id)
    Hook-->>Layout: lastSaved update
    User->>Layout: navigate steps / click Publish
    Layout->>Hook: handlePublish()
    Hook->>API: update draft + publish
    API-->>Hook: publish success
    Hook-->>Page: redirect to /me/projects
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • UI fixes #458 — Modifies the same project types surface (features/projects/types/index.ts), directly related to the new types added here.
  • Feat/minor ux fixes #380 — Alters project/team type shapes that overlap with TeamMember/Draft types introduced by this PR.
  • Feat/update,refactor,improve #436 — Changes navbar behavior and types; overlaps with navbar navigation and creation-link changes in this PR.

Suggested reviewers

  • Benjtalkshow

"🐰
I hopped through forms and fields so wide,
Drafts tucked safe in a sidebar hide,
Milestones dragged with a nimble paw,
Autosaves hum — no panic, no flaw,
Publish! — the carrot shines with pride." 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of this comprehensive pull request, which implements a multi-step project/campaign creation flow and public campaign pages.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/project-creation-page

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
Copy Markdown

@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: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/landing-page/navbar.tsx (1)

198-212: ⚠️ Potential issue | 🟡 Minor

Remove unused useProtectedAction hook or implement protection via executeProtectedAction before navigation.

The hook is initialized but executeProtectedAction is never invoked. The dropdown Links navigate directly to /projects/create without wallet/auth checks, while CreationLayout has no fallback protection. Either wrap the Links' navigation in executeProtectedAction() to enforce protection, or remove the unused hook setup if protection is not required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/landing-page/navbar.tsx` around lines 198 - 212, In
AuthenticatedActions, the useProtectedAction hook is initialized but its
executeProtectedAction is never used so Links navigate to /projects/create
without protection; either remove the useProtectedAction hook and its returned
handlers (executeProtectedAction, showWalletModal, showNotReadyModal,
notReadyReasons, closeWalletModal, closeNotReadyModal, handleWalletConnected) if
protection is not needed, or update the Link click handlers to call
executeProtectedAction(...) (with ACTIONS.CREATE_PROJECT) before performing
navigation so wallet/auth checks run (ensure CreationLayout still has safe
fallback); modify the click handlers in AuthenticatedActions to await/handle
executeProtectedAction and then programmatically navigate to /projects/create on
success.
🧹 Nitpick comments (12)
features/projects/components/CampaignPageHero.tsx (1)

7-33: Consider renaming to handleScrollToCampaigns per coding guidelines.

The event handler doesn't follow the handle* prefix convention specified in the coding guidelines.

✏️ Suggested rename
-  const scrollToCampaigns = () => {
+  const handleScrollToCampaigns = () => {
     const section = document.getElementById('explore-campaigns');
     // ... rest of function
   };

And update the button's onClick:

-            onClick={scrollToCampaigns}
+            onClick={handleScrollToCampaigns}

As per coding guidelines: "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CampaignPageHero.tsx` around lines 7 - 33,
Rename the event handler function scrollToCampaigns to follow the coding
guideline (handleScrollToCampaigns) and update all internal references to it
(e.g., the button's onClick prop) so behavior is unchanged; ensure you rename
the function declaration and any calls/usages inside the component (search for
scrollToCampaigns and replace with handleScrollToCampaigns) and leave the
animation logic intact.
features/projects/hooks/use-project-creation.ts (1)

137-146: Consider memoizing steps array.

The steps array and activeSteps are recomputed on every render. Since they depend only on isCampaign, consider using useMemo to avoid unnecessary recalculations.

♻️ Proposed optimization
+import { useState, useEffect, useCallback, useMemo } from 'react';
 
-  const steps: { key: CreationStep; label: string; hidden?: boolean }[] = [
-    { key: 'basic', label: 'Basic Info' },
-    { key: 'details', label: 'Project Details' },
-    { key: 'team', label: 'Team Info' },
-    { key: 'social', label: 'Contact Info' },
-    { key: 'funding', label: 'Funding & Milestones', hidden: !isCampaign },
-    { key: 'review', label: 'Review & Submit' },
-  ];
-
-  const activeSteps = steps.filter(s => !s.hidden);
+  const activeSteps = useMemo(() => {
+    const steps: { key: CreationStep; label: string; hidden?: boolean }[] = [
+      { key: 'basic', label: 'Basic Info' },
+      { key: 'details', label: 'Project Details' },
+      { key: 'team', label: 'Team Info' },
+      { key: 'social', label: 'Contact Info' },
+      { key: 'funding', label: 'Funding & Milestones', hidden: !isCampaign },
+      { key: 'review', label: 'Review & Submit' },
+    ];
+    return steps.filter(s => !s.hidden);
+  }, [isCampaign]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/hooks/use-project-creation.ts` around lines 137 - 146, Wrap
the steps array and the derived activeSteps in React.useMemo so they are only
recomputed when isCampaign changes: memoize the steps definition (const steps =
useMemo(() => [...], [isCampaign])) and compute activeSteps from that memoized
steps (const activeSteps = useMemo(() => steps.filter(s => !s.hidden),
[steps])); update any references to use the memoized variables and ensure
React.useMemo (or useMemo import) is available in the hook file.
features/projects/components/CreationFlow/Steps/CampaignDetails.tsx (1)

9-9: Remove unused import Video.

The Video icon is imported but never used in this component.

♻️ Proposed fix
-import { Plus, X, Calendar, Video, Info } from 'lucide-react';
+import { Plus, X, Calendar, Info } from 'lucide-react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/CampaignDetails.tsx` at line
9, The import statement in CampaignDetails.tsx includes an unused symbol Video;
remove Video from the named imports (e.g., change "import { Plus, X, Calendar,
Video, Info } from 'lucide-react';" to omit Video) so only used icons (Plus, X,
Calendar, Info) are imported to eliminate the unused import warning.
features/projects/components/CreationFlow/CreationUI.tsx (1)

436-457: Event handlers should use 'handle' prefix per coding guidelines.

The drag event handlers onDrag and onDrop should be renamed to handleDrag and handleDrop to follow the naming convention.

♻️ Proposed renaming
-  const onDrag = useCallback(
+  const handleDrag = useCallback(
     (e: React.DragEvent) => {
       // ...
     },
     [disabled]
   );

-  const onDrop = useCallback(
+  const handleDrop = useCallback(
     (e: React.DragEvent) => {
       // ...
     },
     [disabled, handleUpload]
   );

   // Update usage in JSX:
   <div
-    onDragEnter={onDrag}
-    onDragLeave={onDrag}
-    onDragOver={onDrag}
-    onDrop={onDrop}
+    onDragEnter={handleDrag}
+    onDragLeave={handleDrag}
+    onDragOver={handleDrag}
+    onDrop={handleDrop}
     // ...
   >

As per coding guidelines: "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationUI.tsx` around lines 436 -
457, Rename the drag event handlers to follow the "handle" prefix convention:
change onDrag to handleDrag and onDrop to handleDrop, update their useCallback
declarations (handleDrag and handleDrop), replace any references to
onDrag/onDrop in JSX or props, ensure the dependency arrays still include
disabled and any other used symbols (setDragActive, handleUpload), and remove or
adjust the eslint-disable comment if no longer needed; keep the handler logic
unchanged (preventDefault/stopPropagation, setDragActive, disabled guard, and
file handling via handleUpload).
features/projects/components/CreationFlow/Steps/ReviewStep.tsx (1)

43-125: Redundant type assertions can be removed.

The as CreationStep casts on lines 46, 65, 76, 88, and 104 are unnecessary since the string literals ('basic', 'details', etc.) already match the CreationStep union type exactly.

♻️ Proposed simplification
   const sections: ReviewSection[] = [
     {
       title: 'Basic Information',
-      step: 'basic' as CreationStep,
+      step: 'basic',
       fields: [
         // ...
       ],
     },
     // Apply same change to other sections...
   ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/ReviewStep.tsx` around lines
43 - 125, The review notes redundant "as CreationStep" casts in the sections
array; remove the unnecessary type assertions on the objects where step is set
to the string literals ('basic', 'details', 'team', 'social', 'funding') so the
literals naturally infer the CreationStep type (update the objects in the
sections constant and the pushed funding section). Locate the step properties in
the ReviewStep component (the sections: ReviewSection[] definition and the
conditional sections.push block) and delete each "as CreationStep" cast to
simplify the code.
app/(landing)/campaigns/[slug]/page.tsx (2)

45-45: Avoid using any type casts.

Multiple uses of as any to bypass TypeScript type checking violate coding guidelines. Consider defining proper intermediate types or using type guards.

The ParticipantSubmission type should be extended or a proper interface created to represent the expected shape with all the fields being accessed (e.g., hackathonId, links, teamMembers, videoUrl, etc.).

As per coding guidelines: **/*.{ts,tsx}: Do not use 'any' type; always search for proper Trustless Work entity types.

Also applies to: 142-143, 161-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/campaigns/[slug]/page.tsx at line 45, The code uses a raw cast
"const subData = submission as any;" which bypasses TypeScript checks—replace
the any cast by defining a proper interface (e.g., extend ParticipantSubmission
or create SubmissionDetails) that includes the accessed fields (hackathonId,
links, teamMembers, videoUrl, etc.), update the variable declaration to use that
interface, and add a type guard or runtime checks where submission is received
(or when parsing API response) to safely narrow to that interface; also remove
other "as any" occurrences mentioned (around the other uses at the same file)
and replace them with the new type or guard to ensure compile-time safety.

91-99: Clarify error handling fallback behavior.

When getCrowdfundingProject fails with any error, the code falls back to fetchSubmission. This might hide legitimate errors (e.g., network failures, server errors) by attempting submission fetch instead of surfacing the original error.

♻️ Suggested improvement
         try {
           const projectData = await getCrowdfundingProject(id);
           if (projectData) {
             setProject(projectData);
             return;
           }
-        } catch (e) {
+        } catch (e: unknown) {
+          // Only fallback to submission if project not found (404-like)
+          // For other errors, report and don't mask with submission fetch
+          const isNotFound = e instanceof Error && e.message.includes('not found');
+          if (!isNotFound) {
+            throw e;
+          }
           await fetchSubmission(id);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/campaigns/[slug]/page.tsx around lines 91 - 99, The current
try/catch around getCrowdfundingProject silently falls back to fetchSubmission
on any error, hiding real failures; update the catch to inspect the thrown error
from getCrowdfundingProject and only call fetchSubmission(id) for a not-found
condition (e.g., HTTP 404 or a specific "NotFound" error), otherwise rethrow or
surface the error (or set an error state) so real network/server errors from
getCrowdfundingProject are not masked; locate the block around
getCrowdfundingProject, catch the error, check its status/type, and
conditionally call fetchSubmission(id) or propagate/log the original error
instead of always falling back.
app/(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx (1)

65-67: Consider logging errors for debugging.

The catch block silently swallows errors. While the UI handles the null milestone state gracefully, logging the error would help with debugging production issues.

♻️ Suggested improvement
   } catch (err) {
-    // Handle error silently - milestone will be null
+    console.error('Failed to fetch milestone data:', err);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx around lines
65 - 67, The catch block that swallows errors when fetching the milestone in the
page component should log the caught error for debugging: update the catch in
the async milestone fetch (inside the page component in page.tsx) to call a
logger (e.g., console.error or the app's processLogger) with a clear message
like "Error fetching milestone" plus the error object and contextual identifiers
(campaign slug and milestoneId) so you still return null for milestone but
retain useful diagnostics.
features/projects/components/CreationFlow/Steps/TeamInfo.tsx (1)

12-15: Avoid using any type for props.

Same issue as in SocialLinks.tsx - the props use any which violates coding guidelines. Use proper types from the project creation types.

♻️ Suggested type improvement
+import type { ProjectDraft } from '@/features/projects/hooks/use-project-creation';
+
 interface TeamInfoProps {
-  formData: any;
-  updateFormData: (updates: any) => void;
+  formData: Partial<ProjectDraft>;
+  updateFormData: (updates: Partial<ProjectDraft>) => void;
 }

As per coding guidelines: **/*.{ts,tsx}: Do not use 'any' type; always search for proper Trustless Work entity types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/TeamInfo.tsx` around lines 12
- 15, TeamInfoProps currently uses `any` for formData and updateFormData;
replace those with the project creation form types used across the feature
(e.g., the shared form data type and its partial update handler) instead of
`any`. Update the interface so `formData` is the concrete form data type (for
example ProjectCreationForm or ProjectFormData from the feature's types) and
`updateFormData` accepts a Partial of that type or a typed update callback
`(updates: Partial<ProjectCreationForm>) => void`; update usages in the TeamInfo
component to match the new types (keep the symbol names TeamInfoProps, formData,
updateFormData).
features/projects/components/CreationFlow/Steps/SocialLinks.tsx (1)

7-10: Avoid using any type for props.

The formData and updateFormData props use any which violates the coding guidelines. Consider using proper types from the project creation types.

♻️ Suggested type improvement
+import type { ProjectDraft } from '@/features/projects/hooks/use-project-creation';
+
 interface SocialLinksProps {
-  formData: any;
-  updateFormData: (updates: any) => void;
+  formData: Partial<ProjectDraft>;
+  updateFormData: (updates: Partial<ProjectDraft>) => void;
 }

As per coding guidelines: **/*.{ts,tsx}: Do not use 'any' type; always search for proper Trustless Work entity types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/SocialLinks.tsx` around lines
7 - 10, The SocialLinksProps interface currently uses `any` for `formData` and
`updateFormData`; replace these with the project's proper creation types by
importing the appropriate project creation/type definitions (e.g., use the
canonical ProjectFormData type for `formData` and type `updateFormData` as a
function that accepts a Partial<ProjectFormData> or a typed patch callback) so
`SocialLinksProps` explicitly types `formData` and `updateFormData` instead of
`any`; update references inside the SocialLinks component accordingly (look for
SocialLinksProps, formData, updateFormData).
features/projects/components/CreationFlow/Steps/BasicInfo.tsx (2)

233-236: Category selection toggle behavior may cause accidental deselection.

Clicking an already-selected category clears the selection. This could confuse users who expect clicking the selected item to simply close the popover without changing the value. Consider removing the toggle behavior for a required field.

♻️ Proposed fix to prevent accidental deselection
                      onSelect={() => {
-                       onChange(cat.value === value ? '' : cat.value);
+                       onChange(cat.value);
                        setOpen(false);
                      }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx` around lines
233 - 236, The current onSelect handler in BasicInfo.tsx toggles category
selection (onChange(cat.value === value ? '' : cat.value)) which clears a
required field when clicking the already-selected category; change the handler
to always set the selected category (onChange(cat.value)) and simply close the
popover with setOpen(false) so clicking the selected item only closes the
popover and does not clear the value (update the onSelect in the category item
where cat.value and value are referenced).

288-289: Consider memoizing derived values or using default props.

socialLinks creates a new array ['', '', ''] on every render when formData.socialLinks is undefined. While not a performance issue currently, consider initializing this default in the parent component or form state to ensure consistent identity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx` around lines
288 - 289, The current BasicInfo component computes socialLinks as "const
socialLinks: string[] = formData.socialLinks ?? ['', '', ''];" which allocates a
new default array on every render; fix by ensuring the default is stable—either
initialize formData.socialLinks to ['','',''] in the parent/form state or
memoize the derived value in BasicInfo (e.g., useMemo to return
formData.socialLinks if present or a single shared default array) so the
identity of socialLinks stays consistent across renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`(landing)/campaigns/[slug]/page.tsx:
- Around line 115-121: There are two identical conditional checks calling
notFound() (if (error || !project) { notFound(); }) back-to-back; remove the
duplicate so the condition referencing error and project appears only once (keep
the first or the more contextually correct one) to eliminate dead code in the
page component where error and project are evaluated and notFound() is invoked.

In `@features/projects/components/CreationFlow/CreationLayout.tsx`:
- Around line 215-218: The Save Draft button in CreationLayout is missing an
onClick handler; wire it to the form's save function (e.g., call the
hook-provided saveDraft or handleSaveDraft method from the project form hook
used in CreationLayout) so the button triggers a manual save, and update the
button to reflect saving state (disable while saving and show a spinner/icon)
using the existing loading flag (e.g., isSaving or savingDraft) so users get
feedback; ensure the handler is named clearly (handleSaveDraft) and referenced
where the <Save /> button JSX is rendered.
- Around line 222-242: The "Publish Project" button sets onClick to undefined
when isLastStep is true, so clicking does nothing; change the button to call a
publish handler instead of undefined (e.g., set onClick={isLastStep ?
handlePublish : handleNext}) and implement a handlePublish function in the same
component that performs the publish/submit logic (or calls the existing
submit/mutation helper), handles loading/error states, and updates UI as needed;
reference the isLastStep flag, the existing handleNext function, and the Publish
Project button JSX in CreationLayout.tsx when adding the handler and wiring it
up.

In `@features/projects/components/CreationFlow/CreationSidebar.tsx`:
- Around line 22-26: The CreationSidebarProps currently declares an unused
onDraftSelect prop; either remove it from the CreationSidebarProps interface and
any related usage, or implement draft-selection by wiring onDraftSelect into the
component that renders drafts (e.g., pass onDraftSelect into SidebarContent or
the draft list item renderer inside CreationSidebar) and add click handlers on
draft items to call onDraftSelect(draft). Locate CreationSidebar,
CreationSidebarProps, SidebarContent and the draft list mapping of recentDrafts
to add the prop forwarding and onClick handlers (or delete the onDraftSelect
declaration and any references) so the prop is no longer unused.

In `@features/projects/components/CreationFlow/CreationUI.tsx`:
- Around line 405-457: The handleUpload function should be wrapped in
useCallback to avoid a stale closure: convert handleUpload into a useCallback
hook and include all external dependencies it uses (validateFile, uploadService,
aspectRatio, onChange, setUploadError, setIsUploading, and any others
referenced). Then remove the eslint-disable and add the memoized handleUpload to
the dependency array of the onDrop useCallback (and adjust onDrag if needed).
Ensure the wrapped handleUpload preserves current behavior and returns the same
results.

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx`:
- Around line 262-267: Define a concrete TypeScript interface for the
component's form data and replace the `any` usages in BasicInfoProps (the
`formData` and `updateFormData` signatures) with that interface (or a properly
imported Trustless Work entity type) so type checking prevents invalid property
access and updates; update the `updateFormData` parameter to accept a partial of
that interface (e.g., Partial<YourFormInterface>) or a typed updater function,
and import or reference the existing domain type instead of `any` in
BasicInfoProps, keeping `isCampaign` and `setIsCampaign` unchanged.

In `@features/projects/components/CreationFlow/Steps/CampaignDetails.tsx`:
- Around line 274-289: The useEffect that initializes the first milestone reads
formData.milestones and calls updateFormData but currently has an empty
dependency array, causing stale-closure/lint issues; fix by adding a ref guard
(e.g., initializedRef via useRef(false)) and include formData.milestones and
updateFormData in the dependency array, then inside the effect return early if
initializedRef.current is true, otherwise if !formData.milestones ||
formData.milestones.length === 0 call updateFormData(...) and set
initializedRef.current = true; reference the existing useEffect,
formData.milestones, updateFormData, and add initializedRef.
- Around line 34-37: Update the CampaignDetailsProps interface to remove all
`any` usage: replace `formData: any` with the specific form data type used by
this flow (e.g., CampaignFormData or the Trustless Work entity type that
contains fields like name, budget, dates, etc.) and change `updateFormData:
(updates: any) => void` to a typed updater such as `updateFormData: (updates:
Partial<CampaignFormData>) => void` (or the appropriate setter signature used
elsewhere). Locate the interface declaration `CampaignDetailsProps` and the
component/consumers that pass `formData`/`updateFormData` (e.g., the
CampaignDetails component) and import or define the correct CampaignFormData (or
Trustless Work entity) type, then update usages to match the new types.

In `@features/projects/components/CreationFlow/Steps/ProjectDetails.tsx`:
- Around line 7-10: ProjectDetailsProps currently uses `any` for `formData` and
`updateFormData`; replace those with the correct Trustless Work entity/form
types (e.g., a Project or ProjectDraft/FormValues type) instead of `any`. Update
the interface so `formData` is the concrete form data type and `updateFormData`
is a function accepting a partial of that type (e.g., (updates:
Partial<ProjectFormType>) => void), and import the appropriate type from the
domain/types module where project entities/forms are defined; ensure references
to `ProjectDetailsProps`, `formData`, and `updateFormData` in the component
remain consistent.

In `@features/projects/components/CreationFlow/Steps/ReviewStep.tsx`:
- Around line 16-41: Replace the use of any by using the real form/types or a
safe union: change isFilled(value: any) to isFilled(value: unknown) and update
its implementation signatures accordingly; replace ReviewField.value: any with a
concrete union or referenced entity type (e.g., ReviewField.value: string |
number | Array<unknown> | Record<string, unknown> | ContactType) or, better,
import and use the project's form payload type (e.g., CreationFormData or
ProjectFormValues) and use that type for ReviewField.value and for the
ReviewSection fields; update ReviewStep({ formData, onNavigate }:
ReviewStepProps) to type formData with the same imported form type so all checks
and Object.values(value).some(v => isFilled(v)) remain type-safe without any
usage of any.

In `@features/projects/components/GenericProjectHero.tsx`:
- Around line 28-47: The two CTAs (BoundlessButton instances "Create New
Project" and "Manage Projects") are missing navigation handlers; import Link
from 'next/link' and wrap the corresponding BoundlessButton elements with <Link
href="/projects/new"> and <Link href="/projects"> respectively (preserve
existing props like variant/size/className and inner content including Plus and
Layout icons) so clicking the buttons navigates to the new-project and projects
list routes; ensure the Link import is added at the top of
GenericProjectHero.tsx and that the buttons remain accessible (keep span
contents intact).

In `@features/projects/components/GenericProjectsClient.tsx`:
- Around line 19-22: The "Create New Project" BoundlessButton in
GenericProjectsClient.tsx is missing navigation; add an onClick handler (or wrap
with a Link) so clicking the BoundlessButton (the one displaying "Create New
Project" and using the Plus icon) navigates to the project creation route (e.g.,
'/projects/create'); implement using your app's routing utility (for Next.js use
useRouter().push('/projects/create') or next/link, or for react-router use
history.push('/projects/create')), and ensure the handler is attached to the
BoundlessButton component.

In `@features/projects/hooks/use-project-creation.ts`:
- Around line 168-176: Remove the debug console.log in the simulated auto-save
useEffect: inside the effect that references formData and calls setLastSaved,
delete the console.log('Simulated Auto-save:', formData) line so no console
output remains; keep the setTimeout, setLastSaved(Date.now()) and the cleanup
clearTimeout(timer) intact in the useEffect callback to preserve debounce
behavior.
- Around line 119-135: The mock recentDrafts array in use-project-creation.ts is
missing id properties so CreationSidebar's delete logic (which checks draft.id
&& onDeleteDraft?.(draft.id)) never triggers; update the recentDrafts state
initializer (the recentDrafts constant) to include unique id values (string or
number matching ProjectDraft.id type) for each draft object so onDeleteDraft
will receive a valid id when the delete button is pressed.

---

Outside diff comments:
In `@components/landing-page/navbar.tsx`:
- Around line 198-212: In AuthenticatedActions, the useProtectedAction hook is
initialized but its executeProtectedAction is never used so Links navigate to
/projects/create without protection; either remove the useProtectedAction hook
and its returned handlers (executeProtectedAction, showWalletModal,
showNotReadyModal, notReadyReasons, closeWalletModal, closeNotReadyModal,
handleWalletConnected) if protection is not needed, or update the Link click
handlers to call executeProtectedAction(...) (with ACTIONS.CREATE_PROJECT)
before performing navigation so wallet/auth checks run (ensure CreationLayout
still has safe fallback); modify the click handlers in AuthenticatedActions to
await/handle executeProtectedAction and then programmatically navigate to
/projects/create on success.

---

Nitpick comments:
In `@app/`(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx:
- Around line 65-67: The catch block that swallows errors when fetching the
milestone in the page component should log the caught error for debugging:
update the catch in the async milestone fetch (inside the page component in
page.tsx) to call a logger (e.g., console.error or the app's processLogger) with
a clear message like "Error fetching milestone" plus the error object and
contextual identifiers (campaign slug and milestoneId) so you still return null
for milestone but retain useful diagnostics.

In `@app/`(landing)/campaigns/[slug]/page.tsx:
- Line 45: The code uses a raw cast "const subData = submission as any;" which
bypasses TypeScript checks—replace the any cast by defining a proper interface
(e.g., extend ParticipantSubmission or create SubmissionDetails) that includes
the accessed fields (hackathonId, links, teamMembers, videoUrl, etc.), update
the variable declaration to use that interface, and add a type guard or runtime
checks where submission is received (or when parsing API response) to safely
narrow to that interface; also remove other "as any" occurrences mentioned
(around the other uses at the same file) and replace them with the new type or
guard to ensure compile-time safety.
- Around line 91-99: The current try/catch around getCrowdfundingProject
silently falls back to fetchSubmission on any error, hiding real failures;
update the catch to inspect the thrown error from getCrowdfundingProject and
only call fetchSubmission(id) for a not-found condition (e.g., HTTP 404 or a
specific "NotFound" error), otherwise rethrow or surface the error (or set an
error state) so real network/server errors from getCrowdfundingProject are not
masked; locate the block around getCrowdfundingProject, catch the error, check
its status/type, and conditionally call fetchSubmission(id) or propagate/log the
original error instead of always falling back.

In `@features/projects/components/CampaignPageHero.tsx`:
- Around line 7-33: Rename the event handler function scrollToCampaigns to
follow the coding guideline (handleScrollToCampaigns) and update all internal
references to it (e.g., the button's onClick prop) so behavior is unchanged;
ensure you rename the function declaration and any calls/usages inside the
component (search for scrollToCampaigns and replace with
handleScrollToCampaigns) and leave the animation logic intact.

In `@features/projects/components/CreationFlow/CreationUI.tsx`:
- Around line 436-457: Rename the drag event handlers to follow the "handle"
prefix convention: change onDrag to handleDrag and onDrop to handleDrop, update
their useCallback declarations (handleDrag and handleDrop), replace any
references to onDrag/onDrop in JSX or props, ensure the dependency arrays still
include disabled and any other used symbols (setDragActive, handleUpload), and
remove or adjust the eslint-disable comment if no longer needed; keep the
handler logic unchanged (preventDefault/stopPropagation, setDragActive, disabled
guard, and file handling via handleUpload).

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx`:
- Around line 233-236: The current onSelect handler in BasicInfo.tsx toggles
category selection (onChange(cat.value === value ? '' : cat.value)) which clears
a required field when clicking the already-selected category; change the handler
to always set the selected category (onChange(cat.value)) and simply close the
popover with setOpen(false) so clicking the selected item only closes the
popover and does not clear the value (update the onSelect in the category item
where cat.value and value are referenced).
- Around line 288-289: The current BasicInfo component computes socialLinks as
"const socialLinks: string[] = formData.socialLinks ?? ['', '', ''];" which
allocates a new default array on every render; fix by ensuring the default is
stable—either initialize formData.socialLinks to ['','',''] in the parent/form
state or memoize the derived value in BasicInfo (e.g., useMemo to return
formData.socialLinks if present or a single shared default array) so the
identity of socialLinks stays consistent across renders.

In `@features/projects/components/CreationFlow/Steps/CampaignDetails.tsx`:
- Line 9: The import statement in CampaignDetails.tsx includes an unused symbol
Video; remove Video from the named imports (e.g., change "import { Plus, X,
Calendar, Video, Info } from 'lucide-react';" to omit Video) so only used icons
(Plus, X, Calendar, Info) are imported to eliminate the unused import warning.

In `@features/projects/components/CreationFlow/Steps/ReviewStep.tsx`:
- Around line 43-125: The review notes redundant "as CreationStep" casts in the
sections array; remove the unnecessary type assertions on the objects where step
is set to the string literals ('basic', 'details', 'team', 'social', 'funding')
so the literals naturally infer the CreationStep type (update the objects in the
sections constant and the pushed funding section). Locate the step properties in
the ReviewStep component (the sections: ReviewSection[] definition and the
conditional sections.push block) and delete each "as CreationStep" cast to
simplify the code.

In `@features/projects/components/CreationFlow/Steps/SocialLinks.tsx`:
- Around line 7-10: The SocialLinksProps interface currently uses `any` for
`formData` and `updateFormData`; replace these with the project's proper
creation types by importing the appropriate project creation/type definitions
(e.g., use the canonical ProjectFormData type for `formData` and type
`updateFormData` as a function that accepts a Partial<ProjectFormData> or a
typed patch callback) so `SocialLinksProps` explicitly types `formData` and
`updateFormData` instead of `any`; update references inside the SocialLinks
component accordingly (look for SocialLinksProps, formData, updateFormData).

In `@features/projects/components/CreationFlow/Steps/TeamInfo.tsx`:
- Around line 12-15: TeamInfoProps currently uses `any` for formData and
updateFormData; replace those with the project creation form types used across
the feature (e.g., the shared form data type and its partial update handler)
instead of `any`. Update the interface so `formData` is the concrete form data
type (for example ProjectCreationForm or ProjectFormData from the feature's
types) and `updateFormData` accepts a Partial of that type or a typed update
callback `(updates: Partial<ProjectCreationForm>) => void`; update usages in the
TeamInfo component to match the new types (keep the symbol names TeamInfoProps,
formData, updateFormData).

In `@features/projects/hooks/use-project-creation.ts`:
- Around line 137-146: Wrap the steps array and the derived activeSteps in
React.useMemo so they are only recomputed when isCampaign changes: memoize the
steps definition (const steps = useMemo(() => [...], [isCampaign])) and compute
activeSteps from that memoized steps (const activeSteps = useMemo(() =>
steps.filter(s => !s.hidden), [steps])); update any references to use the
memoized variables and ensure React.useMemo (or useMemo import) is available in
the hook file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b309a13-bf28-415a-a9f4-457ae3b201a5

📥 Commits

Reviewing files that changed from the base of the PR and between 896fdcd and 803979c.

📒 Files selected for processing (25)
  • app/(landing)/campaigns/[slug]/contributions/page.tsx
  • app/(landing)/campaigns/[slug]/milestone/[milestoneId]/page.tsx
  • app/(landing)/campaigns/[slug]/page.tsx
  • app/(landing)/campaigns/layout.tsx
  • app/(landing)/campaigns/page.tsx
  • app/(landing)/projects/page.tsx
  • app/me/projects/create/page.tsx
  • app/projects/create/page.tsx
  • components/landing-page/Explore.tsx
  • components/landing-page/navbar.tsx
  • features/projects/components/CampaignPageHero.tsx
  • features/projects/components/CreationFlow/CreationLayout.tsx
  • features/projects/components/CreationFlow/CreationNavigation.tsx
  • features/projects/components/CreationFlow/CreationSidebar.tsx
  • features/projects/components/CreationFlow/CreationUI.tsx
  • features/projects/components/CreationFlow/Steps/BasicInfo.tsx
  • features/projects/components/CreationFlow/Steps/CampaignDetails.tsx
  • features/projects/components/CreationFlow/Steps/ProjectDetails.tsx
  • features/projects/components/CreationFlow/Steps/ReviewStep.tsx
  • features/projects/components/CreationFlow/Steps/SocialLinks.tsx
  • features/projects/components/CreationFlow/Steps/TeamInfo.tsx
  • features/projects/components/GenericProjectHero.tsx
  • features/projects/components/GenericProjectsClient.tsx
  • features/projects/components/ProjectsPage.tsx
  • features/projects/hooks/use-project-creation.ts

Comment on lines +115 to +121
if (error || !project) {
notFound();
}

if (error || !project) {
notFound();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate notFound() check.

Lines 115-117 and 119-121 are identical, resulting in dead code. The second check will never execute.

🐛 Proposed fix
   if (error || !project) {
     notFound();
   }

-  if (error || !project) {
-    notFound();
-  }
-
   return (
📝 Committable suggestion

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

Suggested change
if (error || !project) {
notFound();
}
if (error || !project) {
notFound();
}
if (error || !project) {
notFound();
}
return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/campaigns/[slug]/page.tsx around lines 115 - 121, There are
two identical conditional checks calling notFound() (if (error || !project) {
notFound(); }) back-to-back; remove the duplicate so the condition referencing
error and project appears only once (keep the first or the more contextually
correct one) to eliminate dead code in the page component where error and
project are evaluated and notFound() is invoked.

Comment on lines +215 to +218
<button className='hidden items-center gap-2 rounded-xl border border-white/5 px-5 py-3.5 text-[11px] font-black tracking-[0.15em] text-white/30 uppercase transition-all hover:bg-white/5 hover:text-white/60 active:scale-95 md:flex'>
<Save className='h-3.5 w-3.5' />
Save Draft
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Save Draft button has no onClick handler.

The "Save Draft" button is rendered without an onClick handler, making it non-functional. The hook provides simulated auto-save, but manual save is not wired up.

Would you like me to help implement the save draft functionality?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationLayout.tsx` around lines
215 - 218, The Save Draft button in CreationLayout is missing an onClick
handler; wire it to the form's save function (e.g., call the hook-provided
saveDraft or handleSaveDraft method from the project form hook used in
CreationLayout) so the button triggers a manual save, and update the button to
reflect saving state (disable while saving and show a spinner/icon) using the
existing loading flag (e.g., isSaving or savingDraft) so users get feedback;
ensure the handler is named clearly (handleSaveDraft) and referenced where the
<Save /> button JSX is rendered.

Comment on lines +405 to +457
const handleUpload = async (file: File) => {
const validErr = validateFile(file);
if (validErr) {
setUploadError(validErr);
return;
}
setUploadError('');
setIsUploading(true);
try {
const result = await uploadService.uploadSingle(file, {
folder: 'boundless/projects',
transformation:
aspectRatio === 'square'
? { width: 500, height: 500, crop: 'fill' }
: aspectRatio === 'banner'
? { width: 1200, height: 400, crop: 'fill' }
: undefined,
});
if (result.success) {
onChange(result.data.secure_url);
setUploadError('');
} else {
setUploadError('Upload failed. Please try again.');
}
} catch {
setUploadError('Upload failed. Please try again.');
} finally {
setIsUploading(false);
}
};

const onDrag = useCallback(
(e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (disabled) return;
setDragActive(e.type === 'dragenter' || e.type === 'dragover');
},
[disabled]
);

const onDrop = useCallback(
(e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (disabled) return;
const file = e.dataTransfer.files?.[0];
if (file) handleUpload(file);
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[disabled]
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wrap handleUpload in useCallback to fix stale closure in drag handlers.

The handleUpload function is referenced in onDrop but not included in its dependency array (hidden behind eslint-disable). This can cause stale closure bugs if component props change. Wrap handleUpload in useCallback and include it as a dependency.

🔧 Proposed fix
-  const handleUpload = async (file: File) => {
+  const handleUpload = useCallback(async (file: File) => {
     const validErr = validateFile(file);
     if (validErr) {
       setUploadError(validErr);
       return;
     }
     setUploadError('');
     setIsUploading(true);
     try {
       const result = await uploadService.uploadSingle(file, {
         folder: 'boundless/projects',
         transformation:
           aspectRatio === 'square'
             ? { width: 500, height: 500, crop: 'fill' }
             : aspectRatio === 'banner'
               ? { width: 1200, height: 400, crop: 'fill' }
               : undefined,
       });
       if (result.success) {
         onChange(result.data.secure_url);
         setUploadError('');
       } else {
         setUploadError('Upload failed. Please try again.');
       }
     } catch {
       setUploadError('Upload failed. Please try again.');
     } finally {
       setIsUploading(false);
     }
-  };
+  }, [aspectRatio, onChange]);

   // ... then update onDrop:
   const onDrop = useCallback(
     (e: React.DragEvent) => {
       e.preventDefault();
       e.stopPropagation();
       setDragActive(false);
       if (disabled) return;
       const file = e.dataTransfer.files?.[0];
       if (file) handleUpload(file);
-      // eslint-disable-next-line react-hooks/exhaustive-deps
     },
-    [disabled]
+    [disabled, handleUpload]
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationUI.tsx` around lines 405 -
457, The handleUpload function should be wrapped in useCallback to avoid a stale
closure: convert handleUpload into a useCallback hook and include all external
dependencies it uses (validateFile, uploadService, aspectRatio, onChange,
setUploadError, setIsUploading, and any others referenced). Then remove the
eslint-disable and add the memoized handleUpload to the dependency array of the
onDrop useCallback (and adjust onDrag if needed). Ensure the wrapped
handleUpload preserves current behavior and returns the same results.

Comment on lines +16 to +41
export default function ReviewStep({ formData, onNavigate }: ReviewStepProps) {
// Helper to check if a field is "completed"
const isFilled = (value: any): boolean => {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object') {
// For objects like contact, check if any of the values are filled
return Object.values(value).some(v => isFilled(v));
}
if (typeof value === 'number') return true; // fundingAmount > 0 check handled separately
return true;
};

interface ReviewField {
label: string;
value: any;
required: boolean;
customLabel?: string | null;
}

interface ReviewSection {
title: string;
step: CreationStep;
fields: ReviewField[];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace any types with proper type definitions.

The isFilled function parameter and ReviewField.value use any type, which violates the coding guideline: "Do not use 'any' type; always search for proper Trustless Work entity types."

🔧 Proposed fix
+type FieldValue = string | number | boolean | null | undefined | unknown[] | Record<string, unknown>;
+
 export default function ReviewStep({ formData, onNavigate }: ReviewStepProps) {
   // Helper to check if a field is "completed"
-  const isFilled = (value: any): boolean => {
+  const isFilled = (value: FieldValue): boolean => {
     if (value === null || value === undefined) return false;
     if (typeof value === 'string') return value.trim().length > 0;
     if (Array.isArray(value)) return value.length > 0;
     if (typeof value === 'object') {
       // For objects like contact, check if any of the values are filled
-      return Object.values(value).some(v => isFilled(v));
+      return Object.values(value as Record<string, unknown>).some(v => isFilled(v as FieldValue));
     }
     if (typeof value === 'number') return true;
     return true;
   };

   interface ReviewField {
     label: string;
-    value: any;
+    value: FieldValue;
     required: boolean;
     customLabel?: string | null;
   }

As per coding guidelines: "Do not use 'any' type; always search for proper Trustless Work entity types."

📝 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
export default function ReviewStep({ formData, onNavigate }: ReviewStepProps) {
// Helper to check if a field is "completed"
const isFilled = (value: any): boolean => {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object') {
// For objects like contact, check if any of the values are filled
return Object.values(value).some(v => isFilled(v));
}
if (typeof value === 'number') return true; // fundingAmount > 0 check handled separately
return true;
};
interface ReviewField {
label: string;
value: any;
required: boolean;
customLabel?: string | null;
}
interface ReviewSection {
title: string;
step: CreationStep;
fields: ReviewField[];
}
type FieldValue = string | number | boolean | null | undefined | unknown[] | Record<string, unknown>;
export default function ReviewStep({ formData, onNavigate }: ReviewStepProps) {
// Helper to check if a field is "completed"
const isFilled = (value: FieldValue): boolean => {
if (value === null || value === undefined) return false;
if (typeof value === 'string') return value.trim().length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object') {
// For objects like contact, check if any of the values are filled
return Object.values(value as Record<string, unknown>).some(v => isFilled(v as FieldValue));
}
if (typeof value === 'number') return true; // fundingAmount > 0 check handled separately
return true;
};
interface ReviewField {
label: string;
value: FieldValue;
required: boolean;
customLabel?: string | null;
}
interface ReviewSection {
title: string;
step: CreationStep;
fields: ReviewField[];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/ReviewStep.tsx` around lines
16 - 41, Replace the use of any by using the real form/types or a safe union:
change isFilled(value: any) to isFilled(value: unknown) and update its
implementation signatures accordingly; replace ReviewField.value: any with a
concrete union or referenced entity type (e.g., ReviewField.value: string |
number | Array<unknown> | Record<string, unknown> | ContactType) or, better,
import and use the project's form payload type (e.g., CreationFormData or
ProjectFormValues) and use that type for ReviewField.value and for the
ReviewSection fields; update ReviewStep({ formData, onNavigate }:
ReviewStepProps) to type formData with the same imported form type so all checks
and Object.values(value).some(v => isFilled(v)) remain type-safe without any
usage of any.

Comment on lines +28 to +47
<BoundlessButton
size='xl'
className='group bg-primary hover:shadow-primary/25 relative transform rounded-lg px-6 py-3 text-sm font-semibold text-black transition-none duration-300 hover:scale-none! hover:shadow-lg md:px-7 md:py-3.5 md:text-base lg:px-8 lg:py-4 lg:text-base'
>
<span className='flex items-center gap-2'>
Create New Project
<Plus className='h-4 w-4 transition-transform group-hover:scale-110 md:h-4 md:w-4 lg:h-5 lg:w-5' />
</span>
</BoundlessButton>

<BoundlessButton
variant='outline'
size='xl'
className='rounded-lg border-white/20 bg-transparent px-6 py-3 text-sm font-medium text-white/90 transition-all hover:bg-white/5 md:px-7 md:py-3.5 md:text-base'
>
<span className='flex items-center gap-2'>
<Layout className='h-4 w-4' />
Manage Projects
</span>
</BoundlessButton>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Both action buttons are non-functional.

Neither "Create New Project" nor "Manage Projects" buttons have onClick handlers or navigation links, making the hero's primary CTAs inoperable.

🔧 Proposed fix to add navigation

Add Link import and wrap buttons:

 'use client';
 import React from 'react';
 import { Plus, Layout } from 'lucide-react';
 import { BoundlessButton } from '@/components/buttons';
+import Link from 'next/link';
-            <BoundlessButton
-              size='xl'
-              className='group bg-primary hover:shadow-primary/25 relative transform rounded-lg px-6 py-3 text-sm font-semibold text-black transition-none duration-300 hover:scale-none! hover:shadow-lg md:px-7 md:py-3.5 md:text-base lg:px-8 lg:py-4 lg:text-base'
-            >
-              <span className='flex items-center gap-2'>
-                Create New Project
-                <Plus className='h-4 w-4 transition-transform group-hover:scale-110 md:h-4 md:w-4 lg:h-5 lg:w-5' />
-              </span>
-            </BoundlessButton>
+            <BoundlessButton
+              size='xl'
+              className='group bg-primary hover:shadow-primary/25 relative transform rounded-lg px-6 py-3 text-sm font-semibold text-black transition-none duration-300 hover:scale-none! hover:shadow-lg md:px-7 md:py-3.5 md:text-base lg:px-8 lg:py-4 lg:text-base'
+              asChild
+            >
+              <Link href='/projects/create'>
+                <span className='flex items-center gap-2'>
+                  Create New Project
+                  <Plus className='h-4 w-4 transition-transform group-hover:scale-110 md:h-4 md:w-4 lg:h-5 lg:w-5' />
+                </span>
+              </Link>
+            </BoundlessButton>

-            <BoundlessButton
-              variant='outline'
-              size='xl'
-              className='rounded-lg border-white/20 bg-transparent px-6 py-3 text-sm font-medium text-white/90 transition-all hover:bg-white/5 md:px-7 md:py-3.5 md:text-base'
-            >
-              <span className='flex items-center gap-2'>
-                <Layout className='h-4 w-4' />
-                Manage Projects
-              </span>
-            </BoundlessButton>
+            <BoundlessButton
+              variant='outline'
+              size='xl'
+              className='rounded-lg border-white/20 bg-transparent px-6 py-3 text-sm font-medium text-white/90 transition-all hover:bg-white/5 md:px-7 md:py-3.5 md:text-base'
+              asChild
+            >
+              <Link href='/me/projects'>
+                <span className='flex items-center gap-2'>
+                  <Layout className='h-4 w-4' />
+                  Manage Projects
+                </span>
+              </Link>
+            </BoundlessButton>
📝 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
<BoundlessButton
size='xl'
className='group bg-primary hover:shadow-primary/25 relative transform rounded-lg px-6 py-3 text-sm font-semibold text-black transition-none duration-300 hover:scale-none! hover:shadow-lg md:px-7 md:py-3.5 md:text-base lg:px-8 lg:py-4 lg:text-base'
>
<span className='flex items-center gap-2'>
Create New Project
<Plus className='h-4 w-4 transition-transform group-hover:scale-110 md:h-4 md:w-4 lg:h-5 lg:w-5' />
</span>
</BoundlessButton>
<BoundlessButton
variant='outline'
size='xl'
className='rounded-lg border-white/20 bg-transparent px-6 py-3 text-sm font-medium text-white/90 transition-all hover:bg-white/5 md:px-7 md:py-3.5 md:text-base'
>
<span className='flex items-center gap-2'>
<Layout className='h-4 w-4' />
Manage Projects
</span>
</BoundlessButton>
<BoundlessButton
size='xl'
className='group bg-primary hover:shadow-primary/25 relative transform rounded-lg px-6 py-3 text-sm font-semibold text-black transition-none duration-300 hover:scale-none! hover:shadow-lg md:px-7 md:py-3.5 md:text-base lg:px-8 lg:py-4 lg:text-base'
asChild
>
<Link href='/projects/create'>
<span className='flex items-center gap-2'>
Create New Project
<Plus className='h-4 w-4 transition-transform group-hover:scale-110 md:h-4 md:w-4 lg:h-5 lg:w-5' />
</span>
</Link>
</BoundlessButton>
<BoundlessButton
variant='outline'
size='xl'
className='rounded-lg border-white/20 bg-transparent px-6 py-3 text-sm font-medium text-white/90 transition-all hover:bg-white/5 md:px-7 md:py-3.5 md:text-base'
asChild
>
<Link href='/me/projects'>
<span className='flex items-center gap-2'>
<Layout className='h-4 w-4' />
Manage Projects
</span>
</Link>
</BoundlessButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/GenericProjectHero.tsx` around lines 28 - 47,
The two CTAs (BoundlessButton instances "Create New Project" and "Manage
Projects") are missing navigation handlers; import Link from 'next/link' and
wrap the corresponding BoundlessButton elements with <Link href="/projects/new">
and <Link href="/projects"> respectively (preserve existing props like
variant/size/className and inner content including Plus and Layout icons) so
clicking the buttons navigates to the new-project and projects list routes;
ensure the Link import is added at the top of GenericProjectHero.tsx and that
the buttons remain accessible (keep span contents intact).

Comment on lines +19 to +22
<BoundlessButton variant='default' className='flex items-center gap-2'>
<Plus className='h-4 w-4' />
Create New Project
</BoundlessButton>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The "Create New Project" button is non-functional.

The button lacks an onClick handler or navigation link. Based on the PR's creation flow, this should navigate to the project creation page.

🔧 Proposed fix to add navigation
+'use client';
 import React from 'react';
 import { Layout, Plus } from 'lucide-react';
 import { BoundlessButton } from '@/components/buttons';
+import Link from 'next/link';

 export default function GenericProjectsClient() {
   return (
     <div className='flex flex-col items-center justify-center rounded-2xl border border-white/10 bg-white/5 py-20 text-center'>
       <div className='mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-white/10'>
         <Layout className='h-8 w-8 text-white/40' />
       </div>
       <h2 className='mb-2 text-2xl font-semibold text-white'>
         No Projects Yet
       </h2>
       <p className='mb-8 max-w-sm text-white/60'>
         Create your first project on Boundless to get started. Your project can
         later be used for hackathons, grants, or crowdfunding campaigns.
       </p>
-      <BoundlessButton variant='default' className='flex items-center gap-2'>
-        <Plus className='h-4 w-4' />
-        Create New Project
-      </BoundlessButton>
+      <BoundlessButton variant='default' className='flex items-center gap-2' asChild>
+        <Link href='/projects/create'>
+          <Plus className='h-4 w-4' />
+          Create New Project
+        </Link>
+      </BoundlessButton>
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/GenericProjectsClient.tsx` around lines 19 - 22,
The "Create New Project" BoundlessButton in GenericProjectsClient.tsx is missing
navigation; add an onClick handler (or wrap with a Link) so clicking the
BoundlessButton (the one displaying "Create New Project" and using the Plus
icon) navigates to the project creation route (e.g., '/projects/create');
implement using your app's routing utility (for Next.js use
useRouter().push('/projects/create') or next/link, or for react-router use
history.push('/projects/create')), and ensure the handler is attached to the
BoundlessButton component.

Copy link
Copy Markdown

@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: 12

♻️ Duplicate comments (3)
features/projects/components/CreationFlow/CreationLayout.tsx (1)

283-288: ⚠️ Potential issue | 🟡 Minor

Wire up or remove the Save Draft button.

This CTA is still a deliberate no-op, so the visible button never saves anything when clicked. Either expose a real handleSaveDraft from the hook and call it here, or hide the control until it exists.

Based on learnings: "Always ensure final code is fully functional with no placeholders, TODOs, or missing parts".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationLayout.tsx` around lines
283 - 288, The "Save Draft" button currently has a no-op onClick; either wire it
to the real save handler or remove/hide it. Expose and import a handleSaveDraft
(or similarly named saveDraft) function from the creation hook used by this
component, then replace the empty onClick with onClick={handleSaveDraft} and
ensure the button's disabled state still respects isPersisting and isPublishing;
alternatively, remove the button markup entirely (or keep it hidden) until
handleSaveDraft is implemented to avoid a visible non-functional CTA.
features/projects/hooks/use-project-creation.ts (1)

259-260: ⚠️ Potential issue | 🔴 Critical

Remove the draft debug log before merge.

Pre-commit is already failing on console.log in changed files, and this line keeps the PR red.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/hooks/use-project-creation.ts` around lines 259 - 260,
Remove the temporary debug console.log in use-project-creation.ts: delete the
console.log('myDrafts', myDrafts) statement that follows the useMyProjects({
status: 'IDEA' }) call (the myDrafts variable) so the pre-commit lint/no-console
check passes; if you need to inspect data keep a commented note or use a proper
logger instead.
features/projects/components/CreationFlow/Steps/BasicInfo.tsx (1)

264-266: 🛠️ Refactor suggestion | 🟠 Major

Replace any with the shared draft type.

This step already reads and writes the ProjectDraft shape from use-project-creation.ts; keeping both props as any disables checks for every field access and update in the form.

♻️ Suggested change
+import type { ProjectDraft } from '@/features/projects/hooks/use-project-creation';
+
 interface BasicInfoProps {
-  formData: any;
-  updateFormData: (updates: any) => void;
+  formData: Partial<ProjectDraft>;
+  updateFormData: (updates: Partial<ProjectDraft>) => void;
   isCampaign: boolean;
   setIsCampaign: (val: boolean) => void;
 }
#!/bin/bash
rg -n 'formData:\s*any|updateFormData:\s*\(updates:\s*any\)' features/projects/components/CreationFlow/Steps/BasicInfo.tsx
rg -n 'export interface ProjectDraft' features/projects/hooks/use-project-creation.ts

As per coding guidelines: "Do not use 'any' type; always search for proper Trustless Work entity types".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx` around lines
264 - 266, The BasicInfoProps interface uses broad any types—replace them with
the shared ProjectDraft type exported from the project creation hook: change
formData to ProjectDraft and change updateFormData to accept updates typed as
Partial<ProjectDraft> (or ProjectDraft if it expects full replacements) so all
field reads/writes in BasicInfo (and its handlers) get proper type safety;
reference the exported ProjectDraft type from the use-project-creation hook and
update the BasicInfoProps declaration (formData, updateFormData) and any
call-sites in BasicInfo to match the new signature.
🧹 Nitpick comments (2)
app/sentry-example-page/page.tsx (2)

73-92: Extract the inline click callback into a named handle... function.

Please move the inline onClick logic to a named handler (e.g., handleThrowSampleError) for consistency and readability.

♻️ Suggested refactor
+  const handleThrowSampleError = async (): Promise<void> => {
+    Sentry.logger.info('User clicked the button, throwing a sample error');
+    await Sentry.startSpan(
+      { name: 'Example Frontend/Backend Span', op: 'test' },
+      async () => {
+        const res = await fetch('/api/sentry-example-api');
+        if (!res.ok) setHasSentError(true);
+      }
+    );
+    throw new SentryExampleFrontendError(
+      'This error is raised on the frontend of the example page.'
+    );
+  };
...
-          onClick={async () => {
-            ...
-          }}
+          onClick={handleThrowSampleError}

As per coding guidelines "**/*.{jsx,tsx}: Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sentry-example-page/page.tsx` around lines 73 - 92, Extract the inline
async onClick callback into a named handler called e.g. handleThrowSampleError:
create an async function (preferably wrapped with useCallback inside the
component) that runs the same logic currently inside the onClick — call
Sentry.logger.info, await Sentry.startSpan({...}, async () => { const res =
await fetch('/api/sentry-example-api'); if (!res.ok) setHasSentError(true); }),
then throw new SentryExampleFrontendError(...); and replace the inline onClick
with onClick={handleThrowSampleError}; keep all referenced symbols
(Sentry.startSpan, setHasSentError, SentryExampleFrontendError) and behavior
identical.

115-236: Replace embedded <style> CSS with Tailwind utility classes.

Line 115-236 adds a large raw CSS block. This diverges from the project’s Tailwind-first JSX styling rule.

As per coding guidelines "**/*.{jsx,tsx}: Use TailwindCSS classes for styling; avoid plain CSS".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sentry-example-page/page.tsx` around lines 115 - 236, The file contains a
large embedded <style> block (styles for main, h1, p, a, button, .description,
.success, .connectivity-error, etc.)—remove that raw CSS and convert the styling
to Tailwind utility classes directly on the JSX elements in
app/sentry-example-page/page.tsx (replace styles applied to main, h1, p, a,
button and elements using classNames like "description", "success",
"success_placeholder", "flex-spacer", "connectivity-error" with equivalent
Tailwind utilities, including dark: variants and responsive utilities where the
CSS used `@media`); update the JSX to add those className attributes (and replace
.success_placeholder with a utility height class on its element) so no plain
<style> block remains and styling follows the project's Tailwind-first rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/sentry-example-api/route.ts`:
- Around line 12-16: The GET route currently always throws a
SentryExampleAPIError and logs via Sentry.logger, which will generate errors in
production; update the GET handler to guard the throw by checking the
environment or an explicit feature flag (e.g., process.env.NODE_ENV !==
'production' or a NEXT_PUBLIC feature flag) or require an authenticated/dev-only
header before throwing, and otherwise return a safe non-error response (e.g.,
200/404) so that SentryExampleAPIError is only raised in development; keep
references to GET, SentryExampleAPIError and Sentry.logger when locating and
modifying the handler.

In `@app/sentry-example-page/page.tsx`:
- Around line 4-5: The page currently imports Head and uses it inside the client
component (import Head from 'next/head' and the JSX block that renders <Head>),
which is unsupported in App Router; refactor by creating a server wrapper that
exports metadata (export const metadata or generateMetadata) and renders a
separate client component for interactive logic: remove the Head import from
app/sentry-example-page/page.tsx, add a new server component (e.g.,
SentryExamplePageServer) that exports metadata with title/description and
renders the client component (e.g., SentryExamplePageClient), and move all
state, useEffect and event handlers into the client component so the server file
only handles metadata and mounts the client.

In `@features/projects/components/CreationFlow/CreationSidebar.tsx`:
- Around line 85-88: The icon-only delete and mobile-menu buttons lack
accessible names; update the button inside AlertDialogTrigger (the Trash2 icon
button) and the mobile menu button (the icon-only button around lines ~181-183)
to include a descriptive accessible name—either add an aria-label (e.g.
aria-label="Delete project" and aria-label="Open project menu") or include
visually hidden text inside the button—so assistive technology announces their
purpose; ensure labels are concise, descriptive and match the action.
- Around line 67-82: The absolutely-positioned hover border overlay that sits
above the draft row is intercepting clicks meant for the Link (the
continue-editing action) in CreationSidebar; make that overlay non-interactive
by adding pointer-events-none to the overlay element's class list, and ensure
the trash button keeps pointer-events-auto (and its z-index) so it remains
clickable; update the overlay element (the absolutely positioned div that draws
the hover border) and the trash button element accordingly in
CreationSidebar.tsx so the Link retains clickability.

In `@features/projects/hooks/use-project-creation.ts`:
- Around line 460-465: The onDeleteDraft handler in use-project-creation.ts
currently only logs and is wired to CreationSidebar after confirmation, which
exposes a non-functional destructive action; either implement a real delete
mutation in features/projects/api/index.ts (e.g., add deleteDraft API/mutation,
export deleteDraft function) and update onDeleteDraft to call that mutation and
invalidate or refetch the drafts list (or use the query-client to remove the
draft), or remove/hide the trash affordance in CreationSidebar until the API
exists; update onDeleteDraft to call the new deleteDraft function (or remove the
handler) and ensure no console.log remains so CI console rules aren’t tripped.
- Around line 368-390: Autosave and publish can race because both call
updateDraftMutation.mutateAsync(payload); to fix, centralize save logic and
serialize writes: create a single async saveDraft(payload) used by the autosave
timer and handlePublish, store its in-flight Promise on
autosaveRef.current.inFlightSave and store the timeout id on
autosaveRef.current.timer; in the autosave useEffect set
autosaveRef.current.timer = setTimeout(...) and inside call saveDraft(payload)
which assigns autosaveRef.current.inFlightSave =
updateDraftMutation.mutateAsync(...) (or createDraftMutation for first save),
and in handlePublish first clearTimeout(autosaveRef.current.timer), then if
(autosaveRef.current.inFlightSave) await autosaveRef.current.inFlightSave before
calling updateDraftMutation.mutateAsync(payload) for the publish so no two
concurrent PATCHes occur; also ensure setDraftId and setLastSaved are handled by
saveDraft and errors still surface via persistError.
- Around line 239-245: The current logic sets draftId from editingId immediately
while formData remains INITIAL_FORM_DATA, causing the autosave debounce (in the
save/patch handler used by the effect around the debounced PATCH) to send
mostly-empty data; change initialization so draftId is null until the real draft
is loaded (i.e., delay calling setDraftId(editingId) until after the
useMyProject / draft hydration completes and you call setFormData with the
loaded draft), and guard the autosave debounce handler to no-op when editingId
exists but a new "hydrated" flag is false or formData still equals
INITIAL_FORM_DATA; update references to draftId/setDraftId,
formData/setFormData, and the debounce autosave function so autosave only runs
after hydration.
- Around line 113-127: The payload currently prefers stale aliases for some
fields (e.g., title uses data.title before data.projectName) causing edited
values to be lost; update the creation in use-project-creation.ts so the
form-edited fields are preferred first (use data.projectName || data.title for
title, data.websiteUrl || data.projectWebsite for projectWebsite (already
correct), and data.demoVideoUrl || data.demoVideo for demoVideo), and apply the
same ordering fix to the duplicate block mentioned (lines ~279-294) so
toApiPayload always serializes from the edited field names (data.projectName,
data.websiteUrl, data.demoVideoUrl) before their older aliases.

In `@features/projects/hooks/use-project-queries.ts`:
- Around line 51-52: projectKeys.mine currently returns ['projects','me',
params] so calling projectKeys.mine() yields ['projects','me', undefined] which
won't prefix-match parameterized caches; add a separate prefix-only key factory
(e.g., projectKeys.mineList = () => ['projects','me'] as const or
projectKeys.mine.base = () => ['projects','me'] as const) and use that
prefix-only key when invalidating or invalidatingQueries (replace places that
call projectKeys.mine() for prefix invalidation with projectKeys.mineList()),
and update the other two usages of projectKeys.mine mentioned in the review to
use this new prefix key so TanStack Query v5 prefix filters match parameterized
cached queries.

In `@next.config.ts`:
- Around line 115-117: The Sentry config currently uses hardcoded fallbacks for
org/project which causes accidental uploads; update the Sentry options so that
org and project do not default to 'collins-ki' or 'javascript-nextjs' but
instead are omitted/undefined when SENTRY_ORG or SENTRY_PROJECT are not set
(reference the org and project properties in next.config.ts and the environment
vars SENTRY_ORG and SENTRY_PROJECT) so that source map uploads are skipped when
credentials are absent.

In `@sentry.edge.config.ts`:
- Around line 14-22: The Sentry edge config currently uses unsafe production
defaults: set tracesSampleRate, enableLogs, and sendDefaultPii based on
environment rather than hardcoding 1/true/true; detect production (e.g.,
process.env.NODE_ENV === 'production' or an existing isProd helper) and use
tracesSampleRate: 0.1, enableLogs: false, sendDefaultPii: false in production,
while keeping higher sampling/logging for non-production. Update the config
object keys tracesSampleRate, enableLogs, and sendDefaultPii accordingly so
production is gated by the environment check.

In `@sentry.server.config.ts`:
- Around line 13-21: The Sentry server config currently enables max sampling and
PII by default (tracesSampleRate: 1, enableLogs: true, sendDefaultPii: true);
change these defaults to safe values and make them environment-configurable: set
tracesSampleRate to a lower baseline (e.g., 0.1) or read from an env var like
SENTRY_TRACES_SAMPLE_RATE, set enableLogs to false in production (or toggle via
SENTRY_ENABLE_LOGS), and set sendDefaultPii to false by default (enable via
SENTRY_SEND_DEFAULT_PII only when explicitly required); update the code that
sets tracesSampleRate, enableLogs, and sendDefaultPii to read and parse these
env vars (falling back to the safer defaults) so production doesn't run with
high-volume telemetry or PII on by default.

---

Duplicate comments:
In `@features/projects/components/CreationFlow/CreationLayout.tsx`:
- Around line 283-288: The "Save Draft" button currently has a no-op onClick;
either wire it to the real save handler or remove/hide it. Expose and import a
handleSaveDraft (or similarly named saveDraft) function from the creation hook
used by this component, then replace the empty onClick with
onClick={handleSaveDraft} and ensure the button's disabled state still respects
isPersisting and isPublishing; alternatively, remove the button markup entirely
(or keep it hidden) until handleSaveDraft is implemented to avoid a visible
non-functional CTA.

In `@features/projects/components/CreationFlow/Steps/BasicInfo.tsx`:
- Around line 264-266: The BasicInfoProps interface uses broad any types—replace
them with the shared ProjectDraft type exported from the project creation hook:
change formData to ProjectDraft and change updateFormData to accept updates
typed as Partial<ProjectDraft> (or ProjectDraft if it expects full replacements)
so all field reads/writes in BasicInfo (and its handlers) get proper type
safety; reference the exported ProjectDraft type from the use-project-creation
hook and update the BasicInfoProps declaration (formData, updateFormData) and
any call-sites in BasicInfo to match the new signature.

In `@features/projects/hooks/use-project-creation.ts`:
- Around line 259-260: Remove the temporary debug console.log in
use-project-creation.ts: delete the console.log('myDrafts', myDrafts) statement
that follows the useMyProjects({ status: 'IDEA' }) call (the myDrafts variable)
so the pre-commit lint/no-console check passes; if you need to inspect data keep
a commented note or use a proper logger instead.

---

Nitpick comments:
In `@app/sentry-example-page/page.tsx`:
- Around line 73-92: Extract the inline async onClick callback into a named
handler called e.g. handleThrowSampleError: create an async function (preferably
wrapped with useCallback inside the component) that runs the same logic
currently inside the onClick — call Sentry.logger.info, await
Sentry.startSpan({...}, async () => { const res = await
fetch('/api/sentry-example-api'); if (!res.ok) setHasSentError(true); }), then
throw new SentryExampleFrontendError(...); and replace the inline onClick with
onClick={handleThrowSampleError}; keep all referenced symbols (Sentry.startSpan,
setHasSentError, SentryExampleFrontendError) and behavior identical.
- Around line 115-236: The file contains a large embedded <style> block (styles
for main, h1, p, a, button, .description, .success, .connectivity-error,
etc.)—remove that raw CSS and convert the styling to Tailwind utility classes
directly on the JSX elements in app/sentry-example-page/page.tsx (replace styles
applied to main, h1, p, a, button and elements using classNames like
"description", "success", "success_placeholder", "flex-spacer",
"connectivity-error" with equivalent Tailwind utilities, including dark:
variants and responsive utilities where the CSS used `@media`); update the JSX to
add those className attributes (and replace .success_placeholder with a utility
height class on its element) so no plain <style> block remains and styling
follows the project's Tailwind-first rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27650f8a-e979-4849-a9df-996c0863b312

📥 Commits

Reviewing files that changed from the base of the PR and between 803979c and 72ae193.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • .env.example
  • .gitignore
  • app/api/sentry-example-api/route.ts
  • app/sentry-example-page/page.tsx
  • features/projects/api/index.ts
  • features/projects/components/CreationFlow/CreationLayout.tsx
  • features/projects/components/CreationFlow/CreationSidebar.tsx
  • features/projects/components/CreationFlow/Steps/BasicInfo.tsx
  • features/projects/components/CreationFlow/Steps/CampaignDetails.tsx
  • features/projects/components/CreationFlow/Steps/ProjectDetails.tsx
  • features/projects/components/CreationFlow/Steps/ReviewStep.tsx
  • features/projects/components/CreationFlow/Steps/TeamInfo.tsx
  • features/projects/hooks/use-project-creation.ts
  • features/projects/hooks/use-project-queries.ts
  • features/projects/types/index.ts
  • next.config.ts
  • package.json
  • sentry.edge.config.ts
  • sentry.server.config.ts
✅ Files skipped from review due to trivial changes (4)
  • package.json
  • .gitignore
  • features/projects/components/CreationFlow/Steps/TeamInfo.tsx
  • features/projects/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • features/projects/components/CreationFlow/Steps/ProjectDetails.tsx
  • features/projects/components/CreationFlow/Steps/ReviewStep.tsx
  • features/projects/components/CreationFlow/Steps/CampaignDetails.tsx

Comment on lines +12 to +16
export function GET() {
Sentry.logger.info('Sentry example API called');
throw new SentryExampleAPIError(
'This error is raised on the backend called by the example page.'
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not expose an always-throwing public API route in production.

On Line 12-16, every request intentionally throws. This can be spammed and will inflate error volume/billing in production.

🔧 Suggested guard
 export function GET() {
+  if (process.env.NODE_ENV === 'production') {
+    return new Response('Not Found', { status: 404 });
+  }
+
   Sentry.logger.info('Sentry example API called');
   throw new SentryExampleAPIError(
     'This error is raised on the backend called by the example page.'
   );
 }
📝 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
export function GET() {
Sentry.logger.info('Sentry example API called');
throw new SentryExampleAPIError(
'This error is raised on the backend called by the example page.'
);
export function GET() {
if (process.env.NODE_ENV === 'production') {
return new Response('Not Found', { status: 404 });
}
Sentry.logger.info('Sentry example API called');
throw new SentryExampleAPIError(
'This error is raised on the backend called by the example page.'
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/sentry-example-api/route.ts` around lines 12 - 16, The GET route
currently always throws a SentryExampleAPIError and logs via Sentry.logger,
which will generate errors in production; update the GET handler to guard the
throw by checking the environment or an explicit feature flag (e.g.,
process.env.NODE_ENV !== 'production' or a NEXT_PUBLIC feature flag) or require
an authenticated/dev-only header before throwing, and otherwise return a safe
non-error response (e.g., 200/404) so that SentryExampleAPIError is only raised
in development; keep references to GET, SentryExampleAPIError and Sentry.logger
when locating and modifying the handler.

Comment on lines +4 to +5
import Head from 'next/head';
import { useEffect, useState } from 'react';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Next.js App Router (v15+ / v16), is next/headsupported insideapp/**/page.tsx, or should metadata be defined via export const metadata/generateMetadata in server components?

💡 Result:

In Next.js App Router (v15+ / v16), next/head is not supported inside app//page.tsx. Metadata should be defined via export const metadata for static metadata or generateMetadata function for dynamic metadata in server components.

Citations:


🏁 Script executed:

cat -n app/sentry-example-page/page.tsx

Repository: boundlessfi/boundless

Length of output: 8625


Refactor component to use metadata APIs instead of next/head in App Router.

Line 4 imports next/head and lines 29-32 use the Head component, which is unsupported in App Router pages. Replace with metadata APIs (export const metadata or generateMetadata) exported from a server component.

However, this file is a client component ('use client' on line 1) with state and event handlers, which prevents exporting metadata directly. Split this into a server component wrapper (for metadata) and a client component (for interactive features):

  • Create a new server component to export metadata and render the client component
  • Move interactive logic (state, event handlers, useEffect) to the client component
Current usage
<Head>
  <title>sentry-example-page</title>
  <meta name='description' content='Test Sentry for your Next.js app!' />
</Head>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/sentry-example-page/page.tsx` around lines 4 - 5, The page currently
imports Head and uses it inside the client component (import Head from
'next/head' and the JSX block that renders <Head>), which is unsupported in App
Router; refactor by creating a server wrapper that exports metadata (export
const metadata or generateMetadata) and renders a separate client component for
interactive logic: remove the Head import from app/sentry-example-page/page.tsx,
add a new server component (e.g., SentryExamplePageServer) that exports metadata
with title/description and renders the client component (e.g.,
SentryExamplePageClient), and move all state, useEffect and event handlers into
the client component so the server file only handles metadata and mounts the
client.

Comment on lines +67 to +82
<Link
href={`/projects/create?id=${draft.id}`}
className='flex flex-1 items-center gap-3 overflow-hidden outline-none'
>
<div className='group-hover:border-primary/20 group-hover:bg-primary/5 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-white/5 bg-white/3 transition-all'>
<FileText className='group-hover:text-primary h-4 w-4 text-white/20 transition-colors' />
</div>
<div className='flex min-w-0 flex-1 flex-col'>
<span className='truncate text-sm font-bold text-white/60 transition-colors group-hover:text-white'>
{draft.title || 'Untitled Project'}
</span>
<span className='text-[10px] font-black tracking-wider text-white/20 uppercase'>
{draft.isCampaign ? 'Crowdfunding' : 'Standard'}
</span>
</div>
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the hover border non-interactive.

The absolutely positioned div on Line 118 sits on top of the draft row, so it can eat clicks meant for the Link on Lines 67-82. That leaves the trash button usable via z-10, but the main “continue editing” action can become non-clickable.

🔧 Suggested change
-              <div className='absolute inset-0 rounded-2xl border border-transparent transition-all group-hover:border-white/5'></div>
+              <div className='pointer-events-none absolute inset-0 rounded-2xl border border-transparent transition-all group-hover:border-white/5'></div>

Also applies to: 118-118

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationSidebar.tsx` around lines
67 - 82, The absolutely-positioned hover border overlay that sits above the
draft row is intercepting clicks meant for the Link (the continue-editing
action) in CreationSidebar; make that overlay non-interactive by adding
pointer-events-none to the overlay element's class list, and ensure the trash
button keeps pointer-events-auto (and its z-index) so it remains clickable;
update the overlay element (the absolutely positioned div that draws the hover
border) and the trash button element accordingly in CreationSidebar.tsx so the
Link retains clickability.

Comment on lines +85 to +88
<AlertDialogTrigger asChild>
<button className='z-10 rounded-lg p-2 text-white/20 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500/10 hover:text-red-500'>
<Trash2 className='h-3.5 w-3.5' />
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessible names to the icon-only controls.

The delete button and mobile menu button have no accessible label, so assistive tech will announce them as unlabeled buttons. Add an aria-label or visually hidden text for both actions.

Also applies to: 181-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/components/CreationFlow/CreationSidebar.tsx` around lines
85 - 88, The icon-only delete and mobile-menu buttons lack accessible names;
update the button inside AlertDialogTrigger (the Trash2 icon button) and the
mobile menu button (the icon-only button around lines ~181-183) to include a
descriptive accessible name—either add an aria-label (e.g. aria-label="Delete
project" and aria-label="Open project menu") or include visually hidden text
inside the button—so assistive technology announces their purpose; ensure labels
are concise, descriptive and match the action.

Comment on lines +113 to +127
const payload: Record<string, unknown> = {
title: data.title || data.projectName || undefined,
tagline: data.tagline || undefined,
category: data.category || undefined,
description: data.description || undefined,
summary: data.summary || undefined,
vision: data.vision || undefined,
details: data.details || undefined,
banner: data.bannerUrl || undefined,
logo: data.logoUrl || undefined,
githubUrl: data.githubUrl || undefined,
gitlabUrl: data.gitlabUrl || undefined,
bitbucketUrl: data.bitbucketUrl || undefined,
projectWebsite: data.websiteUrl || data.projectWebsite || undefined,
demoVideo: data.demoVideoUrl || data.demoVideo || undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Serialize from the edited fields, not the stale aliases.

After hydration you keep both title/projectName, projectWebsite/websiteUrl, and demoVideo/demoVideoUrl populated, but the form updates only the latter fields. Because toApiPayload still prefers the old alias first, renames and clears on those inputs are silently lost on the next save.

🔧 Suggested change
-    title: data.title || data.projectName || undefined,
+    title: data.projectName ?? data.title ?? undefined,
...
-    projectWebsite: data.websiteUrl || data.projectWebsite || undefined,
-    demoVideo: data.demoVideoUrl || data.demoVideo || undefined,
+    projectWebsite: data.websiteUrl ?? data.projectWebsite ?? undefined,
+    demoVideo: data.demoVideoUrl ?? data.demoVideo ?? undefined,

Also applies to: 279-294

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/hooks/use-project-creation.ts` around lines 113 - 127, The
payload currently prefers stale aliases for some fields (e.g., title uses
data.title before data.projectName) causing edited values to be lost; update the
creation in use-project-creation.ts so the form-edited fields are preferred
first (use data.projectName || data.title for title, data.websiteUrl ||
data.projectWebsite for projectWebsite (already correct), and data.demoVideoUrl
|| data.demoVideo for demoVideo), and apply the same ordering fix to the
duplicate block mentioned (lines ~279-294) so toApiPayload always serializes
from the edited field names (data.projectName, data.websiteUrl,
data.demoVideoUrl) before their older aliases.

Comment on lines +460 to +465
const onDeleteDraft = (id: string) => {
// We haven't built a delete mutation yet, but we can add one or invalidate.
// Since we're lists drafts, ideally we have a DELETE hook.
// For now, let's assume Sidebar handles it or we'll add it to queries later.
console.log('Delete draft', id);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t expose draft deletion until it actually deletes.

CreationSidebar calls this after a confirmation dialog, but the handler only logs. With no delete draft API wired in features/projects/api/index.ts, users get a destructive flow that can never succeed. Either add the mutation first or remove the trash affordance for now. This log will also trip the same console rule in CI.

Do you want me to sketch the query/mutation wiring once the backend delete endpoint is available?
Based on learnings: "Always ensure final code is fully functional with no placeholders, TODOs, or missing parts".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/hooks/use-project-creation.ts` around lines 460 - 465, The
onDeleteDraft handler in use-project-creation.ts currently only logs and is
wired to CreationSidebar after confirmation, which exposes a non-functional
destructive action; either implement a real delete mutation in
features/projects/api/index.ts (e.g., add deleteDraft API/mutation, export
deleteDraft function) and update onDeleteDraft to call that mutation and
invalidate or refetch the drafts list (or use the query-client to remove the
draft), or remove/hide the trash affordance in CreationSidebar until the API
exists; update onDeleteDraft to call the new deleteDraft function (or remove the
handler) and ensure no console.log remains so CI console rules aren’t tripped.

Comment on lines +51 to +52
mine: (params?: GetMyProjectsParams) => ['projects', 'me', params] as const,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In TanStack Query v5, does invalidateQueries({ queryKey: ['projects', 'me', undefined] }) invalidate a query keyed as ['projects', 'me', { status: 'IDEA' }]? How does partial array key matching work when the invalidation key includes an extra undefined element?

💡 Result:

No, invalidateQueries({ queryKey: ['projects', 'me', undefined] }) does not invalidate a query keyed as ['projects', 'me', { status: 'IDEA' }]. TanStack Query v5 uses prefix matching for partial query key matching in invalidateQueries. A filter key matches a query key if the filter key is a strict prefix of the query key—meaning the filter key must be shorter or equal in length, and every element must match exactly up to the filter's length. Here: - Filter key: ['projects', 'me', undefined] (length 3) - Query key: ['projects', 'me', { status: 'IDLE' }] (length 3) Both have length 3, so it's an exact match check: - Index 0: 'projects' === 'projects' ✅ - Index 1: 'me' === 'me' ✅ - Index 2: undefined === { status: 'IDEA' } ❌ (undefined !== {}) Since the third elements don't match exactly, there's no match. If the filter were ['projects', 'me'] (length 2), it would match because: - Index 0 & 1 match ✅ - Filter is shorter, so it's a valid prefix ✅ undefined in the filter acts as an exact value match at that position, not a wildcard. Array order and element equality matter strictly (using ===). This behavior is confirmed across official docs and GitHub discussions: - Prefix matching requires the filter to be a prefix (shorter/equal length with exact element matches). - Examples show ['todos'] matches ['todos', {page:1}] but not vice versa. - undefined vs object mismatch fails exact position matching, as seen in related issues (#3741, #9497).

Citations:


🏁 Script executed:

# Check the current implementation of projectKeys and invalidation calls
cat -n features/projects/hooks/use-project-queries.ts | head -60

Repository: boundlessfi/boundless

Length of output: 2323


🏁 Script executed:

# Check the invalidation calls at the mentioned lines
cat -n features/projects/hooks/use-project-queries.ts | sed -n '155,165p;178,188p;200,210p'

Repository: boundlessfi/boundless

Length of output: 1365


🏁 Script executed:

# Verify how useMyProjects is called with parameters
rg "useMyProjects\(" features/projects/hooks/use-project-creation.ts -A 2 -B 2

Repository: boundlessfi/boundless

Length of output: 208


Invalidate owner-project queries with a prefix-only key.

projectKeys.mine() with no arguments produces ['projects', 'me', undefined], which does not match cached queries with parameters like ['projects', 'me', { status: 'IDEA' }] due to TanStack Query v5's exact position matching in prefix filters. This leaves the sidebar draft list stale after create/update/publish operations.

Add a separate prefix-only key and use it for invalidations:

🔧 Suggested change
 export const projectKeys = {
   all: ['projects'] as const,
+  mineAll: () => ['projects', 'me'] as const,
   list: () => ['projects', 'list'] as const,
   search: (query: string) => ['projects', 'search', query] as const,
   featured: () => ['projects', 'featured'] as const,
   mine: (params?: GetMyProjectsParams) => ['projects', 'me', params] as const,
   myProject: (id: string) => ['projects', 'me', id] as const,
   detail: (slug: string) => ['projects', 'detail', slug] as const,
 } as const;
-      queryClient.invalidateQueries({ queryKey: projectKeys.mine() });
+      queryClient.invalidateQueries({ queryKey: projectKeys.mineAll() });

Also applies to lines 182 and 204.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@features/projects/hooks/use-project-queries.ts` around lines 51 - 52,
projectKeys.mine currently returns ['projects','me', params] so calling
projectKeys.mine() yields ['projects','me', undefined] which won't prefix-match
parameterized caches; add a separate prefix-only key factory (e.g.,
projectKeys.mineList = () => ['projects','me'] as const or projectKeys.mine.base
= () => ['projects','me'] as const) and use that prefix-only key when
invalidating or invalidatingQueries (replace places that call projectKeys.mine()
for prefix invalidation with projectKeys.mineList()), and update the other two
usages of projectKeys.mine mentioned in the review to use this new prefix key so
TanStack Query v5 prefix filters match parameterized cached queries.

Comment on lines +115 to +117
org: process.env.SENTRY_ORG ?? 'collins-ki',

project: process.env.SENTRY_PROJECT ?? 'javascript-nextjs',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For @sentry/nextjs v10.x, what is the recommended production setup to prevent sourcemap upload when SENTRY_ORG or SENTRY_PROJECT is unset?

💡 Result:

The recommended production setup for @sentry/nextjs v10.x to prevent sourcemap upload when SENTRY_ORG or SENTRY_PROJECT is unset is to simply not provide the org and project options in the withSentryConfig wrapper in next.config.ts/(mjs). The Sentry Next.js SDK requires org and project (or equivalent environment variables) to be explicitly configured for source map uploads to occur during next build. Without these, the SDK will not attempt to upload source maps to Sentry, as confirmed by official documentation requiring manual configuration of these values and troubleshooting notes emphasizing the need for SENTRY_AUTH_TOKEN alongside them. Issues and GitHub discussions further indicate that uploads are skipped with warnings like "No auth token provided. Will not upload source maps" when credentials are missing. Do not wrap your Next.js config with withSentryConfig, or provide an empty/incomplete config: typescript // next.config.ts - Recommended: Omit withSentryConfig entirely if no Sentry integration needed const nextConfig = { // your config }; export default nextConfig; Source maps will still be generated by Next.js for local debugging (controlled by productionBrowserSourceMaps: false if desired), but no upload will occur without Sentry configuration. If using Sentry for error reporting without sourcemaps, init the SDK separately in sentry.client.config.ts/sentry.server.config.ts with just dsn. For explicit disable when config is present (v10.x supports): typescript export default withSentryConfig(nextConfig, { sourcemaps: { disable: true, }, }); This fully disables source map functionality while allowing other Sentry features.

Citations:


🏁 Script executed:

# Find and read the next.config.ts file
find . -name "next.config.ts" -o -name "next.config.js" | head -5

Repository: boundlessfi/boundless

Length of output: 81


🏁 Script executed:

# Once we locate it, read the relevant section around lines 115-117
if [ -f "next.config.ts" ]; then
  echo "=== Lines 110-125 of next.config.ts ==="
  sed -n '110,125p' next.config.ts
fi

Repository: boundlessfi/boundless

Length of output: 651


🏁 Script executed:

# Also check if withSentryConfig is being used and how
if [ -f "next.config.ts" ]; then
  echo "=== Full next.config.ts to see Sentry config structure ==="
  wc -l next.config.ts
  echo "=== Searching for Sentry-related config ==="
  rg "withSentryConfig|sentry|org|project" next.config.ts -n -A 2 -B 2
fi

Repository: boundlessfi/boundless

Length of output: 1221


🏁 Script executed:

# Check environment variable configuration and CI setup
rg "SENTRY_ORG|SENTRY_PROJECT|SENTRY_AUTH_TOKEN" . -n --max-count=20

Repository: boundlessfi/boundless

Length of output: 387


🏁 Script executed:

# Check .env files and CI configuration
find . -name ".env*" -o -name "*.yml" -o -name "*.yaml" | grep -E "(\.env|workflows|github|ci)" | head -10

Repository: boundlessfi/boundless

Length of output: 194


Remove hardcoded Sentry org/project fallbacks to prevent source maps uploading to the wrong project.

Lines 115 and 117 use fallback values ('collins-ki' / 'javascript-nextjs') when SENTRY_ORG or SENTRY_PROJECT environment variables are unset. In development or when env vars are missing, source maps will silently upload to these hardcoded projects instead of failing safely. Per Sentry v10.x documentation, omitting org and project values prevents upload attempts when credentials are absent; the build simply skips source map uploads with a warning instead. This is safer than accidentally publishing source maps to the wrong organization.

🔧 Suggested change
-  org: process.env.SENTRY_ORG ?? 'collins-ki',
+  org: process.env.SENTRY_ORG,

-  project: process.env.SENTRY_PROJECT ?? 'javascript-nextjs',
+  project: process.env.SENTRY_PROJECT,
📝 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
org: process.env.SENTRY_ORG ?? 'collins-ki',
project: process.env.SENTRY_PROJECT ?? 'javascript-nextjs',
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.ts` around lines 115 - 117, The Sentry config currently uses
hardcoded fallbacks for org/project which causes accidental uploads; update the
Sentry options so that org and project do not default to 'collins-ki' or
'javascript-nextjs' but instead are omitted/undefined when SENTRY_ORG or
SENTRY_PROJECT are not set (reference the org and project properties in
next.config.ts and the environment vars SENTRY_ORG and SENTRY_PROJECT) so that
source map uploads are skipped when credentials are absent.

Comment on lines +14 to +22
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,

// Enable logs to be sent to Sentry
enableLogs: true,

// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In @sentry/nextjs, what production defaults are recommended for tracesSampleRate, enableLogs, and sendDefaultPii?

💡 Result:

In @sentry/nextjs, the production defaults/recommendations are: - tracesSampleRate: 0.1 (10%) - Official docs recommend starting with 10-20% in production, often shown as 0.1 in code examples for prod (e.g., process.env.NODE_ENV === "production" ? 0.1 : 1.0). Default is unset (effectively 0, no tracing) unless configured; must be explicitly set to enable tracing. - enableLogs: false - Default is false; explicitly set to true to enable sending logs to Sentry. Docs show it as an optional feature to enable. - sendDefaultPii: false - Default is false; set to true to enable sending default PII (e.g., IP addresses, request headers). Recent wizard setups (2025+) include true in generated configs, but SDK default remains false; use cautiously in server configs due to potential ISR issues.

Citations:


🏁 Script executed:

fd -t f sentry.edge.config.ts

Repository: boundlessfi/boundless

Length of output: 86


🏁 Script executed:

cat -n sentry.edge.config.ts

Repository: boundlessfi/boundless

Length of output: 1167


Sentry edge config has overly permissive production settings.

Lines 15, 18, and 22 enable full tracing (100% sample rate), all logs, and default PII transmission for every edge request. This is high-cost and high-risk in production. Gate these by environment and disable PII by default:

  • tracesSampleRate: 1 should be reduced to 0.1 (10%) in production
  • enableLogs: true should be false in production
  • sendDefaultPii: true should be false (default safe value)
Suggested changes
-    tracesSampleRate: 1,
+    tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1,

-    enableLogs: true,
+    enableLogs: process.env.NODE_ENV !== 'production',

-    sendDefaultPii: true,
+    sendDefaultPii: false,
📝 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
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Enable logs to be sent to Sentry
enableLogs: true,
// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: true,
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1,
// Enable logs to be sent to Sentry
enableLogs: process.env.NODE_ENV !== 'production',
// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sentry.edge.config.ts` around lines 14 - 22, The Sentry edge config currently
uses unsafe production defaults: set tracesSampleRate, enableLogs, and
sendDefaultPii based on environment rather than hardcoding 1/true/true; detect
production (e.g., process.env.NODE_ENV === 'production' or an existing isProd
helper) and use tracesSampleRate: 0.1, enableLogs: false, sendDefaultPii: false
in production, while keeping higher sampling/logging for non-production. Update
the config object keys tracesSampleRate, enableLogs, and sendDefaultPii
accordingly so production is gated by the environment check.

Comment on lines +13 to +21
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,

// Enable logs to be sent to Sentry
enableLogs: true,

// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For Sentry's Next.js server SDK, what sampling and PII defaults are recommended for production deployments?

💡 Result:

For Sentry's Next.js server SDK (configured in sentry.server.config.ts), the recommended defaults for production deployments are: Sampling: - sampleRate: 1.0 (default; all errors sent) - tracesSampleRate: 0.1 (10%, as per official setup guides and wizard examples; adjust based on volume) PII: - sendDefaultPii: false (SDK default for privacy); wizard sets to true, but docs recommend scrubbing sensitive data with beforeSend hooks and caution against sending PII. Use server-side scrubbing or SDK filters for production.

Citations:


🏁 Script executed:

find . -name "sentry.server.config.ts" -o -name "sentry.server.config.js" | head -20

Repository: boundlessfi/boundless

Length of output: 90


🏁 Script executed:

cat -n ./sentry.server.config.ts

Repository: boundlessfi/boundless

Length of output: 1005


Server Sentry config should not run at max sampling + PII by default.

Lines 14, 17, and 21 enable costly/high-volume telemetry and PII collection for all server requests. Sentry's official documentation recommends tracesSampleRate: 0.1 for production deployments, enableLogs should be disabled in production, and sendDefaultPii should default to false for privacy and compliance.

🔧 Suggested baseline
-    tracesSampleRate: 1,
+    tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1,

-    enableLogs: true,
+    enableLogs: process.env.NODE_ENV !== 'production',

-    sendDefaultPii: true,
+    sendDefaultPii: false,
📝 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
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: 1,
// Enable logs to be sent to Sentry
enableLogs: true,
// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: true,
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1,
// Enable logs to be sent to Sentry
enableLogs: process.env.NODE_ENV !== 'production',
// Enable sending user PII (Personally Identifiable Information)
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
sendDefaultPii: false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sentry.server.config.ts` around lines 13 - 21, The Sentry server config
currently enables max sampling and PII by default (tracesSampleRate: 1,
enableLogs: true, sendDefaultPii: true); change these defaults to safe values
and make them environment-configurable: set tracesSampleRate to a lower baseline
(e.g., 0.1) or read from an env var like SENTRY_TRACES_SAMPLE_RATE, set
enableLogs to false in production (or toggle via SENTRY_ENABLE_LOGS), and set
sendDefaultPii to false by default (enable via SENTRY_SEND_DEFAULT_PII only when
explicitly required); update the code that sets tracesSampleRate, enableLogs,
and sendDefaultPii to read and parse these env vars (falling back to the safer
defaults) so production doesn't run with high-volume telemetry or PII on by
default.

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