Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 210 additions & 4 deletions apps/web/ui/guides/guide-action-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
BookOpen,
Button,
Check,
Codex,
Cursor,
OpenAI,
Popover,
useCopyToClipboard,
Expand All @@ -14,6 +16,169 @@
import { toast } from "sonner";
import { IntegrationGuide } from "./integrations";

const CONVERSION_TRACKING_PATTERNS = [
/data-publishable-key=/,
/publishableKey=/,
/s\.setAttribute\("data-publishable-key"/,
];

const PUBLISHABLE_KEY_PLACEHOLDER = "<YOUR_DUB_PUBLISHABLE_KEY>";
const OUTBOUND_DOMAIN_PLACEHOLDER = '["<YOUR_DOMAIN_1>", "<YOUR_DOMAIN_2>"]';
const REFER_DOMAIN_PLACEHOLDER = "<YOUR_REFERRING_DOMAIN>";

function getGuideOptionName(guide: IntegrationGuide) {
return guide.description || [guide.title, guide.subtitle].filter(Boolean).join(" ");
}

function sanitizeClientScriptMarkdown(markdown: string) {
return markdown
.replace(/<!--[\s\S]*?-->/g, "")
Comment thread
marcusljf marked this conversation as resolved.
.replace(/dub_pk_[A-Za-z0-9_-]+/g, PUBLISHABLE_KEY_PLACEHOLDER)
.replace(
/data-publishable-key="[^"]*"/g,
`data-publishable-key="${PUBLISHABLE_KEY_PLACEHOLDER}"`,
)
.replace(
/publishableKey="[^"]*"/g,
`publishableKey="${PUBLISHABLE_KEY_PLACEHOLDER}"`,
)
.replace(
/s\.setAttribute\("data-publishable-key", "[^"]*"\);/g,
`s.setAttribute("data-publishable-key", "${PUBLISHABLE_KEY_PLACEHOLDER}");`,
)
.replace(
/"outbound": \["example\.com", "example\.sh"\]/g,
`"outbound": ${OUTBOUND_DOMAIN_PLACEHOLDER}`,
)
.replace(
/outbound: \["example\.com", "example\.sh"\]/g,
`outbound: ${OUTBOUND_DOMAIN_PLACEHOLDER}`,
)
.replace(
/"refer":\s*"[^"]*"/g,
`"refer": "${REFER_DOMAIN_PLACEHOLDER}"`,
)
.replace(
/refer:\s*"[^"]*"/g,
`refer: "${REFER_DOMAIN_PLACEHOLDER}"`,
)
.trim();
}

function sanitizeGuideInstructions(markdown: string) {
return markdown.replace(/<!--[\s\S]*?-->/g, "").trim();
Comment thread
marcusljf marked this conversation as resolved.
}

