Skip to content

Commit 017d833

Browse files
authored
Add UTM parameter preservation for Copilot plans page (#57674)
1 parent bf43f58 commit 017d833

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useEffect } from 'react'
2+
import { useRouter } from 'next/router'
3+
4+
type UtmPreserverProps = {
5+
// CSS selector for links that should preserve UTM parameters
6+
linkSelector?: string
7+
// Specific page paths where this component should be active
8+
activePaths?: string[]
9+
}
10+
11+
export const UtmPreserver = ({
12+
linkSelector = 'a[href*="github.com/copilot"], a[href*="github.com/github-copilot"]',
13+
activePaths = ['/copilot/get-started/plans'],
14+
}: UtmPreserverProps) => {
15+
const router = useRouter()
16+
17+
useEffect(() => {
18+
// Check if current page should have UTM preservation
19+
const shouldPreserveUtm = activePaths.some((path) => router.asPath.includes(path))
20+
if (!shouldPreserveUtm) return
21+
22+
// Extract UTM parameters from current URL
23+
const getUtmParams = (): URLSearchParams => {
24+
const urlParams = new URLSearchParams(window.location.search)
25+
const utmParams = new URLSearchParams()
26+
27+
for (const [key, value] of urlParams) {
28+
if (key.startsWith('utm_')) {
29+
utmParams.set(key, value)
30+
}
31+
}
32+
33+
return utmParams
34+
}
35+
36+
// Add UTM parameters to a URL
37+
const addUtmParamsToUrl = (url: string, utmParams: URLSearchParams): string => {
38+
try {
39+
const urlObj = new URL(url)
40+
41+
for (const [key, value] of utmParams) {
42+
urlObj.searchParams.set(key, value)
43+
}
44+
45+
return urlObj.toString()
46+
} catch {
47+
// If URL parsing fails, return original URL
48+
return url
49+
}
50+
}
51+
52+
// Apply UTM parameters to relevant links
53+
const applyUtmToLinks = (): void => {
54+
const utmParams = getUtmParams()
55+
56+
if (utmParams.toString() === '') return
57+
58+
const links = document.querySelectorAll<HTMLAnchorElement>(linkSelector)
59+
60+
links.forEach((link) => {
61+
if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) {
62+
link.href = addUtmParamsToUrl(link.href, utmParams)
63+
}
64+
})
65+
}
66+
67+
// Handle click events for dynamic link modification
68+
const handleLinkClick = (event: Event): void => {
69+
const link = (event.target as Element)?.closest('a') as HTMLAnchorElement
70+
if (!link) return
71+
72+
// Check if this link matches our selector
73+
if (!link.matches(linkSelector)) return
74+
75+
const utmParams = getUtmParams()
76+
if (utmParams.toString() === '') return
77+
78+
if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) {
79+
link.href = addUtmParamsToUrl(link.href, utmParams)
80+
}
81+
}
82+
83+
// Apply UTM parameters immediately to existing links
84+
applyUtmToLinks()
85+
86+
// Also handle clicks for any dynamically added links
87+
document.addEventListener('click', handleLinkClick, true)
88+
89+
// Re-apply when the route changes (for single-page navigation)
90+
const handleRouteChange = () => {
91+
// Small delay to ensure DOM has updated
92+
setTimeout(applyUtmToLinks, 100)
93+
}
94+
95+
router.events.on('routeChangeComplete', handleRouteChange)
96+
97+
// Cleanup
98+
return () => {
99+
document.removeEventListener('click', handleLinkClick, true)
100+
router.events.off('routeChangeComplete', handleRouteChange)
101+
}
102+
}, [router.asPath, router.events, linkSelector, activePaths])
103+
104+
// This component doesn't render anything
105+
return null
106+
}

src/frame/components/article/ArticlePage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
2121
import { Link } from '@/frame/components/Link'
2222
import { useTranslation } from '@/languages/components/useTranslation'
2323
import { LinkPreviewPopover } from '@/links/components/LinkPreviewPopover'
24+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
2425

2526
const ClientSideRefresh = dynamic(() => import('@/frame/components/ClientSideRefresh'), {
2627
ssr: false,
@@ -101,6 +102,7 @@ export const ArticlePage = () => {
101102
return (
102103
<DefaultLayout>
103104
<LinkPreviewPopover />
105+
<UtmPreserver />
104106
{isDev && <ClientSideRefresh />}
105107
{router.pathname.includes('/rest/') && <RestRedirect />}
106108
{currentLayout === 'inline' ? (

0 commit comments

Comments
 (0)