chore: use monorepo organization

This commit is contained in:
Rafael Taranto 2025-05-12 10:52:54 +01:00
parent deaf7d6ecc
commit a687827f7e
1099 changed files with 8184 additions and 11535 deletions

View file

@ -0,0 +1,287 @@
import { useQuery, useMutation, useLazyQuery, gql } from '@apollo/client'
import Chip from '@mui/material/Chip'
import Switch from '@mui/material/Switch'
import { startAttestation } from '@simplewebauthn/browser'
import * as R from 'ramda'
import React, { useReducer, useState, useContext } from 'react'
import TitleSection from 'src/components/layout/TitleSection'
import DataTable from 'src/components/tables/DataTable'
import WhiteKeyIcon from 'src/styling/icons/button/key/white.svg?react'
import KeyIcon from 'src/styling/icons/button/key/zodiac.svg?react'
import WhiteLockIcon from 'src/styling/icons/button/lock/white.svg?react'
import LockIcon from 'src/styling/icons/button/lock/zodiac.svg?react'
import WhiteUserRoleIcon from 'src/styling/icons/button/user-role/white.svg?react'
import UserRoleIcon from 'src/styling/icons/button/user-role/zodiac.svg?react'
import AppContext from 'src/AppContext'
import { ActionButton, Link } from 'src/components/buttons'
import { IP_CHECK_REGEX } from 'src/utils/constants'
import ChangeRoleModal from './modals/ChangeRoleModal'
import CreateUserModal from './modals/CreateUserModal'
import EnableUserModal from './modals/EnableUserModal'
import FIDOModal from './modals/FIDOModal'
import Reset2FAModal from './modals/Reset2FAModal'
import ResetPasswordModal from './modals/ResetPasswordModal'
import classes from './UserManagement.module.css'
const GET_USERS = gql`
query users {
users {
id
username
role
enabled
last_accessed
last_accessed_from
last_accessed_address
}
}
`
const GENERATE_ATTESTATION = gql`
query generateAttestationOptions($userID: ID!, $domain: String!) {
generateAttestationOptions(userID: $userID, domain: $domain)
}
`
const VALIDATE_ATTESTATION = gql`
mutation validateAttestation(
$userID: ID!
$attestationResponse: JSONObject!
$domain: String!
) {
validateAttestation(
userID: $userID
attestationResponse: $attestationResponse
domain: $domain
)
}
`
const initialState = {
showCreateUserModal: false,
showResetPasswordModal: false,
showReset2FAModal: false,
showRoleModal: false,
showEnableUserModal: false
}
const reducer = (_, action) => {
const { type, payload } = action
switch (type) {
case 'close':
return initialState
case 'open':
return { ...initialState, [payload]: true }
default:
return initialState
}
}
const roleMapper = {
user: 'Regular',
superuser: 'Superuser'
}
const Users = () => {
const { userData } = useContext(AppContext)
const { data: userResponse } = useQuery(GET_USERS)
const [state, dispatch] = useReducer(reducer, initialState)
const [userInfo, setUserInfo] = useState(null)
const [validateAttestation] = useMutation(VALIDATE_ATTESTATION, {
onCompleted: res => {
// TODO: show a brief popup to have UX feedback?
}
})
const [generateAttestationOptions] = useLazyQuery(GENERATE_ATTESTATION, {
onCompleted: ({ generateAttestationOptions: options }) => {
return startAttestation(options).then(res => {
validateAttestation({
variables: {
userID: userInfo.id,
attestationResponse: res,
domain: window.location.hostname
}
})
})
}
})
const elements = [
{
header: 'Login',
width: 307,
textAlign: 'left',
size: 'sm',
view: u => {
if (userData.id === u.id)
return (
<div className={classes.loginWrapper}>
<span className={classes.username}>{u.username}</span>
<Chip size="small" label="You" className={classes.chip} />
</div>
)
return <span className={classes.username}>{u.username}</span>
}
},
{
header: 'Role',
width: 160,
textAlign: 'left',
size: 'sm',
view: u => (
<div className={classes.loginWrapper}>
<span>{roleMapper[u.role]}</span>
<Switch
className={classes.roleSwitch}
disabled={userData.id === u.id}
checked={u.role === 'superuser'}
onClick={() => {
setUserInfo(u)
dispatch({
type: 'open',
payload: 'showRoleModal'
})
}}
value={u.role === 'superuser'}
/>
</div>
)
},
{
header: 'Actions',
width: 565,
textAlign: 'left',
size: 'sm',
view: u => {
return (
<div className={classes.actionButtonWrapper}>
<ActionButton
Icon={KeyIcon}
InverseIcon={WhiteKeyIcon}
color="primary"
onClick={() => {
setUserInfo(u)
dispatch({
type: 'open',
payload: 'showResetPasswordModal'
})
}}>
Reset password
</ActionButton>
<ActionButton
Icon={LockIcon}
InverseIcon={WhiteLockIcon}
color="primary"
onClick={() => {
setUserInfo(u)
dispatch({
type: 'open',
payload: 'showReset2FAModal'
})
}}>
Reset 2FA
</ActionButton>
<ActionButton
Icon={UserRoleIcon}
InverseIcon={WhiteUserRoleIcon}
color="primary"
onClick={() => {
if (IP_CHECK_REGEX.test(window.location.hostname)) {
dispatch({
type: 'open',
payload: 'showFIDOModal'
})
} else {
setUserInfo(u)
generateAttestationOptions({
variables: {
userID: u.id,
domain: window.location.hostname
}
})
}
}}>
Add FIDO
</ActionButton>
</div>
)
}
},
{
header: 'Enabled',
width: 100,
textAlign: 'center',
size: 'sm',
view: u => (
<Switch
disabled={userData.id === u.id}
checked={u.enabled}
onClick={() => {
setUserInfo(u)
dispatch({
type: 'open',
payload: 'showEnableUserModal'
})
}}
value={u.enabled}
/>
)
}
]
return (
<>
<TitleSection
title="User management"
appendixRight={
<Link
color="primary"
onClick={() => {
dispatch({
type: 'open',
payload: 'showCreateUserModal'
})
}}>
Add new user
</Link>
}
/>
<DataTable elements={elements} data={R.path(['users'])(userResponse)} />
<CreateUserModal state={state} dispatch={dispatch} />
<ResetPasswordModal
state={state}
dispatch={dispatch}
user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'}
/>
<Reset2FAModal
state={state}
dispatch={dispatch}
user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'}
/>
<ChangeRoleModal
state={state}
dispatch={dispatch}
user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'}
/>
<EnableUserModal
state={state}
dispatch={dispatch}
user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'}
/>
<FIDOModal state={state} dispatch={dispatch} />
</>
)
}
export default Users

