Skip to content

Add status cycling and lead attribution tracking#6

Open
saifsoub wants to merge 1 commit into
mainfrom
claude/install-superpowers-plugin-wMzGg
Open

Add status cycling and lead attribution tracking#6
saifsoub wants to merge 1 commit into
mainfrom
claude/install-superpowers-plugin-wMzGg

Conversation

@saifsoub
Copy link
Copy Markdown
Owner

@saifsoub saifsoub commented Apr 3, 2026

Summary

This PR introduces clickable status cycling for all entities (leads, opportunities, offers, assets) and adds content attribution tracking to leads. Users can now advance entity statuses by clicking a button, and leads capture which content item referred them.

Key Changes

  • New StatusCycleButton component: A reusable client component that cycles through entity statuses with visual feedback. Includes:

    • Status-specific color coding for leads, opportunities, offers, and assets
    • Loading state with spinner during API calls
    • Automatic router refresh after status update
    • Hover and active state animations
  • API endpoints for status updates: Added PATCH routes for:

    • /api/leads/[id]
    • /api/opportunities/[id]
    • /api/offers/[id]
    • /api/assets/[id]
  • Lead attribution tracking:

    • Added refContentId field to leads to track which content item referred them
    • Updated LeadForm to capture and pass refContentId from URL search params
    • Leads page now displays content attribution with a link icon
    • Content lookup map built from db.contentItems for display
  • Offer enhancements:

    • Added optional calUrl field for Cal.com booking integration
    • Offer landing page now conditionally renders Cal.com iframe or lead form based on calUrl presence
    • When Cal.com is available, shows both booking widget and optional message form
    • Supports ref query parameter for content attribution
  • UI improvements:

    • Replaced SectionCard components with custom card layouts in assets and offers pages
    • Added external link indicators and status badges to asset/offer cards
    • Leads table now includes "Via" column showing content attribution
    • Opportunities table uses new StatusCycleButton instead of static badge
    • Better visual hierarchy with improved spacing and typography
  • Store updates:

    • Added updateLead, updateOffer, updateOpportunity, updateAsset functions
    • Extended type definitions to include new fields (calUrl, refContentId)
    • Updated validators for new optional fields

Implementation Details

  • Status cycling uses modulo arithmetic to loop through predefined status arrays per entity type
  • Color scheme is consistent across all entity types with semantic meaning (e.g., green for completed states)
  • Lead form now passes refContentId to API, enabling attribution analytics
  • Cal.com integration is optional and gracefully falls back to lead form

https://claude.ai/code/session_01J9wgN7CnCeMnJkAhTyNFMb

P0 — Write-only no more:
- PATCH API endpoints for leads, opportunities, offers, assets
- updateX() store functions for all four entities
- StatusCycleButton client component: click any status badge to advance it
  through the pipeline (persisted to DB, page refreshes)
- Opportunity table: status badges now clickable
- Offers page: status badges now clickable
- Assets page: status badges now clickable

P1 — Revenue actualization:
- buyUrl field exposed in Assets QuickCreate form + inline link on each card
- Assets with a buyUrl show "Buy link active" badge; others show public /a/:id link
- calUrl field added to Offer type, demo data, and Offers QuickCreate form
- Offer landing pages (/o/:id) embed a Cal.com iframe when calUrl is set,
  falling back to lead form if no calUrl

P1 — Content → lead attribution:
- Public pages (/o/:id, /a/:id) read ?ref=content_01 from URL searchParams
- LeadForm passes refContentId to /api/leads
- addLead() stores refContentId on every lead
- createLeadSchema validates the new field
- Leads page shows "Via" column: resolves refContentId → content topic,
  or "Direct" if none set

https://claude.ai/code/session_01J9wgN7CnCeMnJkAhTyNFMb
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 3, 2026

Deploy Preview for personalcommandcenter ready!

Name Link
🔨 Latest commit 471f260
🔍 Latest deploy log https://app.netlify.com/projects/personalcommandcenter/deploys/69cf6b144088650008c069a3
😎 Deploy Preview https://deploy-preview-6--personalcommandcenter.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 3, 2026

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

