Skip to content

Commit 4d1dc0e

Browse files
committed
chore: meeting coderabbit suggestions
1 parent 5fd45ef commit 4d1dc0e

File tree

1 file changed

+79
-24
lines changed

1 file changed

+79
-24
lines changed

app/[lang]/_components/LocalizedLink.tsx

Lines changed: 79 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> & {
@@ -32,19 +31,61 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
3231
const pathname = usePathname()
3332
const currentLanguage = getLanguageFromPath(pathname) || DEFAULT_LANGUAGE
3433

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-
: ''
34+
// Preserve UrlObject hrefs (pathname + query + hash) when provided
35+
const hrefObj = typeof href === 'object' && href !== null ? (href as UrlObject) : undefined
4536

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

4990
// Compose click handler for external app links
5091
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
@@ -57,11 +98,15 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
5798
// ignore
5899
}
59100
e.preventDefault()
101+
const absolute = hrefObj
102+
? fullUrlFromObj(hrefObj)
103+
: hrefString || (typeof href === 'string' ? href : '')
104+
if (!absolute) return
60105
try {
61-
window.open(hrefString || (typeof href === 'string' ? href : ''), '_blank', 'noopener,noreferrer')
106+
window.open(absolute, '_blank', 'noopener,noreferrer')
62107
} catch {
63-
if (hrefString) {
64-
window.location.assign(hrefString)
108+
if (absolute) {
109+
window.location.assign(absolute)
65110
}
66111
}
67112
}
@@ -71,12 +116,16 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
71116
}
72117

73118
// Don't modify external links, anchors, or already localized paths
119+
const isExternalObject = !!hrefObj && !!(hrefObj.host || hrefObj.hostname || hrefObj.protocol)
74120
if (
75-
hrefString?.startsWith('http') ||
76-
hrefString?.startsWith('#') ||
77-
hrefString?.startsWith('mailto:') ||
78-
hrefString?.startsWith('tel:') ||
79-
!hrefString?.startsWith('/')
121+
typeof href === 'string'
122+
? hrefString.startsWith('http') ||
123+
hrefString.startsWith('#') ||
124+
hrefString.startsWith('mailto:') ||
125+
hrefString.startsWith('tel:') ||
126+
!hrefString.startsWith('/')
127+
: // href is an object
128+
isExternalObject
80129
) {
81130
return (
82131
<Link
@@ -92,12 +141,18 @@ export const LocalizedLink = forwardRef<HTMLAnchorElement, TLocalizedLinkProps>(
92141
// Check if the href already has a language prefix
93142
const hasLanguagePrefix = hrefString.match(/^\/([a-z]{2})(\/|$)/)
94143

95-
// Build the localized href
96-
let localizedHref = hrefString
144+
// Build the localized href. Preserve query/hash by returning a UrlObject when the original
145+
// href was a UrlObject, otherwise return a string.
146+
let localizedPathname = hrefString
97147
if (!hasLanguagePrefix && currentLanguage !== DEFAULT_LANGUAGE) {
98-
localizedHref = `/${currentLanguage}${hrefString}`
148+
localizedPathname = `/${currentLanguage}${hrefString}`
99149
}
100150

151+
const localizedHref = hrefObj
152+
? // copy the original UrlObject but replace/normalize pathname
153+
({...hrefObj, pathname: localizedPathname} as UrlObject)
154+
: localizedPathname
155+
101156
return (
102157
<Link
103158
href={localizedHref}

0 commit comments

Comments
 (0)