View file

@ -0,0 +1,134 @@
.footer {
display: flex;
flex-direction: row;
margin: auto 0 24px 0;
}
.modalTitle {
margin-top: -5px;
color: var(--zodiac);
font-family: var(--mont);
}
.modalLabel1 {
margin-top: 20px;
}
.modalLabel2 {
margin-top: 40px;
}
.inputLabel {
color: var(--zodiac);
font-family: var(--mont);
font-size: 24px;
margin-left: 8px;
margin-top: 15px;
}
.tableWidth {
width: 1132px;
}
.radioGroup {
flex-direction: row;
width: 500px;
}
.radioLabel {
width: 150px;
height: 48px;
}
.copyToClipboard {
margin-left: auto;
padding-top: 7px;
margin-right: -5px;
}
.chip {
background-color: var(--zircon);
font-family: var(--mont);
margin-left: 10px;
}
.info {
font-family: var(--museo);
text-align: justify;
}
.addressWrapper {
background-color: var(--zircon);
margin-top: 8px;
height: 35px;
}
.address {
margin: 0px 16px 0px 16px;
padding-right: -15px;
}
.errorMessage {
font-family: var(--museo);
color: var(--tomato);
}
.codeContainer {
margin-top: 15px;
margin-bottom: 15px;
}
.form {
display: flex;
flex-direction: column;
height: 100%;
}
.submit {
margin: auto 0 0 auto;
}
.error {
color: var(--tomato);
}
.link {
position: absolute;
top: 10px;
left: 0;
bottom: -20px;
right: -20px;
white-space: nowrap;
overflow-x: auto;
width: 92.5%;
}
.linkWrapper {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.loginWrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.username {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
}
.roleSwitch {
margin-left: 15px;
}
.actionButtonWrapper {
display: flex;
gap: 12px;
}

View file

@ -0,0 +1,88 @@
import { useMutation, gql } from '@apollo/client'
import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P } from 'src/components/typography'
import { Button } from 'src/components/buttons'
import Input2FAModal from './Input2FAModal'
import classes from '../UserManagement.module.css'
const CHANGE_USER_ROLE = gql`
mutation changeUserRole(
$confirmationCode: String
$id: ID!
$newRole: String!
) {
changeUserRole(
confirmationCode: $confirmationCode
id: $id
newRole: $newRole
) {
id
}
}
`
const ChangeRoleModal = ({ state, dispatch, user, requiresConfirmation }) => {
const [changeUserRole, { error }] = useMutation(CHANGE_USER_ROLE, {
onCompleted: () => handleClose(),
refetchQueries: () => ['users']
})
const [confirmation, setConfirmation] = useState(null)
const submit = () => {
changeUserRole({
variables: {
confirmationCode: confirmation,
id: user.id,
newRole: user.role === 'superuser' ? 'user' : 'superuser'
}
})
}
const handleClose = () => {
setConfirmation(null)
dispatch({
type: 'close',
payload: 'showRoleModal'
})
}
return (
(state.showRoleModal && requiresConfirmation && !confirmation && (
<Input2FAModal
showModal={state.showRoleModal}
handleClose={handleClose}
setConfirmation={setConfirmation}
/>
)) ||
(state.showRoleModal && (
<Modal
closeOnBackdropClick={true}
width={450}
height={250}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>
Change {user.username}'s role?
</Info2>
<P className={classes.info}>
You are about to alter {user.username}'s role. This will change this
user's permission to access certain resources.
</P>
<P className={classes.info}>Do you wish to proceed?</P>
<div className={classes.footer}>
{error && <ErrorMessage>{error}</ErrorMessage>}
<Button className={classes.submit} onClick={() => submit()}>
Confirm
</Button>
</div>
</Modal>
))
)
}
export default ChangeRoleModal

