Add support to update user password in selfhosted version with email or smtp

This commit is contained in:
Mo Tarbin 2024-11-17 14:22:20 -05:00
commit b212e1c105
5 changed files with 159 additions and 3 deletions

1
.env
View file

@ -1,3 +1,4 @@
VITE_APP_API_URL=http://localhost:2021 VITE_APP_API_URL=http://localhost:2021
VITE_IS_LANDING_DEFAULT=false VITE_IS_LANDING_DEFAULT=false
VITE_OPENREPLAY_PROJECT_KEY= VITE_OPENREPLAY_PROJECT_KEY=
VITE_IS_SELF_HOSTED=false

View file

@ -1,3 +1,4 @@
VITE_APP_API_URL=http://localhost:2021 VITE_APP_API_URL=http://localhost:2021
VITE_APP_REDIRECT_URL=http://localhost:5173 VITE_APP_REDIRECT_URL=http://localhost:5173
VITE_APP_GOOGLE_CLIENT_ID=USE_YOUR_OWN_CLIENT_ID VITE_APP_GOOGLE_CLIENT_ID=USE_YOUR_OWN_CLIENT_ID
VITE_IS_SELF_HOSTED=true

View file

@ -1,3 +1,4 @@
VITE_APP_API_URL= VITE_APP_API_URL=
VITE_APP_REDIRECT_URL=http://localhost:5173 VITE_APP_REDIRECT_URL=http://localhost:5173
VITE_APP_GOOGLE_CLIENT_ID=USE_YOUR_OWN_CLIENT_ID VITE_APP_GOOGLE_CLIENT_ID=USE_YOUR_OWN_CLIENT_ID
VITE_IS_SELF_HOSTED=true

View file

@ -0,0 +1,117 @@
import {
Box,
Button,
FormControl,
FormHelperText,
Input,
Modal,
ModalDialog,
Typography,
} from '@mui/joy'
import React, { useEffect } from 'react'
function PassowrdChangeModal({ isOpen, onClose }) {
const [password, setPassword] = React.useState('')
const [confirmPassword, setConfirmPassword] = React.useState('')
const [passwordError, setPasswordError] = React.useState(false)
const [passwordTouched, setPasswordTouched] = React.useState(false)
const [confirmPasswordTouched, setConfirmPasswordTouched] =
React.useState(false)
useEffect(() => {
if (!passwordTouched || !confirmPasswordTouched) {
return
} else if (password !== confirmPassword) {
setPasswordError('Passwords do not match')
} else if (password.length < 8) {
setPasswordError('Password must be at least 8 characters')
} else if (password.length > 50) {
setPasswordError('Password must be less than 50 characters')
} else {
setPasswordError(null)
}
}, [password, confirmPassword, passwordTouched, confirmPasswordTouched])
const handleAction = isConfirmed => {
if (!isConfirmed) {
onClose(null)
return
}
onClose(password)
}
return (
<Modal open={isOpen} onClose={onClose}>
<ModalDialog>
<Typography level='h4' mb={1}>
Change Password
</Typography>
<Typography level='body-md' gutterBottom>
Please enter your new password.
</Typography>
<FormControl>
<Typography level='body2' alignSelf={'start'}>
New Password
</Typography>
<Input
margin='normal'
required
fullWidth
name='password'
label='Password'
type='password'
id='password'
value={password}
onChange={e => {
setPasswordTouched(true)
setPassword(e.target.value)
}}
/>
</FormControl>
<FormControl>
<Typography level='body2' alignSelf={'start'}>
Confirm Password
</Typography>
<Input
margin='normal'
required
fullWidth
name='confirmPassword'
label='confirmPassword'
type='password'
id='confirmPassword'
value={confirmPassword}
onChange={e => {
setConfirmPasswordTouched(true)
setConfirmPassword(e.target.value)
}}
/>
<FormHelperText>{passwordError}</FormHelperText>
</FormControl>
<Box display={'flex'} justifyContent={'space-around'} mt={1}>
<Button
disabled={passwordError != null}
onClick={() => {
handleAction(true)
}}
fullWidth
sx={{ mr: 1 }}
>
Change Password
</Button>
<Button
onClick={() => {
handleAction(false)
}}
variant='outlined'
>
Cancel
</Button>
</Box>
</ModalDialog>
</Modal>
)
}
export default PassowrdChangeModal

View file

@ -24,7 +24,9 @@ import {
GetUserProfile, GetUserProfile,
JoinCircle, JoinCircle,
LeaveCircle, LeaveCircle,
UpdatePassword,
} from '../../utils/Fetcher' } from '../../utils/Fetcher'
import PassowrdChangeModal from '../Modals/Inputs/PasswordChangeModal'
import APITokenSettings from './APITokenSettings' import APITokenSettings from './APITokenSettings'
import NotificationSetting from './NotificationSetting' import NotificationSetting from './NotificationSetting'
import ThemeToggle from './ThemeToggle' import ThemeToggle from './ThemeToggle'
@ -35,6 +37,7 @@ const Settings = () => {
const [circleMemberRequests, setCircleMemberRequests] = useState([]) const [circleMemberRequests, setCircleMemberRequests] = useState([])
const [circleInviteCode, setCircleInviteCode] = useState('') const [circleInviteCode, setCircleInviteCode] = useState('')
const [circleMembers, setCircleMembers] = useState([]) const [circleMembers, setCircleMembers] = useState([])
const [changePasswordModal, setChangePasswordModal] = useState(false)
useEffect(() => { useEffect(() => {
GetUserProfile().then(resp => { GetUserProfile().then(resp => {
resp.json().then(data => { resp.json().then(data => {
@ -314,7 +317,7 @@ const Settings = () => {
<Typography level='h3'>Account Settings</Typography> <Typography level='h3'>Account Settings</Typography>
<Divider /> <Divider />
<Typography level='body-md'> <Typography level='body-md'>
Change your account settings, including your password, display name Change your account settings, type or update your password
</Typography> </Typography>
<Typography level='title-md' mb={-1}> <Typography level='title-md' mb={-1}>
Account Type : {getSubscriptionStatus()} Account Type : {getSubscriptionStatus()}
@ -365,6 +368,39 @@ const Settings = () => {
</Button> </Button>
)} )}
</Box> </Box>
{import.meta.env.VITE_IS_SELF_HOSTED === 'true' && (
<Box>
<Typography level='title-md' mb={1}>
Password :
</Typography>
<Typography mb={1} level='body-sm'></Typography>
<Button
variant='soft'
onClick={() => {
setChangePasswordModal(true)
}}
>
Change Password
</Button>
{changePasswordModal ? (
<PassowrdChangeModal
isOpen={changePasswordModal}
onClose={password => {
if (password) {
UpdatePassword(password).then(resp => {
if (resp.ok) {
alert('Password changed successfully')
} else {
alert('Password change failed')
}
})
}
setChangePasswordModal(false)
}}
/>
) : null}
</Box>
)}
</div> </div>
<NotificationSetting /> <NotificationSetting />
<APITokenSettings /> <APITokenSettings />