@riosst100/pwa-marketplace 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -0
- package/src/Utilities/graphQL.js +76 -0
- package/src/componentOverrideMapping.js +11 -0
- package/src/components/Header/websiteSwitcher.js +109 -0
- package/src/components/Header/websiteSwitcher.module.css +111 -0
- package/src/components/Header/websiteSwitcher.shimmer.js +6 -0
- package/src/components/Header/websiteSwitcherItem.js +47 -0
- package/src/components/Header/websiteSwitcherItem.module.css +20 -0
- package/src/index.js +7 -0
- package/src/intercept.js +50 -0
- package/src/moduleOverrideWebpackPlugin.js +71 -0
- package/src/overwrites/peregrine/lib/talons/Adapter/useAdapter.js +208 -0
- package/src/overwrites/peregrine/lib/talons/Header/storeSwitcher.gql.js +45 -0
- package/src/overwrites/peregrine/lib/talons/Header/useStoreSwitcher.js +204 -0
- package/src/overwrites/pwa-buildpack/lib/queries/getAvailableStoresConfigData.graphql +11 -0
- package/src/overwrites/venia-ui/lib/components/Adapter/adapter.js +110 -0
- package/src/overwrites/venia-ui/lib/components/Header/header.js +116 -0
- package/src/overwrites/venia-ui/lib/components/Header/storeSwitcher.js +118 -0
- package/src/overwrites/venia-ui/lib/components/Header/switcherItem.js +47 -0
- package/src/overwrites/venia-ui/lib/components/StoreCodeRoute/storeCodeRoute.js +75 -0
- package/src/queries/getAvailableWebsitesConfigData.graphql +14 -0
- package/src/queries/index.js +30 -0
- package/src/talons/Header/useWebsiteSwitcher.js +219 -0
- package/src/talons/Header/websiteSwitcher.gql.js +45 -0
- package/src/talons/WebsiteByIp/getWebsiteByIp.gql.js +14 -0
- package/src/talons/WebsiteByIp/useWebsiteByIp.js +33 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ApolloLink } from '@apollo/client';
|
|
2
|
+
import { InMemoryCache } from '@apollo/client/cache';
|
|
3
|
+
import { ApolloClient } from '@apollo/client/core';
|
|
4
|
+
import { CachePersistor } from 'apollo-cache-persist';
|
|
5
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import attachClient from '@magento/peregrine/lib/Apollo/attachClientToStore';
|
|
8
|
+
import { clearCartDataFromCache } from '@magento/peregrine/lib/Apollo/clearCartDataFromCache';
|
|
9
|
+
import { clearCustomerDataFromCache } from '@magento/peregrine/lib/Apollo/clearCustomerDataFromCache';
|
|
10
|
+
import { CACHE_PERSIST_PREFIX } from '@magento/peregrine/lib/Apollo/constants';
|
|
11
|
+
import getLinks from '@magento/peregrine/lib/Apollo/links';
|
|
12
|
+
import typePolicies from '@magento/peregrine/lib/Apollo/policies';
|
|
13
|
+
import { BrowserPersistence } from '@magento/peregrine/lib/util';
|
|
14
|
+
|
|
15
|
+
const isServer = !globalThis.document;
|
|
16
|
+
const storage = new BrowserPersistence();
|
|
17
|
+
|
|
18
|
+
export const useAdapter = props => {
|
|
19
|
+
const { apiUrl, configureLinks, origin, store, styles } = props;
|
|
20
|
+
|
|
21
|
+
// Custom
|
|
22
|
+
const websiteCodes = [];
|
|
23
|
+
|
|
24
|
+
AVAILABLE_WEBSITES.forEach(store => {
|
|
25
|
+
websiteCodes.push(store.website_code);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
websiteCodes.sort((a, b) => b.length - a.length);
|
|
29
|
+
|
|
30
|
+
const { location } = globalThis;
|
|
31
|
+
const match = location && location.pathname.split("/")[1];
|
|
32
|
+
let websiteCodeInUrl = websiteCodes.find((str) => str === match);
|
|
33
|
+
|
|
34
|
+
let isBaseWebsite = false;
|
|
35
|
+
|
|
36
|
+
let websiteCode = storage.getItem('website_code');
|
|
37
|
+
|
|
38
|
+
if (!websiteCodeInUrl) {
|
|
39
|
+
isBaseWebsite = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let basename = '/';
|
|
43
|
+
|
|
44
|
+
const urlHasStoreCode = isBaseWebsite ? false : true;
|
|
45
|
+
if (urlHasStoreCode) {
|
|
46
|
+
basename = `/${websiteCode}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const [initialized, setInitialized] = useState(false);
|
|
50
|
+
|
|
51
|
+
const apiBase = useMemo(
|
|
52
|
+
() => apiUrl || new URL('/graphql', origin).toString(),
|
|
53
|
+
[apiUrl, origin]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const apolloLink = useMemo(() => {
|
|
57
|
+
let links = getLinks(apiBase);
|
|
58
|
+
|
|
59
|
+
if (configureLinks) {
|
|
60
|
+
links = configureLinks(links, apiBase);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return ApolloLink.from(Array.from(links.values()));
|
|
64
|
+
}, [apiBase, configureLinks]);
|
|
65
|
+
|
|
66
|
+
const createApolloClient = useCallback((cache, link) => {
|
|
67
|
+
return new ApolloClient({
|
|
68
|
+
cache,
|
|
69
|
+
link,
|
|
70
|
+
ssrMode: isServer
|
|
71
|
+
});
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const createCachePersistor = useCallback((storeCode, cache) => {
|
|
75
|
+
return isServer
|
|
76
|
+
? null
|
|
77
|
+
: new CachePersistor({
|
|
78
|
+
key: `${CACHE_PERSIST_PREFIX}-${storeCode}`,
|
|
79
|
+
cache,
|
|
80
|
+
storage: globalThis.localStorage,
|
|
81
|
+
debug: process.env.NODE_ENV === 'development'
|
|
82
|
+
});
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const clearCacheData = useCallback(
|
|
86
|
+
async (client, cacheType) => {
|
|
87
|
+
const storeCode = storage.getItem('store_view_code') || 'default';
|
|
88
|
+
|
|
89
|
+
// Clear current store
|
|
90
|
+
if (cacheType === 'cart') {
|
|
91
|
+
await clearCartDataFromCache(client);
|
|
92
|
+
} else if (cacheType === 'customer') {
|
|
93
|
+
await clearCustomerDataFromCache(client);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Clear other stores
|
|
97
|
+
for (const store of AVAILABLE_STORE_VIEWS) {
|
|
98
|
+
if (store.store_code !== storeCode) {
|
|
99
|
+
// Get saved data directly from local storage
|
|
100
|
+
const existingStorePersistor = globalThis.localStorage.getItem(
|
|
101
|
+
`${CACHE_PERSIST_PREFIX}-${store.store_code}`
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Make sure we have data available
|
|
105
|
+
if (
|
|
106
|
+
existingStorePersistor &&
|
|
107
|
+
Object.keys(existingStorePersistor).length > 0
|
|
108
|
+
) {
|
|
109
|
+
const storeCache = new InMemoryCache();
|
|
110
|
+
|
|
111
|
+
// Restore available data
|
|
112
|
+
storeCache.restore(JSON.parse(existingStorePersistor));
|
|
113
|
+
|
|
114
|
+
const storeClient = createApolloClient(
|
|
115
|
+
storeCache,
|
|
116
|
+
apolloLink
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
storeClient.persistor = isServer
|
|
120
|
+
? null
|
|
121
|
+
: createCachePersistor(
|
|
122
|
+
store.store_code,
|
|
123
|
+
storeCache
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Clear other store
|
|
127
|
+
if (cacheType === 'cart') {
|
|
128
|
+
await clearCartDataFromCache(storeClient);
|
|
129
|
+
} else if (cacheType === 'customer') {
|
|
130
|
+
await clearCustomerDataFromCache(storeClient);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
[apolloLink, createApolloClient, createCachePersistor]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const apolloClient = useMemo(() => {
|
|
140
|
+
const storeCode = storage.getItem('store_view_code') || 'default';
|
|
141
|
+
const client = createApolloClient(preInstantiatedCache, apolloLink);
|
|
142
|
+
const persistor = isServer
|
|
143
|
+
? null
|
|
144
|
+
: createCachePersistor(storeCode, preInstantiatedCache);
|
|
145
|
+
|
|
146
|
+
client.apiBase = apiBase;
|
|
147
|
+
client.persistor = persistor;
|
|
148
|
+
client.clearCacheData = clearCacheData;
|
|
149
|
+
|
|
150
|
+
return client;
|
|
151
|
+
}, [
|
|
152
|
+
apiBase,
|
|
153
|
+
apolloLink,
|
|
154
|
+
clearCacheData,
|
|
155
|
+
createApolloClient,
|
|
156
|
+
createCachePersistor
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
const getUserConfirmation = useCallback(async (message, callback) => {
|
|
160
|
+
if (typeof globalThis.handleRouteChangeConfirmation === 'function') {
|
|
161
|
+
return globalThis.handleRouteChangeConfirmation(message, callback);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return callback(globalThis.confirm(message));
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
const apolloProps = { client: apolloClient };
|
|
168
|
+
const reduxProps = { store };
|
|
169
|
+
const routerProps = { basename, getUserConfirmation };
|
|
170
|
+
const styleProps = { initialState: styles };
|
|
171
|
+
|
|
172
|
+
// perform blocking async work here
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (initialized) return;
|
|
175
|
+
|
|
176
|
+
// immediately invoke this async function
|
|
177
|
+
(async () => {
|
|
178
|
+
// restore persisted data to the Apollo cache
|
|
179
|
+
await apolloClient.persistor.restore();
|
|
180
|
+
|
|
181
|
+
// attach the Apollo client to the Redux store
|
|
182
|
+
await attachClient(apolloClient);
|
|
183
|
+
|
|
184
|
+
// mark this routine as complete
|
|
185
|
+
setInitialized(true);
|
|
186
|
+
})();
|
|
187
|
+
}, [apolloClient, initialized]);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
apolloProps,
|
|
191
|
+
initialized,
|
|
192
|
+
reduxProps,
|
|
193
|
+
routerProps,
|
|
194
|
+
styleProps,
|
|
195
|
+
urlHasStoreCode
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* To improve initial load time, create an apollo cache object as soon as
|
|
201
|
+
* this module is executed, since it doesn't depend on any component props.
|
|
202
|
+
* The tradeoff is that we may be creating an instance we don't end up needing.
|
|
203
|
+
*/
|
|
204
|
+
const preInstantiatedCache = new InMemoryCache({
|
|
205
|
+
// POSSIBLE_TYPES is injected into the bundle by webpack at build time.
|
|
206
|
+
possibleTypes: POSSIBLE_TYPES,
|
|
207
|
+
typePolicies
|
|
208
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { gql } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
export const GET_STORE_CONFIG_DATA = gql`
|
|
4
|
+
query getStoreConfigData {
|
|
5
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
6
|
+
storeConfig {
|
|
7
|
+
store_code
|
|
8
|
+
store_name
|
|
9
|
+
store_group_name
|
|
10
|
+
website_code
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const GET_ROUTE_DATA = gql`
|
|
16
|
+
query getRouteData($url: String!) {
|
|
17
|
+
route(url: $url) {
|
|
18
|
+
relative_url
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const GET_AVAILABLE_STORES_DATA = gql`
|
|
24
|
+
query getAvailableStoresData {
|
|
25
|
+
# eslint-disable-next-line @graphql-eslint/require-id-when-available
|
|
26
|
+
availableStores {
|
|
27
|
+
default_display_currency_code
|
|
28
|
+
locale
|
|
29
|
+
secure_base_media_url
|
|
30
|
+
store_code
|
|
31
|
+
store_group_code
|
|
32
|
+
store_group_name
|
|
33
|
+
store_name
|
|
34
|
+
store_sort_order
|
|
35
|
+
website_code
|
|
36
|
+
is_default_store
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
getStoreConfigData: GET_STORE_CONFIG_DATA,
|
|
43
|
+
getRouteData: GET_ROUTE_DATA,
|
|
44
|
+
getAvailableStoresData: GET_AVAILABLE_STORES_DATA
|
|
45
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { useQuery } from '@apollo/client';
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useLocation } from 'react-router-dom';
|
|
4
|
+
import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
|
|
5
|
+
import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
|
|
6
|
+
import { BrowserPersistence } from '@magento/peregrine/lib/util';
|
|
7
|
+
import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
|
|
8
|
+
import DEFAULT_OPERATIONS from '@magento/peregrine/lib/talons/Header/storeSwitcher.gql';
|
|
9
|
+
|
|
10
|
+
const storage = new BrowserPersistence();
|
|
11
|
+
|
|
12
|
+
const mapAvailableOptions = (config, stores) => {
|
|
13
|
+
const { store_code: configCode } = config;
|
|
14
|
+
|
|
15
|
+
return stores.reduce((map, store) => {
|
|
16
|
+
const {
|
|
17
|
+
default_display_currency_code: currency,
|
|
18
|
+
locale,
|
|
19
|
+
secure_base_media_url,
|
|
20
|
+
store_code: storeCode,
|
|
21
|
+
website_code: websiteCode,
|
|
22
|
+
store_group_code: storeGroupCode,
|
|
23
|
+
store_group_name: storeGroupName,
|
|
24
|
+
store_name: storeName,
|
|
25
|
+
store_sort_order: sortOrder
|
|
26
|
+
} = store;
|
|
27
|
+
|
|
28
|
+
const isCurrent = storeCode === configCode;
|
|
29
|
+
const option = {
|
|
30
|
+
currency,
|
|
31
|
+
isCurrent,
|
|
32
|
+
locale,
|
|
33
|
+
secure_base_media_url,
|
|
34
|
+
sortOrder,
|
|
35
|
+
storeCode,
|
|
36
|
+
websiteCode,
|
|
37
|
+
storeGroupCode,
|
|
38
|
+
storeGroupName,
|
|
39
|
+
storeName
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return map.set(storeCode, option);
|
|
43
|
+
}, new Map());
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The useStoreSwitcher talon complements the StoreSwitcher component.
|
|
48
|
+
*
|
|
49
|
+
* @param {Array<Object>} [props.availableRoutes] - Hardcoded app routes.
|
|
50
|
+
* @param {Object} [props.operations] - GraphQL operations to be run by the hook.
|
|
51
|
+
*
|
|
52
|
+
* @returns {Map} talonProps.availableStores - Details about the available store views.
|
|
53
|
+
* @returns {String} talonProps.currentStoreName - Name of the current store view.
|
|
54
|
+
* @returns {Boolean} talonProps.storeMenuIsOpen - Whether the menu that this trigger toggles is open or not.
|
|
55
|
+
* @returns {Ref} talonProps.storeMenuRef - A React ref to the menu that this trigger toggles.
|
|
56
|
+
* @returns {Ref} talonProps.storeMenuTriggerRef - A React ref to the trigger element itself.
|
|
57
|
+
* @returns {Function} talonProps.handleTriggerClick - A function for handling when the trigger is clicked.
|
|
58
|
+
* @returns {Function} talonProps.handleSwitchStore - A function for handling when the menu item is clicked.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export const useStoreSwitcher = (props = {}) => {
|
|
62
|
+
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
|
|
63
|
+
const { availableRoutes = [] } = props;
|
|
64
|
+
const internalRoutes = useMemo(() => {
|
|
65
|
+
return availableRoutes.map(path => {
|
|
66
|
+
if (path.exact) {
|
|
67
|
+
return path.pattern;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, [availableRoutes]);
|
|
71
|
+
|
|
72
|
+
const {
|
|
73
|
+
getStoreConfigData,
|
|
74
|
+
getRouteData,
|
|
75
|
+
getAvailableStoresData
|
|
76
|
+
} = operations;
|
|
77
|
+
const { pathname, search: searchParams } = useLocation();
|
|
78
|
+
const {
|
|
79
|
+
elementRef: storeMenuRef,
|
|
80
|
+
expanded: storeMenuIsOpen,
|
|
81
|
+
setExpanded: setStoreMenuIsOpen,
|
|
82
|
+
triggerRef: storeMenuTriggerRef
|
|
83
|
+
} = useDropdown();
|
|
84
|
+
|
|
85
|
+
const { data: storeConfigData } = useQuery(getStoreConfigData, {
|
|
86
|
+
fetchPolicy: 'cache-and-network',
|
|
87
|
+
nextFetchPolicy: 'cache-first'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const fetchRouteData = useAwaitQuery(getRouteData);
|
|
91
|
+
|
|
92
|
+
const { data: availableStoresData } = useQuery(getAvailableStoresData, {
|
|
93
|
+
fetchPolicy: 'cache-and-network',
|
|
94
|
+
nextFetchPolicy: 'cache-first'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const currentStoreName = useMemo(() => {
|
|
98
|
+
if (storeConfigData) {
|
|
99
|
+
return storeConfigData.storeConfig.store_name;
|
|
100
|
+
}
|
|
101
|
+
}, [storeConfigData]);
|
|
102
|
+
|
|
103
|
+
const currentGroupName = useMemo(() => {
|
|
104
|
+
if (storeConfigData) {
|
|
105
|
+
return storeConfigData.storeConfig.store_group_name;
|
|
106
|
+
}
|
|
107
|
+
}, [storeConfigData]);
|
|
108
|
+
|
|
109
|
+
// availableStores => mapped options or empty map if undefined.
|
|
110
|
+
const availableStores = useMemo(() => {
|
|
111
|
+
return (
|
|
112
|
+
(storeConfigData &&
|
|
113
|
+
availableStoresData &&
|
|
114
|
+
mapAvailableOptions(
|
|
115
|
+
storeConfigData.storeConfig,
|
|
116
|
+
availableStoresData.availableStores
|
|
117
|
+
)) ||
|
|
118
|
+
new Map()
|
|
119
|
+
);
|
|
120
|
+
}, [storeConfigData, availableStoresData]);
|
|
121
|
+
|
|
122
|
+
// Create a map of sorted store views for each group.
|
|
123
|
+
const storeGroups = useMemo(() => {
|
|
124
|
+
const groups = new Map();
|
|
125
|
+
|
|
126
|
+
availableStores.forEach(store => {
|
|
127
|
+
const groupCode = store.storeGroupCode;
|
|
128
|
+
if (!groups.has(groupCode)) {
|
|
129
|
+
const groupViews = [store];
|
|
130
|
+
groups.set(groupCode, groupViews);
|
|
131
|
+
} else {
|
|
132
|
+
const groupViews = groups.get(groupCode);
|
|
133
|
+
// Insert store at configured position
|
|
134
|
+
groupViews.splice(store.sortOrder, 0, store);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return groups;
|
|
139
|
+
}, [availableStores]);
|
|
140
|
+
|
|
141
|
+
const getPathname = useCallback(
|
|
142
|
+
async storeCode => {
|
|
143
|
+
if (pathname === '' || pathname === '/') return '';
|
|
144
|
+
let newPath = '';
|
|
145
|
+
if (internalRoutes.includes(pathname)) {
|
|
146
|
+
newPath = pathname;
|
|
147
|
+
} else {
|
|
148
|
+
const { data: routeData } = await fetchRouteData({
|
|
149
|
+
fetchPolicy: 'no-cache',
|
|
150
|
+
variables: {
|
|
151
|
+
url: pathname
|
|
152
|
+
},
|
|
153
|
+
context: { headers: { store: storeCode } }
|
|
154
|
+
});
|
|
155
|
+
if (routeData.route) {
|
|
156
|
+
newPath = routeData.route.relative_url;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return newPath.startsWith('/') ? newPath.substr(1) : newPath;
|
|
160
|
+
},
|
|
161
|
+
[pathname, fetchRouteData, internalRoutes]
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const handleSwitchStore = useCallback(
|
|
165
|
+
// Change store view code and currency to be used in Apollo link request headers
|
|
166
|
+
async (storeCode, websiteCode) => {
|
|
167
|
+
// Do nothing when store view is not present in available stores
|
|
168
|
+
if (!availableStores.has(storeCode)) return;
|
|
169
|
+
|
|
170
|
+
storage.setItem('store_view_code', storeCode);
|
|
171
|
+
storage.setItem('website_code', websiteCode);
|
|
172
|
+
storage.setItem(
|
|
173
|
+
'store_view_currency',
|
|
174
|
+
availableStores.get(storeCode).currency
|
|
175
|
+
);
|
|
176
|
+
storage.setItem(
|
|
177
|
+
'store_view_secure_base_media_url',
|
|
178
|
+
availableStores.get(storeCode).secure_base_media_url
|
|
179
|
+
);
|
|
180
|
+
const pathName = await getPathname(storeCode);
|
|
181
|
+
const newPath = pathName ? `/${pathName}${searchParams}` : '';
|
|
182
|
+
|
|
183
|
+
globalThis.location.assign(`/${websiteCode}${newPath || ''}`);
|
|
184
|
+
},
|
|
185
|
+
[availableStores, getPathname, searchParams]
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const handleTriggerClick = useCallback(() => {
|
|
189
|
+
// Toggle Stores Menu.
|
|
190
|
+
setStoreMenuIsOpen(isOpen => !isOpen);
|
|
191
|
+
}, [setStoreMenuIsOpen]);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
availableStores,
|
|
195
|
+
currentGroupName,
|
|
196
|
+
currentStoreName,
|
|
197
|
+
storeGroups,
|
|
198
|
+
storeMenuRef,
|
|
199
|
+
storeMenuTriggerRef,
|
|
200
|
+
storeMenuIsOpen,
|
|
201
|
+
handleTriggerClick,
|
|
202
|
+
handleSwitchStore
|
|
203
|
+
};
|
|
204
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ApolloProvider } from '@apollo/client';
|
|
3
|
+
import { Provider as ReduxProvider } from 'react-redux';
|
|
4
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
5
|
+
import { useAdapter } from '@magento/peregrine/lib/talons/Adapter/useAdapter';
|
|
6
|
+
import App, { AppContextProvider } from '@magento/venia-ui/lib/components/App';
|
|
7
|
+
import StoreCodeRoute from '@magento/venia-ui/lib/components/StoreCodeRoute';
|
|
8
|
+
import { useWebsiteByIp } from '@riosst100/pwa-multi-website/src/talons/WebsiteByIp/useWebsiteByIp';
|
|
9
|
+
import { BrowserPersistence } from '@magento/peregrine/lib/util';
|
|
10
|
+
|
|
11
|
+
const storage = new BrowserPersistence();
|
|
12
|
+
|
|
13
|
+
const Adapter = props => {
|
|
14
|
+
const [verifyUserIp, setVerifyUserIp] = useState(true);
|
|
15
|
+
const [newRouterProps, setNewRouterProps] = useState(true);
|
|
16
|
+
|
|
17
|
+
const talonProps = useAdapter(props);
|
|
18
|
+
let {
|
|
19
|
+
apolloProps,
|
|
20
|
+
initialized,
|
|
21
|
+
reduxProps,
|
|
22
|
+
routerProps
|
|
23
|
+
} = talonProps;
|
|
24
|
+
|
|
25
|
+
const websiteCodes = [];
|
|
26
|
+
const websiteStores = useMemo(() => ({}), []);
|
|
27
|
+
const storeCurrencies = useMemo(() => ({}), []);
|
|
28
|
+
const storeSecureBaseMediaUrl = useMemo(() => ({}), []);
|
|
29
|
+
|
|
30
|
+
AVAILABLE_WEBSITES.forEach(store => {
|
|
31
|
+
websiteCodes.push(store.website_code);
|
|
32
|
+
websiteStores[store.website_code] = store.store_code;
|
|
33
|
+
storeCurrencies[store.store_code] = store.default_display_currency_code;
|
|
34
|
+
storeSecureBaseMediaUrl[store.store_code] = store.secure_base_media_url;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
websiteCodes.sort((a, b) => b.length - a.length);
|
|
38
|
+
|
|
39
|
+
const { location } = globalThis;
|
|
40
|
+
const match = location && location.pathname.split("/")[1];
|
|
41
|
+
let websiteCodeInUrl = websiteCodes.find((str) => str === match);
|
|
42
|
+
|
|
43
|
+
const { getWebsiteByUserIp } = useWebsiteByIp(websiteCodeInUrl);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (websiteCodeInUrl) {
|
|
47
|
+
setVerifyUserIp(false);
|
|
48
|
+
|
|
49
|
+
storage.removeItem('access_base_website');
|
|
50
|
+
|
|
51
|
+
routerProps.basename = websiteCodeInUrl != process.env.WEBSITE_CODE ? '/'+websiteCodeInUrl : '/';
|
|
52
|
+
|
|
53
|
+
setNewRouterProps(routerProps);
|
|
54
|
+
}
|
|
55
|
+
}, [websiteCodeInUrl])
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!websiteCodeInUrl && getWebsiteByUserIp) {
|
|
59
|
+
setVerifyUserIp(false);
|
|
60
|
+
|
|
61
|
+
const query = new URLSearchParams(location.search);
|
|
62
|
+
const isAccessBaseWebsite = query.get('access_base_website') || (
|
|
63
|
+
storage.getItem('access_base_website') || null
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const websiteCode = getWebsiteByUserIp.countryCode && !isAccessBaseWebsite ? getWebsiteByUserIp.countryCode.toLowerCase() : process.env.WEBSITE_CODE;
|
|
67
|
+
|
|
68
|
+
routerProps.basename = websiteCode != process.env.WEBSITE_CODE && !isAccessBaseWebsite ? '/'+websiteCode : '/';
|
|
69
|
+
|
|
70
|
+
setNewRouterProps(routerProps);
|
|
71
|
+
|
|
72
|
+
if (isAccessBaseWebsite) {
|
|
73
|
+
storage.setItem('access_base_website', 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let storeCodeInUrl = websiteStores[websiteCode];
|
|
77
|
+
|
|
78
|
+
storage.setItem('store_view_code', storeCodeInUrl);
|
|
79
|
+
storage.setItem('website_code', websiteCode);
|
|
80
|
+
storage.setItem(
|
|
81
|
+
'store_view_currency',
|
|
82
|
+
storeCurrencies[storeCodeInUrl]
|
|
83
|
+
);
|
|
84
|
+
storage.setItem(
|
|
85
|
+
'store_view_secure_base_media_url',
|
|
86
|
+
storeSecureBaseMediaUrl[storeCodeInUrl]
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}, [websiteCodeInUrl, getWebsiteByUserIp])
|
|
90
|
+
|
|
91
|
+
// TODO: Replace with app skeleton. See PWA-547.
|
|
92
|
+
if (!initialized) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const children = props.children || <App />;
|
|
97
|
+
|
|
98
|
+
return !verifyUserIp && (
|
|
99
|
+
<ApolloProvider {...apolloProps}>
|
|
100
|
+
<ReduxProvider {...reduxProps}>
|
|
101
|
+
<BrowserRouter {...newRouterProps}>
|
|
102
|
+
<StoreCodeRoute />
|
|
103
|
+
<AppContextProvider>{children}</AppContextProvider>
|
|
104
|
+
</BrowserRouter>
|
|
105
|
+
</ReduxProvider>
|
|
106
|
+
</ApolloProvider>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default Adapter;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { Fragment, Suspense } from 'react';
|
|
2
|
+
import { shape, string } from 'prop-types';
|
|
3
|
+
import { Link, Route } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
import Logo from '@magento/venia-ui/lib/components/Logo';
|
|
6
|
+
import AccountTrigger from '@magento/venia-ui/lib/components/Header/accountTrigger';
|
|
7
|
+
import CartTrigger from '@magento/venia-ui/lib/components/Header/cartTrigger';
|
|
8
|
+
import NavTrigger from '@magento/venia-ui/lib/components/Header/navTrigger';
|
|
9
|
+
import SearchTrigger from '@magento/venia-ui/lib/components/Header/searchTrigger';
|
|
10
|
+
import OnlineIndicator from '@magento/venia-ui/lib/components/Header/onlineIndicator';
|
|
11
|
+
import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
|
|
12
|
+
import resourceUrl from '@magento/peregrine/lib/util/makeUrl';
|
|
13
|
+
|
|
14
|
+
import { useStyle } from '@magento/venia-ui/lib/classify';
|
|
15
|
+
import defaultClasses from '@magento/venia-ui/lib/components/Header/header.module.css';
|
|
16
|
+
import StoreSwitcher from '@magento/venia-ui/lib/components/Header/storeSwitcher';
|
|
17
|
+
import WebsiteSwitcher from '@riosst100/pwa-multi-website/src/components/Header/websiteSwitcher';
|
|
18
|
+
import CurrencySwitcher from '@magento/venia-ui/lib/components/Header/currencySwitcher';
|
|
19
|
+
import MegaMenu from '@magento/venia-ui/lib/components/MegaMenu';
|
|
20
|
+
import PageLoadingIndicator from '@magento/venia-ui/lib/components/PageLoadingIndicator';
|
|
21
|
+
import { useIntl } from 'react-intl';
|
|
22
|
+
|
|
23
|
+
const SearchBar = React.lazy(() => import('@magento/venia-ui/lib/components/SearchBar'));
|
|
24
|
+
|
|
25
|
+
const Header = props => {
|
|
26
|
+
const {
|
|
27
|
+
handleSearchTriggerClick,
|
|
28
|
+
hasBeenOffline,
|
|
29
|
+
isOnline,
|
|
30
|
+
isSearchOpen,
|
|
31
|
+
searchRef,
|
|
32
|
+
searchTriggerRef
|
|
33
|
+
} = useHeader();
|
|
34
|
+
|
|
35
|
+
const classes = useStyle(defaultClasses, props.classes);
|
|
36
|
+
const rootClass = isSearchOpen ? classes.open : classes.closed;
|
|
37
|
+
|
|
38
|
+
const searchBarFallback = (
|
|
39
|
+
<div className={classes.searchFallback} ref={searchRef}>
|
|
40
|
+
<div className={classes.input}>
|
|
41
|
+
<div className={classes.loader}>
|
|
42
|
+
<div className={classes.loaderBefore} />
|
|
43
|
+
<div className={classes.loaderAfter} />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
const searchBar = isSearchOpen ? (
|
|
49
|
+
<Suspense fallback={searchBarFallback}>
|
|
50
|
+
<Route>
|
|
51
|
+
<SearchBar isOpen={isSearchOpen} ref={searchRef} />
|
|
52
|
+
</Route>
|
|
53
|
+
</Suspense>
|
|
54
|
+
) : null;
|
|
55
|
+
|
|
56
|
+
const { formatMessage } = useIntl();
|
|
57
|
+
const title = formatMessage({ id: 'logo.title', defaultMessage: 'Venia' });
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Fragment>
|
|
61
|
+
<div className={classes.switchersContainer}>
|
|
62
|
+
<div className={classes.switchers} data-cy="Header-switchers">
|
|
63
|
+
<WebsiteSwitcher />
|
|
64
|
+
<StoreSwitcher />
|
|
65
|
+
<CurrencySwitcher />
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<header className={rootClass} data-cy="Header-root">
|
|
69
|
+
<div className={classes.toolbar}>
|
|
70
|
+
<div className={classes.primaryActions}>
|
|
71
|
+
<NavTrigger />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<Link
|
|
75
|
+
aria-label={title}
|
|
76
|
+
to={resourceUrl('/')}
|
|
77
|
+
className={classes.logoContainer}
|
|
78
|
+
data-cy="Header-logoContainer"
|
|
79
|
+
>
|
|
80
|
+
<Logo classes={{ logo: classes.logo }} />
|
|
81
|
+
</Link>
|
|
82
|
+
<MegaMenu />
|
|
83
|
+
<div className={classes.secondaryActions}>
|
|
84
|
+
<SearchTrigger
|
|
85
|
+
onClick={handleSearchTriggerClick}
|
|
86
|
+
ref={searchTriggerRef}
|
|
87
|
+
/>
|
|
88
|
+
<AccountTrigger />
|
|
89
|
+
<CartTrigger />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
{searchBar}
|
|
93
|
+
<PageLoadingIndicator absolute />
|
|
94
|
+
</header>
|
|
95
|
+
<OnlineIndicator
|
|
96
|
+
hasBeenOffline={hasBeenOffline}
|
|
97
|
+
isOnline={isOnline}
|
|
98
|
+
/>
|
|
99
|
+
</Fragment>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Header.propTypes = {
|
|
104
|
+
classes: shape({
|
|
105
|
+
closed: string,
|
|
106
|
+
logo: string,
|
|
107
|
+
open: string,
|
|
108
|
+
primaryActions: string,
|
|
109
|
+
secondaryActions: string,
|
|
110
|
+
toolbar: string,
|
|
111
|
+
switchers: string,
|
|
112
|
+
switchersContainer: string
|
|
113
|
+
})
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default Header;
|