View file

@ -0,0 +1,171 @@
import { useMutation, gql } from '@apollo/client'
import classnames from 'classnames'
import { Field, Form, Formik } from 'formik'
import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { H1, H3, Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import * as Yup from 'yup'
import { Button } from 'src/components/buttons'
import { TextInput, RadioGroup } from 'src/components/inputs/formik'
import { urlResolver } from 'src/utils/urlResolver'
import classes from '../UserManagement.module.css'
const CREATE_USER = gql`
mutation createRegisterToken($username: String!, $role: String!) {
createRegisterToken(username: $username, role: $role) {
token
expire
}
}
`
const validationSchema = Yup.object().shape({
username: Yup.string()
.email('Username field should be in an email format!')
.required('Username field is required!'),
role: Yup.string().required('Role field is required!')
})
const initialValues = {
username: '',
role: ''
}
const radioOptions = [
{
code: 'user',
display: 'Regular user'
},
{
code: 'superuser',
display: 'Superuser'
}
]
const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
if (!formikErrors || !formikTouched) return null
if (mutationError) return 'Internal server error'
if (formikErrors.username && formikTouched.username)
return formikErrors.username
return null
}
const CreateUserModal = ({ state, dispatch }) => {
const [usernameField, setUsernameField] = useState('')
const [createUserURL, setCreateUserURL] = useState(null)
const handleClose = () => {
setCreateUserURL(null)
dispatch({
type: 'close',
payload: 'showCreateUserModal'
})
}
const [createUser, { error }] = useMutation(CREATE_USER, {
onCompleted: ({ createRegisterToken: token }) => {
setCreateUserURL(urlResolver(`/register?t=${token.token}`))
}
})
const roleClass = (formikErrors, formikTouched) => ({
[classes.error]: formikErrors.role && formikTouched.role
})
return (
<>
{state.showCreateUserModal && !createUserURL && (
<Modal
closeOnBackdropClick={true}
width={600}
height={400}
handleClose={handleClose}
open={true}>
<Formik
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={values => {
setUsernameField(values.username)
createUser({
variables: { username: values.username, role: values.role }
})
}}>
{({ errors, touched }) => (
<Form id="register-user-form" className={classes.form}>
<H1 className={classes.modalTitle}>Create new user</H1>
<Field
component={TextInput}
name="username"
width={338}
autoFocus
label="User login"
/>
<H3
className={classnames(
roleClass(errors, touched),
classes.modalLabel2
)}>
Role
</H3>
<Field
component={RadioGroup}
name="role"
labelClassName={classes.radioLabel}
className={classes.radioGroup}
options={radioOptions}
/>
<div className={classes.footer}>
{getErrorMsg(errors, touched, error) && (
<ErrorMessage>
{getErrorMsg(errors, touched, error)}
</ErrorMessage>
)}
<Button
type="submit"
form="register-user-form"
className={classes.submit}>
Finish
</Button>
</div>
</Form>
)}
</Formik>
</Modal>
)}
{state.showCreateUserModal && createUserURL && (
<Modal
closeOnBackdropClick={true}
width={500}
height={200}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>
Creating {usernameField}...
</Info2>
<P className={classes.info}>
Safely share this link with {usernameField} to finish the
registration process.
</P>
<div className={classes.addressWrapper}>
<Mono className={classes.address}>
<strong>
<CopyToClipboard
className={classes.link}
buttonClassname={classes.copyToClipboard}
wrapperClassname={classes.linkWrapper}>
{createUserURL}
</CopyToClipboard>
</strong>
</Mono>
</div>
</Modal>
)}
</>
)
}
export default CreateUserModal

