react-router#useRouteMatch JavaScript Examples
The following examples show how to use
react-router#useRouteMatch.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.js From frontend-app-discussions with GNU Affero General Public License v3.0 | 6 votes |
/**
* Hook to return the path for the current comments page
* @returns {string}
*/
export function useCommentsPagePath() {
const { params } = useRouteMatch(Routes.COMMENTS.PAGE);
return Routes.COMMENTS.PAGES[params.page];
}
Example #2
Source File: wallet-router.js From albedo with MIT License | 6 votes |
export default function WalletRouter() {
const {path} = useRouteMatch()
return <AccountContextView>
<Switch>
<Route path={`${path}/swap`} component={SwapView}/>
<Route path={`${path}/transfer`} component={TransferView}/>
<Route path={`${path}/add-trustline`} component={AddTrustlineView}/>
<Route path={`${path}/liquidity-pool`} component={LiquidityPoolsRouter}/>
<Route component={NotFound}/>
</Switch>
</AccountContextView>
}
Example #3
Source File: liquidity-pools-router.js From albedo with MIT License | 6 votes |
export default function LiquidityPoolsRouter() {
const {path} = useRouteMatch()
return <Switch>
<Route path={`${path}/deposit`} component={DepositView}/>
<Route path={`${path}/withdraw`} component={WithdrawView}/>
<Route path={`${path}`} exact component={LiquidityPoolView}/>
<Route component={NotFound}/>
</Switch>
}
Example #4
Source File: WalletHeader.jsx From one-wallet with Apache License 2.0 | 6 votes |
WalletHeader = () => {
const { isMobile } = useWindowDimensions()
const dev = useSelector(state => state.global.dev)
const history = useHistory()
const match = useRouteMatch('/:action/:address?')
const { action, address: routeAddress } = match ? match.params : {}
const address = routeAddress && util.safeNormalizedAddress(routeAddress) || ''
const wallets = useSelector(state => state.wallet)
const wallet = wallets[address] || {}
const subtitle = address && <>{wallet.name && <Hint style={{ marginRight: 32 }}>{wallet.name}</Hint>}<WalletAddress address={address} shorten={false} alwaysShowOptions /></>
const [settingsVisible, setSettingsVisible] = useState(false)
const [relayerEditVisible, setRelayerEditVisible] = useState(false)
return (
<PageHeader
style={{ background: '#ffffff', padding: isMobile ? 8 : undefined }}
onBack={action && (() => history.goBack())}
title={!isMobile && titleCase(action || '')}
subTitle={!isMobile && <Hint>{subtitle}</Hint>}
extra={[
dev && <Button key='toggle' shape='circle' icon={relayerEditVisible ? <CloseOutlined /> : <SettingOutlined />} onClick={() => setRelayerEditVisible(!relayerEditVisible)} />,
dev && relayerEditVisible &&
<Space size='small' key='relayer'>
<Button shape='circle' icon={<LockOutlined />} onClick={() => setSettingsVisible(true)} />
<RelayerSelector />
<Divider type='vertical' />
</Space>,
<NetworkSelector key='network' />,
<SecretSettings key='settings' visible={settingsVisible} onClose={() => setSettingsVisible(false)} />
]}
/>
)
}
Example #5
Source File: SiderMenu.jsx From one-wallet with Apache License 2.0 | 6 votes |
MobileSiderMenuV2 = ({ nav, ...args }) => {
const theme = useTheme()
const { secondaryTextColor } = getColorPalette(theme)
const match = useRouteMatch(Paths.matchStructure)
const { category, section } = match ? match.params : {}
const action = RouteActionMap[section] ?? RouteActionMap[category]
return (
<Menu
theme={theme}
mode='horizontal'
onClick={nav}
selectedKeys={[action]}
>
{[
{ key: RouteActionMap.show, IconEl: OverviewIcon, label: 'Overview' },
{ key: RouteActionMap.assets, IconEl: AssetsIcon, label: 'Assets' },
{ key: RouteActionMap.nft, IconEl: NFTIcon, label: 'NFTs' },
{ key: RouteActionMap.swap, IconEl: SwapIcon, label: 'Swap' },
{ key: RouteActionMap.stake, IconEl: StakeIcon, label: 'Stake' },
{ key: RouteActionMap.restore, IconEl: RestoreIcon, label: 'Restore' },
].map(({ key, IconEl, label }) => <Menu.Item key={key} style={{ display: 'flex', alignItems: 'center' }} icon={<IconEl fill={action === 'overview' ? 'currentColor' : secondaryTextColor} />}>{label}</Menu.Item>)}
<Menu.Item key='external/grant'><SiderLink href='https://harmony.one/wallet'>Grants</SiderLink></Menu.Item>
<Menu.Item key='external/audit'><SiderLink href='https://github.com/polymorpher/one-wallet/tree/master/audits'>Audits</SiderLink></Menu.Item>
<Menu.Item key='external/wiki'><SiderLink href='https://github.com/polymorpher/one-wallet/wiki'>Wiki</SiderLink></Menu.Item>
<Menu.Item key='external/bug'><SiderLink href='https://github.com/polymorpher/one-wallet/issues'>Bugs</SiderLink></Menu.Item>
<Menu.Item key='external/network'><SiderLink href='https://github.com/polymorpher/one-wallet/issues'>Network</SiderLink></Menu.Item>
<Menu.Item key='internal/tools'>Tools</Menu.Item>
</Menu>
)
}
Example #6
Source File: SiderMenu.jsx From one-wallet with Apache License 2.0 | 6 votes |
SiderMenu = ({ ...args }) => {
const { isMobile } = useWindowDimensions()
const history = useHistory()
const match = useRouteMatch('/:action')
const { action } = match ? match.params : {}
args.action = action
args.nav = ({ key }) => {
history.push(Paths[key])
}
return isMobile
? <MobileSiderMenu {...args} />
: <DeskstopSiderMenu {...args} />
}
Example #7
Source File: hooks.js From frontend-app-discussions with GNU Affero General Public License v3.0 | 6 votes |
useSidebarVisible = () => {
const isFiltered = useSelector(selectAreThreadsFiltered);
const totalThreads = useSelector(selectPostThreadCount);
const isViewingTopics = useRouteMatch(Routes.TOPICS.PATH);
const isViewingLearners = useRouteMatch(Routes.LEARNERS.PATH);
if (isFiltered) {
return true;
}
if (isViewingTopics || isViewingLearners) {
return true;
}
return totalThreads > 0;
}
Example #8
Source File: CourseAuthoringRoutes.jsx From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 6 votes |
/**
* As of this writing, these routes are mounted at a path prefixed with the following:
*
* /course/:courseId
*
* Meaning that their absolute paths look like:
*
* /course/:courseId/course-pages
* /course/:courseId/proctored-exam-settings
* /course/:courseId/editor/:blockType/:blockId
*
* This component and CourseAuthoringPage should maybe be combined once we no longer need to have
* CourseAuthoringPage split out for use in LegacyProctoringRoute. Once that route is removed, we
* can move the Header/Footer rendering to this component and likely pull the course detail loading
* in as well, and it'd feel a bit better-factored and the roles would feel more clear.
*/
export default function CourseAuthoringRoutes({ courseId }) {
const { path } = useRouteMatch();
return (
<CourseAuthoringPage courseId={courseId}>
<Switch>
<PageRoute path={`${path}/pages-and-resources`}>
<PagesAndResources courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/proctored-exam-settings`}>
<ProctoredExamSettings courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/editor/:blockType/:blockId`}>
{process.env.ENABLE_NEW_EDITOR_PAGES === 'true'
&& (
<EditorContainer
courseId={courseId}
/>
)}
</PageRoute>
</Switch>
</CourseAuthoringPage>
);
}
Example #9
Source File: EmptyTopics.jsx From frontend-app-discussions with GNU Affero General Public License v3.0 | 5 votes |
function EmptyTopics({ intl }) {
const match = useRouteMatch(ALL_ROUTES);
const dispatch = useDispatch();
const hasGlobalThreads = useTotalTopicThreadCount() > 0;
const topicThreadCount = useSelector(selectTopicThreadCount(match.params.topicId));
function addPost() {
return dispatch(showPostEditor());
}
const isOnDesktop = useIsOnDesktop();
let title = messages.emptyTitle;
let fullWidth = false;
let subTitle;
let action;
let actionText;
if (!isOnDesktop) {
return null;
}
if (match.params.topicId) {
if (topicThreadCount > 0) {
title = messages.noPostSelected;
} else {
action = addPost;
actionText = postMessages.addAPost;
subTitle = messages.emptyTopic;
fullWidth = true;
}
} else if (hasGlobalThreads) {
title = messages.noTopicSelected;
} else {
action = addPost;
actionText = postMessages.addAPost;
subTitle = messages.emptyAllTopics;
fullWidth = true;
}
return (
<EmptyPage
title={intl.formatMessage(title)}
subTitle={subTitle ? intl.formatMessage(subTitle) : null}
action={action}
actionText={actionText ? intl.formatMessage(actionText) : null}
fullWidth={fullWidth}
/>
);
}
Example #10
Source File: LegacyBreadcrumbMenu.jsx From frontend-app-discussions with GNU Affero General Public License v3.0 | 5 votes |
function LegacyBreadcrumbMenu() {
const {
params: {
courseId,
category,
topicId: currentTopicId,
},
} = useRouteMatch([Routes.TOPICS.CATEGORY, Routes.TOPICS.TOPIC]);
const currentTopic = useSelector(selectTopic(currentTopicId));
const currentCategory = category || currentTopic?.categoryId;
const topicsInCategory = useSelector(selectTopicsInCategory(currentCategory));
const nonCoursewareTopics = useSelector(selectNonCoursewareTopics);
const categories = useSelector(selectCategories);
const isNonCoursewareTopic = currentTopic && !currentCategory;
return (
<div className="breadcrumb-menu d-flex flex-row mt-2 mx-3">
{isNonCoursewareTopic ? (
<BreadcrumbDropdown
currentItem={currentTopic}
itemLabelFunc={(item) => item?.name}
itemActiveFunc={(topic) => topic?.id === currentTopicId}
items={nonCoursewareTopics}
showAllPath={discussionsPath(Routes.TOPICS.ALL, { courseId })}
itemPathFunc={(topic) => discussionsPath(Routes.TOPICS.TOPIC, {
courseId,
topicId: topic.id,
})}
/>
) : (
<BreadcrumbDropdown
currentItem={currentCategory}
itemLabelFunc={(catId) => catId}
itemActiveFunc={(catId) => catId === currentCategory}
items={categories}
showAllPath={discussionsPath(Routes.TOPICS.ALL, { courseId })}
itemPathFunc={(catId) => discussionsPath(Routes.TOPICS.CATEGORY, {
courseId,
category: catId,
})}
/>
)}
{currentCategory && (
<>
<div className="d-flex py-2">/</div>
<BreadcrumbDropdown
currentItem={currentTopic}
itemLabelFunc={(item) => item?.name}
itemActiveFunc={(topic) => topic?.id === currentTopicId}
items={topicsInCategory}
showAllPath={discussionsPath(Routes.TOPICS.CATEGORY, {
courseId,
category: currentCategory,
})}
itemPathFunc={(topic) => discussionsPath(Routes.TOPICS.TOPIC, {
courseId,
topicId: topic.id,
})}
/>
</>
)}
</div>
);
}
Example #11
Source File: SiderMenu.jsx From one-wallet with Apache License 2.0 | 5 votes |
DeskstopSiderMenuV2 = ({ nav, ...args }) => {
const history = useHistory()
const theme = useTheme()
const match = useRouteMatch(Paths.matchStructure)
const { category, section } = match ? match.params : {}
const action = RouteActionMap[section] ?? RouteActionMap[category]
const { primaryTextColor, secondaryTextColor } = getColorPalette(theme)
return (
<Layout.Sider collapsed={false} {...args} theme={theme} style={{ color: primaryTextColor }}>
<Row justify='center'>
<SiderLink href='https://harmony.one/'>
<Image preview={false} src={OneWalletLogo} style={{ cursor: 'pointer', padding: 32 }} onClick={() => history.push('/')} />
</SiderLink>
</Row>
<Row justify='center' style={{ marginBottom: 24 }}><SiderLink href='https://harmony.one/1wallet'>{config.appName} {config.version}</SiderLink></Row>
<Menu theme={theme} mode='inline' onClick={nav} selectedKeys={[action]}>
{[
{ key: RouteActionMap.show, IconEl: OverviewIcon, label: 'Overview' },
{ key: RouteActionMap.assets, IconEl: AssetsIcon, label: 'Assets' },
{ key: RouteActionMap.nft, IconEl: NFTIcon, label: 'NFTs' },
{ key: RouteActionMap.swap, IconEl: SwapIcon, label: 'Swap' },
{ key: RouteActionMap.stake, IconEl: StakeIcon, label: 'Stake' },
{ key: RouteActionMap.restore, IconEl: RestoreIcon, label: 'Restore' },
].map(({ key, IconEl, label }) => <Menu.Item key={key} icon={<IconEl fill={action === 'overview' ? 'currentColor' : secondaryTextColor} />}>{label}</Menu.Item>)}
</Menu>
<LineDivider />
<Menu theme={theme} mode='inline' className='secondary-menu' onClick={nav} selectedKeys={[action]} style={{ color: secondaryTextColor, textTransform: 'uppercase' }}>
<Menu.Item key='external/pro'><SiderLink href='https://modulo.so'>Pro Version</SiderLink></Menu.Item>
<Menu.Item key='external/audit'><SiderLink href='https://github.com/polymorpher/one-wallet/tree/master/audits'>Audits</SiderLink></Menu.Item>
<Menu.Item key='external/wiki'><SiderLink href='https://github.com/polymorpher/one-wallet/wiki'>Wiki</SiderLink></Menu.Item>
<Menu.Item key='external/bug'><SiderLink href='https://github.com/polymorpher/one-wallet/issues'>Bugs</SiderLink></Menu.Item>
<Menu.Item key='external/network'><SiderLink href='https://github.com/polymorpher/one-wallet/issues'>Network</SiderLink></Menu.Item>
<Menu.Item key='internal/tools'>Tools</Menu.Item>
</Menu>
</Layout.Sider>
)
}
Example #12
Source File: PagesAndResources.jsx From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 5 votes |
function PagesAndResources({ courseId, intl }) {
const { path, url } = useRouteMatch();
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchCourseApps(courseId));
}, [courseId]);
const courseAppIds = useSelector(state => state.pagesAndResources.courseAppIds);
const loadingStatus = useSelector(getLoadingStatus);
const { config } = useContext(AppContext);
const learningCourseURL = `${config.LEARNING_BASE_URL}/course/${courseId}`;
// Each page here is driven by a course app
const pages = useModels('courseApps', courseAppIds);
if (loadingStatus === RequestStatus.IN_PROGRESS) {
return <></>;
}
return (
<PagesAndResourcesProvider courseId={courseId}>
<main className="container container-mw-md px-3">
<div className="d-flex justify-content-between my-4 my-md-5 align-items-center">
<h3 className="m-0">{intl.formatMessage(messages.heading)}</h3>
<Hyperlink
destination={learningCourseURL}
target="_blank"
rel="noopener noreferrer"
showLaunchIcon={false}
>
<Button variant="outline-primary" className="p-2"> {intl.formatMessage(messages.viewLiveButton)}</Button>
</Hyperlink>
</div>
<PageGrid pages={pages} />
<Switch>
<PageRoute
path={[
`${path}/discussion/configure/:appId`,
`${path}/discussion`,
]}
>
<DiscussionsSettings courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/:appId/settings`}>
{
({ match, history }) => {
const SettingsComponent = React.lazy(async () => {
try {
// There seems to be a bug in babel-eslint that causes the checker to crash with the following error
// if we use a template string here:
// TypeError: Cannot read property 'range' of null with using template strings here.
// Ref: https://github.com/babel/babel-eslint/issues/530
return await import('./' + match.params.appId + '/Settings.jsx'); // eslint-disable-line
} catch (error) {
console.trace(error); // eslint-disable-line no-console
return null;
}
});
return (
<Suspense fallback="...">
<SettingsComponent onClose={() => history.push(url)} />
</Suspense>
);
}
}
</PageRoute>
</Switch>
</main>
</PagesAndResourcesProvider>
);
}
Example #13
Source File: WalletHeader.jsx From one-wallet with Apache License 2.0 | 5 votes |
WalletHeaderV2 = () => {
const dispatch = useDispatch()
const history = useHistory()
const { isMobile, width } = useWindowDimensions()
const theme = useTheme()
const dev = useSelector(state => state.global.dev)
const selectedAddress = useSelector(state => state.global.selectedWallet)
const wallets = useSelector(state => state.wallet)
const network = useSelector(state => state.global.network)
const networkWallets = util.filterNetworkWallets(wallets, network)
const matchedWallet = networkWallets.filter(w => w.address === selectedAddress)[0]
const [settingsVisible, setSettingsVisible] = useState(false)
const [relayerEditVisible, setRelayerEditVisible] = useState(false)
const { primaryBgColor, secondaryBgColor, secondaryBorderColor } = getColorPalette(theme)
const match = useRouteMatch(Paths.matchStructure)
const onAddressSelected = (e) => {
// Only change if a new address is selected.
if (selectedAddress !== e.value) {
history.push(Paths.showAddress(e.value, match?.params?.section))
dispatch(globalActions.selectWallet(e.value))
}
}
const onThemeChange = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
ConfigProvider.config({
theme: getColorPalette(newTheme),
})
dispatch(globalActions.setUiTheme(newTheme))
}
return (
<div
style={{
background: primaryBgColor,
padding: isMobile ? 8 : 24,
alignItems: 'center',
display: 'flex',
justifyContent: 'space-around',
flexWrap: 'wrap',
gap: '16px'
}}
>
{/* Wallet selector + send + receive if wallet exists */}
{matchedWallet && (
<div style={{ display: 'flex', height: '100%', alignItems: 'center', gap: '8px' }}>
<WalletSelectorV2 onAddressSelected={onAddressSelected} filter={w => w.majorVersion >= 10} selectedAddress={selectedAddress} useHex style={{ background: secondaryBgColor, border: `1px solid ${secondaryBorderColor}`, margin: '0 4px', padding: '4px 0', borderRadius: '16px' }} selectStyle={{}} />
<SecondaryButton onClick={() => history.push(Paths.showAddress(selectedAddress, 'transfer'))} style={{ padding: '8px 16px', height: '100%', borderRadius: '15px' }}>Send</SecondaryButton>
<SecondaryButton onClick={() => history.push(Paths.showAddress(selectedAddress, 'qr'))} style={{ padding: '8px 16px', height: '100%', borderRadius: '15px' }}>Receive</SecondaryButton>
</div>)}
{width >= Breakpoints.LARGE && <StatsInfoV2 />}
<div style={{ display: 'flex', height: '100%', alignItems: 'center' }}>
{dev && <Button key='toggle' shape='circle' icon={relayerEditVisible ? <CloseOutlined /> : <SettingOutlined />} onClick={() => setRelayerEditVisible(!relayerEditVisible)} />}
{dev && relayerEditVisible &&
<Space size='small' key='relayer'>
<Button shape='circle' icon={<LockOutlined />} onClick={() => setSettingsVisible(true)} />
<RelayerSelector />
<Divider type='vertical' />
</Space>}
<NetworkSelector key='network' />
</div>
<SecretSettings key='settings' visible={settingsVisible} onClose={() => setSettingsVisible(false)} />
<Switch checkedChildren='Dark' unCheckedChildren='Light' onChange={onThemeChange} checked={theme === 'dark'} />
</div>
)
}
Example #14
Source File: WalletAuth.jsx From one-wallet with Apache License 2.0 | 5 votes |
WalletAuth = () => {
const dispatch = useDispatch()
const location = useLocation()
const match = useRouteMatch(Paths.auth)
const { action } = match ? match.params : {}
const qs = querystring.parse(location.search)
const callback = qs.callback && Buffer.from(qs.callback, 'base64').toString()
const caller = qs.caller
const network = qs.network
const { amount, dest, from, calldata } = qs
const { message: msg, raw, duration, comment } = qs
// if (!action || !callback || !caller) {
// return <Redirect to={Paths.wallets} />
// }
useEffect(() => {
if (network) {
if (!config.networks[network]) {
message.error(`App requested invalid network: ${network}`)
} else {
message.success(`Switched to: ${network} (per request from ${caller})`, 10)
dispatch(globalActions.setNetwork(network))
}
}
}, [network])
if (!action || !callback || !caller) {
message.error('The app did not specify a callback, an action, or its identity. Please ask the app developer to fix it.')
return (
<AnimatedSection
show
style={{ minHeight: 320, maxWidth: 720 }}
title='Broken App'
>
<Text>The app did not specify a callback, an action, or its identity. Please ask the app developer to fix it.</Text>
</AnimatedSection>
)
}
return (
<>
{action === 'connect' && <ConnectWallet caller={caller} callback={callback} />}
{action === 'pay' && <RequestPayment caller={caller} callback={callback} amount={amount} dest={dest} from={from} />}
{action === 'call' && <RequestCall caller={caller} callback={callback} amount={amount} calldata={calldata} from={from} dest={dest} />}
{action === 'sign' && <RequestSignature caller={caller} callback={callback} messageB64Encoded={msg} commentB64Encoded={comment} raw={raw} duration={duration} from={from} />}
</>
)
}
Example #15
Source File: index.js From choerodon-front-base with Apache License 2.0 | 5 votes |
IAMIndex = () => {
const match = useRouteMatch();
const langauge = useCurrentLanguage();
const IntlProviderAsync = asyncLocaleProvider(langauge, () => import(`./locale/${langauge}`));
return (
<IntlProviderAsync>
<div className="c7ncd-base-root">
<Switch>
<Route path={`${match.url}/system-setting`} component={siteSetting} />
<Route path={`${match.url}/org-role`} component={orgRole} />
<Route path={`${match.url}/root-user`} component={rootUser} />
<Route
path={`${match.url}/team-member`}
component={projectUser}
/>
<Route path={`${match.url}/org-user`} component={orguser} />
<Route path={`${match.url}/project-setting/info`} component={generalSetting} />
<Route path={`${match.url}/user-info`} component={userInfo} />
<Route path={`${match.url}/permission-info`} component={permissionInfo} />
<PermissionRoute
path={`${match.url}/organization-setting`}
component={organizationSetting}
service={[
'choerodon.code.organization.setting.general-setting.ps.info',
'choerodon.code.organization.setting.general-setting.ps.ldap',
'choerodon.code.organization.setting.general-setting.ps.working-calendar',
]}
/>
<Route path={`${match.url}/org-safe`} component={orgSafe} />
<Route path={`${match.url}/safe`} component={siteSafe} />
<Route path={`${match.url}/client`} component={orgClient} />
<Route path={`${match.url}/pro-client`} component={orgClient} />
<PermissionRoute
path={`${match.url}/org-msg-log`}
component={orgMsgLog}
service={[
'choerodon.code.organization.manager.msglog.ps.default',
]}
/>
<Route path={`${match.url}/org-admin`} component={orgAdmin} />
<Route path={`${match.url}/org-overview`} component={orgOverview} />
<Route path={`${match.url}/platform-overview`} component={platformOverview} />
<Route path={`${match.url}/hzero/user`} component={heroPage} />
<Route path={`${match.url}/hzero/role`} component={heroPage} />
<Route path={`${match.url}/hzero/menu`} component={heroPage} />
<Route path={`${match.url}/hzero/instance`} component={heroPage} />
<Route path={`${match.url}/hzero/api-test`} component={heroPage} />
<Route path={`${match.url}/hzero/api`} component={heroPage} />
<Route path={`${match.url}/enterprise`} component={enterpriseInfo} />
<Route path="*" component={NoMatch} />
</Switch>
<ModalContainer />
</div>
</IntlProviderAsync>
);
}
Example #16
Source File: DiscussionsHome.jsx From frontend-app-discussions with GNU Affero General Public License v3.0 | 4 votes |
export default function DiscussionsHome() {
const location = useLocation();
const postEditorVisible = useSelector(
(state) => state.threads.postEditorVisible,
);
const {
params: { page },
} = useRouteMatch(`${Routes.COMMENTS.PAGE}?`);
const { params: { path } } = useRouteMatch(`${Routes.DISCUSSIONS.PATH}/:path*`);
const { params } = useRouteMatch(ALL_ROUTES);
const {
courseId,
postId,
topicId,
category,
learnerUsername,
} = params;
const inContext = new URLSearchParams(location.search).get('inContext') !== null;
// Display the content area if we are currently viewing/editing a post or creating one.
const displayContentArea = postId || postEditorVisible || (learnerUsername && postId);
let displaySidebar = useSidebarVisible();
const isOnDesktop = useIsOnDesktop();
if (displayContentArea) {
// If the window is larger than a particular size, show the sidebar for navigating between posts/topics.
// However, for smaller screens or embeds, only show the sidebar if the content area isn't displayed.
displaySidebar = isOnDesktop;
}
const provider = useSelector(selectDiscussionProvider);
useCourseDiscussionData(courseId);
useRedirectToThread(courseId);
useEffect(() => {
if (path && path !== 'undefined') {
postMessageToParent('discussions.navigate', { path });
}
}, [path]);
return (
<DiscussionContext.Provider value={{
page,
courseId,
postId,
topicId,
inContext,
category,
learnerUsername,
}}
>
<main className="container-fluid d-flex flex-column p-0 h-100 w-100 overflow-hidden">
<div
className="d-flex flex-row justify-content-between navbar fixed-top"
style={{ boxShadow: '0px 2px 4px rgb(0 0 0 / 15%), 0px 2px 8px rgb(0 0 0 / 15%)' }}
>
{!inContext && (
<Route path={Routes.DISCUSSIONS.PATH} component={NavigationBar} />
)}
<PostActionsBar inContext={inContext} />
</div>
<Route
path={[Routes.POSTS.PATH, Routes.TOPICS.CATEGORY]}
component={provider === DiscussionProvider.LEGACY ? LegacyBreadcrumbMenu : BreadcrumbMenu}
/>
<div className="d-flex flex-row overflow-hidden flex-grow-1">
<DiscussionSidebar displaySidebar={displaySidebar} />
{displayContentArea && <DiscussionContent />}
{!displayContentArea && (
<Switch>
<Route path={Routes.TOPICS.PATH} component={EmptyTopics} />
<Route
path={Routes.POSTS.MY_POSTS}
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyMyPosts} />}
/>
<Route
path={[Routes.POSTS.PATH, Routes.POSTS.ALL_POSTS, Routes.LEARNERS.POSTS]}
render={routeProps => <EmptyPosts {...routeProps} subTitleMessage={messages.emptyAllPosts} />}
/>
<Route path={Routes.LEARNERS.PATH} component={EmptyLearners} />
</Switch>
)}
</div>
</main>
</DiscussionContext.Provider>
);
}
Example #17
Source File: Edit.js From dshop with MIT License | 4 votes |
EditProduct = () => {
const history = useHistory()
const match = useRouteMatch('/admin/products/:productId')
const [{ admin, config }, dispatch] = useStateValue()
const { productId } = match.params
const { post } = useBackendApi({ authToken: true })
const [submitting, setSubmitting] = useState(false)
const [, setSubmitError] = useState(null)
const [formState, setFormState] = useSetState({
options: [],
variants: [],
collections: []
})
const [hasOptions, setHasOptions] = useState(false)
const [useOriginalImage, setOriginalImage] = useState(false)
const isNewProduct = productId === 'new'
const externallyManaged = formState.externalId ? true : false
const [allowDescEdit, setAllowDescEdit] = useState(true)
const input = formInput(formState, (newState) => {
setFormState({ ...newState, hasChanges: true })
})
const Feedback = formFeedback(formState)
const title = isNewProduct
? fbt('Add product', 'admin.products.addProduct')
: fbt('Edit product', 'admin.products.editProduct')
const { product } = useAdminProduct(productId)
const { collections } = useCollections()
const [media, setMedia] = useState([])
useEffect(() => {
if (product === null) {
history.push('/admin/products')
return
}
if (product === undefined) {
return
}
const newFormState = {
...product,
price: (product.price / 100).toFixed(2),
variants: (product.variants || []).map((variant) => ({
...variant,
price: (variant.price / 100).toFixed(2)
})),
printfulDesc: product.printfulDesc || product.description
}
if (newFormState.externalId) {
// Externally managed product
// Check if it has limited stock
newFormState.limitedEdition = get(newFormState, 'quantity', -1) >= 0
}
let imageArray = product.images
if (!imageArray && product.image) {
imageArray = [product.image]
} else if (!imageArray) {
imageArray = []
}
const mappedImages = imageArray.map((image) => ({
src: image.includes('/__tmp/')
? image
: `/${localStorage.activeShop}/${product.id}/orig/${image}`,
path: image
}))
const shouldBackfillOptions =
newFormState.options &&
(!newFormState.availableOptions ||
newFormState.availableOptions.length !== product.options.length)
if (shouldBackfillOptions) {
// While editing existing products
newFormState.availableOptions = newFormState.options.map(
(option, index) => {
// Parse possible values from generated variants
return Array.from(
new Set(
(product.variants || [])
.map((v) => v.options[index])
.filter((o) => !!o)
)
)
}
)
}
// Regenerate variants
newFormState.variants = generateVariants(newFormState)
setAllowDescEdit(
!product.externalId || newFormState.printfulDesc !== product.description
)
setMedia(mappedImages)
setFormState(newFormState)
setHasOptions(!!product.options && product.options.length > 0)
}, [product])
useEffect(() => {
if (collections && collections.length) {
setFormState({
collections: collections
.filter((c) => c.products.includes(productId))
.map((c) => c.id)
})
}
}, [collections, productId])
useEffect(() => {
if (hasOptions && (!formState.options || !formState.options.length)) {
setFormState({
// Enforce at least one option if checkbox is selected
options: [''],
availableOptions: [[]]
})
}
}, [hasOptions, formState])
const createProduct = async () => {
if (submitting) return
setSubmitError(null)
const { valid, newState } = validate(formState, {
hasOptions,
inventory: config.inventory
})
setFormState({ ...newState, hasChanges: false })
if (!valid) {
setSubmitError(
fbt(
'Please fill in all required fields',
'admin.products.missingFieldsError'
)
)
dispatch({
type: 'toast',
message: fbt(
'Please fill in all required fields',
'admin.products.missingFieldsError'
),
style: 'error'
})
return
}
setSubmitting(true)
const variants = (newState.variants || []).map((variant) => ({
...variant,
price: variant.price * 100
}))
try {
const { product } = await post(`/products`, {
method: 'POST',
body: JSON.stringify({
...newState,
price: hasOptions
? variants.reduce((min, v) => {
return v.price < min ? v.price : min
}, get(variants, '0.price', newState.price * 100))
: newState.price * 100,
images: media.map((file) => file.path),
collections: newState.collections,
variants
})
})
// Clear memoize cache for existing product
fetchProduct.cache.delete(`${config.dataSrc}-${product.id}`)
dispatch({
type: 'toast',
message: fbt('Product saved', 'admin.products.productSaved')
})
dispatch({
type: 'reload',
target: ['products', 'collections', 'shopConfig']
})
if (!newState.id) {
history.push(`/admin/products/${product.id}`)
}
return
} catch (error) {
console.error('Could not update the product', error)
setSubmitError(
fbt('Could not update the product', 'admin.products.updateFailed')
)
} finally {
setSubmitting(false)
}
}
const actions = (
<div className="actions">
{isNewProduct ? (
<button
className="btn btn-outline-primary"
type="button"
onClick={() => {
setFormState({ hasChanges: false, redirectTo: '/admin/products' })
}}
children="Discard"
/>
) : (
<DeleteButton type="button" product={product}>
<fbt desc="Delete">Delete</fbt>
</DeleteButton>
)}
<button
className={`btn btn-primary${formState.hasChanges ? '' : ' disabled'}`}
type="submit"
children="Save"
/>
</div>
)
if (formState.redirectTo) {
return <Redirect to={formState.redirectTo} />
}
return (
<div className="admin-edit-product">
<Prompt
when={formState.hasChanges ? true : false}
message={fbt(
'Are you sure? You have unsaved changes.',
'admin.products.unsavedChanges'
).toString()}
/>
<form
autoComplete="off"
onSubmit={(e) => {
e.preventDefault()
createProduct()
}}
>
<h3 className="admin-title with-border">
<Link to="/admin/products" className="muted">
<fbt desc="Products">Products</fbt>
</Link>
<span className="chevron" />
{title}
{actions}
</h3>
<div className="row">
<div className="col-md-9">
<div className="form-section">
{!externallyManaged ? null : (
<div className="alert alert-info">
<fbt desc="admin.products.manageViaPrintful">
Please manage this product
<a
className="ml-1"
style={{ textDecoration: 'underline' }}
href={`https://www.printful.com/dashboard/sync/update?id=${formState.externalId}`}
>
via Printful
</a>
</fbt>
.
</div>
)}
<div className="form-group">
<label>
<fbt desc="Title">Title</fbt>
</label>
<input
type="text"
{...input('title')}
autoFocus={isNewProduct && !externallyManaged}
disabled={externallyManaged}
/>
{Feedback('title')}
</div>
<div className="form-group">
<div className="d-flex justify-content-between">
<label>
<fbt desc="Description">Description</fbt>
</label>
{!externallyManaged ? null : (
<div className="form-check mb-0">
<label className="font-weight-normal">
<input
checked={allowDescEdit}
onChange={(e) => {
if (!e.target.checked) {
setFormState({
description: formState.printfulDesc,
// To not lose any changes
customDesc: formState.description
})
} else {
setFormState({
description:
formState.customDesc || formState.description
})
}
setAllowDescEdit(e.target.checked)
}}
type="checkbox"
className="mr-2"
/>
<fbt desc="admin.products.edit.overrideDesc">
Override Printful's description
</fbt>
</label>
</div>
)}
</div>
<textarea {...input('description')} disabled={!allowDescEdit} />
{Feedback('description')}
</div>
<div className="media-uploader">
<div className="d-flex">
<label>
<fbt desc="Photos">Photos</fbt>{' '}
{externallyManaged ? null : (
<span>
(
<fbt desc="admin.products.addManyPhotos">
add as many as you like
</fbt>
)
</span>
)}
</label>
{!admin.superuser ? null : (
<div className="ml-auto d-flex align-items-center">
<input
type="checkbox"
className="mr-1"
checked={useOriginalImage}
onChange={(e) => setOriginalImage(e.target.checked)}
/>
Use original
</div>
)}
</div>
<ImagePicker
useOriginal={useOriginalImage}
images={media}
onChange={(media) => {
setFormState({ hasChanges: true, imagesUpdated: true })
setMedia(media)
}}
/>
</div>
<div className={`row${hasOptions ? ' d-none' : ''}`}>
<div className="col-md-6">
<div className="form-group">
<label>
<fbt desc="Price">Price</fbt>
</label>
<div className="input-group">
<div className="input-group-prepend">
<span className="input-group-text">
{formatPrice(0, {
symbolOnly: true,
currency: config.currency
})}
</span>
</div>
<input
{...input('price')}
disabled={externallyManaged || hasOptions}
/>
</div>
{Feedback('price')}
</div>
<div className="form-group">
<label>
<fbt desc="SKU">SKU</fbt>{' '}
<span>
(<fbt desc="StockKeepingUnit">Stock Keeping Unit</fbt>)
</span>
</label>
<input
type="text"
{...input('sku')}
disabled={hasOptions}
/>
{Feedback('sku')}
</div>
</div>
</div>
<div className="row">
<div className="col-md-6">
{!config.inventory || !externallyManaged ? null : (
<div className="row">
<div className="col-md-12">
<label>
<fbt desc="LimitedEdition">Limited Edition</fbt>
</label>
<div className="form-check">
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={formState.limitedEdition ? true : false}
onChange={() =>
setFormState({
limitedEdition: formState.limitedEdition
? false
: true,
hasChanges: true
})
}
/>
<fbt desc="admin.products.limitedEdition">
This is a Limited Edition product
</fbt>
</label>
</div>
</div>
</div>
)}
{!config.inventory ||
(externallyManaged && !formState.limitedEdition) ? null : (
<div className="form-group">
<label>
<fbt desc="AvailableStock">Available Stock</fbt>
</label>
<input
type="number"
min="0"
step="1"
{...input('quantity')}
disabled={externallyManaged ? false : hasOptions}
/>
{Feedback('quantity')}
</div>
)}
</div>
</div>
</div>
<div className="row">
<div className="col-md-12">
<label>
<fbt desc="Variants">Variants</fbt>
</label>
<div className="form-check">
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={hasOptions}
disabled={externallyManaged}
onChange={(e) => {
setHasOptions(e.target.checked)
setFormState({ hasChanges: true })
}}
/>
<fbt desc="admin.products.hasVariants">
This product has multiple options, like different sizes
</fbt>
</label>
</div>
</div>
</div>
{!hasOptions ? null : (
<>
{(formState.options || []).map((option, index) => {
return (
<EditOptions
key={index}
label={
<fbt desc="admin.products.optionTitle">
Option{' '}
<FbtParam name="optionNumber">{index + 1}</FbtParam>
</fbt>
}
placeholder={
index === 0
? fbt('eg Size', 'admin.products.optionExampleSize')
: index === 1
? fbt('eg Color', 'admin.products.optionExampleColor')
: null
}
formState={{
title: option,
individualOpts: formState.availableOptions[index]
}}
setFormState={(newState) => {
const updatedState = {
options: [...formState.options],
availableOptions: [...formState.availableOptions]
}
const keysToUpdate = Object.keys(newState)
if (keysToUpdate.includes('title')) {
updatedState.options[index] = newState.title
}
if (keysToUpdate.includes('individualOpts')) {
updatedState.availableOptions[index] =
newState.individualOpts
updatedState.variants = generateVariants({
...formState,
...updatedState
})
}
updatedState.hasChanges = true
setFormState(updatedState)
}}
onRemove={() => {
const optionsContainer = [...formState.options]
const availableOptionsContainer = [
...formState.availableOptions
]
optionsContainer.splice(index, 1)
availableOptionsContainer.splice(index, 1)
const updatedState = {
options: optionsContainer,
availableOptions: availableOptionsContainer,
hasChanges: true,
imagesUpdated: externallyManaged ? true : undefined
}
updatedState.variants = generateVariants({
...updatedState
})
setFormState(updatedState) // TODO: inspect the impact of/evaluate the need for, inserting the EditVariants component after this operation
}}
disabled={externallyManaged}
/>
)
})}
<div className="mb-5">
{get(formState, 'options.length') >= 3 ||
externallyManaged ? null : (
<button
className="btn btn-outline-primary"
type="button"
onClick={() => {
setFormState({
options: [...formState.options, ''],
availableOptions: [...formState.availableOptions, []],
hasChanges: true
})
}}
>
<fbt desc="admin.products.addOption">Add option</fbt>
</button>
)}
</div>
<EditVariants
currency={config.currency}
options={formState.options}
variants={formState.variants}
media={media}
disabled={externallyManaged}
onChange={(variants) => {
setFormState({
variants,
hasChanges: true,
imagesUpdated: externallyManaged ? true : undefined
})
}}
/>
</>
)}
<div className="row">
<div className="col-md-12">
<label>
<fbt desc="NFT">NFT</fbt>
</label>
<div className="form-check">
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={formState.nft ? true : false}
onChange={() =>
setFormState({
nft: formState.nft ? false : true,
hasChanges: true
})
}
/>
<fbt desc="admin.products.isNFT">
This product has an associated NFT
</fbt>
</label>
</div>
</div>
</div>
</div>
<div className="col-md-3">
<LinkCollections
selectedValues={formState.collections}
onChange={(collections) =>
setFormState({ collections, hasChanges: true })
}
/>
</div>
</div>
<div className="footer-actions">{actions}</div>
</form>
</div>
)
}
Example #18
Source File: AppConfigForm.jsx From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 4 votes |
function AppConfigForm({
courseId, intl,
}) {
const dispatch = useDispatch();
const { formRef } = useContext(AppConfigFormContext);
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
const { params: { appId: routeAppId } } = useRouteMatch();
const [isLoading, setLoading] = useState(true);
const {
activeAppId, selectedAppId, status, saveStatus,
} = useSelector(state => state.discussions);
const [confirmationDialogVisible, setConfirmationDialogVisible] = useState(false);
useEffect(() => {
(async () => {
await dispatch(fetchDiscussionSettings(courseId, selectedAppId));
setLoading(false);
})();
}, [courseId, selectedAppId]);
useEffect(() => {
if (status === LOADED) {
if (routeAppId !== selectedAppId) {
dispatch(selectApp({ appId: routeAppId }));
}
}
}, [selectedAppId, routeAppId, status]);
// This is a callback that gets called after the form has been submitted successfully.
const handleSubmit = useCallback((values) => {
const needsConfirmation = (activeAppId !== selectedAppId);
if (needsConfirmation && !confirmationDialogVisible) {
setConfirmationDialogVisible(true);
} else {
setConfirmationDialogVisible(false);
// Note that when this action succeeds, we redirect to pagesAndResourcesPath in the thunk.
dispatch(saveProviderConfig(courseId, selectedAppId, values, pagesAndResourcesPath));
}
}, [activeAppId, confirmationDialogVisible, courseId, selectedAppId]);
if (!selectedAppId || status === LOADING || isLoading) {
return (
<Loading />
);
}
let alert = null;
if (saveStatus === FAILED) {
alert = (
<SaveFormConnectionErrorAlert />
);
}
if (saveStatus === DENIED) {
alert = <PermissionDeniedAlert />;
}
let form;
if (selectedAppId === 'legacy') {
form = (
<OpenedXConfigForm
formRef={formRef}
onSubmit={handleSubmit}
legacy
/>
);
} else if (selectedAppId === 'openedx') {
form = (
<OpenedXConfigForm
formRef={formRef}
onSubmit={handleSubmit}
legacy={false}
/>
);
} else {
form = (
<LtiConfigForm
formRef={formRef}
onSubmit={handleSubmit}
/>
);
}
return (
<Container size="sm" className="px-sm-0 py-sm-5 p-0" data-testid="appConfigForm">
{alert}
{form}
<ModalDialog
hasCloseButton={false}
isOpen={confirmationDialogVisible}
onClose={() => setConfirmationDialogVisible(false)}
title={intl.formatMessage(messages.ok)}
>
<ModalDialog.Header className="pt-4">
<ModalDialog.Title className="h4 m-0" style={{ fontSize: '1.125rem' }}>
{intl.formatMessage(messages.confirmConfigurationChange)}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body className="overflow-hidden text-primary-700">
{intl.formatMessage(messages.configurationChangeConsequence)}
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.cancel)}
</ModalDialog.CloseButton>
<AppConfigFormSaveButton labelText={intl.formatMessage(messages.ok)} />
</ActionRow>
</ModalDialog.Footer>
</ModalDialog>
</Container>
);
}
Example #19
Source File: DiscussionsSettings.jsx From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 4 votes |
function DiscussionsSettings({ courseId, intl }) {
const dispatch = useDispatch();
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
const { status, hasValidationError } = useSelector(state => state.discussions);
const { canChangeProviders } = useSelector(state => state.courseDetail);
const courseDetail = useModel('courseDetails', courseId);
useEffect(() => {
dispatch(fetchProviders(courseId));
}, [courseId]);
const discussionsPath = `${pagesAndResourcesPath}/discussion`;
const { params: { appId } } = useRouteMatch();
const startStep = appId ? SETTINGS_STEP : SELECTION_STEP;
const [currentStep, setCurrentStep] = useState(startStep);
useEffect(() => {
setCurrentStep(appId ? SETTINGS_STEP : SELECTION_STEP);
}, [appId]);
const handleClose = useCallback(() => {
history.push(pagesAndResourcesPath);
}, [pagesAndResourcesPath]);
const handleBack = useCallback(() => {
history.push(discussionsPath);
}, [discussionsPath]);
if (!courseDetail) {
return <Loading />;
}
if (status === FAILED) {
return (
<FullscreenModal
className="bg-light-200"
title={intl.formatMessage(messages.configure)}
onClose={handleClose}
isOpen
>
<ConnectionErrorAlert />
</FullscreenModal>
);
}
if (status === DENIED) {
return (
<FullscreenModal
className="bg-light-200"
title={intl.formatMessage(messages.configure)}
onClose={handleClose}
isOpen
>
<PermissionDeniedAlert />
</FullscreenModal>
);
}
return (
<DiscussionsProvider path={discussionsPath}>
<AppConfigForm.Provider>
<Stepper activeKey={currentStep}>
<FullscreenModal
className="bg-light-200"
modalBodyClassName="px-sm-4"
title={intl.formatMessage(messages.configure)}
onClose={handleClose}
isOpen
beforeBodyNode={<Stepper.Header className="border-bottom border-light" />}
footerNode={(
<>
<Stepper.ActionRow eventKey={SELECTION_STEP}>
<AppList.NextButton />
</Stepper.ActionRow>
<Stepper.ActionRow eventKey={SETTINGS_STEP}>
<div className="d-flex w-100 justify-content-between">
<Button
variant="outline-primary"
onClick={handleBack}
>
{intl.formatMessage(messages.backButton)}
</Button>
<AppConfigForm.SaveButton />
</div>
</Stepper.ActionRow>
</>
)}
>
<Stepper.Step
eventKey={SELECTION_STEP}
title={intl.formatMessage(messages.providerSelection)}
>
{
!canChangeProviders && (
<Alert variant="warning">
{intl.formatMessage(messages.noProviderSwitchAfterCourseStarted)}
</Alert>
)
}
<AppList />
</Stepper.Step>
<Stepper.Step
eventKey={SETTINGS_STEP}
title={intl.formatMessage(messages.settings)}
description={hasValidationError ? intl.formatMessage(messages.Incomplete) : ''}
hasError={hasValidationError}
>
<AppConfigForm
courseId={courseId}
/>
</Stepper.Step>
</FullscreenModal>
</Stepper>
</AppConfigForm.Provider>
</DiscussionsProvider>
);
}
Example #20
Source File: Show.jsx From one-wallet with Apache License 2.0 | 4 votes |
Show = () => {
const history = useHistory()
const location = useLocation()
const dispatch = useDispatch()
const wallets = useSelector(state => state.wallet)
const match = useRouteMatch(Paths.show)
const { address: routeAddress, action } = match ? match.params : {}
const oneAddress = util.safeOneAddress(routeAddress)
const address = util.safeNormalizedAddress(routeAddress)
const selectedAddress = useSelector(state => state.global.selectedWallet)
const wallet = wallets[address] || {}
const [section, setSection] = useState(action)
const [command, setCommand] = useState(action)
const network = useSelector(state => state.global.network)
const [activeTab, setActiveTab] = useState('coins')
const { expert } = wallet
const dev = useSelector(state => state.global.dev)
useEffect(() => {
if (!wallet.address) {
return history.push(Paths.wallets)
}
if (address && (address !== selectedAddress)) {
dispatch(globalActions.selectWallet(address))
}
const fetch = () => dispatch(balanceActions.fetchBalance({ address }))
const handler = setInterval(() => {
if (!document.hidden) { fetch() }
}, WalletConstants.fetchBalanceFrequency)
batch(() => {
fetch()
dispatch(walletActions.fetchWallet({ address }))
})
return () => { clearInterval(handler) }
}, [address])
const selectedToken = wallet?.selectedToken || HarmonyONE
useEffect(() => {
const m = matchPath(location.pathname, { path: Paths.show })
const { action } = m ? m.params : {}
if (action !== 'nft' && action !== 'transfer' && selectedToken.key !== 'one' && selectedToken.tokenType !== ONEConstants.TokenType.ERC20) {
dispatch(walletActions.setSelectedToken({ token: null, address }))
}
if (SpecialCommands.includes(action)) {
setCommand(action)
} else {
setCommand('')
}
if (tabList.find(t => t.key === action)) {
setSection(undefined)
setActiveTab(action)
return
} else if (SectionList.includes(action)) {
setSection(action)
return
}
setSection('')
}, [location])
const showTab = (tab) => { history.push(Paths.showAddress(oneAddress, tab)) }
const showStartScreen = () => { history.push(Paths.showAddress(oneAddress)) }
// UI Rendering below
if (!wallet.address || wallet.network !== network) {
return <Redirect to={Paths.wallets} />
}
const displayTabList = tabList.filter(e => e.tab && ((!e.expert || expert) || (!e.dev || dev)) && (!e.requireNetwork || e.requireNetwork(network)))
return (
<>
{!section &&
<AnimatedSection
title={<WalletTitle address={address} onQrCodeClick={() => showTab('qr')} onScanClick={() => showTab('scan')} />}
tabList={displayTabList}
activeTabKey={activeTab}
onTabChange={key => showTab(key)}
wide
>
<Warnings address={address} />
{activeTab === 'about' && <About address={address} />}
{activeTab === 'coins' && <Balance address={address} />}
{activeTab === 'coins' && <ERC20Grid address={address} />}
{activeTab === 'nft' && <NFTDashboard address={address} />}
{activeTab === 'help' && <Recovery address={address} />}
{activeTab === 'swap' && <Swap address={address} />}
{activeTab === 'gift' && <Gift address={address} />}
{activeTab === 'qr' && <QRCode address={address} name={wallet.name} />}
{activeTab === 'scan' && <Scan address={address} />}
{activeTab === 'call' && <Call address={address} headless />}
{activeTab === 'sign' && <Sign address={address} headless />}
{activeTab === 'history' && <TransactionViewer address={address} />}
<Upgrade address={address} prompt={command === 'upgrade'} onClose={showStartScreen} />
<CheckForwardState address={address} onClose={() => history.push(Paths.wallets)} />
<CheckRoots address={address} onClose={() => history.push(Paths.wallets)} />
</AnimatedSection>}
{section === 'transfer' && <Send address={address} onClose={showStartScreen} />}
{section === 'limit' && <Limit address={address} onClose={showStartScreen} />}
{section === 'recover' && <DoRecover address={address} onClose={showStartScreen} />}
{section === 'setRecoveryAddress' && <SetRecovery address={address} onClose={showStartScreen} />}
{section === 'domain' && <PurchaseDomain address={address} onClose={showStartScreen} />}
{section === 'domainTransfer' && <TransferDomain address={address} onClose={showStartScreen} />}
{section === 'reclaim' && <Reclaim address={address} onClose={showStartScreen} />}
{section === 'extend' && <Extend address={address} onClose={showStartScreen} />}
{section === 'stake' && <Stake address={address} onClose={showStartScreen} />}
{section === 'unstake' && <Unstake address={address} />}
{section === 'collectStakeReward' && <CollectStakeReward address={address} />}
</>
)
}