[PULSE-36] Funnels UI - builder and report #8

Merged
uz1mani merged 12 commits from staging into main 2026-02-04 23:16:04 +00:00
uz1mani commented 2026-02-04 22:02:08 +00:00 (Migrated from github.com)

Work Item

PULSE-36

Summary

  • Add Funnels UI: list funnels, create funnel with configurable steps (path + match type), and funnel report (bar chart + table) with date range.
  • Integrate with backend funnel API; follow Ciphera branding and existing Pulse dashboard patterns.
  • Fix React hooks order on report page; use theme-aware chart and shared button/radius styles.

Changes

  • API client: lib/api/funnels.ts – list, get, create, update, delete funnels; get funnel stats (optional from/to).
  • List: app/sites/[id]/funnels/page.tsx – list funnels, empty state, Create Funnel (btn-primary), delete with confirm.
  • Create: app/sites/[id]/funnels/new/page.tsx – form (name, description, steps: name/path/type exact|contains|regex), add/remove steps, Cancel (btn-secondary), submit (btn-primary).
  • Report: app/sites/[id]/funnels/[funnelId]/page.tsx – funnel name/description, date range, theme-aware bar chart, stats table (visitors, drop-off %, conversion %), delete; hooks (useTheme, useMemo) before early return (fix React #310).
  • Nav: app/sites/[id]/page.tsx – Funnels button on site dashboard; back links and actions use btn-primary / btn-secondary and rounded-xl.
  • Consistency: Chart uses useTheme + CHART_COLORS_LIGHT/DARK; step pills rounded-lg; empty state aligned with GoalStats.

Test Plan

[ ] From site dashboard, open Funnels; list and empty state render.
[ ] Create funnel with multiple steps (exact, contains, regex); save and see it in list.
[ ] Open funnel report; date range changes update chart and table; drop-off/conversion values are sensible.
[ ] Delete funnel from list and from report; confirm flow works.
[ ] UI uses Ciphera buttons/radii and chart respects light/dark theme.

## Work Item PULSE-36 ## Summary - Add Funnels UI: list funnels, create funnel with configurable steps (path + match type), and funnel report (bar chart + table) with date range. - Integrate with backend funnel API; follow Ciphera branding and existing Pulse dashboard patterns. - Fix React hooks order on report page; use theme-aware chart and shared button/radius styles. ## Changes - **API client:** `lib/api/funnels.ts` – list, get, create, update, delete funnels; get funnel stats (optional `from`/`to`). - **List:** `app/sites/[id]/funnels/page.tsx` – list funnels, empty state, Create Funnel (btn-primary), delete with confirm. - **Create:** `app/sites/[id]/funnels/new/page.tsx` – form (name, description, steps: name/path/type exact|contains|regex), add/remove steps, Cancel (btn-secondary), submit (btn-primary). - **Report:** `app/sites/[id]/funnels/[funnelId]/page.tsx` – funnel name/description, date range, theme-aware bar chart, stats table (visitors, drop-off %, conversion %), delete; hooks (useTheme, useMemo) before early return (fix React #310). - **Nav:** `app/sites/[id]/page.tsx` – Funnels button on site dashboard; back links and actions use `btn-primary` / `btn-secondary` and `rounded-xl`. - **Consistency:** Chart uses `useTheme` + CHART_COLORS_LIGHT/DARK; step pills `rounded-lg`; empty state aligned with GoalStats. ## Test Plan [ ] From site dashboard, open Funnels; list and empty state render. [ ] Create funnel with multiple steps (exact, contains, regex); save and see it in list. [ ] Open funnel report; date range changes update chart and table; drop-off/conversion values are sensible. [ ] Delete funnel from list and from report; confirm flow works. [ ] UI uses Ciphera buttons/radii and chart respects light/dark theme.
greptile-apps[bot] commented 2026-02-04 22:07:39 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

This PR implements a comprehensive funnels feature for the Pulse analytics dashboard, adding the ability to track user journeys through multi-step conversion paths.

Key additions:

  • API client (lib/api/funnels.ts) with full CRUD operations and RFC3339 date normalization for UTC-aware date range queries
  • Funnels list page with empty state and delete confirmation
  • Funnel creation form with validation for step names, paths, and regex patterns (including client-side regex syntax validation)
  • Funnel report page with theme-aware Recharts visualization, date range controls (7/30/custom days), and granular error handling for 404/403/500 responses
  • Integration into site dashboard with navigation button

Code quality highlights:

  • All React hooks issues from previous review threads have been resolved (proper useCallback usage, correct dependency arrays, hooks before early returns)
  • Comprehensive form validation prevents empty names/paths and invalid regex patterns
  • Error states properly distinguish between "not found", "access denied", and transient failures with appropriate retry UX
  • Date range selection uses stable datePreset state to avoid reference comparison issues
  • Chart respects theme (light/dark) and uses brand colors with progressive opacity for multiple steps
  • Unused imports have been cleaned up

All previously flagged issues from review threads have been addressed. The implementation follows existing Pulse patterns and integrates cleanly with the Ciphera branding system.

Confidence Score: 5/5

  • This PR is safe to merge with no critical issues
  • All previously identified issues from review threads have been resolved. The code demonstrates proper React patterns with correct hook usage and dependency management. Form validation is comprehensive covering edge cases like empty strings, whitespace, and invalid regex. Error handling distinguishes between different failure modes with appropriate UX. The implementation follows established patterns in the codebase and integrates cleanly with existing infrastructure.
  • No files require special attention

Important Files Changed

Filename Overview
lib/api/funnels.ts Added funnel API client with full CRUD operations and date normalization for stats queries
app/sites/[id]/funnels/page.tsx Added funnels list page with proper useCallback hooks, empty state, and delete functionality
app/sites/[id]/funnels/new/page.tsx Added funnel creation form with comprehensive validation for step names, paths, and regex patterns
app/sites/[id]/funnels/[funnelId]/page.tsx Added funnel report page with theme-aware charts, date range controls, and proper error handling for 404/403/500

Sequence Diagram

sequenceDiagram
    participant User
    participant Dashboard
    participant FunnelsList
    participant CreateForm
    participant ReportPage
    participant API

    User->>Dashboard: Click Funnels button
    Dashboard->>FunnelsList: Navigate to list
    
    FunnelsList->>API: GET funnels
    API-->>FunnelsList: Return funnels array
    
    alt No funnels
        FunnelsList->>User: Show empty state
    else Has funnels
        FunnelsList->>User: Show funnel cards
    end
    
    User->>CreateForm: Click create button
    CreateForm->>User: Show form
    
    User->>CreateForm: Fill and submit
    
    CreateForm->>CreateForm: Validate inputs
    
    alt Invalid inputs
        CreateForm->>User: Show error toast
    else Valid inputs
        CreateForm->>API: POST create funnel
        API-->>CreateForm: Return created funnel
        CreateForm->>FunnelsList: Navigate back
        FunnelsList->>User: Show success
    end
    
    User->>ReportPage: Click funnel
    ReportPage->>API: GET funnel details
    ReportPage->>API: GET stats with dates
    
    alt 404 error
        API-->>ReportPage: Not found
        ReportPage->>User: Show not found
    else 403 error
        API-->>ReportPage: Forbidden
        ReportPage->>User: Show access denied
    else 500 error
        API-->>ReportPage: Server error
        ReportPage->>User: Show error with retry
    else Success
        API-->>ReportPage: Return data
        ReportPage->>ReportPage: Generate chart
        ReportPage->>User: Display visualization
    end
    
    User->>ReportPage: Change date range
    ReportPage->>API: GET updated stats
    API-->>ReportPage: Return new data
    ReportPage->>User: Update display
    
    User->>ReportPage: Delete funnel
    ReportPage->>User: Confirm deletion
    User->>ReportPage: Confirm
    ReportPage->>API: DELETE funnel
    API-->>ReportPage: Success
    ReportPage->>FunnelsList: Navigate back
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR implements a comprehensive funnels feature for the Pulse analytics dashboard, adding the ability to track user journeys through multi-step conversion paths. **Key additions:** - API client (`lib/api/funnels.ts`) with full CRUD operations and RFC3339 date normalization for UTC-aware date range queries - Funnels list page with empty state and delete confirmation - Funnel creation form with validation for step names, paths, and regex patterns (including client-side regex syntax validation) - Funnel report page with theme-aware Recharts visualization, date range controls (7/30/custom days), and granular error handling for 404/403/500 responses - Integration into site dashboard with navigation button **Code quality highlights:** - All React hooks issues from previous review threads have been resolved (proper `useCallback` usage, correct dependency arrays, hooks before early returns) - Comprehensive form validation prevents empty names/paths and invalid regex patterns - Error states properly distinguish between "not found", "access denied", and transient failures with appropriate retry UX - Date range selection uses stable `datePreset` state to avoid reference comparison issues - Chart respects theme (light/dark) and uses brand colors with progressive opacity for multiple steps - Unused imports have been cleaned up All previously flagged issues from review threads have been addressed. The implementation follows existing Pulse patterns and integrates cleanly with the Ciphera branding system. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with no critical issues - All previously identified issues from review threads have been resolved. The code demonstrates proper React patterns with correct hook usage and dependency management. Form validation is comprehensive covering edge cases like empty strings, whitespace, and invalid regex. Error handling distinguishes between different failure modes with appropriate UX. The implementation follows established patterns in the codebase and integrates cleanly with existing infrastructure. - No files require special attention <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | lib/api/funnels.ts | Added funnel API client with full CRUD operations and date normalization for stats queries | | app/sites/[id]/funnels/page.tsx | Added funnels list page with proper useCallback hooks, empty state, and delete functionality | | app/sites/[id]/funnels/new/page.tsx | Added funnel creation form with comprehensive validation for step names, paths, and regex patterns | | app/sites/[id]/funnels/[funnelId]/page.tsx | Added funnel report page with theme-aware charts, date range controls, and proper error handling for 404/403/500 | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant Dashboard participant FunnelsList participant CreateForm participant ReportPage participant API User->>Dashboard: Click Funnels button Dashboard->>FunnelsList: Navigate to list FunnelsList->>API: GET funnels API-->>FunnelsList: Return funnels array alt No funnels FunnelsList->>User: Show empty state else Has funnels FunnelsList->>User: Show funnel cards end User->>CreateForm: Click create button CreateForm->>User: Show form User->>CreateForm: Fill and submit CreateForm->>CreateForm: Validate inputs alt Invalid inputs CreateForm->>User: Show error toast else Valid inputs CreateForm->>API: POST create funnel API-->>CreateForm: Return created funnel CreateForm->>FunnelsList: Navigate back FunnelsList->>User: Show success end User->>ReportPage: Click funnel ReportPage->>API: GET funnel details ReportPage->>API: GET stats with dates alt 404 error API-->>ReportPage: Not found ReportPage->>User: Show not found else 403 error API-->>ReportPage: Forbidden ReportPage->>User: Show access denied else 500 error API-->>ReportPage: Server error ReportPage->>User: Show error with retry else Success API-->>ReportPage: Return data ReportPage->>ReportPage: Generate chart ReportPage->>User: Display visualization end User->>ReportPage: Change date range ReportPage->>API: GET updated stats API-->>ReportPage: Return new data ReportPage->>User: Update display User->>ReportPage: Delete funnel ReportPage->>User: Confirm deletion User->>ReportPage: Confirm ReportPage->>API: DELETE funnel API-->>ReportPage: Success ReportPage->>FunnelsList: Navigate back ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 22:07:44 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>7 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +1,331 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:07:42 +00:00

ReferenceError on mount

useEffect calls loadData() before loadData is defined (const loadData = async () => { ... }), which throws ReferenceError: Cannot access 'loadData' before initialization on first render. Move the loadData definition above the useEffect (or switch to a hoisted function loadData() {}) so the page doesn’t crash.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 46:52

Comment:
**ReferenceError on mount**

`useEffect` calls `loadData()` before `loadData` is defined (`const loadData = async () => { ... }`), which throws `ReferenceError: Cannot access 'loadData' before initialization` on first render. Move the `loadData` definition above the `useEffect` (or switch to a hoisted `function loadData() {}`) so the page doesn’t crash.

How can I resolve this? If you propose a fix, please make it concise.
**ReferenceError on mount** `useEffect` calls `loadData()` before `loadData` is defined (`const loadData = async () => { ... }`), which throws `ReferenceError: Cannot access 'loadData' before initialization` on first render. Move the `loadData` definition above the `useEffect` (or switch to a hoisted `function loadData() {}`) so the page doesn’t crash. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 46:52 Comment: **ReferenceError on mount** `useEffect` calls `loadData()` before `loadData` is defined (`const loadData = async () => { ... }`), which throws `ReferenceError: Cannot access 'loadData' before initialization` on first render. Move the `loadData` definition above the `useEffect` (or switch to a hoisted `function loadData() {}`) so the page doesn’t crash. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,149 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:07:43 +00:00

ReferenceError on mount

Same pattern as the report page: useEffect calls loadFunnels() before const loadFunnels = async () => { ... } is initialized, so this page will throw ReferenceError: Cannot access 'loadFunnels' before initialization on mount. Define loadFunnels above the effect (or use a hoisted function declaration).

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/page.tsx
Line: 15:21

Comment:
**ReferenceError on mount**

Same pattern as the report page: `useEffect` calls `loadFunnels()` before `const loadFunnels = async () => { ... }` is initialized, so this page will throw `ReferenceError: Cannot access 'loadFunnels' before initialization` on mount. Define `loadFunnels` above the effect (or use a hoisted function declaration).

How can I resolve this? If you propose a fix, please make it concise.
**ReferenceError on mount** Same pattern as the report page: `useEffect` calls `loadFunnels()` before `const loadFunnels = async () => { ... }` is initialized, so this page will throw `ReferenceError: Cannot access 'loadFunnels' before initialization` on mount. Define `loadFunnels` above the effect (or use a hoisted function declaration). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/page.tsx Line: 15:21 Comment: **ReferenceError on mount** Same pattern as the report page: `useEffect` calls `loadFunnels()` before `const loadFunnels = async () => { ... }` is initialized, so this page will throw `ReferenceError: Cannot access 'loadFunnels' before initialization` on mount. Define `loadFunnels` above the effect (or use a hoisted function declaration). How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:17:00 +00:00
@@ -0,0 +1,149 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:17:00 +00:00

Change: The loadFunnels definition was moved above the useEffect that calls it.
Why: Same pattern as the report page: the effect was calling loadFunnels() before the const loadFunnels = async () => { ... } was initialized. Defining loadFunnels before the useEffect fixes the ReferenceError on mount.

Change: The loadFunnels definition was moved above the useEffect that calls it. Why: Same pattern as the report page: the effect was calling loadFunnels() before the const loadFunnels = async () => { ... } was initialized. Defining loadFunnels before the useEffect fixes the ReferenceError on mount.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:17:09 +00:00
@@ -0,0 +1,331 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:17:09 +00:00

Change: The loadData definition was moved above the useEffect that calls it.
Why: const loadData = async () => { ... } is not hoisted, so when the effect ran on the first render it was still in the temporal dead zone and threw “Cannot access 'loadData' before initialization”. Defining loadData before the useEffect ensures it exists when the effect runs.

Change: The loadData definition was moved above the useEffect that calls it. Why: const loadData = async () => { ... } is not hoisted, so when the effect ran on the first render it was still in the temporal dead zone and threw “Cannot access 'loadData' before initialization”. Defining loadData before the useEffect ensures it exists when the effect runs.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 22:21:47 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

<sub>4 files reviewed, 7 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +1,331 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:40 +00:00

Missing loadData in dependency array causes React exhaustive-deps warning and stale closure issues. Either add loadData to the dependency array or use useCallback to memoize loadData.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 50:52

Comment:
Missing `loadData` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadData` to the dependency array or use `useCallback` to memoize `loadData`.

How can I resolve this? If you propose a fix, please make it concise.
Missing `loadData` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadData` to the dependency array or use `useCallback` to memoize `loadData`. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 50:52 Comment: Missing `loadData` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadData` to the dependency array or use `useCallback` to memoize `loadData`. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:42 +00:00

Date comparison using string equality (dateRange.start === getDateRange(7).start) creates new Date objects on every render, causing unnecessary re-renders and always evaluating to false since objects are different references.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 132:143

Comment:
Date comparison using string equality (`dateRange.start === getDateRange(7).start`) creates new Date objects on every render, causing unnecessary re-renders and always evaluating to false since objects are different references.

How can I resolve this? If you propose a fix, please make it concise.
Date comparison using string equality (`dateRange.start === getDateRange(7).start`) creates new Date objects on every render, causing unnecessary re-renders and always evaluating to false since objects are different references. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 132:143 Comment: Date comparison using string equality (`dateRange.start === getDateRange(7).start`) creates new Date objects on every render, causing unnecessary re-renders and always evaluating to false since objects are different references. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:45 +00:00

Unused import useAuth - not referenced in the component.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 3:3

Comment:
Unused import `useAuth` - not referenced in the component.

How can I resolve this? If you propose a fix, please make it concise.
Unused import `useAuth` - not referenced in the component. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 3:3 Comment: Unused import `useAuth` - not referenced in the component. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:47 +00:00

Opacity calculation can go negative for funnels with more than 6 steps, causing render issues. Wrap in Math.max(0.1, ...) to prevent negative opacity.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 218:218

Comment:
Opacity calculation can go negative for funnels with more than 6 steps, causing render issues. Wrap in `Math.max(0.1, ...)` to prevent negative opacity.

How can I resolve this? If you propose a fix, please make it concise.
Opacity calculation can go negative for funnels with more than 6 steps, causing render issues. Wrap in `Math.max(0.1, ...)` to prevent negative opacity. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 218:218 Comment: Opacity calculation can go negative for funnels with more than 6 steps, causing render issues. Wrap in `Math.max(0.1, ...)` to prevent negative opacity. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,235 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:44 +00:00

Unused import useAuth - not referenced in the component.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 3:3

Comment:
Unused import `useAuth` - not referenced in the component.

How can I resolve this? If you propose a fix, please make it concise.
Unused import `useAuth` - not referenced in the component. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 3:3 Comment: Unused import `useAuth` - not referenced in the component. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,149 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:41 +00:00

Missing loadFunnels in dependency array causes React exhaustive-deps warning and stale closure issues. Either add loadFunnels to the dependency array or use useCallback to memoize loadFunnels.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/page.tsx
Line: 19:21

Comment:
Missing `loadFunnels` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadFunnels` to the dependency array or use `useCallback` to memoize `loadFunnels`.

How can I resolve this? If you propose a fix, please make it concise.
Missing `loadFunnels` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadFunnels` to the dependency array or use `useCallback` to memoize `loadFunnels`. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/page.tsx Line: 19:21 Comment: Missing `loadFunnels` in dependency array causes React exhaustive-deps warning and stale closure issues. Either add `loadFunnels` to the dependency array or use `useCallback` to memoize `loadFunnels`. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:21:44 +00:00

Unused variable user destructured from useAuth() - not referenced in the component.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/page.tsx
Line: 11:11

Comment:
Unused variable `user` destructured from `useAuth()` - not referenced in the component.

How can I resolve this? If you propose a fix, please make it concise.
Unused variable `user` destructured from `useAuth()` - not referenced in the component. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/page.tsx Line: 11:11 Comment: Unused variable `user` destructured from `useAuth()` - not referenced in the component. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:28:23 +00:00
@@ -0,0 +1,331 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:28:23 +00:00

Change: Replaced fillOpacity={1 - (index * 0.15)} with fillOpacity={Math.max(0.1, 1 - index * 0.15)}.
Why: For index ≥ 7, 1 - index * 0.15 becomes negative; capping with Math.max(0.1, ...) keeps opacity in a valid range and avoids render issues.

Change: Replaced fillOpacity={1 - (index * 0.15)} with fillOpacity={Math.max(0.1, 1 - index * 0.15)}. Why: For index ≥ 7, 1 - index * 0.15 becomes negative; capping with Math.max(0.1, ...) keeps opacity in a valid range and avoids render issues.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:28:33 +00:00
@@ -0,0 +1,331 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:28:33 +00:00

Change: Removed import { useAuth } from '@/lib/auth/context' from app/sites/[id]/funnels/[funnelId]/page.tsx.
Why: The report page doesn’t use useAuth.

Change: Removed import { useAuth } from '@/lib/auth/context' from app/sites/[id]/funnels/[funnelId]/page.tsx. Why: The report page doesn’t use useAuth.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:28:45 +00:00
@@ -0,0 +1,149 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:28:45 +00:00

Change: Removed useAuth() and its import from app/sites/[id]/funnels/page.tsx (the list page).
Why: Only user was destructured and it wasn’t used, so the whole hook and import were removed.

Change: Removed useAuth() and its import from app/sites/[id]/funnels/page.tsx (the list page). Why: Only user was destructured and it wasn’t used, so the whole hook and import were removed.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:28:56 +00:00
@@ -0,0 +1,235 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:28:56 +00:00

Change: Removed import { useAuth } from '@/lib/auth/context' from app/sites/[id]/funnels/new/page.tsx.
Why: The component doesn’t use useAuth.

Change: Removed import { useAuth } from '@/lib/auth/context' from app/sites/[id]/funnels/new/page.tsx. Why: The component doesn’t use useAuth.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:29:04 +00:00
@@ -0,0 +1,331 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:29:04 +00:00

Change: Introduced datePreset state: '7' | '30' | 'custom', default '30'. The Select uses value={datePreset}. In onChange: for '7'/'30' we call setDateRange(getDateRange(7)) / setDateRange(getDateRange(30)) and setDatePreset('7') / setDatePreset('30'); for 'custom' we only open the picker. In the DatePicker onApply we call setDatePreset('custom') when applying a custom range.
Why: We no longer call getDateRange(7) / getDateRange(30) on every render to derive the Select value; the value comes from datePreset, so no extra Date allocations or reference issues.

Change: Introduced datePreset state: '7' | '30' | 'custom', default '30'. The Select uses value={datePreset}. In onChange: for '7'/'30' we call setDateRange(getDateRange(7)) / setDateRange(getDateRange(30)) and setDatePreset('7') / setDatePreset('30'); for 'custom' we only open the picker. In the DatePicker onApply we call setDatePreset('custom') when applying a custom range. Why: We no longer call getDateRange(7) / getDateRange(30) on every render to derive the Select value; the value comes from datePreset, so no extra Date allocations or reference issues.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:29:14 +00:00
@@ -0,0 +1,149 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:29:13 +00:00

Change: Wrapped loadFunnels in useCallback with deps [siteId], and set the effect dependency array to [loadFunnels].
Why: Same as above: correct deps and no stale closure.

Change: Wrapped loadFunnels in useCallback with deps [siteId], and set the effect dependency array to [loadFunnels]. Why: Same as above: correct deps and no stale closure.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:29:24 +00:00
@@ -0,0 +1,331 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 22:29:23 +00:00

Change: Wrapped the loader in useCallback with deps [siteId, funnelId, dateRange], and made the effect depend only on [loadData].
Why: loadData is now stable per (siteId, funnelId, dateRange), so the effect runs when any of those change and satisfies exhaustive-deps without stale closures.

Change: Wrapped the loader in useCallback with deps [siteId, funnelId, dateRange], and made the effect depend only on [loadData]. Why: loadData is now stable per (siteId, funnelId, dateRange), so the effect runs when any of those change and satisfies exhaustive-deps without stale closures.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 22:35:39 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>7 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +1,331 @@
'use client'
import { useCallback, useEffect, useMemo, useState } from 'react'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:35:37 +00:00

Unused auth import

useAuth is imported here but never referenced, which will fail lint/typecheck in projects enforcing no-unused-vars. Remove the import to avoid CI failures.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 1:3

Comment:
**Unused auth import**

`useAuth` is imported here but never referenced, which will fail lint/typecheck in projects enforcing `no-unused-vars`. Remove the import to avoid CI failures.

How can I resolve this? If you propose a fix, please make it concise.
**Unused auth import** `useAuth` is imported here but never referenced, which will fail lint/typecheck in projects enforcing `no-unused-vars`. Remove the import to avoid CI failures. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 1:3 Comment: **Unused auth import** `useAuth` is imported here but never referenced, which will fail lint/typecheck in projects enforcing `no-unused-vars`. Remove the import to avoid CI failures. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,235 @@
'use client'
import { useState } from 'react'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:35:38 +00:00

Unused auth import

useAuth is imported but not used in this page, which will trip no-unused-vars/typecheck in many setups. Remove the import.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 1:3

Comment:
**Unused auth import**

`useAuth` is imported but not used in this page, which will trip `no-unused-vars`/typecheck in many setups. Remove the import.

How can I resolve this? If you propose a fix, please make it concise.
**Unused auth import** `useAuth` is imported but not used in this page, which will trip `no-unused-vars`/typecheck in many setups. Remove the import. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 1:3 Comment: **Unused auth import** `useAuth` is imported but not used in this page, which will trip `no-unused-vars`/typecheck in many setups. Remove the import. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:38:27 +00:00
@@ -0,0 +1,331 @@
'use client'
import { useCallback, useEffect, useMemo, useState } from 'react'
uz1mani (Migrated from github.com) commented 2026-02-04 22:38:27 +00:00

Already fixed.

Already fixed.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:38:32 +00:00
@@ -0,0 +1,235 @@
'use client'
import { useState } from 'react'
uz1mani (Migrated from github.com) commented 2026-02-04 22:38:31 +00:00

Already fixed.

Already fixed.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 22:45:44 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

<sub>3 files reviewed, 3 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +34,4 @@
}
const handleRemoveStep = (index: number) => {
if (steps.length <= 1) return
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:45:42 +00:00

Minimum step validation (≥1) only happens in UI. Backend may require at least 2 steps for a funnel to be meaningful. Verify backend requirements.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 26:27

Comment:
Minimum step validation (≥1) only happens in UI. Backend may require at least 2 steps for a funnel to be meaningful. Verify backend requirements.

How can I resolve this? If you propose a fix, please make it concise.
Minimum step validation (≥1) only happens in UI. Backend may require at least 2 steps for a funnel to be meaningful. Verify backend requirements. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 26:27 Comment: Minimum step validation (≥1) only happens in UI. Backend may require at least 2 steps for a funnel to be meaningful. Verify backend requirements. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +58,4 @@
return
}
if (steps.some(s => !s.value.trim())) {
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:45:41 +00:00

Step validation only checks if value is empty but doesn't validate regex syntax when type === 'regex'. Invalid regex will cause backend errors or runtime issues when matching. Consider adding client-side regex validation:

    if (steps.some(s => !s.value.trim() || (s.type === 'regex' && !isValidRegex(s.value)))) {

Add a helper:

function isValidRegex(pattern: string): boolean {
  try { new RegExp(pattern); return true } catch { return false }
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 46:46

Comment:
Step validation only checks if `value` is empty but doesn't validate regex syntax when `type === 'regex'`. Invalid regex will cause backend errors or runtime issues when matching. Consider adding client-side regex validation:

```suggestion
    if (steps.some(s => !s.value.trim() || (s.type === 'regex' && !isValidRegex(s.value)))) {
```

Add a helper:
```typescript
function isValidRegex(pattern: string): boolean {
  try { new RegExp(pattern); return true } catch { return false }
}
```

How can I resolve this? If you propose a fix, please make it concise.
Step validation only checks if `value` is empty but doesn't validate regex syntax when `type === 'regex'`. Invalid regex will cause backend errors or runtime issues when matching. Consider adding client-side regex validation: ```suggestion if (steps.some(s => !s.value.trim() || (s.type === 'regex' && !isValidRegex(s.value)))) { ``` Add a helper: ```typescript function isValidRegex(pattern: string): boolean { try { new RegExp(pattern); return true } catch { return false } } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 46:46 Comment: Step validation only checks if `value` is empty but doesn't validate regex syntax when `type === 'regex'`. Invalid regex will cause backend errors or runtime issues when matching. Consider adding client-side regex validation: ```suggestion if (steps.some(s => !s.value.trim() || (s.type === 'regex' && !isValidRegex(s.value)))) { ``` Add a helper: ```typescript function isValidRegex(pattern: string): boolean { try { new RegExp(pattern); return true } catch { return false } } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +71,4 @@
return {
from: DATE_ONLY_REGEX.test(from) ? `${from}T00:00:00.000Z` : from,
to: DATE_ONLY_REGEX.test(to) ? `${to}T23:59:59.999Z` : to,
}
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:45:43 +00:00

Date normalization hardcodes T23:59:59.999Z for end dates, but this may not account for user's timezone. If to is "2024-02-04", this sets end to "2024-02-04T23:59:59.999Z" (UTC), which might exclude data from users in timezones ahead of UTC who accessed the site on Feb 4th local time. Consider if timezone handling is needed or document the UTC assumption.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/api/funnels.ts
Line: 72:74

Comment:
Date normalization hardcodes `T23:59:59.999Z` for end dates, but this may not account for user's timezone. If `to` is "2024-02-04", this sets end to "2024-02-04T23:59:59.999Z" (UTC), which might exclude data from users in timezones ahead of UTC who accessed the site on Feb 4th local time. Consider if timezone handling is needed or document the UTC assumption.

How can I resolve this? If you propose a fix, please make it concise.
Date normalization hardcodes `T23:59:59.999Z` for end dates, but this may not account for user's timezone. If `to` is "2024-02-04", this sets end to "2024-02-04T23:59:59.999Z" (UTC), which might exclude data from users in timezones ahead of UTC who accessed the site on Feb 4th local time. Consider if timezone handling is needed or document the UTC assumption. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/api/funnels.ts Line: 72:74 Comment: Date normalization hardcodes `T23:59:59.999Z` for end dates, but this may not account for user's timezone. If `to` is "2024-02-04", this sets end to "2024-02-04T23:59:59.999Z" (UTC), which might exclude data from users in timezones ahead of UTC who accessed the site on Feb 4th local time. Consider if timezone handling is needed or document the UTC assumption. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:47:34 +00:00
@@ -0,0 +71,4 @@
return {
from: DATE_ONLY_REGEX.test(from) ? `${from}T00:00:00.000Z` : from,
to: DATE_ONLY_REGEX.test(to) ? `${to}T23:59:59.999Z` : to,
}
uz1mani (Migrated from github.com) commented 2026-02-04 22:47:34 +00:00

Change: Updated the JSDoc for toRFC3339Range to: Normalize date-only (YYYY-MM-DD) to RFC3339 for backend funnel stats API. Uses UTC for boundaries (API/server timestamps are UTC).
Why: It documents that the T23:59:59.999Z end-of-day is intentional UTC and that the API/server use UTC; no code change for timezone handling.

Change: Updated the JSDoc for toRFC3339Range to: Normalize date-only (YYYY-MM-DD) to RFC3339 for backend funnel stats API. Uses UTC for boundaries (API/server timestamps are UTC). Why: It documents that the T23:59:59.999Z end-of-day is intentional UTC and that the API/server use UTC; no code change for timezone handling.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:47:44 +00:00
@@ -0,0 +34,4 @@
}
const handleRemoveStep = (index: number) => {
if (steps.length <= 1) return
uz1mani (Migrated from github.com) commented 2026-02-04 22:47:44 +00:00

Change: Added a comment above the steps state: // * Backend requires at least one step (API binding min=1, DB rejects empty steps).
No change to the minimum step count: the backend uses min=1 (API binding:"required,min=1" and DB len(steps) < 1), and the UI already enforces that (e.g. “remove step” disabled when only one step).
Why: The comment records that the backend requirement is “at least 1 step” and that the UI matches it; no change to require 2 steps.

Change: Added a comment above the steps state: // * Backend requires at least one step (API binding min=1, DB rejects empty steps). No change to the minimum step count: the backend uses min=1 (API binding:"required,min=1" and DB len(steps) < 1), and the UI already enforces that (e.g. “remove step” disabled when only one step). Why: The comment records that the backend requirement is “at least 1 step” and that the UI matches it; no change to require 2 steps.
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:47:56 +00:00
@@ -0,0 +58,4 @@
return
}
if (steps.some(s => !s.value.trim())) {
uz1mani (Migrated from github.com) commented 2026-02-04 22:47:56 +00:00

Change:
Added isValidRegex(pattern: string): boolean that does try { new RegExp(pattern); return true } catch { return false }.
After the “path for all steps” check, added a check: steps.some(s => s.type === 'regex' && !isValidRegex(s.value)). If true, show toast.error('Invalid regex in one or more steps. Check the pattern for steps with type "regex".') and return without submitting.
Why: Invalid regex for type === 'regex' is caught on the client so the user gets a clear error instead of a backend/runtime failure.

Change: Added isValidRegex(pattern: string): boolean that does try { new RegExp(pattern); return true } catch { return false }. After the “path for all steps” check, added a check: steps.some(s => s.type === 'regex' && !isValidRegex(s.value)). If true, show toast.error('Invalid regex in one or more steps. Check the pattern for steps with type "regex".') and return without submitting. Why: Invalid regex for type === 'regex' is caught on the client so the user gets a clear error instead of a backend/runtime failure.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 22:51:46 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>4 files reviewed, 1 comment</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +46,4 @@
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 22:51:45 +00:00

Regex validation missing despite previous thread mentioning it was added. When step.type === 'regex', invalid regex patterns will cause backend errors. The previous thread indicated isValidRegex helper and validation were added, but they're not present in the code.

    if (steps.some(s => !s.value.trim())) {
      toast.error('Please enter a path for all steps')
      return
    }

    // Validate regex patterns
    const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value))
    if (invalidRegexStep) {
      toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`)
      return
    }

function isValidRegex(pattern: string): boolean {
  try { 
    new RegExp(pattern)
    return true
  } catch {
    return false
  }
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 46:49

Comment:
Regex validation missing despite previous thread mentioning it was added. When `step.type === 'regex'`, invalid regex patterns will cause backend errors. The previous thread indicated `isValidRegex` helper and validation were added, but they're not present in the code.

```suggestion
    if (steps.some(s => !s.value.trim())) {
      toast.error('Please enter a path for all steps')
      return
    }

    // Validate regex patterns
    const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value))
    if (invalidRegexStep) {
      toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`)
      return
    }

function isValidRegex(pattern: string): boolean {
  try { 
    new RegExp(pattern)
    return true
  } catch {
    return false
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.
Regex validation missing despite previous thread mentioning it was added. When `step.type === 'regex'`, invalid regex patterns will cause backend errors. The previous thread indicated `isValidRegex` helper and validation were added, but they're not present in the code. ```suggestion if (steps.some(s => !s.value.trim())) { toast.error('Please enter a path for all steps') return } // Validate regex patterns const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value)) if (invalidRegexStep) { toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`) return } function isValidRegex(pattern: string): boolean { try { new RegExp(pattern) return true } catch { return false } } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 46:49 Comment: Regex validation missing despite previous thread mentioning it was added. When `step.type === 'regex'`, invalid regex patterns will cause backend errors. The previous thread indicated `isValidRegex` helper and validation were added, but they're not present in the code. ```suggestion if (steps.some(s => !s.value.trim())) { toast.error('Please enter a path for all steps') return } // Validate regex patterns const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value)) if (invalidRegexStep) { toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`) return } function isValidRegex(pattern: string): boolean { try { new RegExp(pattern) return true } catch { return false } } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 22:54:36 +00:00
@@ -0,0 +46,4 @@
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
uz1mani (Migrated from github.com) commented 2026-02-04 22:54:36 +00:00

Already in place: The isValidRegex helper (lines 9–16) and regex validation for steps with type === 'regex' were already there.
Change made: The validation was updated to match the suggested style:
Use steps.find(s => s.type === 'regex' && !isValidRegex(s.value)) instead of steps.some(...).
If an invalid regex step is found, show toast.error(\Invalid regex pattern in step: ${invalidRegexStep.name}) so the user sees which step failed.
Why: Behavior is the same (invalid regex still blocks submit), but the error message now names the step, as in the review suggestion.

Already in place: The isValidRegex helper (lines 9–16) and regex validation for steps with type === 'regex' were already there. Change made: The validation was updated to match the suggested style: Use steps.find(s => s.type === 'regex' && !isValidRegex(s.value)) instead of steps.some(...). If an invalid regex step is found, show toast.error(\Invalid regex pattern in step: ${invalidRegexStep.name}\) so the user sees which step failed. Why: Behavior is the same (invalid regex still blocks submit), but the error message now names the step, as in the review suggestion.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 23:03:03 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>7 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +130,4 @@
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
<p className="text-neutral-600 dark:text-neutral-400">Funnel not found</p>
</div>
)
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 23:03:01 +00:00

