[PULSE-41] Implement UTM Campaign URL Builder & Tools #7

Merged
uz1mani merged 5 commits from staging into main 2026-02-04 19:58:19 +00:00
uz1mani commented 2026-02-04 19:50:16 +00:00 (Migrated from github.com)

Work Item

PULSE-41

Summary

  • Implemented a comprehensive UTM Campaign URL Builder accessible via a new global "Tools" page and directly from site dashboards.
  • Updated the global navigation to include a "Tools" link for logged-in users.
  • Ensured full compliance with the Ciphera design system by using shared UI components and standard styling.

Changes

  • New Component: Created UtmBuilder.tsx in Pulse/pulse-frontend/components/tools/ to handle URL construction, site selection, and domain enforcement.
  • New Page: Added Pulse/pulse-frontend/app/tools/page.tsx to host the standalone builder tool.
  • Dashboard Integration: Updated Campaigns.tsx to include a "Build URL" button that opens the builder in a modal with the current site pre-selected.
  • Navigation: Modified Pulse/pulse-frontend/app/layout-content.tsx and ciphera-ui/Header.tsx to inject a "Tools" link into the main header bar when a user is logged in.
  • Design System: Refactored new components to use Input and Button from @ciphera-net/ui for consistent styling (focus rings, dark mode, hover states).
  • Versioning: Bumped ciphera-ui to 0.0.44 and updated all frontend dependencies (auth, drop, pulse, website) to match.

Test Plan

[ ] Log in to Pulse and verify the "Tools" link appears in the top navigation bar.
[ ] Navigate to /tools and verify the UTM Builder loads and allows selecting any site.
[ ] Go to a specific site dashboard, click "Build URL" in the Campaigns widget, and verify the current site is pre-selected and the domain is locked.
[ ] Generate a URL with special characters in the campaign name and verify it is correctly URL-encoded.
[ ] Check that the "Copy to Clipboard" button works.
[ ] Verify dark mode styling for all new inputs and modals.

## Work Item PULSE-41 ## Summary * Implemented a comprehensive UTM Campaign URL Builder accessible via a new global "Tools" page and directly from site dashboards. * Updated the global navigation to include a "Tools" link for logged-in users. * Ensured full compliance with the Ciphera design system by using shared UI components and standard styling. ## Changes * **New Component:** Created `UtmBuilder.tsx` in `Pulse/pulse-frontend/components/tools/` to handle URL construction, site selection, and domain enforcement. * **New Page:** Added `Pulse/pulse-frontend/app/tools/page.tsx` to host the standalone builder tool. * **Dashboard Integration:** Updated `Campaigns.tsx` to include a "Build URL" button that opens the builder in a modal with the current site pre-selected. * **Navigation:** Modified `Pulse/pulse-frontend/app/layout-content.tsx` and `ciphera-ui/Header.tsx` to inject a "Tools" link into the main header bar when a user is logged in. * **Design System:** Refactored new components to use `Input` and `Button` from `@ciphera-net/ui` for consistent styling (focus rings, dark mode, hover states). * **Versioning:** Bumped `ciphera-ui` to `0.0.44` and updated all frontend dependencies (`auth`, `drop`, `pulse`, `website`) to match. ## Test Plan [ ] Log in to Pulse and verify the "Tools" link appears in the top navigation bar. [ ] Navigate to `/tools` and verify the UTM Builder loads and allows selecting any site. [ ] Go to a specific site dashboard, click "Build URL" in the Campaigns widget, and verify the current site is pre-selected and the domain is locked. [ ] Generate a URL with special characters in the campaign name and verify it is correctly URL-encoded. [ ] Check that the "Copy to Clipboard" button works. [ ] Verify dark mode styling for all new inputs and modals.
greptile-apps[bot] commented 2026-02-04 19:53:09 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

This PR implements a comprehensive UTM Campaign URL Builder feature that integrates seamlessly into the Pulse analytics platform. The implementation adds both a standalone Tools page accessible via global navigation and a contextual modal accessible from individual site dashboards.

Key Changes:

  • New UtmBuilder component handles URL construction with automatic site selection, domain enforcement, and clipboard copy functionality
  • Global "Tools" navigation link added to header for authenticated users
  • Dashboard integration via "Build URL" button that opens the builder in a modal with the current site pre-selected
  • Proper error handling for API failures and malformed URLs
  • Design system compliance using shared UI components from @ciphera-net/ui

Architecture:

  • The component uses two separate useEffect hooks to avoid dependency issues: one for fetching sites on mount, another for initialization logic
  • When a site is selected, the domain is locked and users can only modify the path portion
  • URL generation uses the native URL API for proper encoding and parameter handling
  • All UTM parameters are automatically lowercased for consistency (with a minor inconsistency in utm_term and utm_content)

Minor Issue Found:

  • Inconsistent lowercasing of optional UTM parameters (utm_term and utm_content) compared to required parameters