Project Deployment Actions Updated (UTC)
agent-empire Error Error Apr 3, 2026 7:24am

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces PATCH endpoints for assets, leads, offers, and opportunities, alongside a new StatusCycleButton component for interactive status management. It also implements referral tracking via refContentId and adds support for calUrl in offers. The review feedback highlights critical security and reliability concerns: the new API endpoints are vulnerable to mass assignment due to unvalidated request bodies and have improper error serialization. Additionally, the StatusCycleButton lacks a check for successful network responses, which could lead to the UI becoming out of sync with the server state.

Comment on lines +7 to +12
const patch = await req.json();
const updated = await updateAsset(id, patch);
if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(updated);
} catch (error) {
return NextResponse.json({ error }, { status: 400 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This endpoint is vulnerable to mass assignment and has an issue with error serialization.

  1. Mass Assignment: The patch object is passed directly from req.json() to the store update function without validation. This allows a client to potentially overwrite internal fields like id or createdAt if they are included in the request body.
  2. Error Serialization: In JavaScript, JSON.stringify(new Error()) typically returns {}. Returning the raw error object in the response will likely result in an empty object being sent to the client. It is better to return the error message.
    const body = await req.json();
    const { status, buyUrl, price } = body;
    const updated = await updateAsset(id, { status, buyUrl, price });
    if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
    return NextResponse.json(updated);
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 400 }
    );

Comment on lines +7 to +12
const patch = await req.json();
const updated = await updateLead(id, patch);
if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(updated);
} catch (error) {
return NextResponse.json({ error }, { status: 400 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This endpoint is vulnerable to mass assignment and has an issue with error serialization.

  1. Mass Assignment: The patch object is passed directly from req.json() to the store update function without validation. This allows a client to potentially overwrite internal fields like id or createdAt if they are included in the request body.
  2. Error Serialization: In JavaScript, JSON.stringify(new Error()) typically returns {}. Returning the raw error object in the response will likely result in an empty object being sent to the client. It is better to return the error message.
    const body = await req.json();
    const { status, refContentId } = body;
    const updated = await updateLead(id, { status, refContentId });
    if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
    return NextResponse.json(updated);
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 400 }
    );

Comment on lines +7 to +12
const patch = await req.json();
const updated = await updateOffer(id, patch);
if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(updated);
} catch (error) {
return NextResponse.json({ error }, { status: 400 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This endpoint is vulnerable to mass assignment and has an issue with error serialization.

  1. Mass Assignment: The patch object is passed directly from req.json() to the store update function without validation. This allows a client to potentially overwrite internal fields like id or createdAt if they are included in the request body.
  2. Error Serialization: In JavaScript, JSON.stringify(new Error()) typically returns {}. Returning the raw error object in the response will likely result in an empty object being sent to the client. It is better to return the error message.
    const body = await req.json();
    const { status, ctaUrl, calUrl } = body;
    const updated = await updateOffer(id, { status, ctaUrl, calUrl });
    if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
    return NextResponse.json(updated);
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 400 }
    );

Comment on lines +7 to +12
const patch = await req.json();
const updated = await updateOpportunity(id, patch);
if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(updated);
} catch (error) {
return NextResponse.json({ error }, { status: 400 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This endpoint is vulnerable to mass assignment and has an issue with error serialization.

  1. Mass Assignment: The patch object is passed directly from req.json() to the store update function without validation. This allows a client to potentially overwrite internal fields like id or createdAt if they are included in the request body.
  2. Error Serialization: In JavaScript, JSON.stringify(new Error()) typically returns {}. Returning the raw error object in the response will likely result in an empty object being sent to the client. It is better to return the error message.
    const body = await req.json();
    const { status, nextAction, expectedRevenue } = body;
    const updated = await updateOpportunity(id, { status, nextAction, expectedRevenue });
    if (!updated) return NextResponse.json({ error: "Not found" }, { status: 404 });
    return NextResponse.json(updated);
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Unknown error" },
      { status: 400 }
    );

Comment on lines +70 to +76
await fetch(endpointMap[entityType], {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: next }),
});
setStatus(next);
router.refresh();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The fetch call does not check if the response was successful (res.ok). If the API request fails (e.g., due to a 404 or 500 error), the component will still update its local state to the "next" status and trigger a router refresh, leading to a UI that is out of sync with the server data.

      const res = await fetch(endpointMap[entityType], {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ status: next }),
      });
      if (!res.ok) throw new Error("Failed to update status");
      setStatus(next);
      router.refresh();

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.

2 participants