react-router#StaticContext TypeScript Examples

The following examples show how to use react-router#StaticContext. 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: AccountEnterPage.tsx    From clearflask with Apache License 2.0 4 votes vote down vote up
class AccountEnterPage extends Component<Props & WithTranslation<'site'> & RouteComponentProps<{}, StaticContext, LocationState | undefined> & ConnectProps & WithStyles<typeof styles, true>, State> {
  readonly cfReturnUrl?: string;
  readonly oauthFlow = new OAuthFlow({ accountType: 'admin', redirectPath: '/login' });

  constructor(props) {
    super(props);

    this.state = {
      couponId: this.props.couponId,
    };

    try {
      const paramCfr = new URL(windowIso.location.href).searchParams.get('cfr');
      if (paramCfr && new URL(paramCfr).host.endsWith(windowIso.location.host)) {
        this.cfReturnUrl = paramCfr;
      }
    } catch (er) { }
  }

  async componentDidMount() {
    const oauthToken = this.oauthFlow.checkResult();
    if (!!oauthToken) {
      this.setState({ isSubmitting: true });
      var basePlanId: string | undefined;
      var couponId: string | undefined;
      var invitationId: string | undefined;
      var redirectTo: string | undefined;
      if (oauthToken?.extraData) {
        try {
          const oauthExtraData = JSON.parse(oauthToken?.extraData) as OauthExtraData;
          couponId = oauthExtraData?.couponId;
          invitationId = oauthExtraData?.invitationId;
          basePlanId = oauthExtraData?.selectedPlanId;
          redirectTo = oauthExtraData?.redirectTo;
        } catch (e) { }
      }
      try {
        const result = await (await ServerAdmin.get().dispatchAdmin()).accountBindAdmin({
          accountBindAdmin: {
            oauthToken: !oauthToken ? undefined : {
              id: oauthToken.id,
              code: oauthToken.code,
              basePlanId,
              invitationId,
              couponId,
            },
          },
        });
        if (result.account) {
          this.setState({
            accountWasCreated: !!result.created,
            redirectTo,
            isSubmitting: false,
          });
        } else {
          this.setState({ isSubmitting: false });
        }
      } catch (e) {
        this.setState({ isSubmitting: false });
        throw e;
      }
    } else if (this.props.type === 'coupon' || this.props.type === 'invitation') {
      if (this.props.accountStatus === undefined && !this.props.account) {
        ServerAdmin.get().dispatchAdmin().then(d => d.accountBindAdmin({ accountBindAdmin: {} }));
      }
    }

    if (!!this.props.couponId) {
      this.onCouponCheck(this.props.couponId);
    }
  }