Confidence Score: 4.5/5

  • This PR is safe to merge with only a minor style inconsistency that doesn't affect functionality
  • The implementation is well-structured with proper error handling, React best practices (resolved dependency array issue), and good UX design. The only issue found is a minor style inconsistency in parameter casing that doesn't impact functionality or security. All changes follow existing patterns in the codebase and use the design system consistently.
  • No files require special attention - the casing inconsistency in UtmBuilder.tsx is optional to fix

Important Files Changed

Filename Overview
components/tools/UtmBuilder.tsx New UTM builder component with site selection, URL construction, and clipboard copy functionality - well-structured with proper error handling
app/tools/page.tsx Simple page wrapper for the UTM builder tool - minimal code, no issues
components/dashboard/Campaigns.tsx Added "Build URL" button and modal integration for UTM builder with proper state management
app/layout-content.tsx Added Tools navigation link to header for authenticated users - clean integration

Sequence Diagram

sequenceDiagram
    participant User
    participant LayoutContent
    participant ToolsPage
    participant Campaigns
    participant UtmBuilder
    participant API as listSites API
    participant Clipboard

    Note over User,Clipboard: Standalone Tools Page Flow
    User->>LayoutContent: Click "Tools" nav link
    LayoutContent->>ToolsPage: Navigate to /tools
    ToolsPage->>UtmBuilder: Render (no initialSiteId)
    UtmBuilder->>API: Fetch all sites
    API-->>UtmBuilder: Return sites array
    UtmBuilder->>UtmBuilder: Auto-select first site
    User->>UtmBuilder: Enter UTM parameters
    UtmBuilder->>UtmBuilder: Generate URL with params
    User->>UtmBuilder: Click copy button
    UtmBuilder->>Clipboard: Write generated URL
    UtmBuilder->>User: Show checkmark (copied)

    Note over User,Clipboard: Dashboard Modal Flow
    User->>Campaigns: Click "Build URL" button
    Campaigns->>UtmBuilder: Open modal with initialSiteId
    UtmBuilder->>API: Fetch all sites
    API-->>UtmBuilder: Return sites array
    UtmBuilder->>UtmBuilder: Pre-select site by initialSiteId
    UtmBuilder->>UtmBuilder: Lock domain to selected site
    User->>UtmBuilder: Add path and UTM params
    UtmBuilder->>UtmBuilder: Generate URL with params
    User->>UtmBuilder: Click copy button
    UtmBuilder->>Clipboard: Write generated URL
    UtmBuilder->>User: Show checkmark (copied)
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR implements a comprehensive UTM Campaign URL Builder feature that integrates seamlessly into the Pulse analytics platform. The implementation adds both a standalone Tools page accessible via global navigation and a contextual modal accessible from individual site dashboards. **Key Changes:** - New `UtmBuilder` component handles URL construction with automatic site selection, domain enforcement, and clipboard copy functionality - Global "Tools" navigation link added to header for authenticated users - Dashboard integration via "Build URL" button that opens the builder in a modal with the current site pre-selected - Proper error handling for API failures and malformed URLs - Design system compliance using shared UI components from `@ciphera-net/ui` **Architecture:** - The component uses two separate `useEffect` hooks to avoid dependency issues: one for fetching sites on mount, another for initialization logic - When a site is selected, the domain is locked and users can only modify the path portion - URL generation uses the native `URL` API for proper encoding and parameter handling - All UTM parameters are automatically lowercased for consistency (with a minor inconsistency in `utm_term` and `utm_content`) **Minor Issue Found:** - Inconsistent lowercasing of optional UTM parameters (`utm_term` and `utm_content`) compared to required parameters <h3>Confidence Score: 4.5/5</h3> - This PR is safe to merge with only a minor style inconsistency that doesn't affect functionality - The implementation is well-structured with proper error handling, React best practices (resolved dependency array issue), and good UX design. The only issue found is a minor style inconsistency in parameter casing that doesn't impact functionality or security. All changes follow existing patterns in the codebase and use the design system consistently. - No files require special attention - the casing inconsistency in UtmBuilder.tsx is optional to fix <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | components/tools/UtmBuilder.tsx | New UTM builder component with site selection, URL construction, and clipboard copy functionality - well-structured with proper error handling | | app/tools/page.tsx | Simple page wrapper for the UTM builder tool - minimal code, no issues | | components/dashboard/Campaigns.tsx | Added "Build URL" button and modal integration for UTM builder with proper state management | | app/layout-content.tsx | Added Tools navigation link to header for authenticated users - clean integration | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant LayoutContent participant ToolsPage participant Campaigns participant UtmBuilder participant API as listSites API participant Clipboard Note over User,Clipboard: Standalone Tools Page Flow User->>LayoutContent: Click "Tools" nav link LayoutContent->>ToolsPage: Navigate to /tools ToolsPage->>UtmBuilder: Render (no initialSiteId) UtmBuilder->>API: Fetch all sites API-->>UtmBuilder: Return sites array UtmBuilder->>UtmBuilder: Auto-select first site User->>UtmBuilder: Enter UTM parameters UtmBuilder->>UtmBuilder: Generate URL with params User->>UtmBuilder: Click copy button UtmBuilder->>Clipboard: Write generated URL UtmBuilder->>User: Show checkmark (copied) Note over User,Clipboard: Dashboard Modal Flow User->>Campaigns: Click "Build URL" button Campaigns->>UtmBuilder: Open modal with initialSiteId UtmBuilder->>API: Fetch all sites API-->>UtmBuilder: Return sites array UtmBuilder->>UtmBuilder: Pre-select site by initialSiteId UtmBuilder->>UtmBuilder: Lock domain to selected site User->>UtmBuilder: Add path and UTM params UtmBuilder->>UtmBuilder: Generate URL with params User->>UtmBuilder: Click copy button UtmBuilder->>Clipboard: Write generated URL UtmBuilder->>User: Show checkmark (copied) ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 19:53:14 +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 +1,213 @@
'use client'
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 19:53:13 +00:00