View file

@ -0,0 +1,124 @@
import { useMutation, gql } from '@apollo/client'
import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P } from 'src/components/typography'
import { Button } from 'src/components/buttons'
import Input2FAModal from './Input2FAModal'
import classes from '../UserManagement.module.css'
const ENABLE_USER = gql`
mutation enableUser($confirmationCode: String, $id: ID!) {
enableUser(confirmationCode: $confirmationCode, id: $id) {
id
}
}
`
const DISABLE_USER = gql`
mutation disableUser($confirmationCode: String, $id: ID!) {
disableUser(confirmationCode: $confirmationCode, id: $id) {
id
}
}
`
const EnableUserModal = ({ state, dispatch, user, requiresConfirmation }) => {
const [enableUser, { error: enableError }] = useMutation(ENABLE_USER, {
onCompleted: () => handleClose(),
refetchQueries: () => ['users']
})
const [disableUser, { error: disableError }] = useMutation(DISABLE_USER, {
onCompleted: () => handleClose(),
refetchQueries: () => ['users']
})
const [confirmation, setConfirmation] = useState(null)
const disable = () => {
disableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
}
const enable = () => {
enableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
}
const submit = () => {
user?.enabled ? disable() : enable()
}
const handleClose = () => {
setConfirmation(null)
dispatch({
type: 'close',
payload: 'showEnableUserModal'
})
}
return (
(state.showEnableUserModal && requiresConfirmation && !confirmation && (
<Input2FAModal
showModal={state.showEnableUserModal}
handleClose={handleClose}
setConfirmation={setConfirmation}
/>
)) ||
(state.showEnableUserModal && (
<Modal
closeOnBackdropClick={true}
width={450}
height={275}
handleClose={handleClose}
open={true}>
{!user.enabled && (
<>
<Info2 className={classes.modalTitle}>
Enable {user.username}?
</Info2>
<P className={classes.info}>
You are about to enable {user.username} into the system,
activating previous eligible sessions and grant permissions to
access the system.
</P>
<P className={classes.info}>Do you wish to proceed?</P>
</>
)}
{user.enabled && (
<>
<Info2 className={classes.modalTitle}>
Disable {user.username}?
</Info2>
<P className={classes.info}>
You are about to disable {user.username} from the system,
deactivating previous eligible sessions and removing permissions
to access the system.
</P>
<P className={classes.info}>Do you wish to proceed?</P>
</>
)}
<div className={classes.footer}>
{disableError && <ErrorMessage>{disableError}</ErrorMessage>}
{enableError && <ErrorMessage>{enableError}</ErrorMessage>}
<Button className={classes.submit} onClick={() => submit()}>
Confirm
</Button>
</div>
</Modal>
))
)
}
export default EnableUserModal

View file