  render() {
    if (this.props.plansStatus === undefined) {
      ServerAdmin.get().dispatchAdmin({ debounce: true, ssr: true }).then(d => d
        .plansGet());
    }
    const isLoggedIn = this.props.accountStatus === Status.FULFILLED && !!this.props.account;
    if (this.props.type === 'invitation') {
      return this.renderInvitation(isLoggedIn);
    }
    if (this.props.type === 'coupon') {
      return this.renderCoupon(isLoggedIn);
    }

    if (this.props.accountStatus === Status.FULFILLED && !!this.props.account
      // Only redirect once submission is over (and redirectTo and accountWasCreated is set appropriately)
      && !this.state.isSubmitting) {
      if (this.props.cfJwt && this.cfReturnUrl) {
        windowIso.location.href = `${this.cfReturnUrl}?${SSO_TOKEN_PARAM_NAME}=${this.props.cfJwt}`;
        return (<ErrorPage msg={this.props.t('redirecting-you-back')} variant='success' />);
      }
      return (<RedirectIso to={this.state.redirectTo
        || this.props.location.state?.[ADMIN_LOGIN_REDIRECT_TO]
        || (this.state.accountWasCreated
          ? '/dashboard/welcome' :
          '/dashboard')} />);
    }

    const selectedPlanId = this.props.location.state?.[PRE_SELECTED_BASE_PLAN_ID]
      || (this.props.plans ? this.props.plans[0].basePlanId : undefined);

    if (this.props.type === 'signup') {
      if (!selectedPlanId && !this.props.plans) {
        return <LoadingPage />
      }
      if (!selectedPlanId || !SIGNUP_PROD_ENABLED && isProd() && new URL(windowIso.location.href).searchParams.get('please') !== undefined) {
        return <ErrorPage variant='warning' msg={(
          <div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', }} >
            Direct sign ups are currently disabled. Instead,&nbsp;
            <NavLink to='/contact/demo' className={this.props.classes.link}>schedule a demo</NavLink>
            &nbsp;with us.
          </div>
        )} />
      }
    }

    const isSingleCustomer = detectEnv() === Environment.PRODUCTION_SELF_HOST;
    const isOauthEnabled = !isSingleCustomer;
    const signUpOrLogIn = this.props.type === 'signup' ? this.props.t('sign-up-with') : this.props.t('log-in-with');

    return (
      <EnterTemplate
        title={(
          <>
            {(this.props.type === 'signup' ? this.props.t('get-started-with') : this.props.t('welcome-back-to')) + ' '}
            <span className={this.props.classes.titleClearFlask}>ClearFlask</span>
          </>
        )}
        renderContent={submitButton => (
          <>
            {this.state.couponPlan && (
              <Alert className={this.props.classes.alert} severity='info'>
                Redeeming <span className={this.props.classes.bold}>{this.state.couponPlan.title}</span> plan.
              </Alert>
            )}
            {this.props.invitation?.projectName && (
              <Alert className={this.props.classes.alert} severity='info'>
                Invitation to <span className={this.props.classes.bold}>{this.props.invitation?.projectName}</span>.
              </Alert>
            )}
            {isOauthEnabled && (
              <>
                <Button
                  className={this.props.classes.oauthEnter}
                  variant='outlined'
                  fullWidth
                  size='large'
                  onClick={e => !!selectedPlanId && this.onOauth('google', selectedPlanId)}
                  disabled={this.state.isSubmitting}
                >
                  <GoogleIcon />
                  &nbsp;&nbsp;{signUpOrLogIn}&nbsp;Google
                </Button>
                <Button
                  className={this.props.classes.oauthEnter}
                  variant='outlined'
                  fullWidth
                  size='large'
                  onClick={e => !!selectedPlanId && this.onOauth('github', selectedPlanId)}
                  disabled={this.state.isSubmitting}
                >
                  <GithubIcon />
                  &nbsp;&nbsp;{signUpOrLogIn}&nbsp;GitHub
                </Button>
                {!isProd() && (
                  <Button
                    className={this.props.classes.oauthEnter}
                    variant='outlined'
                    fullWidth
                    size='large'
                    onClick={e => !!selectedPlanId && this.onOauth('bathtub', selectedPlanId)}
                    disabled={this.state.isSubmitting}
                  >
                    <BathtubIcon />
                    &nbsp;&nbsp;{signUpOrLogIn}&nbsp;Bathtub
                  </Button>
                )}
              </>
            )}
            <Collapse in={!this.state.useEmail}>
              <Button
                className={this.props.classes.oauthEnter}
                variant='outlined'
                fullWidth
                size='large'
                onClick={e => this.setState({ useEmail: true })}
                disabled={this.state.isSubmitting}
              >
                <EmailIcon />
                &nbsp;&nbsp;{signUpOrLogIn}&nbsp;{this.props.t('email')}
              </Button>
            </Collapse>
            <Collapse in={this.state.useEmail}>
              <div>
                {isOauthEnabled && (
                  <Hr isInsidePaper length={120} margins={15}>{this.props.t('or')}</Hr>
                )}
                <Collapse in={this.props.type === 'signup'}>
                  <TextField
                    variant='outlined'
                    fullWidth
                    margin='normal'
                    placeholder={this.props.t('your-name-organization')}
                    required
                    value={this.state.name || ''}
                    onChange={e => this.setState({ name: e.target.value })}
                    disabled={this.state.isSubmitting}
                  />
                </Collapse>
                <TextField
                  variant='outlined'
                  fullWidth
                  required
                  value={this.state.email || ''}
                  onChange={e => {
                    const newEmail = e.target.value;
                    this.setState({ email: newEmail });
                    if (this.props.type === 'signup') {
                      import(/* webpackChunkName: "emailDisposableList" */'../common/util/emailDisposableList')
                        .then(eu => this.setState({
                          emailIsFreeOrDisposable: eu.isDisposable(newEmail),
                        }));
                    }
                  }}
                  placeholder={this.props.type === 'login' ? this.props.t('email') : this.props.t('business-email')}
                  type='email'
                  margin='normal'
                  disabled={this.state.isSubmitting}
                />
                <Collapse in={this.props.type === 'signup' && !!this.state.emailIsFreeOrDisposable}>
                  <Message severity='warning' message={(
                    <div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', }} >
                      {this.props.t('cannot-use-a-disposable-email')} Is this a mistake?&nbsp;
                      <NavLink to='/contact/demo' className={this.props.classes.link}>Schedule a demo</NavLink>
                      &nbsp;with us.
                    </div>
                  )} />
                </Collapse>
                <TextField
                  variant='outlined'
                  fullWidth
                  required
                  value={this.state.pass || ''}
                  onChange={e => this.setState({ pass: e.target.value })}
                  placeholder={this.props.t('password')}
                  type={this.state.revealPassword ? 'text' : 'password'}
                  InputProps={{
                    endAdornment: (
                      <InputAdornment position='end'>
                        <IconButton
                          aria-label='Toggle password visibility'
                          onClick={() => this.setState({ revealPassword: !this.state.revealPassword })}
                        >
                          {this.state.revealPassword ? <VisibilityIcon fontSize='small' /> : <VisibilityOffIcon fontSize='small' />}
                        </IconButton>
                      </InputAdornment>
                    )
                  }}
                  margin='normal'
                  disabled={this.state.isSubmitting}
                />
                {this.props.type === 'signup' && (
                  <AcceptTerms />
                )}
                {submitButton}
              </div>
            </Collapse>
          </>
        )}
        submitTitle={this.props.type === 'signup' ? this.props.t('create-account') : this.props.t('continue')}
        submitDisabled={
          !this.state.email || !this.state.pass
          || this.props.type === 'signup' && (
            !this.state.name
            || !!this.state.emailIsFreeOrDisposable)}
        isSubmitting={this.state.isSubmitting}
        onSubmit={this.props.type === 'signup' ? this.signUp.bind(this, selectedPlanId!) : this.onLogin.bind(this)}
        footer={this.props.type === 'signup' ? {
          text: this.props.t('have-an-account'),
          actionText: this.props.t('log-in-here'),
          linkTo: {
            pathname: '/login',
            state: this.props.location.state,
          },
        } : {
          text: this.props.t('no-account'),
          actionText: this.props.t('sign-up-here'),
          linkTo: {
            pathname: '/signup',
            state: this.props.location.state,
          },
        }}
        layout={this.props.type}
      />
    );
  }

