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 vote down vote up
/**
 * 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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
 * 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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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&apos;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 vote down vote up
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 vote down vote up
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 vote down vote up
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} />}
    </>
  )
}