The useEffect references values.url on line 39 but doesn't include it in the dependency array on line 49. This violates React's exhaustive-deps rule and could cause stale closures.

  }, [initialSiteId, values.url])
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/tools/UtmBuilder.tsx
Line: 27:49

Comment:
The `useEffect` references `values.url` on line 39 but doesn't include it in the dependency array on line 49. This violates React's exhaustive-deps rule and could cause stale closures.

```suggestion
  }, [initialSiteId, values.url])
```

How can I resolve this? If you propose a fix, please make it concise.
The `useEffect` references `values.url` on line 39 but doesn't include it in the dependency array on line 49. This violates React's exhaustive-deps rule and could cause stale closures. ```suggestion }, [initialSiteId, values.url]) ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/tools/UtmBuilder.tsx Line: 27:49 Comment: The `useEffect` references `values.url` on line 39 but doesn't include it in the dependency array on line 49. This violates React's exhaustive-deps rule and could cause stale closures. ```suggestion }, [initialSiteId, values.url]) ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-04 19:56:59 +00:00
@@ -0,0 +1,213 @@
'use client'
uz1mani (Migrated from github.com) commented 2026-02-04 19:56:59 +00:00

Resolved the React hook dependency warning by splitting the logic into two separate useEffect hooks:
Data Fetching: Runs only once on mount ([] dependencies) to fetch the list of sites.
Initialization: Runs when sites, initialSiteId, selectedSiteId, or values.url changes. It correctly handles setting the default site without causing infinite loops or stale closures, ensuring the linter is happy and the behavior remains correct (including respecting user input if they type before sites load).

Resolved the React hook dependency warning by splitting the logic into two separate useEffect hooks: Data Fetching: Runs only once on mount ([] dependencies) to fetch the list of sites. Initialization: Runs when sites, initialSiteId, selectedSiteId, or values.url changes. It correctly handles setting the default site without causing infinite loops or stale closures, ensuring the linter is happy and the behavior remains correct (including respecting user input if they type before sites load).
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 20:06:51 +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 +73,4 @@
url.searchParams.set('utm_medium', values.medium.toLowerCase())
url.searchParams.set('utm_campaign', values.campaign.toLowerCase())
if (values.term) url.searchParams.set('utm_term', values.term)
if (values.content) url.searchParams.set('utm_content', values.content)
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 20:06:50 +00:00

Inconsistent casing: utm_term and utm_content aren't lowercased like the other UTM parameters

      if (values.term) url.searchParams.set('utm_term', values.term.toLowerCase())
      if (values.content) url.searchParams.set('utm_content', values.content.toLowerCase())

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/tools/UtmBuilder.tsx
Line: 75:76

Comment:
Inconsistent casing: `utm_term` and `utm_content` aren't lowercased like the other UTM parameters

```suggestion
      if (values.term) url.searchParams.set('utm_term', values.term.toLowerCase())
      if (values.content) url.searchParams.set('utm_content', values.content.toLowerCase())
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.
Inconsistent casing: `utm_term` and `utm_content` aren't lowercased like the other UTM parameters ```suggestion if (values.term) url.searchParams.set('utm_term', values.term.toLowerCase()) if (values.content) url.searchParams.set('utm_content', values.content.toLowerCase()) ``` <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/tools/UtmBuilder.tsx Line: 75:76 Comment: Inconsistent casing: `utm_term` and `utm_content` aren't lowercased like the other UTM parameters ```suggestion if (values.term) url.searchParams.set('utm_term', values.term.toLowerCase()) if (values.content) url.searchParams.set('utm_content', values.content.toLowerCase()) ``` <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> How can I resolve this? If you propose a fix, please make it concise. ````` </details>
Sign in to join this conversation.
No description provided.