mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 19:44:35 +00:00
feat(web): add the UI for updating user password
This commit is contained in:
141
web/src/components/passwordModal.tsx
Normal file
141
web/src/components/passwordModal.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
OutlinedInput,
|
||||||
|
InputAdornment,
|
||||||
|
IconButton,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
FormHelperText
|
||||||
|
} from '@mui/material'
|
||||||
|
import Visibility from '@mui/icons-material/Visibility'
|
||||||
|
import VisibilityOff from '@mui/icons-material/VisibilityOff'
|
||||||
|
|
||||||
|
import { BootstrapDialogTitle } from './dialogTitle'
|
||||||
|
import { BootstrapDialog } from './modal'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
title: string
|
||||||
|
updatePassword: (currentPassword: string, newPassword: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdatePasswordModal = (props: Props) => {
|
||||||
|
const { open, setOpen, title, updatePassword } = props
|
||||||
|
const [currentPassword, setCurrentPassword] = useState('')
|
||||||
|
const [newPassword, setNewPassword] = useState('')
|
||||||
|
const [hasError, setHasError] = useState(false)
|
||||||
|
const [errorText, setErrorText] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (newPassword.length >= 6) {
|
||||||
|
setErrorText('')
|
||||||
|
setHasError(false)
|
||||||
|
}
|
||||||
|
}, [newPassword])
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
if (newPassword.length < 6) {
|
||||||
|
setErrorText('Password length should be at least 6 characters.')
|
||||||
|
setHasError(true)
|
||||||
|
} else {
|
||||||
|
setErrorText('')
|
||||||
|
setHasError(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BootstrapDialog onClose={() => setOpen(false)} open={open}>
|
||||||
|
<BootstrapDialogTitle id="abort-modal" handleOpen={setOpen}>
|
||||||
|
{title}
|
||||||
|
</BootstrapDialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<PasswordInput
|
||||||
|
label="Current Password"
|
||||||
|
password={currentPassword}
|
||||||
|
setPassword={setCurrentPassword}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<PasswordInput
|
||||||
|
label="New Password"
|
||||||
|
password={newPassword}
|
||||||
|
setPassword={setNewPassword}
|
||||||
|
hasError={hasError}
|
||||||
|
errorText={errorText}
|
||||||
|
handleBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={() => setOpen(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => updatePassword(currentPassword, newPassword)}
|
||||||
|
disabled={hasError || !currentPassword || !newPassword}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</BootstrapDialog>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdatePasswordModal
|
||||||
|
|
||||||
|
type PasswordInputProps = {
|
||||||
|
label: string
|
||||||
|
password: string
|
||||||
|
setPassword: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
hasError?: boolean
|
||||||
|
errorText?: string
|
||||||
|
handleBlur?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasswordInput = ({
|
||||||
|
label,
|
||||||
|
password,
|
||||||
|
setPassword,
|
||||||
|
hasError,
|
||||||
|
errorText,
|
||||||
|
handleBlur
|
||||||
|
}: PasswordInputProps) => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl sx={{ width: '100%' }} variant="outlined" error={hasError}>
|
||||||
|
<InputLabel htmlFor="outlined-adornment-password">{label}</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
id="outlined-adornment-password"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
label={label}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowPassword((val) => !val)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{errorText && <FormHelperText>{errorText}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -17,11 +17,13 @@ import {
|
|||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import { AppContext, ModeType } from '../../context/appContext'
|
import { AppContext, ModeType } from '../../context/appContext'
|
||||||
|
import UpdatePasswordModal from '../../components/passwordModal'
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const appContext = useContext(AppContext)
|
const appContext = useContext(AppContext)
|
||||||
const [user, setUser] = useState({} as any)
|
const [user, setUser] = useState({} as any)
|
||||||
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
@@ -36,7 +38,7 @@ const Profile = () => {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [appContext.userId])
|
||||||
|
|
||||||
const handleChange = (event: any) => {
|
const handleChange = (event: any) => {
|
||||||
const { name, value } = event.target
|
const { name, value } = event.target
|
||||||
@@ -68,82 +70,124 @@ const Profile = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatePassword = (currentPassword: string, newPassword: string) => {
|
||||||
|
setIsLoading(true)
|
||||||
|
setIsPasswordModalOpen(false)
|
||||||
|
axios
|
||||||
|
.patch(`/SASjsApi/auth/updatePassword`, {
|
||||||
|
currentPassword,
|
||||||
|
newPassword
|
||||||
|
})
|
||||||
|
.then((res: any) => {
|
||||||
|
toast.success('Password updated', {
|
||||||
|
theme: 'dark',
|
||||||
|
position: toast.POSITION.BOTTOM_RIGHT
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed: ' + err.response?.data || err.text, {
|
||||||
|
theme: 'dark',
|
||||||
|
position: toast.POSITION.BOTTOM_RIGHT
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<>
|
||||||
<CardHeader title="Profile Information" />
|
<Card>
|
||||||
<Divider />
|
<CardHeader title="Profile Information" />
|
||||||
<CardContent>
|
<Divider />
|
||||||
<Grid container spacing={4}>
|
<CardContent>
|
||||||
<Grid item md={6} xs={12}>
|
<Grid container spacing={4}>
|
||||||
<TextField
|
<Grid item md={6} xs={12}>
|
||||||
fullWidth
|
<TextField
|
||||||
error={user.displayName?.length === 0}
|
fullWidth
|
||||||
helperText="Please specify display name"
|
error={user.displayName?.length === 0}
|
||||||
label="Display Name"
|
helperText="Please specify display name"
|
||||||
name="displayName"
|
label="Display Name"
|
||||||
onChange={handleChange}
|
name="displayName"
|
||||||
required
|
onChange={handleChange}
|
||||||
value={user.displayName}
|
required
|
||||||
variant="outlined"
|
value={user.displayName}
|
||||||
disabled={appContext.mode === ModeType.Desktop}
|
variant="outlined"
|
||||||
/>
|
disabled={appContext.mode === ModeType.Desktop}
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={6} xs={12}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
error={user.username?.length === 0}
|
|
||||||
helperText="Please specify username"
|
|
||||||
label="Username"
|
|
||||||
name="username"
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
value={user.username}
|
|
||||||
variant="outlined"
|
|
||||||
disabled={appContext.mode === ModeType.Desktop}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item lg={6} md={8} sm={12} xs={12}>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label="autoExec"
|
|
||||||
name="autoExec"
|
|
||||||
onChange={handleChange}
|
|
||||||
multiline
|
|
||||||
rows="10"
|
|
||||||
value={user.autoExec}
|
|
||||||
variant="outlined"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<FormGroup row>
|
|
||||||
<FormControlLabel
|
|
||||||
disabled
|
|
||||||
control={<Checkbox checked={user.isActive} />}
|
|
||||||
label="isActive"
|
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
</Grid>
|
||||||
disabled
|
|
||||||
control={<Checkbox checked={user.isAdmin} />}
|
<Grid item md={6} xs={12}>
|
||||||
label="isAdmin"
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
error={user.username?.length === 0}
|
||||||
|
helperText="Please specify username"
|
||||||
|
label="Username"
|
||||||
|
name="username"
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
value={user.username}
|
||||||
|
variant="outlined"
|
||||||
|
disabled={appContext.mode === ModeType.Desktop}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item lg={6} md={8} sm={12} xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="autoExec"
|
||||||
|
name="autoExec"
|
||||||
|
onChange={handleChange}
|
||||||
|
multiline
|
||||||
|
rows="10"
|
||||||
|
value={user.autoExec}
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<FormGroup row>
|
||||||
|
<FormControlLabel
|
||||||
|
disabled
|
||||||
|
control={<Checkbox checked={user.isActive} />}
|
||||||
|
label="isActive"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
disabled
|
||||||
|
control={<Checkbox checked={user.isAdmin} />}
|
||||||
|
label="isAdmin"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => setIsPasswordModalOpen(true)}
|
||||||
|
>
|
||||||
|
Update Password
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</CardContent>
|
||||||
</CardContent>
|
<Divider />
|
||||||
<Divider />
|
<CardActions>
|
||||||
<CardActions>
|
<Button type="submit" variant="contained" onClick={handleSubmit}>
|
||||||
<Button type="submit" variant="contained" onClick={handleSubmit}>
|
Save Changes
|
||||||
Save Changes
|
</Button>
|
||||||
</Button>
|
</CardActions>
|
||||||
</CardActions>
|
</Card>
|
||||||
</Card>
|
<UpdatePasswordModal
|
||||||
|
open={isPasswordModalOpen}
|
||||||
|
setOpen={setIsPasswordModalOpen}
|
||||||
|
title="Update Password"
|
||||||
|
updatePassword={updatePassword}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user