@ -0,0 +1,42 @@
import React from 'react'
import Modal from 'src/components/Modal'
import { Info2, P } from 'src/components/typography'
import { Button } from 'src/components/buttons'
import classes from '../UserManagement.module.css'
const ChangeRoleModal = ({ state, dispatch }) => {
const handleClose = () => {
dispatch({
type: 'close',
payload: 'showFIDOModal'
})
}
return (
<Modal
closeOnBackdropClick={true}
width={450}
height={275}
handleClose={handleClose}
open={state.showFIDOModal}>
<Info2 className={classes.modalTitle}>About FIDO authentication</Info2>
<P className={classes.info}>
This feature is only available for websites with configured domains, and
we detected that a domain is not configured at the moment.
</P>
<P>
Make sure that a domain is configured for this website and try again
later.
</P>
<div className={classes.footer}>
<Button className={classes.submit} onClick={() => handleClose()}>
Confirm
</Button>
</div>
</Modal>
)
}
export default ChangeRoleModal

View file

@ -0,0 +1,93 @@
import { useLazyQuery, gql } from '@apollo/client'
import { Form, Formik } from 'formik'
import React, { useState } from 'react'
import Modal from 'src/components/Modal'
import { Info2, P } from 'src/components/typography'
import { Button } from 'src/components/buttons'
import { CodeInput } from 'src/components/inputs/base'
import classes from '../UserManagement.module.css'
const CONFIRM_2FA = gql`
query confirm2FA($code: String!) {
confirm2FA(code: $code)
}
`
const Input2FAModal = ({ showModal, handleClose, setConfirmation }) => {
const [twoFACode, setTwoFACode] = useState('')
const [invalidCode, setInvalidCode] = useState(false)
const handleCodeChange = value => {
setTwoFACode(value)
setInvalidCode(false)
}
const onContinue = () => {
setConfirmation(twoFACode)
setTwoFACode('')
setInvalidCode(false)
}
const [confirm2FA, { error: queryError }] = useLazyQuery(CONFIRM_2FA, {
onCompleted: ({ confirm2FA: success }) =>
!success ? setInvalidCode(true) : onContinue()
})
const getErrorMsg = () => {
if (queryError) return 'Internal server error'
if (twoFACode.length !== 6 && invalidCode)
return 'The code should have 6 characters!'
if (invalidCode) return 'Code is invalid. Please try again.'
return null
}
const handleSubmit = () => {
if (twoFACode.length !== 6) {
setInvalidCode(true)
return
}
confirm2FA({ variables: { code: twoFACode } })
}
return (
showModal && (
<Modal
closeOnBackdropClick={true}
width={500}
height={350}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>Confirm action</Info2>
<P className={classes.info}>
To make changes on this user, please confirm this action by entering
your two-factor authentication code below.
</P>
{/* TODO: refactor the 2FA CodeInput to properly use Formik */}
<Formik onSubmit={() => {}} initialValues={{}}>
<Form>
<CodeInput
name="2fa"
value={twoFACode}
onChange={handleCodeChange}
numInputs={6}
error={invalidCode}
containerStyle={classes.codeContainer}
/>
{getErrorMsg() && (
<P className={classes.errorMessage}>{getErrorMsg()}</P>
)}
<div className={classes.footer}>
<Button className={classes.submit} onClick={handleSubmit}>
Confirm
</Button>
</div>
</Form>
</Formik>
</Modal>
)
)
}
export default Input2FAModal

View file