  renderInvitation(isLoggedIn: boolean) {
    // Only load invitation if we are on /invitation/...
    // We want to make sure a user is not tricked into accepting an invitation
    if (this.props.invitationId && this.props.invitationStatus === undefined) {
      const invitationId = this.props.invitationId;
      ServerAdmin.get().dispatchAdmin({ debounce: true, ssr: true, ssrStatusPassthrough: true }).then(dispatcher => dispatcher
        .accountViewInvitationAdmin({ invitationId }));
    }

    const expired = !this.props.invitationId || this.props.invitationStatus === Status.REJECTED || (this.props.invitationStatus === Status.FULFILLED && !this.props.invitation);
    const accepted = this.props.invitation?.isAcceptedByYou;
    return (
      <EnterTemplate
        title={!!this.props.invitation ? (accepted ? (
          this.props.t('invitation-accepted')
        ) : (
          <>
            {this.props.t('invitation-from')}
            <span className={this.props.classes.titleClearFlask}>{this.props.invitation?.inviteeName}</span>
          </>
        )) : (expired ? (
          <span className={this.props.classes.expired}>Invitation expired</span>
        ) : this.props.t('invitation-loading'))}
        renderContent={submitButton => (
          <>
            {!!this.props.invitation ? (
              <div>
                {!!accepted && (
                  <p>You have successfully joined <span className={this.props.classes.bold}>{this.props.invitation?.inviteeName || 'friend'}</span>'s project&nbsp;<span className={this.props.classes.bold}>{this.props.invitation.projectName}</span> on&nbsp;{windowIso.parentDomain}.</p>
                )}
                {!accepted && (
                  <p>You have been invited to join the project&nbsp;<span className={this.props.classes.bold}>{this.props.invitation.projectName}</span> on&nbsp;{windowIso.parentDomain}.</p>
                )}
                {!isLoggedIn && (
                  <p>Please sign up or log in to accept this invitation.</p>
                )}
              </div>
            ) : (expired ? (
              'Please let your friend know to send you another invitation by email.'
            ) : (
              <Loading />
            ))}
            {submitButton}
          </>
        )}
        submitTitle={expired ? undefined : (!isLoggedIn ? this.props.t('sign-up') : (!accepted ? this.props.t('accept') : this.props.t('open')))}
        submitDisabled={!this.props.invitation}
        isSubmitting={this.state.isSubmitting}
        onSubmit={async () => {
          if (!isLoggedIn) {
            this.props.history.push('/signup/', {
              [ADMIN_ENTER_INVITATION_ID]: this.props.invitationId,
              [ADMIN_LOGIN_REDIRECT_TO]: `/invitation/${this.props.invitationId}`,
            })
          } else if (!!this.props.invitationId && !!this.props.invitation?.isAcceptedByYou) {
            this.props.history.push('/dashboard');
          } else if (!!this.props.invitationId) {
            this.setState({ isSubmitting: true });
            try {
              const acceptedInvitation = await (await ServerAdmin.get().dispatchAdmin()).accountAcceptInvitationAdmin({
                invitationId: this.props.invitationId,
              });
              // Refresh projects
              await (await ServerAdmin.get().dispatchAdmin()).configGetAllAndUserBindAllAdmin();
              this.props.history.push(`/dashboard?projectId=${acceptedInvitation.projectId}`)
            } finally {
              this.setState({ isSubmitting: false });
            }
          }
        }}
        footer={!isLoggedIn && !expired ? {
          text: this.props.t('have-an-account'),
          actionText: this.props.t('log-in-here'),
          linkTo: {
            pathname: '/login',
            state: {
              [ADMIN_ENTER_INVITATION_ID]: this.props.invitationId,
              [ADMIN_LOGIN_REDIRECT_TO]: `/invitation/${this.props.invitationId}`,
            },
          },
        } : undefined}
        layout={this.props.type}
      />
    );
  }

