@adobedjangir/commerce-admin-management 0.0.2
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/README.md +637 -0
- package/actions/commerce-creds.js +262 -0
- package/actions/configurations/commerce/index.js +41 -0
- package/actions/configurations/commerce-connection-save/index.js +53 -0
- package/actions/configurations/commerce-connection-status/index.js +27 -0
- package/actions/configurations/commerce-connection-test/index.js +47 -0
- package/actions/configurations/export-config/index.js +256 -0
- package/actions/configurations/ext.config.yaml +192 -0
- package/actions/configurations/import-config/index.js +541 -0
- package/actions/configurations/registration/index.js +37 -0
- package/actions/configurations/sync-store-mappings-from-commerce/index.js +190 -0
- package/actions/configurations/system-config-list/index.js +127 -0
- package/actions/configurations/system-config-save/index.js +160 -0
- package/actions/configurations/system-config-schema/index.js +327 -0
- package/actions/utils.js +73 -0
- package/package.json +80 -0
- package/scripts/build-web.js +58 -0
- package/scripts/setup.js +445 -0
- package/src/abdb-config.js +8 -0
- package/src/abdb-helper.js +8 -0
- package/src/index.js +22 -0
- package/src/oauth1a.js +8 -0
- package/src/system-config-crypto.js +8 -0
- package/src/system-config-shared.js +8 -0
- package/web/dist/index.css +305 -0
- package/web/dist/index.js +2986 -0
- package/web/index.js +7 -0
- package/web/src/components/App.js +54 -0
- package/web/src/components/AppSectionNav.js +49 -0
- package/web/src/components/CommerceSetupWizard.js +160 -0
- package/web/src/components/ExtensionRegistration.js +33 -0
- package/web/src/components/MainPage.js +147 -0
- package/web/src/components/SystemConfig.js +1464 -0
- package/web/src/components/SystemConfigSchemaEditor.js +459 -0
- package/web/src/hooks/useConfirm.js +355 -0
- package/web/src/hooks/useSystemConfig.js +238 -0
- package/web/src/hooks/useSystemConfigSchema.js +102 -0
- package/web/src/index.js +46 -0
- package/web/src/nav-icons.js +30 -0
- package/web/src/nav.json +10 -0
- package/web/src/pages/index.js +17 -0
- package/web/src/schema/systemConfigSchema.js +82 -0
- package/web/src/settings.js +101 -0
- package/web/src/styles/index.css +337 -0
- package/web/src/theme.js +104 -0
- package/web/src/utils/storeMappingsFromCommerceRest.js +73 -0
- package/web/src/utils.js +52 -0
- package/web/styles.css +337 -0
package/web/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import { Provider, lightTheme } from '@adobe/react-spectrum'
|
|
10
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
11
|
+
import { Route, Routes, HashRouter } from 'react-router-dom'
|
|
12
|
+
import ExtensionRegistration from './ExtensionRegistration'
|
|
13
|
+
|
|
14
|
+
function App (props) {
|
|
15
|
+
props.runtime.on('configuration', ({ imsOrg, imsToken }) => {
|
|
16
|
+
console.log('configuration change', { imsOrg, imsToken })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<ErrorBoundary onError={onError} FallbackComponent={fallbackComponent}>
|
|
21
|
+
<HashRouter>
|
|
22
|
+
<Provider
|
|
23
|
+
theme={lightTheme}
|
|
24
|
+
colorScheme="light"
|
|
25
|
+
UNSAFE_className="sm-provider"
|
|
26
|
+
>
|
|
27
|
+
<Routes>
|
|
28
|
+
{/* Catch-all: every hash path renders the same shell. MainPage's
|
|
29
|
+
nav registry (getNavItems/getPageComponent) reads
|
|
30
|
+
location.pathname and chooses which page component to mount,
|
|
31
|
+
so React-Router only needs one route here. */}
|
|
32
|
+
<Route
|
|
33
|
+
path="*"
|
|
34
|
+
element={<ExtensionRegistration runtime={props.runtime} ims={props.ims} />}
|
|
35
|
+
/>
|
|
36
|
+
</Routes>
|
|
37
|
+
</Provider>
|
|
38
|
+
</HashRouter>
|
|
39
|
+
</ErrorBoundary>
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
function onError (e, componentStack) {}
|
|
43
|
+
|
|
44
|
+
function fallbackComponent ({ componentStack, error }) {
|
|
45
|
+
return (
|
|
46
|
+
<React.Fragment>
|
|
47
|
+
<h1 style={{ textAlign: 'center', marginTop: '20px' }}>Something went wrong :(</h1>
|
|
48
|
+
<pre>{componentStack + '\n' + error.message}</pre>
|
|
49
|
+
</React.Fragment>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default App
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
Licensed under the Apache License, Version 2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useLocation, useNavigate } from 'react-router-dom'
|
|
7
|
+
import { getNavItems } from '../settings'
|
|
8
|
+
import { getNavIcon } from '../nav-icons'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Top-level navigation. Driven entirely by nav.json + configureWeb({ extraNav }).
|
|
12
|
+
* To add a tab: register a page in pages/ and add a matching entry to nav.json
|
|
13
|
+
* (or pass it via host configureWeb).
|
|
14
|
+
*/
|
|
15
|
+
export default function AppSectionNav ({ rightSlot } = {}) {
|
|
16
|
+
const navigate = useNavigate()
|
|
17
|
+
const location = useLocation()
|
|
18
|
+
const items = getNavItems()
|
|
19
|
+
const activeKey = items.some((it) => it.path === location.pathname)
|
|
20
|
+
? location.pathname
|
|
21
|
+
: (items[0] && items[0].path) || '/'
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="sm-tab-bar">
|
|
25
|
+
<div className="sm-tab-bar__track" role="tablist" aria-label="Application sections">
|
|
26
|
+
{items.map(({ id, path, label, icon }) => {
|
|
27
|
+
const Icon = getNavIcon(icon)
|
|
28
|
+
const active = path === activeKey
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
key={id}
|
|
32
|
+
type="button"
|
|
33
|
+
role="tab"
|
|
34
|
+
aria-selected={active}
|
|
35
|
+
className={`sm-tab${active ? ' is-active' : ''}`}
|
|
36
|
+
onClick={() => { if (!active) navigate(path) }}
|
|
37
|
+
>
|
|
38
|
+
<span className="sm-tab__icon">
|
|
39
|
+
<Icon size="XS" />
|
|
40
|
+
</span>
|
|
41
|
+
{label}
|
|
42
|
+
</button>
|
|
43
|
+
)
|
|
44
|
+
})}
|
|
45
|
+
</div>
|
|
46
|
+
{rightSlot ? <div className="sm-tab-bar__actions">{rightSlot}</div> : null}
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
Licensed under the Apache License, Version 2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState } from 'react'
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
Flex,
|
|
10
|
+
Heading,
|
|
11
|
+
Text,
|
|
12
|
+
TextField,
|
|
13
|
+
Button,
|
|
14
|
+
ButtonGroup,
|
|
15
|
+
ProgressCircle,
|
|
16
|
+
StatusLight,
|
|
17
|
+
Divider,
|
|
18
|
+
Form
|
|
19
|
+
} from '@adobe/react-spectrum'
|
|
20
|
+
import { callAction } from '../utils'
|
|
21
|
+
import { getActionKey } from '../settings'
|
|
22
|
+
|
|
23
|
+
const FIELD_DEFS = [
|
|
24
|
+
{ key: 'baseUrl', label: 'Commerce base URL', placeholder: 'https://store.example.com/', type: 'text' },
|
|
25
|
+
{ key: 'consumerKey', label: 'Consumer key', placeholder: '', type: 'text' },
|
|
26
|
+
{ key: 'consumerSecret', label: 'Consumer secret', placeholder: '', type: 'password' },
|
|
27
|
+
{ key: 'accessToken', label: 'Access token', placeholder: '', type: 'text' },
|
|
28
|
+
{ key: 'accessTokenSecret', label: 'Access token secret', placeholder: '', type: 'password' }
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const EMPTY = FIELD_DEFS.reduce((a, f) => { a[f.key] = ''; return a }, {})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* First-run wizard for Adobe Commerce REST connection.
|
|
35
|
+
*
|
|
36
|
+
* Props:
|
|
37
|
+
* runtime, ims — passed through to callAction
|
|
38
|
+
* initial — masked saved values (used to prefill the URL when "Reconfigure")
|
|
39
|
+
* onCompleted() — called after a successful save; parent should refetch status
|
|
40
|
+
* onCancel?() — only rendered when present (Reconfigure mode)
|
|
41
|
+
*/
|
|
42
|
+
export default function CommerceSetupWizard ({ runtime, ims, initial, onCompleted, onCancel }) {
|
|
43
|
+
const [values, setValues] = useState(() => ({
|
|
44
|
+
...EMPTY,
|
|
45
|
+
...(initial && initial.baseUrl ? { baseUrl: initial.baseUrl } : {})
|
|
46
|
+
}))
|
|
47
|
+
const [testState, setTestState] = useState({ status: 'idle', message: '' })
|
|
48
|
+
const [saveState, setSaveState] = useState({ status: 'idle', message: '' })
|
|
49
|
+
|
|
50
|
+
const set = (k) => (v) => setValues((prev) => ({ ...prev, [k]: v }))
|
|
51
|
+
|
|
52
|
+
const allFilled = FIELD_DEFS.every((f) => String(values[f.key] || '').trim() !== '')
|
|
53
|
+
|
|
54
|
+
async function handleTest () {
|
|
55
|
+
setTestState({ status: 'running', message: 'Testing connection…' })
|
|
56
|
+
try {
|
|
57
|
+
const res = await callAction(
|
|
58
|
+
{ runtime, ims },
|
|
59
|
+
getActionKey('commerceConnectionTest'),
|
|
60
|
+
'',
|
|
61
|
+
values
|
|
62
|
+
)
|
|
63
|
+
const body = res && res.body ? res.body : res
|
|
64
|
+
if (body && body.ok) {
|
|
65
|
+
setTestState({ status: 'ok', message: body.message || 'Connection OK' })
|
|
66
|
+
} else {
|
|
67
|
+
setTestState({ status: 'fail', message: (body && body.message) || 'Connection failed' })
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
setTestState({ status: 'fail', message: e.message || 'Connection failed' })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function handleSave () {
|
|
75
|
+
setSaveState({ status: 'running', message: 'Saving…' })
|
|
76
|
+
try {
|
|
77
|
+
const res = await callAction(
|
|
78
|
+
{ runtime, ims },
|
|
79
|
+
getActionKey('commerceConnectionSave'),
|
|
80
|
+
'',
|
|
81
|
+
values
|
|
82
|
+
)
|
|
83
|
+
const body = res && res.body ? res.body : res
|
|
84
|
+
if (body && body.ok && body.saved) {
|
|
85
|
+
setSaveState({ status: 'ok', message: 'Saved. Loading the rest of the app…' })
|
|
86
|
+
// Hand off to parent — it should refetch status and unmount the wizard.
|
|
87
|
+
if (typeof onCompleted === 'function') onCompleted(body)
|
|
88
|
+
} else {
|
|
89
|
+
setSaveState({ status: 'fail', message: (body && body.message) || 'Save failed' })
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
setSaveState({ status: 'fail', message: e.message || 'Save failed' })
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const testLight = testState.status === 'ok' ? 'positive'
|
|
97
|
+
: testState.status === 'fail' ? 'negative'
|
|
98
|
+
: testState.status === 'running' ? 'notice'
|
|
99
|
+
: 'neutral'
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<View padding="size-400" maxWidth="size-6000" margin="0 auto">
|
|
103
|
+
<Heading level={2}>Connect to Adobe Commerce</Heading>
|
|
104
|
+
<Text>
|
|
105
|
+
Enter the REST/OAuth credentials for your Commerce instance. They are encrypted before
|
|
106
|
+
being saved to App Builder Database. The rest of the app stays disabled until the
|
|
107
|
+
connection is verified.
|
|
108
|
+
</Text>
|
|
109
|
+
<Divider size="S" marginY="size-300" />
|
|
110
|
+
|
|
111
|
+
<Form isRequired necessityIndicator="icon" labelPosition="top">
|
|
112
|
+
{FIELD_DEFS.map((f) => (
|
|
113
|
+
<TextField
|
|
114
|
+
key={f.key}
|
|
115
|
+
label={f.label}
|
|
116
|
+
placeholder={f.placeholder}
|
|
117
|
+
type={f.type === 'password' ? 'password' : 'text'}
|
|
118
|
+
value={values[f.key]}
|
|
119
|
+
onChange={set(f.key)}
|
|
120
|
+
autoComplete="off"
|
|
121
|
+
isRequired
|
|
122
|
+
width="100%"
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</Form>
|
|
126
|
+
|
|
127
|
+
<View marginTop="size-300">
|
|
128
|
+
<Flex alignItems="center" gap="size-200" wrap>
|
|
129
|
+
<ButtonGroup>
|
|
130
|
+
<Button variant="secondary" onPress={handleTest} isDisabled={!allFilled || testState.status === 'running'}>
|
|
131
|
+
{testState.status === 'running' ? 'Testing…' : 'Test connection'}
|
|
132
|
+
</Button>
|
|
133
|
+
<Button
|
|
134
|
+
variant="cta"
|
|
135
|
+
onPress={handleSave}
|
|
136
|
+
isDisabled={!allFilled || testState.status === 'running' || saveState.status === 'running'}
|
|
137
|
+
>
|
|
138
|
+
{saveState.status === 'running' ? 'Saving…' : 'Save & continue'}
|
|
139
|
+
</Button>
|
|
140
|
+
{onCancel ? (
|
|
141
|
+
<Button variant="secondary" onPress={onCancel}>Cancel</Button>
|
|
142
|
+
) : null}
|
|
143
|
+
</ButtonGroup>
|
|
144
|
+
{testState.status !== 'idle' && (
|
|
145
|
+
<Flex alignItems="center" gap="size-100">
|
|
146
|
+
{testState.status === 'running' && <ProgressCircle size="S" isIndeterminate aria-label="Testing" />}
|
|
147
|
+
<StatusLight variant={testLight}>{testState.message}</StatusLight>
|
|
148
|
+
</Flex>
|
|
149
|
+
)}
|
|
150
|
+
</Flex>
|
|
151
|
+
{saveState.status === 'fail' && (
|
|
152
|
+
<View marginTop="size-150"><StatusLight variant="negative">{saveState.message}</StatusLight></View>
|
|
153
|
+
)}
|
|
154
|
+
{saveState.status === 'ok' && (
|
|
155
|
+
<View marginTop="size-150"><StatusLight variant="positive">{saveState.message}</StatusLight></View>
|
|
156
|
+
)}
|
|
157
|
+
</View>
|
|
158
|
+
</View>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { register } from '@adobe/uix-guest'
|
|
14
|
+
import { MainPage } from './MainPage'
|
|
15
|
+
import { useEffect } from 'react'
|
|
16
|
+
import { getExtensionId } from '../settings'
|
|
17
|
+
|
|
18
|
+
export default function ExtensionRegistration(props) {
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
(async () => {
|
|
22
|
+
|
|
23
|
+
await register({
|
|
24
|
+
id: getExtensionId(),
|
|
25
|
+
methods: {
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
})()
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
return <MainPage ims={props.ims} runtime={props.runtime} />
|
|
33
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
Licensed under the Apache License, Version 2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { View, Flex, ProgressCircle, Text, Button, IllustratedMessage, Heading } from '@adobe/react-spectrum'
|
|
7
|
+
import { attach } from '@adobe/uix-guest'
|
|
8
|
+
import React, { useEffect, useState, useCallback } from 'react'
|
|
9
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
10
|
+
import { useLocation } from 'react-router-dom'
|
|
11
|
+
import { getExtensionId, getActionKey, getNavItems, getPageComponent } from '../settings'
|
|
12
|
+
import { callAction } from '../utils'
|
|
13
|
+
import AppSectionNav from './AppSectionNav'
|
|
14
|
+
import CommerceSetupWizard from './CommerceSetupWizard'
|
|
15
|
+
|
|
16
|
+
export const MainPage = props => {
|
|
17
|
+
const location = useLocation()
|
|
18
|
+
|
|
19
|
+
// Commerce connection gate. `status === 'unknown'` while we're loading,
|
|
20
|
+
// 'configured' / 'unconfigured' after the first probe. `reconfiguring`
|
|
21
|
+
// re-shows the wizard for an already-configured connection.
|
|
22
|
+
const [status, setStatus] = useState('unknown')
|
|
23
|
+
const [maskedCreds, setMaskedCreds] = useState(null)
|
|
24
|
+
const [error, setError] = useState(null)
|
|
25
|
+
const [reconfiguring, setReconfiguring] = useState(false)
|
|
26
|
+
|
|
27
|
+
const fetchStatus = useCallback(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const res = await callAction(
|
|
30
|
+
props,
|
|
31
|
+
getActionKey('commerceConnectionStatus'),
|
|
32
|
+
'',
|
|
33
|
+
{ fresh: true }
|
|
34
|
+
)
|
|
35
|
+
const body = res && res.body ? res.body : res
|
|
36
|
+
setMaskedCreds(body && body.creds ? body.creds : null)
|
|
37
|
+
setStatus(body && body.configured ? 'configured' : 'unconfigured')
|
|
38
|
+
setError(null)
|
|
39
|
+
} catch (e) {
|
|
40
|
+
setError(e.message || 'Failed to load Commerce connection status')
|
|
41
|
+
setStatus('error')
|
|
42
|
+
}
|
|
43
|
+
}, [props])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// Kick off connection-status check immediately — the action is configured
|
|
47
|
+
// with require-adobe-auth: false, so it doesn't need an IMS token. The
|
|
48
|
+
// IMS handshake runs in parallel and is best-effort: in raw localhost
|
|
49
|
+
// (outside the Experience Cloud Shell iframe) `attach()` would hang
|
|
50
|
+
// forever waiting for a parent that isn't there, so we time it out.
|
|
51
|
+
fetchStatus()
|
|
52
|
+
|
|
53
|
+
if (props.ims.token) return
|
|
54
|
+
let cancelled = false
|
|
55
|
+
const handshake = Promise.race([
|
|
56
|
+
attach({ id: getExtensionId() }).then((gc) => ({
|
|
57
|
+
token: gc?.sharedContext?.get('imsToken'),
|
|
58
|
+
org: gc?.sharedContext?.get('imsOrgId')
|
|
59
|
+
})),
|
|
60
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 2000))
|
|
61
|
+
])
|
|
62
|
+
handshake.then((res) => {
|
|
63
|
+
if (cancelled || !res) return
|
|
64
|
+
if (res.token) props.ims.token = res.token
|
|
65
|
+
if (res.org) props.ims.org = res.org
|
|
66
|
+
}).catch(() => {})
|
|
67
|
+
return () => { cancelled = true }
|
|
68
|
+
}, [fetchStatus])
|
|
69
|
+
|
|
70
|
+
if (status === 'unknown') {
|
|
71
|
+
return (
|
|
72
|
+
<Flex alignItems="center" justifyContent="center" height="size-3000">
|
|
73
|
+
<Flex direction="column" alignItems="center" gap="size-150">
|
|
74
|
+
<ProgressCircle size="L" isIndeterminate aria-label="Loading" />
|
|
75
|
+
<Text>Checking Commerce connection…</Text>
|
|
76
|
+
</Flex>
|
|
77
|
+
</Flex>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (status === 'error') {
|
|
82
|
+
return (
|
|
83
|
+
<View padding="size-400">
|
|
84
|
+
<IllustratedMessage>
|
|
85
|
+
<Heading>Could not load connection status</Heading>
|
|
86
|
+
<Text>{error}</Text>
|
|
87
|
+
</IllustratedMessage>
|
|
88
|
+
<Flex marginTop="size-200" justifyContent="center">
|
|
89
|
+
<Button variant="cta" onPress={fetchStatus}>Retry</Button>
|
|
90
|
+
</Flex>
|
|
91
|
+
</View>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (status === 'unconfigured' || reconfiguring) {
|
|
96
|
+
return (
|
|
97
|
+
<CommerceSetupWizard
|
|
98
|
+
runtime={props.runtime}
|
|
99
|
+
ims={props.ims}
|
|
100
|
+
initial={maskedCreds}
|
|
101
|
+
onCompleted={() => {
|
|
102
|
+
setReconfiguring(false)
|
|
103
|
+
fetchStatus()
|
|
104
|
+
}}
|
|
105
|
+
onCancel={reconfiguring ? () => setReconfiguring(false) : undefined}
|
|
106
|
+
/>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Pick the page component for the active path from the nav registry.
|
|
111
|
+
// To add a new page: register it in pages/index.js (or host extraPages)
|
|
112
|
+
// and add an entry to nav.json (or host extraNav).
|
|
113
|
+
const items = getNavItems()
|
|
114
|
+
const match = items.find((it) => it.path === location.pathname) || items[0]
|
|
115
|
+
const Page = match ? getPageComponent(match.id) : null
|
|
116
|
+
|
|
117
|
+
const pageFallback = ({ error }) => (
|
|
118
|
+
<View padding="size-400">
|
|
119
|
+
<Heading level={3}>This page crashed</Heading>
|
|
120
|
+
<Text>
|
|
121
|
+
{match ? `Error in page "${match.id}": ` : ''}{error && error.message ? error.message : String(error)}
|
|
122
|
+
</Text>
|
|
123
|
+
</View>
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<View UNSAFE_style={{ overflowX: 'clip' }}>
|
|
128
|
+
<AppSectionNav
|
|
129
|
+
rightSlot={
|
|
130
|
+
<Button variant="secondary" onPress={() => setReconfiguring(true)}>
|
|
131
|
+
Reconfigure Commerce
|
|
132
|
+
</Button>
|
|
133
|
+
}
|
|
134
|
+
/>
|
|
135
|
+
<View>
|
|
136
|
+
{Page
|
|
137
|
+
? (
|
|
138
|
+
<ErrorBoundary FallbackComponent={pageFallback}>
|
|
139
|
+
<Page runtime={props.runtime} ims={props.ims} />
|
|
140
|
+
</ErrorBoundary>
|
|
141
|
+
)
|
|
142
|
+
: <View padding="size-400"><Text>No page registered for this route.</Text></View>
|
|
143
|
+
}
|
|
144
|
+
</View>
|
|
145
|
+
</View>
|
|
146
|
+
)
|
|
147
|
+
}
|