Skip to content

Commit 5fd45ef

Browse files
committed
click-app-event: instrument LocalizedLink to fire Addressable Pixel on app.shapeshift.com clicks
1 parent 29665de commit 5fd45ef

File tree

2 files changed

+94
-55
lines changed

2 files changed

+94
-55
lines changed
Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,113 @@
1-
'use client';
1+
'use client'
22

3-
import Link from 'next/link';
4-
import {usePathname} from 'next/navigation';
5-
import {forwardRef} from 'react';
3+
import Link from 'next/link'
4+
import {usePathname} from 'next/navigation'
5+
import {forwardRef} from 'react'
66

7-
import {DEFAULT_LANGUAGE, getLanguageFromPath} from '@/app/[lang]/_utils/i18nconfig';
7+
import {DEFAULT_LANGUAGE, getLanguageFromPath} from '@/app/[lang]/_utils/i18nconfig'
88

9-
import type {LinkProps} from 'next/link';
10-
import type {AnchorHTMLAttributes, ReactNode} from 'react';
11-
import type {UrlObject} from 'url';
9+
import type {LinkProps} from 'next/link'
10+
import type {AnchorHTMLAttributes, ReactNode} from 'react'
11+
import type {UrlObject} from 'url'
12+
13+
/* eslint-disable @typescript-eslint/naming-convention */
14+
declare const __adrsbl: {run: (event: string, conversion: boolean) => void} | undefined
15+
/* eslint-enable @typescript-eslint/naming-convention */
1216

1317
type TLocalizedLinkProps = LinkProps &
1418
AnchorHTMLAttributes<HTMLAnchorElement> & {
15-
children: ReactNode;
16-
};
19+
children: ReactNode
20+
}
1721

1822
/**
1923
* A localized version of Next.js Link that automatically prepends the current language
2024
* to internal links when needed.
25+
*
26+
* - Preserves UrlObject hrefs (pathname, query, hash).
27+
* - Detects and leaves external links unchanged.
28+
* - Opens app.shapeshift.com links in a new tab and fires the Addressable pixel event.
2129
*/
22-
export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(({href, children, ...props}, ref) => {
23-
const pathname = usePathname();
24-
const currentLanguage = getLanguageFromPath(pathname) || DEFAULT_LANGUAGE;
30+
export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
31+
({href, children, onClick, ...props}, ref) => {
32+
const pathname = usePathname()
33+
const currentLanguage = getLanguageFromPath(pathname) || DEFAULT_LANGUAGE
34+
35+
// Convert href to string for processing
36+
const hrefString =
37+
typeof href === 'string'
38+
? href
39+
: typeof href === 'object' &&
40+
href !== null &&
41+
'pathname' in href &&
42+
typeof (href as UrlObject).pathname === 'string'
43+
? (href as UrlObject).pathname
44+
: ''
45+
46+
// External app.shapeshift.com link detection
47+
const isAppLink = typeof hrefString === 'string' && /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(hrefString)
48+
49+
// Compose click handler for external app links
50+
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
51+
if (isAppLink) {
52+
try {
53+
if (__adrsbl?.run) {
54+
__adrsbl.run('app_click', true)
55+
}
56+
} catch {
57+
// ignore
58+
}
59+
e.preventDefault()
60+
try {
61+
window.open(hrefString || (typeof href === 'string' ? href : ''), '_blank', 'noopener,noreferrer')
62+
} catch {
63+
if (hrefString) {
64+
window.location.assign(hrefString)
65+
}
66+
}
67+
}
68+
if (onClick) {
69+
onClick(e)
70+
}
71+
}
72+
73+
// Don't modify external links, anchors, or already localized paths
74+
if (
75+
hrefString?.startsWith('http') ||
76+
hrefString?.startsWith('#') ||
77+
hrefString?.startsWith('mailto:') ||
78+
hrefString?.startsWith('tel:') ||
79+
!hrefString?.startsWith('/')
80+
) {
81+
return (
82+
<Link
83+
href={href}
84+
ref={ref}
85+
{...props}
86+
onClick={isAppLink ? handleClick : onClick}>
87+
{children}
88+
</Link>
89+
)
90+
}
2591

26-
// Convert href to string for processing
27-
const hrefString =
28-
typeof href === 'string'
29-
? href
30-
: typeof href === 'object' &&
31-
href !== null &&
32-
'pathname' in href &&
33-
typeof (href as UrlObject).pathname === 'string'
34-
? (href as UrlObject).pathname
35-
: '';
92+
// Check if the href already has a language prefix
93+
const hasLanguagePrefix = hrefString.match(/^\/([a-z]{2})(\/|$)/)
94+
95+
// Build the localized href
96+
let localizedHref = hrefString
97+
if (!hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE) {
98+
localizedHref = `/${currentLanguage}${hrefString}`
99+
}
36100

37-
// Don't modify external links, anchors, or already localized paths
38-
if (
39-
hrefString?.startsWith('http') ||
40-
hrefString?.startsWith('#') ||
41-
hrefString?.startsWith('mailto:') ||
42-
hrefString?.startsWith('tel:') ||
43-
!hrefString?.startsWith('/')
44-
) {
45101
return (
46102
<Link
47-
href={href}
103+
href={localizedHref}
48104
ref={ref}
49-
{...props}>
105+
{...props}
106+
onClick={onClick}>
50107
{children}
51108
</Link>
52-
);
109+
)
53110
}
111+
)
54112

55-
// Check if the href already has a language prefix
56-
const hasLanguagePrefix = hrefString.match(/^\/[a-z]{2}(\/|$)/);
57-
58-
// Build the localized href
59-
let localizedHref = hrefString;
60-
if (!hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE) {
61-
localizedHref = `/${currentLanguage}${hrefString}`;
62-
}
63-
64-
return (
65-
<Link
66-
href={localizedHref}
67-
ref={ref}
68-
{...props}>
69-
{children}
70-
</Link>
71-
);
72-
});
73-
74-
LocalizedLink.displayName = 'LocalizedLink';
113+
LocalizedLink.displayName = 'LocalizedLink'

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@
3333
"eslint.config.mjs",
3434
"postcss.config.mjs"
3535
],
36-
"exclude": ["node_modules", ".next"]
36+
"exclude": ["node_modules", ".next", "scripts"]
3737
}

0 commit comments

Comments
 (0)