function isOutboundDomainTrackingEnabled(markdown: string) {
return (
/script\.[^"'\s]*outbound-domains[^"'\s]*\.js/.test(markdown) ||
/"outbound"\s*:/.test(markdown) ||
/\boutbound\s*:/.test(markdown)
);
}

function getReactStepOnePrompt(markdown: string, guide: IntegrationGuide) {
const conversionTrackingEnabled = CONVERSION_TRACKING_PATTERNS.some((pattern) =>
pattern.test(markdown),
);
const outboundTrackingEnabled = isOutboundDomainTrackingEnabled(markdown);

const analyticsProps = [
conversionTrackingEnabled
? ` publishableKey="${PUBLISHABLE_KEY_PLACEHOLDER}"`
: null,
outboundTrackingEnabled
? ` domainsConfig={{\n refer: "${REFER_DOMAIN_PLACEHOLDER}",\n outbound: ${OUTBOUND_DOMAIN_PLACEHOLDER}\n }}`
: null,
].filter(Boolean);

const analyticsComponent =
analyticsProps.length > 0
? `<DubAnalytics\n${analyticsProps.join("\n")}\n />`
: "<DubAnalytics />";

return [
"I'm using Dub and need to install the client-side script. Help me add the script from the instructions below.",
conversionTrackingEnabled
? "Ask me for my publishable key before finalizing the conversion tracking setup."
: null,
"Ask me if I want to enable client-side click tracking (Dub Partners). If yes, add `domainsConfig={{ refer: \"<YOUR_REFERRING_DOMAIN>\" }}`.",
outboundTrackingEnabled
? "Ask me which outbound domains I want to track before finalizing the setup."
: null,
"Step 1: Install Dub package to your project",
"```bash\nnpm install @dub/analytics\n```",
"Step 2: Initialize package in your code",
`\`\`\`jsx
import { Analytics as DubAnalytics } from '@dub/analytics/react';

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>{children}</body>
${analyticsComponent}
</html>
);
}
\`\`\``,
Comment thread
marcusljf marked this conversation as resolved.
`Full guide link for reference: ${guide.url}`,
]
.filter(Boolean)
.join("\n\n");
}

function getGuidePrompt(guide: IntegrationGuide, markdown: string) {
if (guide.type === "client-sdk" && guide.key === "shopify") {
return `Read from ${guide.url} so I can ask questions about it.`;
}
Comment on lines +137 to +139
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Shopify branch bypasses the new setup-question flow.

At Line 137Line 139, the early return for guide.key === "shopify" skips the publishable-key/outbound-domain/click-tracking prompts and the standardized footer format used by other client SDK options, making Shopify behavior inconsistent.

💡 Proposed fix
 function getGuidePrompt(guide: IntegrationGuide, markdown: string) {
-  if (guide.type === "client-sdk" && guide.key === "shopify") {
-    return `Read from ${guide.url} so I can ask questions about it.`;
-  }
-
   if (guide.type === "client-sdk" && guide.key === "react") {
     return getReactStepOnePrompt(markdown, guide);
   }

   if (guide.type === "client-sdk") {
     const conversionTrackingEnabled = CONVERSION_TRACKING_PATTERNS.some((pattern) =>
       pattern.test(markdown),
     );
     const outboundTrackingEnabled = isOutboundDomainTrackingEnabled(markdown);
     const sanitizedMarkdown = sanitizeClientScriptMarkdown(markdown);

     return [
-      "I'm using Dub and need to install the client-side script.",
+      guide.key === "shopify"
+        ? "I'm using Dub with Shopify and need to install client-side tracking."
+        : "I'm using Dub and need to install the client-side script.",
       "Help me add the script from the instructions below.",
       conversionTrackingEnabled
         ? "Ask me for my publishable key before finalizing the conversion tracking setup."
         : null,
       "Ask me if I want to enable client-side click tracking (Dub Partners). If yes, add `domainsConfig.refer` with my referring domain.",
       outboundTrackingEnabled
         ? "Ask me which outbound domains I want to track before finalizing the setup."
         : null,
       "Instructions:",
       sanitizedMarkdown,
       `Full guide link for reference: ${guide.url}`,
     ]
       .filter(Boolean)
       .join("\n\n");
   }
📝 Committable suggestion

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

Suggested change
if (guide.type === "client-sdk" && guide.key === "shopify") {
return `Read from ${guide.url} so I can ask questions about it.`;
}
function getGuidePrompt(guide: IntegrationGuide, markdown: string) {
if (guide.type === "client-sdk" && guide.key === "react") {
return getReactStepOnePrompt(markdown, guide);
}
if (guide.type === "client-sdk") {
const conversionTrackingEnabled = CONVERSION_TRACKING_PATTERNS.some((pattern) =>
pattern.test(markdown),
);
const outboundTrackingEnabled = isOutboundDomainTrackingEnabled(markdown);
const sanitizedMarkdown = sanitizeClientScriptMarkdown(markdown);
return [
guide.key === "shopify"
? "I'm using Dub with Shopify and need to install client-side tracking."
: "I'm using Dub and need to install the client-side script.",
"Help me add the script from the instructions below.",
conversionTrackingEnabled
? "Ask me for my publishable key before finalizing the conversion tracking setup."
: null,
"Ask me if I want to enable client-side click tracking (Dub Partners). If yes, add `domainsConfig.refer` with my referring domain.",
outboundTrackingEnabled
? "Ask me which outbound domains I want to track before finalizing the setup."
: null,
"Instructions:",
sanitizedMarkdown,
`Full guide link for reference: ${guide.url}`,
]
.filter(Boolean)
.join("\n\n");
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/guides/guide-action-button.tsx` around lines 137 - 139, The early
return in guide-action-button.tsx that checks guide.type === "client-sdk" &&
guide.key === "shopify" causes Shopify to skip the setup-question flow and
standardized footer; remove or refactor that special-case return so Shopify
follows the same client-sdk branch logic that triggers the
publishable-key/outbound-domain/click-tracking prompts and the shared footer
formatting. Locate the conditional using guide.type and guide.key and either
drop the shopify-specific return or fold its messaging into the existing
client-sdk flow so the same prompts and footer are produced for shopify as for
other client-sdk keys.


if (guide.type === "client-sdk" && guide.key === "react") {
return getReactStepOnePrompt(markdown, guide);
}

if (guide.type === "client-sdk") {
const conversionTrackingEnabled = CONVERSION_TRACKING_PATTERNS.some((pattern) =>
pattern.test(markdown),
);
const outboundTrackingEnabled = isOutboundDomainTrackingEnabled(markdown);
const sanitizedMarkdown = sanitizeClientScriptMarkdown(markdown);

return [
"I'm using Dub and need to install the client-side script.",
"Help me add the script from the instructions below.",
conversionTrackingEnabled
? "Ask me for my publishable key before finalizing the conversion tracking setup."
: null,
"Ask me if I want to enable client-side click tracking (Dub Partners). If yes, add `domainsConfig.refer` with my referring domain.",
outboundTrackingEnabled
? "Ask me which outbound domains I want to track before finalizing the setup."
: null,
"Instructions:",
sanitizedMarkdown,
`Full guide link for reference: ${guide.url}`,
]
.filter(Boolean)
.join("\n\n");
}

const optionName = getGuideOptionName(guide);
const intro =
guide.type === "track-lead"
? `I'm using Dub and need to track lead events with ${optionName}. Help set that up with these instructions.`
: `I'm using Dub and need to track sale events with ${optionName}. Help set that up with these instructions.`;

return [
intro,
"Instructions:",
sanitizeGuideInstructions(markdown),
`Full guide link for reference: ${guide.url}`,
].join("\n\n");
}

export const GuideActionButton = ({
guide,
markdown,
Expand All @@ -25,13 +190,15 @@

const [copied, copyToClipboard] = useCopyToClipboard();

const prompt = `Read from ${guide.url} so I can ask questions about it.`;
const prompt = getGuidePrompt(guide, markdown);
const cursorUrl = `https://cursor.com/link/prompt?text=${encodeURIComponent(prompt)}`;
const codexUrl = `https://chatgpt.com/codex?prompt=${encodeURIComponent(prompt)}`;

return (
<div className="border-border-subtle flex h-8 items-center overflow-hidden rounded-lg border">
<Link href={guide.url} target="_blank" rel="noopener noreferrer">
<Button
text="Read full guide"
text="View docs"
variant="secondary"
className="rounded-none border-0 px-3"
icon={<BookOpen className="size-3.5" />}
Expand Down Expand Up @@ -63,7 +230,7 @@
<div className="flex flex-col items-start">
<span className="font-medium">Copy content</span>
<span className="text-xs text-neutral-500">
Copy page as Markdown for LLMs
Copy as Markdown for LLMs
</span>
</div>
</button>
Expand All @@ -72,7 +239,6 @@
className="flex w-full cursor-pointer items-center gap-2 rounded-md p-2 text-sm text-neutral-600 transition-colors hover:bg-neutral-100"
onClick={() => {
const chatgptUrl = `https://chatgpt.com?hints=search&prompt=${encodeURIComponent(prompt)}`;
console.log("chatgptUrl", chatgptUrl);
window.open(chatgptUrl, "_blank", "noopener,noreferrer");
}}
>
Expand Down Expand Up @@ -110,6 +276,46 @@
</span>
</div>
</button>

<button
className="flex w-full cursor-pointer items-center gap-2 rounded-md p-2 text-sm text-neutral-600 transition-colors hover:bg-neutral-100"
onClick={() => {
window.open(cursorUrl, "_blank", "noopener,noreferrer");
}}
>
<div className="flex h-8 w-8 items-center justify-center rounded border border-neutral-200 transition-colors hover:border-neutral-300">
<Cursor className="size-4 text-neutral-600" />
</div>
<div className="flex flex-1 flex-col items-start">
<div className="flex items-center gap-1">
<span className="font-medium">Open in Cursor</span>
<ArrowUpRight className="size-3.5 text-neutral-600" />
</div>
<span className="text-xs text-neutral-500">
Ask questions about this step
</span>
</div>
</button>

<button
className="flex w-full cursor-pointer items-center gap-2 rounded-md p-2 text-sm text-neutral-600 transition-colors hover:bg-neutral-100"
onClick={() => {
window.open(codexUrl, "_blank", "noopener,noreferrer");
}}
>
<div className="flex h-8 w-8 items-center justify-center rounded border border-neutral-200 transition-colors hover:border-neutral-300">
<Codex className="size-4 text-neutral-600" />
</div>
<div className="flex flex-1 flex-col items-start">
<div className="flex items-center gap-1">
<span className="font-medium">Open in Codex</span>
<ArrowUpRight className="size-3.5 text-neutral-600" />
</div>
<span className="text-xs text-neutral-500">
Ask questions about this step
</span>
</div>
</button>
</div>
}
align="end"
Expand Down
4 changes: 1 addition & 3 deletions packages/ui/src/icons/anthropic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ export function Anthropic({ className, ...props }: SVGProps<SVGSVGElement>) {
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M103.702 26.4004H130.725L180 150H152.977L103.702 26.4004ZM49.2675 26.4004H77.52L126.795 150H99.24L89.1675 124.043H37.6275L27.5475 149.993H0L49.275 26.4154L49.2675 26.4004ZM80.2575 101.093L63.3975 57.6529L46.5375 101.1H80.25L80.2575 101.093Z"
d="M46.2533 113.433L74.5791 97.6001L75.0533 96.22L74.5791 95.4574H73.1942L68.4546 95.1672L52.2686 94.7313L38.2331 94.1502L24.6347 93.4241L21.2079 92.6973L18 88.4849L18.3279 86.3787L21.2079 84.4546L25.3277 84.8177L34.4413 85.4346L48.1123 86.3787L58.0286 86.9597L72.72 88.4849H75.0533L75.3812 87.5408L74.5791 86.9597L73.9598 86.3787L59.8146 76.8282L44.5033 66.7331L36.4832 60.9226L32.1446 57.981L29.9576 55.2215L29.0098 49.1936L32.9473 44.872L38.2331 45.2356L39.582 45.5987L44.9409 49.7018L56.3878 58.5268L71.3346 69.4926L73.5221 71.308L74.3968 70.6906L74.5065 70.2552L73.5221 68.6208L65.3923 53.9865L56.7158 39.098L52.8514 32.9245L51.831 29.2208C51.4665 27.6951 51.2111 26.4243 51.2111 24.8627L55.6954 18.7984L58.1737 18L64.1531 18.799L66.6686 20.9775L70.3868 29.4388L76.4021 42.7663L85.7346 60.8868L88.4689 66.2613L89.9269 71.2363L90.4742 72.7615H91.422V71.8896L92.187 61.6858L93.609 49.1572L94.9944 33.0337L95.4686 28.4952L97.7288 23.0478L102.212 20.1062L105.712 21.7765L108.592 25.8802L108.191 28.5305L106.478 39.6067L103.124 56.9652L100.937 68.5855H102.212L103.671 67.1326L109.577 59.3252L119.492 46.9782L123.868 42.076L128.971 36.6651L132.252 34.0871H138.45L143.006 40.8416L140.965 47.8136L134.585 55.8754L129.299 62.7022L121.717 72.8702L116.977 81.0048L117.415 81.6581L118.545 81.5494L135.679 77.918L144.939 76.2472L155.985 74.3589L160.98 76.6831L161.526 79.0437L159.557 83.8736L147.746 86.7782L133.892 89.5383L113.259 94.4046L113.003 94.5861L113.295 94.9492L122.591 95.8205L126.565 96.0385H136.299L154.417 97.3821L159.157 100.505L162 104.318L161.526 107.223L154.235 110.927L144.392 108.603L121.425 103.156L113.55 101.195H112.457V101.849L119.019 108.24L131.049 119.062L146.105 133.006L146.87 136.456L144.939 139.179L142.897 138.889L129.664 128.975L124.56 124.508L113.003 114.813H112.238V115.829L114.899 119.715L128.971 140.777L129.7 147.241L128.68 149.348L125.034 150.618L121.024 149.892L112.785 138.381L104.291 125.416L97.4368 113.796L96.5987 114.268L92.5521 157.663L90.6564 159.878L86.2813 161.548L82.6363 158.789L80.7036 154.322L82.6357 145.498L84.969 133.987L86.8646 124.835L88.578 113.469L89.5989 109.693L89.5258 109.438L88.6877 109.547L80.0843 121.313L66.9966 138.925L56.6432 149.965L54.1642 150.945L49.8623 148.73L50.2633 144.772L52.6691 141.249L66.996 123.093L75.636 111.835L81.2143 105.335L81.1778 104.39H80.8498L42.7894 129.012L36.0084 129.883L33.0919 127.16L33.4564 122.693L34.8418 121.24L46.2887 113.396L46.2533 113.433Z"
fill="currentColor"
/>
</svg>
Expand Down
20 changes: 20 additions & 0 deletions packages/ui/src/icons/codex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SVGProps } from "react";

export function Codex({ className, ...props }: SVGProps<SVGSVGElement>) {
return (
<svg
className={className}
width="83"
height="83"
viewBox="0 0 83 83"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M36.3379 7.2627C38.6203 7.26272 40.7886 7.6962 42.8428 8.56348C44.897 9.43083 46.7235 10.6413 48.3213 12.1934C50.0559 11.6 51.8589 11.3027 53.7305 11.3027C56.789 11.3027 59.6197 12.0561 62.2217 13.5625C64.8236 15.0689 66.9001 17.1237 68.4521 19.7256C70.0498 22.3276 70.8496 25.2264 70.8496 28.4219C70.8496 29.7456 70.6669 31.1835 70.3018 32.7354C72.1276 34.4243 73.5426 36.3873 74.5469 38.624C75.5511 40.8152 76.0537 43.1207 76.0537 45.54C76.0537 48.0051 75.5285 50.3796 74.4785 52.6621C73.4286 54.9445 71.9446 56.9302 70.0273 58.6191C68.1558 60.2624 65.9647 61.4039 63.4541 62.043C62.9519 64.6449 61.8787 66.9732 60.2354 69.0273C58.6377 71.1271 56.6519 72.7702 54.2783 73.957C51.9046 75.1439 49.371 75.7373 46.6777 75.7373C44.3952 75.7373 42.2262 75.3039 40.1719 74.4365C38.1178 73.5692 36.292 72.3596 34.6943 70.8076C32.9598 71.401 31.1567 71.6972 29.2852 71.6973C26.2267 71.6973 23.3959 70.9448 20.7939 69.4385C18.1919 67.932 16.0919 65.8774 14.4941 63.2754C12.9421 60.6734 12.166 57.7745 12.166 54.5791C12.166 53.2553 12.3487 51.8167 12.7139 50.2646C10.8879 48.5756 9.47302 46.6355 8.46875 44.4443C7.46448 42.2075 6.96191 39.8794 6.96191 37.46C6.96194 34.9949 7.48719 32.6213 8.53711 30.3389C9.587 28.0565 11.0474 26.0935 12.9189 24.4502C14.8362 22.7611 17.0508 21.5971 19.5615 20.958C20.0636 18.3561 21.1133 16.0278 22.7109 13.9736C24.3543 11.8737 26.3635 10.2299 28.7373 9.04297C31.111 7.85617 33.6447 7.2627 36.3379 7.2627ZM29.168 30.7559C28.4807 29.5585 26.9533 29.1448 25.7559 29.832C24.5585 30.5193 24.1449 32.0467 24.832 33.2441L29.46 41.3086L24.8398 49.2422C24.1452 50.4352 24.5492 51.9653 25.7422 52.6602C26.9353 53.3549 28.4653 52.9508 29.1602 51.7578L34.2168 43.0752C34.8467 41.9935 34.8515 40.658 34.2285 39.5723L29.168 30.7559ZM44.1211 47.6611C42.7405 47.6612 41.6212 48.7806 41.6211 50.1611C41.6211 51.5418 42.7404 52.6611 44.1211 52.6611H56.6211C58.0018 52.6611 59.1211 51.5418 59.1211 50.1611C59.121 48.7806 58.0017 47.6612 56.6211 47.6611H44.1211Z"
fill="#262626"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions packages/ui/src/icons/cursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SVGProps } from "react";

export function Cursor({ className, ...props }: SVGProps<SVGSVGElement>) {
return (
<svg
className={className}
width="180"
height="180"
viewBox="0 0 180 180"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M154.347 49.9771L93.6488 14.8463C91.6997 13.7179 89.2946 13.7179 87.3455 14.8463L26.6501 49.9771C25.0116 50.9255 24 52.6795 24 54.5792V125.421C24 127.32 25.0116 129.074 26.6501 130.023L87.3484 165.154C89.2975 166.282 91.7025 166.282 93.6516 165.154L154.35 130.023C155.988 129.074 157 127.32 157 125.421V54.5792C157 52.6795 155.988 50.9255 154.35 49.9771H154.347ZM150.534 57.4187L91.939 159.16C91.5429 159.846 90.4972 159.566 90.4972 158.772V92.1525C90.4972 90.8213 89.7876 89.5901 88.6364 88.9216L31.0868 55.6133C30.4029 55.2162 30.6822 54.1678 31.4744 54.1678H148.665C150.329 54.1678 151.369 55.9761 150.537 57.4215H150.534V57.4187Z"
fill="currentColor"
/>
</svg>
);
}
2 changes: 2 additions & 0 deletions packages/ui/src/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { ComponentType, SVGProps } from "react";
// custom icons
export * from "./arrow-up-right-2";
export * from "./copy";
export * from "./codex";
export * from "./crown-small";
export * from "./cursor";
export * from "./dub-analytics";
export * from "./dub-api";
export * from "./dub-crafted-shield";
Expand Down
Loading