ui-soxo-bootstrap-core 2.6.62-dev.4 → 2.6.63-dev.1
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/core/lib/components/global-header/global-header.js +191 -151
- package/core/lib/components/index.js +13 -0
- package/core/lib/components/support-desk/components/{raise-ticket/raise-ticket.js → support-frame/support-frame.js} +42 -19
- package/core/lib/components/support-desk/support-desk.js +8 -8
- package/core/lib/components/support-desk/support-desk.scss +0 -72
- package/core/lib/components/support-desk/support-desk.utils.js +35 -315
- package/core/lib/components/support-desk/support-desk.utils.test.js +51 -308
- package/core/lib/elements/basic/table/column-filter/column-filter.js +299 -0
- package/core/lib/elements/basic/table/column-filter/column-filter.scss +48 -0
- package/core/lib/elements/basic/table/column-filter/column-filter.test.js +148 -0
- package/core/lib/elements/basic/table/readme.md +50 -1
- package/core/lib/elements/basic/table/table.js +59 -4
- package/core/lib/elements/basic/table/table.test.js +80 -0
- package/package.json +1 -1
- package/core/lib/components/support-desk/components/my-tickets/my-tickets.js +0 -343
- package/core/lib/components/support-desk/support-desk.dom.test.js +0 -20
|
@@ -7,7 +7,7 @@ import { motion, useAnimation } from 'framer-motion';
|
|
|
7
7
|
import { boxVariants } from './animations';
|
|
8
8
|
import { GlobalContext, GlobalProvider } from './../../Store';
|
|
9
9
|
import { Link, useLocation } from 'react-router-dom';
|
|
10
|
-
import { Avatar, Input, Tooltip, Typography } from 'antd';
|
|
10
|
+
import { Avatar, Input, Tooltip, Typography, message } from 'antd';
|
|
11
11
|
import ProgressBar from '../progress-bar/progress-bar'; // Adjust the path as needed
|
|
12
12
|
import { Button } from '../../elements';
|
|
13
13
|
import GenericHeader from '../header/generic-header';
|
|
@@ -20,23 +20,53 @@ import LanguageSwitcher from '../language-switcher/language-switcher';
|
|
|
20
20
|
import { useTranslation } from 'react-i18next';
|
|
21
21
|
import SettingsUtil from '../../../utils/settings.utils';
|
|
22
22
|
import SpotlightSearch from '../spotlight-search/spotlight-search.component';
|
|
23
|
-
|
|
23
|
+
import LicenseAlert from '../../../components/license-management/license-alert';
|
|
24
24
|
import SupportDesk from '../support-desk/support-desk';
|
|
25
|
-
import { resolveSupportConfig } from '../support-desk/support-desk.utils';
|
|
25
|
+
import { MODES, getSupportUrl, resolveSupportConfig } from '../support-desk/support-desk.utils';
|
|
26
26
|
|
|
27
27
|
const { Title } = Typography;
|
|
28
|
-
function GlobalHeaderContent({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
function GlobalHeaderContent({
|
|
29
|
+
loading,
|
|
30
|
+
appSettings,
|
|
31
|
+
children,
|
|
32
|
+
isConnected,
|
|
33
|
+
history,
|
|
34
|
+
modules = [],
|
|
35
|
+
sidemenu = [],
|
|
36
|
+
reload,
|
|
37
|
+
meta = {},
|
|
38
|
+
licAlert,
|
|
39
|
+
licenseData,
|
|
40
|
+
...props
|
|
41
|
+
}) {
|
|
32
42
|
let location = useLocation();
|
|
33
43
|
// let location = {};
|
|
34
44
|
const { isMobile, user = { locations: [] }, kiosk, state, settings } = useContext(GlobalContext);
|
|
35
45
|
const [visible, setVisible] = useState(false);
|
|
36
46
|
const helpDeskSetting = settings?.HELPATR || {};
|
|
37
47
|
// Memoised: the drawer's effects depend on this object, so it must keep its identity across renders.
|
|
38
|
-
const supportConfig = useMemo(() => resolveSupportConfig(helpDeskSetting
|
|
48
|
+
const supportConfig = useMemo(() => resolveSupportConfig(helpDeskSetting), [helpDeskSetting]);
|
|
39
49
|
const [supportOpen, setSupportOpen] = useState(false);
|
|
50
|
+
const [supportLoading, setSupportLoading] = useState(false);
|
|
51
|
+
// Link fetched on Help click, handed to the drawer so the form does not request it twice.
|
|
52
|
+
const [supportUrl, setSupportUrl] = useState('');
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fetch the first link before opening the drawer, so the button shows a loader and a
|
|
56
|
+
* failure is reported without opening an empty drawer.
|
|
57
|
+
*/
|
|
58
|
+
const openSupport = async () => {
|
|
59
|
+
if (supportLoading) return;
|
|
60
|
+
setSupportLoading(true);
|
|
61
|
+
try {
|
|
62
|
+
setSupportUrl(await getSupportUrl(supportConfig, MODES.RAISE));
|
|
63
|
+
setSupportOpen(true);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
message.error(err?.message || 'Unable to open Support right now.');
|
|
66
|
+
} finally {
|
|
67
|
+
setSupportLoading(false);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
40
70
|
// Variable to handle toggling of menu
|
|
41
71
|
const [collapsed, setCollapsed] = useState(false);
|
|
42
72
|
// varibale handle branch switcher
|
|
@@ -88,42 +118,28 @@ function GlobalHeaderContent({ loading, appSettings, children, isConnected, hist
|
|
|
88
118
|
useEffect(() => {}, [state.theme]);
|
|
89
119
|
return (
|
|
90
120
|
<>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<SideMenu
|
|
114
|
-
// isOpen={isOpen}
|
|
115
|
-
collapsed={collapsed}
|
|
116
|
-
setCollapsed={setCollapsed}
|
|
117
|
-
loading={loading}
|
|
118
|
-
appSettings={appSettings}
|
|
119
|
-
callback={onClose}
|
|
120
|
-
modules={modules}
|
|
121
|
-
history={history}
|
|
122
|
-
/>
|
|
123
|
-
</motion.div>
|
|
124
|
-
) : (
|
|
125
|
-
<Drawer placement="left" onClose={onClose} visible={visible} className="side-drawer-content">
|
|
126
|
-
<div className={`sidebar-container ${!collapsed ? 'open' : 'close'}`}>
|
|
121
|
+
<div
|
|
122
|
+
className={`global-header ${process.env.REACT_APP_THEME} ${isConnected && !kiosk ? 'connected' : ''}`}
|
|
123
|
+
style={{
|
|
124
|
+
// background: state.theme.colors.bodyBackground,
|
|
125
|
+
height: 'auto',
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{/* <MenuOutlined style={{left:'1%',top:'1%', fontSize:18, position:'absolute', zIndex:999}} onClick={showSidebar}/> */}
|
|
129
|
+
<div className="layout-content">
|
|
130
|
+
<div
|
|
131
|
+
//whileHover="hover"
|
|
132
|
+
//whileTap="tap"
|
|
133
|
+
className={`left-bar ${!isConnected && !isMobile ? '' : 'hide'}${!collapsed ? 'open' : 'close'}${kiosk && !isConnected ? 'kioskon' : ''}`}
|
|
134
|
+
// style={{ background: state.theme.colors.leftSectionBackground }}
|
|
135
|
+
>
|
|
136
|
+
{!isMobile && !isConnected ? (
|
|
137
|
+
<motion.div
|
|
138
|
+
variants={boxVariants}
|
|
139
|
+
initial={['entering']}
|
|
140
|
+
animate={boxControls}
|
|
141
|
+
className={`sidebar-container ${!collapsed ? 'open' : 'close'}${kiosk ? '' : 'kioskon'}`}
|
|
142
|
+
>
|
|
127
143
|
<SideMenu
|
|
128
144
|
// isOpen={isOpen}
|
|
129
145
|
collapsed={collapsed}
|
|
@@ -134,120 +150,144 @@ function GlobalHeaderContent({ loading, appSettings, children, isConnected, hist
|
|
|
134
150
|
modules={modules}
|
|
135
151
|
history={history}
|
|
136
152
|
/>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
</motion.div>
|
|
154
|
+
) : (
|
|
155
|
+
<Drawer placement="left" onClose={onClose} visible={visible} className="side-drawer-content">
|
|
156
|
+
<div className={`sidebar-container ${!collapsed ? 'open' : 'close'}`}>
|
|
157
|
+
<SideMenu
|
|
158
|
+
// isOpen={isOpen}
|
|
159
|
+
collapsed={collapsed}
|
|
160
|
+
setCollapsed={setCollapsed}
|
|
161
|
+
loading={loading}
|
|
162
|
+
appSettings={appSettings}
|
|
163
|
+
callback={onClose}
|
|
164
|
+
modules={modules}
|
|
165
|
+
history={history}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</Drawer>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
{/* Right Section of the Component Loader */}
|
|
172
|
+
<div
|
|
173
|
+
className={`right-section ${!collapsed ? 'open' : 'close'} ${kiosk ? 'kioskon' : ''}`}
|
|
174
|
+
style={{ background: state.theme.colors.bodyBackground }}
|
|
175
|
+
>
|
|
176
|
+
{/* For not connected section */}
|
|
177
|
+
{!isConnected && !kiosk ? (
|
|
178
|
+
<div
|
|
179
|
+
className={`page-wrapper ${!collapsed ? 'open' : 'close'}`}
|
|
180
|
+
// style={{ background: state.theme.colors.headerBg }}
|
|
181
|
+
>
|
|
182
|
+
{/* */}
|
|
183
|
+
<div className="page-header-name">
|
|
184
|
+
<ProgressBar isLoading={loading} />
|
|
185
|
+
<span type onClick={!isMobile ? toggleCollapsed : hideToggle} className="toggle-box toggle-menu">
|
|
186
|
+
<MenuOutlined />
|
|
167
187
|
</span>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<div className="page-menu">
|
|
179
|
-
{/* Search Input in header start */}
|
|
180
|
-
{!isMobile && (
|
|
181
|
-
<div>
|
|
182
|
-
<Input placeholder="Search (Shift + F)" prefix={<SearchOutlined />} onClick={openSearchModal} readOnly style={{ width: 250 }} />
|
|
183
|
-
<SpotlightSearch ref={(elem) => SettingsUtil.registerModal(elem)} props={props} />
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
{/* Search Input in header start */}
|
|
187
|
-
{/** branchswitcher Option */}
|
|
188
|
-
{/* branch switcher controlled with env for matria and nura */}
|
|
189
|
-
{!process.env.REACT_APP_SHOW_BRANCH_SWITCHER ? <div className="branch-switcher">{globalCustomerHeader()}</div> : null}
|
|
190
|
-
{/* <div className="branch-switcher">{globalCustomerHeader()}</div> */}
|
|
191
|
-
{/* Search Option */}
|
|
192
|
-
{/* <ModalSearch /> */}
|
|
193
|
-
{/* Search Option Ends */}
|
|
194
|
-
{/* Configurator Actions */}
|
|
195
|
-
{user.isAdmin ? (
|
|
196
|
-
<>
|
|
197
|
-
{/* Models */}
|
|
198
|
-
{menu && menu.id ? (
|
|
199
|
-
<Link to={`/menus/${menu.id}`}>
|
|
200
|
-
<Button type="default" size={'small'} icon={<SettingOutlined />}></Button>
|
|
201
|
-
</Link>
|
|
202
|
-
) : null}
|
|
203
|
-
{/* Models Ends */}
|
|
204
|
-
</>
|
|
188
|
+
{/* Back Button */}
|
|
189
|
+
{location.pathname !== '/' ? (
|
|
190
|
+
<span
|
|
191
|
+
className="toggle-box"
|
|
192
|
+
onClick={() => {
|
|
193
|
+
history && history.goBack();
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
<GenericHeader />
|
|
197
|
+
</span>
|
|
205
198
|
) : null}
|
|
206
|
-
{/*
|
|
207
|
-
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{/* Shown only when the support settings are complete, so the button never does nothing */}
|
|
212
|
-
{helpDeskSetting?.showSupportBtn && supportConfig.enabled ? (
|
|
213
|
-
<Tooltip title={helpDeskSetting?.toolTipText} overlayClassName="modern-tooltip">
|
|
214
|
-
<span>
|
|
215
|
-
<Button onClick={() => setSupportOpen(true)} type="default" size="small">
|
|
216
|
-
{helpDeskSetting?.buttonText || 'Help'}
|
|
217
|
-
</Button>
|
|
218
|
-
</span>
|
|
219
|
-
</Tooltip>
|
|
199
|
+
{/* Back Button Ends */}
|
|
200
|
+
{location.pathname !== '/' ? (
|
|
201
|
+
<h4 className="menu-caption header-caption" style={{ color: state.theme.colors.headerColor }}>
|
|
202
|
+
{menu.caption}
|
|
203
|
+
</h4>
|
|
220
204
|
) : null}
|
|
221
|
-
{/** Switch Languages starts */}
|
|
222
|
-
{process.env.REACT_APP_ENABLE_LANGUAGE_SWITCHER ? <LanguageSwitcher /> : null}
|
|
223
|
-
{/** Switch Languages ends */}
|
|
224
|
-
{/* User Profile */}
|
|
225
|
-
<div style={{ padding: '10px' }}>
|
|
226
|
-
<ProfileAvatar />
|
|
227
|
-
<span style={{ color: state.theme.colors.colorText }}> {user.name} </span>
|
|
228
|
-
</div>
|
|
229
|
-
{/* User Profile Ends */}
|
|
230
205
|
</div>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
206
|
+
{/* Page Menu Actions */}
|
|
207
|
+
{user.role || user.id ? (
|
|
208
|
+
<div className="page-menu">
|
|
209
|
+
{/* Search Input in header start */}
|
|
210
|
+
{!isMobile && (
|
|
211
|
+
<div>
|
|
212
|
+
<Input
|
|
213
|
+
placeholder="Search (Shift + F)"
|
|
214
|
+
prefix={<SearchOutlined />}
|
|
215
|
+
onClick={openSearchModal}
|
|
216
|
+
readOnly
|
|
217
|
+
style={{ width: 250 }}
|
|
218
|
+
/>
|
|
219
|
+
<SpotlightSearch ref={(elem) => SettingsUtil.registerModal(elem)} props={props} />
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{/* Search Input in header start */}
|
|
223
|
+
{/** branchswitcher Option */}
|
|
224
|
+
{/* branch switcher controlled with env for matria and nura */}
|
|
225
|
+
{!process.env.REACT_APP_SHOW_BRANCH_SWITCHER ? <div className="branch-switcher">{globalCustomerHeader()}</div> : null}
|
|
226
|
+
{/* <div className="branch-switcher">{globalCustomerHeader()}</div> */}
|
|
227
|
+
{/* Search Option */}
|
|
228
|
+
{/* <ModalSearch /> */}
|
|
229
|
+
{/* Search Option Ends */}
|
|
230
|
+
{/* Configurator Actions */}
|
|
231
|
+
{user.isAdmin ? (
|
|
232
|
+
<>
|
|
233
|
+
{/* Models */}
|
|
234
|
+
{menu && menu.id ? (
|
|
235
|
+
<Link to={`/menus/${menu.id}`}>
|
|
236
|
+
<Button type="default" size={'small'} icon={<SettingOutlined />}></Button>
|
|
237
|
+
</Link>
|
|
238
|
+
) : null}
|
|
239
|
+
{/* Models Ends */}
|
|
240
|
+
</>
|
|
241
|
+
) : null}
|
|
242
|
+
{/* Configurator Actions Ends */}
|
|
243
|
+
{/* Reload Button */}
|
|
244
|
+
<Button onClick={reload} icon={<ReloadOutlined />} type="default" size={'small'}></Button>
|
|
245
|
+
{/* Reload Button Ends */}
|
|
246
|
+
{/* Help-desk-btn */}
|
|
247
|
+
{/* Shown only when the support settings are complete, so the button never does nothing */}
|
|
248
|
+
{helpDeskSetting?.showSupportBtn && supportConfig.enabled ? (
|
|
249
|
+
<Tooltip title={helpDeskSetting?.toolTipText} overlayClassName="modern-tooltip">
|
|
250
|
+
<span>
|
|
251
|
+
<Button onClick={openSupport} loading={supportLoading} type="default" size="small">
|
|
252
|
+
{helpDeskSetting?.buttonText || 'Help'}
|
|
253
|
+
</Button>
|
|
254
|
+
</span>
|
|
255
|
+
</Tooltip>
|
|
256
|
+
) : null}
|
|
257
|
+
{/** Switch Languages starts */}
|
|
258
|
+
{process.env.REACT_APP_ENABLE_LANGUAGE_SWITCHER ? <LanguageSwitcher /> : null}
|
|
259
|
+
{/** Switch Languages ends */}
|
|
260
|
+
{/* User Profile */}
|
|
261
|
+
<div style={{ padding: '10px' }}>
|
|
262
|
+
<ProfileAvatar />
|
|
263
|
+
<span style={{ color: state.theme.colors.colorText }}> {user.name} </span>
|
|
264
|
+
</div>
|
|
265
|
+
{/* User Profile Ends */}
|
|
266
|
+
</div>
|
|
267
|
+
) : null}
|
|
268
|
+
{/* Page Menu Actions Ends */}
|
|
269
|
+
</div>
|
|
270
|
+
) : null}
|
|
271
|
+
{/* The children is rendered */}
|
|
272
|
+
{children}
|
|
273
|
+
{/* The children is rendered */}
|
|
274
|
+
</div>
|
|
275
|
+
{/* Right Section of the Component Loader Ends */}
|
|
238
276
|
</div>
|
|
239
|
-
{/* Right Section of the Component Loader Ends */}
|
|
240
277
|
</div>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
278
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
279
|
+
{supportConfig.enabled && supportOpen ? (
|
|
280
|
+
<SupportDesk
|
|
281
|
+
open={supportOpen}
|
|
282
|
+
onClose={() => setSupportOpen(false)}
|
|
283
|
+
config={supportConfig}
|
|
284
|
+
initialUrl={supportUrl}
|
|
285
|
+
title={helpDeskSetting?.buttonText || 'Support'}
|
|
286
|
+
/>
|
|
247
287
|
) : null}
|
|
248
288
|
|
|
249
|
-
|
|
250
|
-
|
|
289
|
+
{licAlert && licenseData && (
|
|
290
|
+
<div
|
|
251
291
|
style={{
|
|
252
292
|
top: 0,
|
|
253
293
|
marginTop: '3rem',
|
|
@@ -258,8 +298,8 @@ function GlobalHeaderContent({ loading, appSettings, children, isConnected, hist
|
|
|
258
298
|
>
|
|
259
299
|
<LicenseAlert data={licenseData} />
|
|
260
300
|
</div>
|
|
261
|
-
)}
|
|
262
|
-
|
|
301
|
+
)}
|
|
302
|
+
</>
|
|
263
303
|
);
|
|
264
304
|
}
|
|
265
305
|
export default function GlobalHeader(props) {
|
|
@@ -56,6 +56,14 @@ import TableWrapper from './table-wrapper/table-wrapper'
|
|
|
56
56
|
|
|
57
57
|
import TableComponent from '../elements/basic/table/table'
|
|
58
58
|
|
|
59
|
+
import {
|
|
60
|
+
withColumnFilters,
|
|
61
|
+
applyColumnFilters,
|
|
62
|
+
hasColumnFilters,
|
|
63
|
+
getColumnFilterValue,
|
|
64
|
+
ColumnFilterDropdown,
|
|
65
|
+
} from '../elements/basic/table/column-filter/column-filter'
|
|
66
|
+
|
|
59
67
|
import CustomActions from '../models/actions/components/custom-actions/custom-actions'
|
|
60
68
|
|
|
61
69
|
// Generic Components Ends
|
|
@@ -154,6 +162,11 @@ export {
|
|
|
154
162
|
|
|
155
163
|
TableWrapper,
|
|
156
164
|
TableComponent,
|
|
165
|
+
withColumnFilters,
|
|
166
|
+
applyColumnFilters,
|
|
167
|
+
hasColumnFilters,
|
|
168
|
+
getColumnFilterValue,
|
|
169
|
+
ColumnFilterDropdown,
|
|
157
170
|
CustomActions,
|
|
158
171
|
|
|
159
172
|
// Generic Components Ends
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* "Raise a Ticket"
|
|
2
|
+
* One Helpdesk page in an iframe ("Raise a Ticket" or "My Tickets").
|
|
3
3
|
*
|
|
4
|
-
* The link is
|
|
4
|
+
* The link is requested when the tab opens and on every Reload, so an idle session never
|
|
5
|
+
* holds an expired token.
|
|
5
6
|
*
|
|
6
|
-
* Frappe's session cookie is SameSite=Lax, so some browsers render the embedded
|
|
7
|
-
* If the
|
|
7
|
+
* Frappe's session cookie is SameSite=Lax, so some browsers render the embedded page blank.
|
|
8
|
+
* If the page has not loaded after FORM_SLOW_MS we offer "Open in new tab", which opens a
|
|
8
9
|
* fresh link as a top level page.
|
|
9
10
|
*/
|
|
10
11
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
12
|
import { Alert, Button, Result, Spin, message } from 'antd';
|
|
12
|
-
import { ExportOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
13
|
-
import { FORM_SLOW_MS,
|
|
13
|
+
import { ExportOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
14
|
+
import { FORM_SLOW_MS, getSupportUrl, openInNewTab } from '../../support-desk.utils';
|
|
14
15
|
|
|
15
|
-
export default function
|
|
16
|
-
const [url, setUrl] = useState(
|
|
16
|
+
export default function SupportFrame({ config, mode, intro, loadingText, errorTitle, initialUrl = '', allowAnother = false }) {
|
|
17
|
+
const [url, setUrl] = useState(initialUrl);
|
|
17
18
|
// minting -> loading (iframe requested) -> ready (iframe finished) | error
|
|
18
|
-
const [phase, setPhase] = useState('minting');
|
|
19
|
+
const [phase, setPhase] = useState(initialUrl ? 'loading' : 'minting');
|
|
20
|
+
// A link passed in by the caller is used once; Reload always asks for a new one.
|
|
21
|
+
const initialRef = useRef(initialUrl);
|
|
19
22
|
const [error, setError] = useState('');
|
|
20
23
|
const [slow, setSlow] = useState(false);
|
|
21
24
|
|
|
@@ -30,19 +33,23 @@ export default function RaiseTicket({ config }) {
|
|
|
30
33
|
setSlow(false);
|
|
31
34
|
|
|
32
35
|
try {
|
|
33
|
-
const next = await
|
|
36
|
+
const next = await getSupportUrl(config, mode);
|
|
34
37
|
if (requestRef.current !== requestId) return;
|
|
35
38
|
setUrl(next);
|
|
36
39
|
setPhase('loading');
|
|
37
40
|
} catch (err) {
|
|
38
41
|
if (requestRef.current !== requestId) return;
|
|
39
42
|
setUrl('');
|
|
40
|
-
setError(err?.message || 'Unable to open
|
|
43
|
+
setError(err?.message || 'Unable to open Support right now.');
|
|
41
44
|
setPhase('error');
|
|
42
45
|
}
|
|
43
|
-
}, [config]);
|
|
46
|
+
}, [config, mode]);
|
|
44
47
|
|
|
45
48
|
useEffect(() => {
|
|
49
|
+
if (initialRef.current) {
|
|
50
|
+
initialRef.current = '';
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
46
53
|
load();
|
|
47
54
|
// Ignore the result of a request that finishes after unmount.
|
|
48
55
|
return () => {
|
|
@@ -50,7 +57,7 @@ export default function RaiseTicket({ config }) {
|
|
|
50
57
|
};
|
|
51
58
|
}, [load]);
|
|
52
59
|
|
|
53
|
-
// Warn when the
|
|
60
|
+
// Warn when the page is slow or blank.
|
|
54
61
|
useEffect(() => {
|
|
55
62
|
if (phase !== 'loading') return undefined;
|
|
56
63
|
const timer = setTimeout(() => setSlow(true), FORM_SLOW_MS);
|
|
@@ -58,13 +65,13 @@ export default function RaiseTicket({ config }) {
|
|
|
58
65
|
}, [phase, url]);
|
|
59
66
|
|
|
60
67
|
const handleOpenInNewTab = () =>
|
|
61
|
-
openInNewTab(() =>
|
|
68
|
+
openInNewTab(() => getSupportUrl(config, mode)).catch((err) => message.error(err?.message || 'Unable to open Support right now.'));
|
|
62
69
|
|
|
63
70
|
if (phase === 'error') {
|
|
64
71
|
return (
|
|
65
72
|
<Result
|
|
66
73
|
status="warning"
|
|
67
|
-
title=
|
|
74
|
+
title={errorTitle}
|
|
68
75
|
subTitle={error}
|
|
69
76
|
extra={
|
|
70
77
|
<Button type="primary" icon={<ReloadOutlined />} onClick={load}>
|
|
@@ -78,8 +85,13 @@ export default function RaiseTicket({ config }) {
|
|
|
78
85
|
return (
|
|
79
86
|
<div className="support-raise">
|
|
80
87
|
<div className="support-raise__bar">
|
|
81
|
-
<span>
|
|
88
|
+
<span>{intro}</span>
|
|
82
89
|
<span>
|
|
90
|
+
{allowAnother && (
|
|
91
|
+
<Button type="primary" size="small" icon={<PlusOutlined />} onClick={load} disabled={phase === 'minting'}>
|
|
92
|
+
Submit another ticket
|
|
93
|
+
</Button>
|
|
94
|
+
)}
|
|
83
95
|
<Button type="link" size="small" icon={<ReloadOutlined />} onClick={load} disabled={phase === 'minting'}>
|
|
84
96
|
Reload
|
|
85
97
|
</Button>
|
|
@@ -94,7 +106,7 @@ export default function RaiseTicket({ config }) {
|
|
|
94
106
|
banner
|
|
95
107
|
type="warning"
|
|
96
108
|
showIcon
|
|
97
|
-
message="
|
|
109
|
+
message="This page is taking longer than usual. If it stays blank, open it in a new tab."
|
|
98
110
|
action={
|
|
99
111
|
<Button size="small" icon={<ReloadOutlined />} onClick={load}>
|
|
100
112
|
Reload
|
|
@@ -106,10 +118,21 @@ export default function RaiseTicket({ config }) {
|
|
|
106
118
|
<div className="support-raise__frame">
|
|
107
119
|
{phase !== 'ready' && (
|
|
108
120
|
<div className="support-raise__loading">
|
|
109
|
-
<Spin tip=
|
|
121
|
+
<Spin tip={loadingText} />
|
|
110
122
|
</div>
|
|
111
123
|
)}
|
|
112
|
-
{url &&
|
|
124
|
+
{url && (
|
|
125
|
+
<iframe
|
|
126
|
+
title="Support"
|
|
127
|
+
src={url}
|
|
128
|
+
allow="clipboard-write"
|
|
129
|
+
// No allow-top-navigation: the embedded page cannot redirect the Nura Desk tab away.
|
|
130
|
+
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads"
|
|
131
|
+
referrerPolicy="no-referrer"
|
|
132
|
+
className="support-frame"
|
|
133
|
+
onLoad={() => setPhase('ready')}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
113
136
|
</div>
|
|
114
137
|
</div>
|
|
115
138
|
);
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Support drawer opened from the global header Help button.
|
|
3
3
|
*
|
|
4
|
-
* "Raise a Ticket" -> Helpdesk form
|
|
5
|
-
* "My Tickets" -> the user's own tickets
|
|
4
|
+
* "Raise a Ticket" -> Helpdesk form
|
|
5
|
+
* "My Tickets" -> the user's own tickets
|
|
6
6
|
*
|
|
7
7
|
* Both tabs stay mounted once opened, so a half written ticket is not lost when the user
|
|
8
|
-
* looks at their list.
|
|
8
|
+
* looks at their list. Each page asks the API for a fresh link on open and on Reload.
|
|
9
9
|
*/
|
|
10
10
|
import React, { useState } from 'react';
|
|
11
11
|
import { Drawer, Tabs } from 'antd';
|
|
12
12
|
import { CustomerServiceOutlined, FormOutlined, UnorderedListOutlined } from '@ant-design/icons';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
13
|
+
import SupportFrame from './components/support-frame/support-frame';
|
|
14
|
+
import { MODES } from './support-desk.utils';
|
|
15
15
|
import './support-desk.scss';
|
|
16
16
|
|
|
17
17
|
const RAISE = 'raise';
|
|
18
18
|
const MINE = 'mine';
|
|
19
19
|
|
|
20
|
-
export default function SupportDesk({ open, onClose, config, title = 'Support' }) {
|
|
20
|
+
export default function SupportDesk({ open, onClose, config, initialUrl = '', title = 'Support' }) {
|
|
21
21
|
const [tab, setTab] = useState(RAISE);
|
|
22
22
|
|
|
23
23
|
return (
|
|
@@ -45,7 +45,7 @@ export default function SupportDesk({ open, onClose, config, title = 'Support' }
|
|
|
45
45
|
<FormOutlined /> Raise a Ticket
|
|
46
46
|
</span>
|
|
47
47
|
),
|
|
48
|
-
children: <
|
|
48
|
+
children: <SupportFrame config={config} mode={MODES.RAISE} initialUrl={initialUrl} allowAnother intro="Tell us about your issue and our support team will reply as soon as possible." loadingText="Preparing the Support form…" errorTitle="We couldn't open the Support form" />,
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
key: MINE,
|
|
@@ -54,7 +54,7 @@ export default function SupportDesk({ open, onClose, config, title = 'Support' }
|
|
|
54
54
|
<UnorderedListOutlined /> My Tickets
|
|
55
55
|
</span>
|
|
56
56
|
),
|
|
57
|
-
children: <
|
|
57
|
+
children: <SupportFrame config={config} mode={MODES.MINE} intro="Your tickets, with their latest status." loadingText="Loading your tickets…" errorTitle="We couldn't open your tickets" />,
|
|
58
58
|
},
|
|
59
59
|
]}
|
|
60
60
|
/>
|