-
Notifications
You must be signed in to change notification settings - Fork 214
Expand file tree
/
Copy pathseo-hoc.tsx
More file actions
145 lines (132 loc) · 5.48 KB
/
seo-hoc.tsx
File metadata and controls
145 lines (132 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
* Copyright (c) 2025, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useState, useEffect, useRef} from 'react'
import {useBlockNavigation, useRoutes} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
import {useUrlMapping} from '@salesforce/commerce-sdk-react'
import {useLocation, Redirect} from 'react-router-dom'
import {useApplicationExtensionsStore} from '@salesforce/pwa-kit-extension-sdk/react'
import {useExtensionConfig} from '../hooks/use-extension-config'
import {matchPath} from '../utils/route-match-utils'
import {ROUTING_MODE} from '../constants'
type SeoHOCProps = React.ComponentPropsWithoutRef<any>
interface UrlMappingResponse {
resourceType?: string
resourceId?: string
destinationUrl?: string
}
const getComponent = (
routes: Array<{component: React.ComponentType<any> | undefined; path: string}>,
resourceTypeToComponentMap: {[key: string]: string},
resourceType: string
) => {
const ComponentClass = routes.find((_route) =>
_route.component?.displayName?.includes(resourceTypeToComponentMap[resourceType])
)?.component
return ComponentClass
}
const seoHOC = <P extends object>(WrappedComponent: React.ComponentType<P>) => {
const SeoHOC: React.FC<P> = (props: SeoHOCProps) => {
const location = useLocation()
const {routes, setRoutes} = useRoutes()
const {resourceTypeToComponentMap, routingMode} = useExtensionConfig()
const [urlSegment, setUrlSegment] = useState(location.pathname)
const {setIsNavigationBlocked, siteLocale} = useApplicationExtensionsStore((state) => {
return state.state['@salesforce/extension-commerce-bm-seo']
})
const resolveRef = useRef<(result?: object) => void>()
// If routingMode is "router_first" and a predefined route matches, skip the getUrlMapping API call.
const skipMappingCall =
routingMode === ROUTING_MODE.ROUTER_FIRST &&
matchPath(location.pathname, routes, {filterWildcardRoutes: true})
// Disabling the hook on render so it's only called when refetch is called
const {refetch} = useUrlMapping(
{
parameters: {
urlSegment: urlSegment,
locale: siteLocale
}
},
{
enabled: false
}
)
useEffect(() => {
const fetchData = async () => {
if (!urlSegment) {
return
}
if (skipMappingCall) {
return
}
const result = await refetch()
if (!resolveRef.current) return
if (!result || result.status === 'error') {
resolveRef.current(undefined)
return
}
if (result.data?.destinationUrl) {
resolveRef.current(result.data)
} else {
resolveRef.current(undefined)
}
}
void fetchData().catch(console.error)
}, [urlSegment, skipMappingCall])
const {isBlocked: isNavigationBlocked} = useBlockNavigation(
async (location: Location, _: string) => {
// Early exit if configured to check the Router Context first and found a matching route
if (skipMappingCall) {
return
}
const urlMappingResponse = await new Promise<UrlMappingResponse | undefined>(
(resolve, __) => {
const nextSegment = location.pathname
// So that this promise can be resolved and navigation is unblocked outside this function
resolveRef.current = resolve
setUrlSegment(nextSegment)
}
)
// If no redirect rule exists, go to original url
if (urlMappingResponse === undefined) {
return
}
let Component
let props
// If the Redirect type is URL do a Redirect, else load matching component
if (!urlMappingResponse.resourceType) {
Component = Redirect
props = {
to: urlMappingResponse.destinationUrl
}
} else {
Component = getComponent(
routes,
resourceTypeToComponentMap,
urlMappingResponse.resourceType
)
props = {
[`${urlMappingResponse.resourceType}Id`]: urlMappingResponse.resourceId
}
}
setRoutes([
{
path: location.pathname,
component: () => <Component {...props} />
},
...routes
])
}
)
// Inform other areas of the app (e.g. other extensions) when navigation is being blocked by SEO logic
useEffect(() => {
setIsNavigationBlocked(isNavigationBlocked)
}, [isNavigationBlocked])
return <WrappedComponent {...(props as P)} />
}
return SeoHOC
}
export default seoHOC