Skip to content

Commit 20a5b98

Browse files
committed
chore: meeting coderabbit suggestions
1 parent a36c855 commit 20a5b98

File tree

1 file changed

+78
-24
lines changed

1 file changed

+78
-24
lines changed

app/[lang]/_components/LocalizedLink.tsx

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {UrlObject} from 'url'
1212

1313
/* eslint-disable @typescript-eslint/naming-convention */
1414
declare const __adrsbl: {run: (event: string, conversion: boolean) => void} | undefined
15-
/* eslint-enable @typescript-eslint/naming-convention */
1615

1716
type TLocalizedLinkProps = LinkProps &
1817
AnchorHTMLAttributes<HTMLAnchorElement> & {
@@ -27,19 +26,61 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
2726
const pathname = usePathname()
2827
const currentLanguage = getLanguageFromPath(pathname) || DEFAULT_LANGUAGE
2928

30-
// Convert href to string for processing
31-
const hrefString =
32-
typeof href === 'string'
33-
? href
34-
: typeof href === 'object' &&
35-
href !== null &&
36-
'pathname' in href &&
37-
typeof (href as UrlObject).pathname === 'string'
38-
? (href as UrlObject).pathname
39-
: ''
29+
// Preserve UrlObject hrefs (pathname + query + hash) when provided
30+
const hrefObj = typeof href === 'object' && href !== null ? (href as UrlObject) : undefined
4031

41-
// External app.shapeshift.com link detection
42-
const isAppLink = typeof hrefString === 'string' && /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(hrefString)
32+
// Convert href to string for pathname-based processing when needed
33+
const hrefString = typeof href === 'string' ? href : (hrefObj?.pathname ?? '')
34+
35+
// Helper to build a full absolute URL string from a UrlObject when it has host/protocol
36+
const fullUrlFromObj = (obj: UrlObject) => {
37+
if (!obj) {
38+
return ''
39+
}
40+
const protocol = (obj.protocol as string) ?? ''
41+
const host = (obj.host as string) ?? (obj.hostname as string) ?? ''
42+
const pathname = (obj.pathname as string) ?? ''
43+
const hash = (obj.hash as string) ?? ''
44+
let query = ''
45+
// Support both string-form and object-form queries.
46+
if (obj.query) {
47+
if (typeof obj.query === 'string') {
48+
// Use provided string query, ensure it starts with '?'
49+
const raw = obj.query as string
50+
query = raw.startsWith('?') ? raw : `?${raw}`
51+
} else if (typeof obj.query === 'object') {
52+
// obj.query can be Record<string, string | string[]>.
53+
const params = new URLSearchParams()
54+
for (const [k, v] of Object.entries(obj.query as Record<string, string | string[]>)) {
55+
if (Array.isArray(v)) {
56+
for (const item of v) {
57+
params.append(k, String(item))
58+
}
59+
} else if (v !== undefined && v !== null) {
60+
params.append(k, String(v))
61+
}
62+
}
63+
const qs = params.toString()
64+
query = qs ? `?${qs}` : ''
65+
}
66+
}
67+
if (host) {
68+
return `${protocol || 'https:'}//${host}${pathname}${query}${hash}`
69+
}
70+
return `${pathname}${query}${hash}`
71+
}
72+
73+
// External app.shapeshift.com link detection (works for string hrefs and UrlObject with host)
74+
const isAppLink = (() => {
75+
if (typeof href === 'string') {
76+
return /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(href)
77+
}
78+
if (hrefObj) {
79+
const full = fullUrlFromObj(hrefObj)
80+
return /^https?:\/\/app\.shapeshift\.com(\/|$)/i.test(full)
81+
}
82+
return false
83+
})()
4384

4485
// Compose click handler for external app links
4586
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
@@ -52,11 +93,14 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
5293
// ignore
5394
}
5495
e.preventDefault()
96+
const absolute = hrefObj
97+
? fullUrlFromObj(hrefObj)
98+
: hrefString || (typeof href === 'string' ? href : '')
5599
try {
56-
window.open(hrefString || (typeof href === 'string' ? href : ''), '_blank', 'noopener,noreferrer')
100+
window.open(absolute, '_blank', 'noopener,noreferrer')
57101
} catch {
58-
if (hrefString) {
59-
window.location.assign(hrefString)
102+
if (absolute) {
103+
window.location.assign(absolute)
60104
}
61105
}
62106
}
@@ -66,12 +110,16 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
66110
}
67111

68112
// Don't modify external links, anchors, or already localized paths
113+
const isExternalObject = !!hrefObj && !!(hrefObj.host || hrefObj.hostname || hrefObj.protocol)
69114
if (
70-
hrefString?.startsWith('http') ||
71-
hrefString?.startsWith('#') ||
72-
hrefString?.startsWith('mailto:') ||
73-
hrefString?.startsWith('tel:') ||
74-
!hrefString?.startsWith('/')
115+
typeof href === 'string'
116+
? hrefString.startsWith('http') ||
117+
hrefString.startsWith('#') ||
118+
hrefString.startsWith('mailto:') ||
119+
hrefString.startsWith('tel:') ||
120+
!hrefString.startsWith('/')
121+
: // href is an object
122+
isExternalObject
75123
) {
76124
return (
77125
<Link
@@ -87,12 +135,18 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
87135
// Check if the href already has a language prefix
88136
const hasLanguagePrefix = hrefString.match(/^\/([a-z]{2})(\/|$)/)
89137

90-
// Build the localized href
91-
let localizedHref = hrefString
138+
// Build the localized href. Preserve query/hash by returning a UrlObject when the original
139+
// href was a UrlObject, otherwise return a string.
140+
let localizedPathname = hrefString
92141
if (!hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE) {
93-
localizedHref = `/${currentLanguage}${hrefString}`
142+
localizedPathname = `/${currentLanguage}${hrefString}`
94143
}
95144

145+
const localizedHref = hrefObj
146+
? // copy the original UrlObject but replace/normalize pathname
147+
({...hrefObj, pathname: localizedPathname} as UrlObject)
148+
: localizedPathname
149+
96150
return (
97151
<Link
98152
href={localizedHref}

0 commit comments

Comments
 (0)