  renderCoupon(isLoggedIn: boolean) {
    const couponId = this.state.couponId !== undefined ? this.state.couponId : this.props.couponId || '';
    return (
      <EnterTemplate
        title={this.props.t('redeem-coupon')}
        renderContent={submitButton => (
          <>
            <TextField
              variant='outlined'
              fullWidth
              margin='normal'
              label={this.props.t('coupon-code')}
              placeholder='XXXXXXXX'
              value={couponId}
              onChange={e => this.setState({ couponId: e.target.value })}
              disabled={this.state.isSubmitting || !!this.state.couponPlan || !!this.state.couponRedeemedByYou}
            />
            <Collapse in={!!this.state.couponRedeemedByYou}>
              <Alert className={this.props.classes.alert} severity='success'>
                <AlertTitle>Success!</AlertTitle>
                This coupon has already been applied to your account.
              </Alert>
            </Collapse>
            <Collapse in={!!this.state.couponPlan && !this.state.couponRedeemedByYou}>
              {!!this.state.couponPlan && (
                <PricingPlan plan={this.state.couponPlan} />
              )}
            </Collapse>
            <Collapse in={!this.state.couponRedeemedByYou && !!this.state.couponPlan}>
              <Alert className={this.props.classes.alert} severity='info'>
                {!isLoggedIn ? 'This plan will be applied upon sign-up or login' : 'This plan will replace your current plan on your account.'}
              </Alert>
            </Collapse>
            {submitButton}
            <Collapse in={!!this.state.couponError}>
              <Alert className={this.props.classes.alert} severity='warning'>
                {this.state.couponError}
              </Alert>
            </Collapse>
          </>
        )}
        submitTitle={!!this.state.couponRedeemedByYou
          ? this.props.t('continue')
          : (!this.state.couponPlan
            ? this.props.t('check')
            : (!isLoggedIn
              ? this.props.t('continue')
              : this.props.t('redeem')))}
        submitDisabled={!couponId}
        isSubmitting={this.state.isSubmitting}
        onSubmit={async () => {
          if (!couponId) return;
          if (!!this.state.couponRedeemedByYou) {
            // Already redeemed by you
            this.props.history.push('/dashboard');
          } else if (!this.state.couponPlan) {
            // Need to check couponn
            await this.onCouponCheck(couponId);
          } else if (!isLoggedIn) {
            // Redirect to signup for a valid code
            this.props.history.push('/signup/', {
              [ADMIN_ENTER_COUPON_ID]: couponId,
              [ADMIN_LOGIN_REDIRECT_TO]: `/coupon/${couponId}`,
            })
          } else {
            // Accept code on own account
            this.setState({ isSubmitting: true });
            try {
              await (await ServerAdmin.get().dispatchAdmin()).accountAcceptCouponAdmin({
                couponId,
              });
              this.setState({
                couponRedeemedByYou: true,
                couponError: undefined,
              });
            } finally {
              this.setState({ isSubmitting: false });
            }
          }
        }}
        footer={(!this.state.couponRedeemedByYou && !!this.state.couponPlan && !isLoggedIn) ? {
          text: this.props.t('have-an-account'),
          actionText: this.props.t('log-in-here'),
          linkTo: {
            pathname: '/login',
            state: {
              [ADMIN_ENTER_COUPON_ID]: couponId,
              [ADMIN_LOGIN_REDIRECT_TO]: `/coupon/${couponId}`,
            },
          },
        } : undefined}
        layout={this.props.type}
      />
    );
  }