@ -0,0 +1,106 @@
import { useMutation, gql } from '@apollo/client'
import React, { useEffect, useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import { urlResolver } from 'src/utils/urlResolver'
import Input2FAModal from './Input2FAModal'
import classes from '../UserManagement.module.css'
const CREATE_RESET_2FA_TOKEN = gql`
mutation createReset2FAToken($confirmationCode: String, $userID: ID!) {
createReset2FAToken(confirmationCode: $confirmationCode, userID: $userID) {
token
user_id
expire
}
}
`
const Reset2FAModal = ({ state, dispatch, user, requiresConfirmation }) => {
const [reset2FAUrl, setReset2FAUrl] = useState('')
const [createReset2FAToken, { loading, error }] = useMutation(
CREATE_RESET_2FA_TOKEN,
{
onCompleted: ({ createReset2FAToken: token }) => {
setReset2FAUrl(urlResolver(`/reset2fa?t=${token.token}`))
}
}
)
const [confirmation, setConfirmation] = useState(null)
useEffect(() => {
state.showReset2FAModal &&
(confirmation || !requiresConfirmation) &&
createReset2FAToken({
variables: {
confirmationCode: confirmation,
userID: user?.id
}
})
}, [
confirmation,
createReset2FAToken,
requiresConfirmation,
state.showReset2FAModal,
user?.id
])
const handleClose = () => {
setConfirmation(null)
dispatch({
type: 'close',
payload: 'showReset2FAModal'
})
}
return (
(state.showReset2FAModal && requiresConfirmation && !confirmation && (
<Input2FAModal
showModal={state.showReset2FAModal}
handleClose={handleClose}
setConfirmation={setConfirmation}
/>
)) ||
(state.showReset2FAModal &&
(confirmation || !requiresConfirmation) &&
!loading && (
<Modal
closeOnBackdropClick={true}
width={500}
height={200}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>
Reset 2FA for {user.username}
</Info2>
<P className={classes.info}>
Safely share this link with {user.username} for a two-factor
authentication reset.
</P>
{!error && (
<div className={classes.addressWrapper}>
<Mono className={classes.address}>
<strong>
<CopyToClipboard
className={classes.link}
buttonClassname={classes.copyToClipboard}
wrapperClassname={classes.linkWrapper}>
{reset2FAUrl}
</CopyToClipboard>
</strong>
</Mono>
</div>
)}
{error && <ErrorMessage>{error}</ErrorMessage>}
</Modal>
))
)
}
export default Reset2FAModal

View file

@ -0,0 +1,113 @@
import { useMutation, gql } from '@apollo/client'
import React, { useEffect, useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/components/CopyToClipboard.jsx'
import { urlResolver } from 'src/utils/urlResolver'
import Input2FAModal from './Input2FAModal'
import classes from '../UserManagement.module.css'
const CREATE_RESET_PASSWORD_TOKEN = gql`
mutation createResetPasswordToken($confirmationCode: String, $userID: ID!) {
createResetPasswordToken(
confirmationCode: $confirmationCode
userID: $userID
) {
token
user_id
expire
}
}
`
const ResetPasswordModal = ({
state,
dispatch,
user,
requiresConfirmation
}) => {
const [resetPasswordUrl, setResetPasswordUrl] = useState('')
const [createResetPasswordToken, { loading, error }] = useMutation(
CREATE_RESET_PASSWORD_TOKEN,
{
onCompleted: ({ createResetPasswordToken: token }) => {
setResetPasswordUrl(urlResolver(`/resetpassword?t=${token.token}`))
}
}
)
const [confirmation, setConfirmation] = useState(null)
useEffect(() => {
state.showResetPasswordModal &&
(confirmation || !requiresConfirmation) &&
createResetPasswordToken({
variables: {
confirmationCode: confirmation,
userID: user?.id
}
})
}, [
confirmation,
createResetPasswordToken,
requiresConfirmation,
state.showResetPasswordModal,
user?.id
])
const handleClose = () => {
setConfirmation(null)
dispatch({
type: 'close',
payload: 'showResetPasswordModal'
})
}
return (
(state.showResetPasswordModal && requiresConfirmation && !confirmation && (
<Input2FAModal
showModal={state.showResetPasswordModal}
handleClose={handleClose}
setConfirmation={setConfirmation}
/>
)) ||
(state.showResetPasswordModal &&
(confirmation || !requiresConfirmation) &&
!loading && (
<Modal
closeOnBackdropClick={true}
width={500}
height={180}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>
Reset password for {user.username}
</Info2>
<P className={classes.info}>
Safely share this link with {user.username} for a password reset.
</P>
{!error && (
<div className={classes.addressWrapper}>
<Mono className={classes.address}>
<strong>
<CopyToClipboard
className={classes.link}
buttonClassname={classes.copyToClipboard}
wrapperClassname={classes.linkWrapper}>
{resetPasswordUrl}
</CopyToClipboard>
</strong>
</Mono>
</div>
)}
{error && <ErrorMessage>{error}</ErrorMessage>}
</Modal>
))
)
}
export default ResetPasswordModal