Misleading error state

When loadData() fails (network/5xx/permission), funnel/stats remain null and the render falls through to if (!funnel || !stats) showing "Funnel not found". That message is only correct for a 404; for transient errors it incorrectly looks like a permanent missing resource and provides no retry path. Consider tracking an explicit error/notFound state and only rendering "not found" on an actual 404 response.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 91:96

Comment:
**Misleading error state**

When `loadData()` fails (network/5xx/permission), `funnel`/`stats` remain `null` and the render falls through to `if (!funnel || !stats)` showing "Funnel not found". That message is only correct for a 404; for transient errors it incorrectly looks like a permanent missing resource and provides no retry path. Consider tracking an explicit `error`/`notFound` state and only rendering "not found" on an actual 404 response.

How can I resolve this? If you propose a fix, please make it concise.
**Misleading error state** When `loadData()` fails (network/5xx/permission), `funnel`/`stats` remain `null` and the render falls through to `if (!funnel || !stats)` showing "Funnel not found". That message is only correct for a 404; for transient errors it incorrectly looks like a permanent missing resource and provides no retry path. Consider tracking an explicit `error`/`notFound` state and only rendering "not found" on an actual 404 response. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 91:96 Comment: **Misleading error state** When `loadData()` fails (network/5xx/permission), `funnel`/`stats` remain `null` and the render falls through to `if (!funnel || !stats)` showing "Funnel not found". That message is only correct for a 404; for transient errors it incorrectly looks like a permanent missing resource and provides no retry path. Consider tracking an explicit `error`/`notFound` state and only rendering "not found" on an actual 404 response. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +65,4 @@
const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value))
if (invalidRegexStep) {
toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`)
return
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 23:03:02 +00:00

Missing step name validation

handleSubmit validates step.value (and regex syntax) but never validates step.name. This allows creating funnels with empty/whitespace step names, which then render as blank labels in the funnels list/report (step.name is displayed in multiple places). Add a check like steps.some(s => !s.name.trim()) and block submit with a toast so funnels stay usable.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 56:63

Comment:
**Missing step name validation**

`handleSubmit` validates `step.value` (and regex syntax) but never validates `step.name`. This allows creating funnels with empty/whitespace step names, which then render as blank labels in the funnels list/report (`step.name` is displayed in multiple places). Add a check like `steps.some(s => !s.name.trim())` and block submit with a toast so funnels stay usable.

How can I resolve this? If you propose a fix, please make it concise.
**Missing step name validation** `handleSubmit` validates `step.value` (and regex syntax) but never validates `step.name`. This allows creating funnels with empty/whitespace step names, which then render as blank labels in the funnels list/report (`step.name` is displayed in multiple places). Add a check like `steps.some(s => !s.name.trim())` and block submit with a toast so funnels stay usable. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 56:63 Comment: **Missing step name validation** `handleSubmit` validates `step.value` (and regex syntax) but never validates `step.name`. This allows creating funnels with empty/whitespace step names, which then render as blank labels in the funnels list/report (`step.name` is displayed in multiple places). Add a check like `steps.some(s => !s.name.trim())` and block submit with a toast so funnels stay usable. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 23:04:53 +00:00
@@ -0,0 +65,4 @@
const invalidRegexStep = steps.find(s => s.type === 'regex' && !isValidRegex(s.value))
if (invalidRegexStep) {
toast.error(`Invalid regex pattern in step: ${invalidRegexStep.name}`)
return
uz1mani (Migrated from github.com) commented 2026-02-04 23:04:53 +00:00

Change: In handleSubmit, after the funnel name check and before the path check, added: if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps'); return }.
Why: Steps with empty or whitespace-only names are rejected so funnel list/report labels stay usable.

Change: In handleSubmit, after the funnel name check and before the path check, added: if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps'); return }. Why: Steps with empty or whitespace-only names are rejected so funnel list/report labels stay usable.
uz1mani (Migrated from github.com) reviewed 2026-02-04 23:05:05 +00:00
@@ -0,0 +130,4 @@
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
<p className="text-neutral-600 dark:text-neutral-400">Funnel not found</p>
</div>
)
uz1mani (Migrated from github.com) commented 2026-02-04 23:05:05 +00:00

Change:
Added loadError state: 'not_found' | 'error' | null, default null.
In loadData: at the start, setLoadError(null); in the catch, if error instanceof ApiError && error.status === 404 then setLoadError('not_found'), else setLoadError('error') and toast.error('Failed to load funnel data') (toast only for non-404).
Render logic:
loadError === 'not_found' or (!funnel && !stats && !loadError) → show "Funnel not found" (no retry).
loadError === 'error' → show "Failed to load funnel data" and a "Try again" button that calls loadData().
Otherwise, if !funnel || !stats → fallback "Funnel not found".
Why: 404 is shown as “Funnel not found”; other failures (network/5xx/permission) are shown as “Failed to load funnel data” with a retry, so transient errors are no longer treated as a permanent missing resource.

Change: Added loadError state: 'not_found' | 'error' | null, default null. In loadData: at the start, setLoadError(null); in the catch, if error instanceof ApiError && error.status === 404 then setLoadError('not_found'), else setLoadError('error') and toast.error('Failed to load funnel data') (toast only for non-404). Render logic: loadError === 'not_found' or (!funnel && !stats && !loadError) → show "Funnel not found" (no retry). loadError === 'error' → show "Failed to load funnel data" and a "Try again" button that calls loadData(). Otherwise, if !funnel || !stats → fallback "Funnel not found". Why: 404 is shown as “Funnel not found”; other failures (network/5xx/permission) are shown as “Failed to load funnel data” with a retry, so transient errors are no longer treated as a permanent missing resource.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 23:08:06 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>3 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -0,0 +95,4 @@
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
}
if (loadError === 'not_found' || (!funnel && !stats && !loadError)) {
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 23:08:05 +00:00

Shows "Funnel not found" for all errors (404, 403, 500, network failures). For transient errors, this suggests the funnel doesn't exist when it might just be a temporary issue. Consider tracking error type separately to show appropriate messages (e.g., "Unable to load funnel" for 500/network, "Access denied" for 403).

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/[funnelId]/page.tsx
Line: 91:96

Comment:
Shows "Funnel not found" for all errors (404, 403, 500, network failures). For transient errors, this suggests the funnel doesn't exist when it might just be a temporary issue. Consider tracking error type separately to show appropriate messages (e.g., "Unable to load funnel" for 500/network, "Access denied" for 403).

How can I resolve this? If you propose a fix, please make it concise.
Shows "Funnel not found" for all errors (404, 403, 500, network failures). For transient errors, this suggests the funnel doesn't exist when it might just be a temporary issue. Consider tracking error type separately to show appropriate messages (e.g., "Unable to load funnel" for 500/network, "Access denied" for 403). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/[funnelId]/page.tsx Line: 91:96 Comment: Shows "Funnel not found" for all errors (404, 403, 500, network failures). For transient errors, this suggests the funnel doesn't exist when it might just be a temporary issue. Consider tracking error type separately to show appropriate messages (e.g., "Unable to load funnel" for 500/network, "Access denied" for 403). How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +56,4 @@
if (steps.some(s => !s.name.trim())) {
toast.error('Please enter a name for all steps')
return
}
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 23:08:04 +00:00

Validates step.value but not step.name. Empty/whitespace step names will render as blank labels throughout the UI (list page line 121, report page line 246).

    if (steps.some(s => !s.value.trim())) {
      toast.error('Please enter a path for all steps')
      return
    }
    
    if (steps.some(s => !s.name.trim())) {
      toast.error('Please enter a name for all steps')
      return
    }
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/sites/[id]/funnels/new/page.tsx
Line: 56:59

Comment:
Validates `step.value` but not `step.name`. Empty/whitespace step names will render as blank labels throughout the UI (list page line 121, report page line 246).

```suggestion
    if (steps.some(s => !s.value.trim())) {
      toast.error('Please enter a path for all steps')
      return
    }
    
    if (steps.some(s => !s.name.trim())) {
      toast.error('Please enter a name for all steps')
      return
    }
```

How can I resolve this? If you propose a fix, please make it concise.
Validates `step.value` but not `step.name`. Empty/whitespace step names will render as blank labels throughout the UI (list page line 121, report page line 246). ```suggestion if (steps.some(s => !s.value.trim())) { toast.error('Please enter a path for all steps') return } if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps') return } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/sites/[id]/funnels/new/page.tsx Line: 56:59 Comment: Validates `step.value` but not `step.name`. Empty/whitespace step names will render as blank labels throughout the UI (list page line 121, report page line 246). ```suggestion if (steps.some(s => !s.value.trim())) { toast.error('Please enter a path for all steps') return } if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps') return } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 23:09:39 +00:00
@@ -0,0 +95,4 @@
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
}
if (loadError === 'not_found' || (!funnel && !stats && !loadError)) {
uz1mani (Migrated from github.com) commented 2026-02-04 23:09:39 +00:00

Change:
loadError is now 'not_found' | 'forbidden' | 'error' | null.
In loadData catch: status === 404 → setLoadError('not_found'); status === 403 → setLoadError('forbidden'); otherwise → setLoadError('error'). Toast only for non-404, non-403.
Render:
loadError === 'not_found' (or fallback) → "Funnel not found" (unchanged).
loadError === 'forbidden' → "Access denied" and a "Back to Funnels" link.
loadError === 'error' → "Unable to load funnel" and "Try again" button that calls loadData().
Why: 404 stays “Funnel not found”, 403 shows “Access denied” with a way back, and 500/network show “Unable to load funnel” with retry so transient errors are no longer treated as “not found”.

Change: loadError is now 'not_found' | 'forbidden' | 'error' | null. In loadData catch: status === 404 → setLoadError('not_found'); status === 403 → setLoadError('forbidden'); otherwise → setLoadError('error'). Toast only for non-404, non-403. Render: loadError === 'not_found' (or fallback) → "Funnel not found" (unchanged). loadError === 'forbidden' → "Access denied" and a "Back to Funnels" link. loadError === 'error' → "Unable to load funnel" and "Try again" button that calls loadData(). Why: 404 stays “Funnel not found”, 403 shows “Access denied” with a way back, and 500/network show “Unable to load funnel” with retry so transient errors are no longer treated as “not found”.
uz1mani (Migrated from github.com) reviewed 2026-02-04 23:09:55 +00:00
@@ -0,0 +56,4 @@
if (steps.some(s => !s.name.trim())) {
toast.error('Please enter a name for all steps')
return
}
uz1mani (Migrated from github.com) commented 2026-02-04 23:09:55 +00:00

Status: Already implemented.
handleSubmit already has if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps'); return } (lines 56–59), before the path check.

Status: Already implemented. handleSubmit already has if (steps.some(s => !s.name.trim())) { toast.error('Please enter a name for all steps'); return } (lines 56–59), before the path check.
Sign in to join this conversation.
No description provided.