  async onCouponCheck(couponId: string) {
    this.setState({ isSubmitting: true });
    try {
      const result = await (await ServerAdmin.get().dispatchAdmin()).accountViewCouponAdmin({
        couponId,
      });
      this.setState({
        couponPlan: result.plan,
        couponRedeemedByYou: !!result.redeemedByYou,
        couponError: (!result.plan && !result.redeemedByYou) ? 'Coupon expired or invalid.' : undefined
      });
    } finally {
      this.setState({ isSubmitting: false });
    }
  }

  onOauth(type: 'google' | 'github' | 'bathtub', selectedPlanId: string) {
    const extraData: OauthExtraData = {
      selectedPlanId,
      invitationId: this.props.location.state?.[ADMIN_ENTER_INVITATION_ID],
      couponId: this.props.location.state?.[ADMIN_ENTER_COUPON_ID],
      redirectTo: this.props.location.state?.[ADMIN_LOGIN_REDIRECT_TO],
    };
    this.oauthFlow.openForAccount(type, 'self', JSON.stringify(extraData));
  }

  onLogin() {
    this.setState({ isSubmitting: true });
    ServerAdmin.get().dispatchAdmin().then(d => d.accountLoginAdmin({
      accountLogin: {
        email: this.state.email || '',
        password: saltHashPassword(this.state.pass || ''),
      }
    })).then((result) => {
      this.setState({
        accountWasCreated: false,
        isSubmitting: false,
      });
    }).catch((e) => {
      if (e && e.status && e.status === 403) {
        this.setState({ isSubmitting: false });
      } else {
        this.setState({ isSubmitting: false });
      }
    });
  }

  async signUp(selectedPlanId: string) {
    trackingBlock(() => {
      ReactGA.event({
        category: 'account-signup',
        action: 'click-create',
        label: selectedPlanId,
      });
      LinkedInTag.track('5353172');
    });

    this.setState({ isSubmitting: true });
    const dispatchAdmin = await ServerAdmin.get().dispatchAdmin();
    try {
      const couponId = this.props.location.state?.[ADMIN_ENTER_COUPON_ID];
      const account = await dispatchAdmin.accountSignupAdmin({
        accountSignupAdmin: {
          name: this.state.name!,
          email: this.state.email!,
          password: saltHashPassword(this.state.pass!),
          basePlanId: selectedPlanId,
          invitationId: this.props.location.state?.[ADMIN_ENTER_INVITATION_ID],
          couponId,
        }
      });
      const preSelectedPlanPrice = this.props.location.state?.[PRE_SELECTED_PLAN_PRICE];
      if (preSelectedPlanPrice !== undefined) {
        dispatchAdmin.supportMessage({
          supportMessage: {
            content: {
              details: 'Pay what you can request',
              amount: preSelectedPlanPrice,
              accountId: account.accountId,
              [SUPPORT_MESSAGE_FIELD_CONTACT]: account.email,
              [SUPPORT_MESSAGE_FIELD_TYPE]: 'price-increase',
            }
          },
        });
      }
      this.setState({
        isSubmitting: false,
        accountWasCreated: true,
        ...(!!couponId ? {
          couponId,
          couponRedeemedByYou: true,
        } : {}),
      });
    } catch (e) {
      this.setState({ isSubmitting: false });
      throw e;
    }
  }
}