155 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2023-2025. Gardel <sunxinao@hotmail.com> and contributors
 | 
						|
 *
 | 
						|
 * This program is free software: you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU Affero General Public License as published by
 | 
						|
 * the Free Software Foundation, either version 3 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU Affero General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU Affero General Public License
 | 
						|
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
						|
 */
 | 
						|
 | 
						|
import React from 'react';
 | 
						|
import Button from '@mui/material/Button';
 | 
						|
import {
 | 
						|
    Box,
 | 
						|
    Container,
 | 
						|
    FilledInput,
 | 
						|
    FormControl,
 | 
						|
    IconButton,
 | 
						|
    InputAdornment,
 | 
						|
    InputLabel,
 | 
						|
    Paper,
 | 
						|
    TextField
 | 
						|
} from '@mui/material';
 | 
						|
import {Visibility, VisibilityOff} from '@mui/icons-material';
 | 
						|
import {AppState} from './types';
 | 
						|
import './reset.css';
 | 
						|
import {SubmitHandler, useForm} from 'react-hook-form';
 | 
						|
import axios from 'axios';
 | 
						|
import {useSnackbar} from 'notistack';
 | 
						|
 | 
						|
type Inputs = {
 | 
						|
    username: string,
 | 
						|
    profileName: string,
 | 
						|
    password: string
 | 
						|
};
 | 
						|
 | 
						|
function PasswordReset(props: { appData: AppState, setAppData: React.Dispatch<React.SetStateAction<AppState>> }) {
 | 
						|
    const {appData, setAppData} = props;
 | 
						|
    const {enqueueSnackbar} = useSnackbar();
 | 
						|
    const {register, handleSubmit, formState: {errors}} = useForm<Inputs>();
 | 
						|
    const [submitting, setSubmitting] = React.useState(false);
 | 
						|
    const onSubmit: SubmitHandler<Inputs> = data => {
 | 
						|
        setSubmitting(true);
 | 
						|
        const hash = window.location.hash;
 | 
						|
        if (!hash) {
 | 
						|
            setSubmitting(false);
 | 
						|
            enqueueSnackbar('链接失效,请重新打开', {variant: 'error'});
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        const params = new URLSearchParams(hash.substring(1));
 | 
						|
        axios.post('/authserver/resetPassword', {
 | 
						|
            email: data.username,
 | 
						|
            password: data.password,
 | 
						|
            accessToken: params.get('passwordResetToken'),
 | 
						|
        })
 | 
						|
            .then(() => {
 | 
						|
                toLogin();
 | 
						|
                window.location.replace('/profile/')
 | 
						|
                enqueueSnackbar("重置成功", {variant: 'success'});
 | 
						|
            })
 | 
						|
            .catch(e => {
 | 
						|
                const response = e.response;
 | 
						|
                if (response && response.status >= 400 && response.status < 500) {
 | 
						|
                    let errorMessage = response.data.errorMessage ?? response.data;
 | 
						|
                    enqueueSnackbar('重置失败: ' + errorMessage, {variant: 'error'});
 | 
						|
                } else {
 | 
						|
                    enqueueSnackbar('网络错误:' + e.message, {variant: 'error'});
 | 
						|
                }
 | 
						|
            })
 | 
						|
            .finally(() => setSubmitting(false))
 | 
						|
    };
 | 
						|
 | 
						|
    const [showPassword, setShowPassword] = React.useState(false);
 | 
						|
 | 
						|
    const handleClickShowPassword = () => setShowPassword((show) => !show);
 | 
						|
 | 
						|
    const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
 | 
						|
        event.preventDefault();
 | 
						|
    };
 | 
						|
 | 
						|
    const toLogin = () => {
 | 
						|
        setAppData({
 | 
						|
            ...appData,
 | 
						|
            passwordReset: false
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    return (
 | 
						|
        <Container maxWidth={'sm'}>
 | 
						|
            <Paper className={'reset-card'}>
 | 
						|
                <section className="header">
 | 
						|
                    <h1>简陋重置密码页</h1>
 | 
						|
                </section>
 | 
						|
                <Box component="form" autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
 | 
						|
                    <div className='username'>
 | 
						|
                        <TextField
 | 
						|
                            id="username-input"
 | 
						|
                            name='username'
 | 
						|
                            fullWidth
 | 
						|
                            label="邮箱"
 | 
						|
                            variant="filled"
 | 
						|
                            required
 | 
						|
                            error={errors.username && true}
 | 
						|
                            type='email'
 | 
						|
                            slotProps={{
 | 
						|
                                htmlInput: {
 | 
						|
                                    ...register('username', {required: true})
 | 
						|
                                }
 | 
						|
                            }}
 | 
						|
                        />
 | 
						|
                    </div>
 | 
						|
                    <div className='password'>
 | 
						|
                        <FormControl fullWidth variant="filled" required error={errors.password && true}>
 | 
						|
                            <InputLabel htmlFor="password-input">新密码</InputLabel>
 | 
						|
                            <FilledInput
 | 
						|
                                id="password-input"
 | 
						|
                                name="password"
 | 
						|
                                required
 | 
						|
                                type={showPassword ? 'text' : 'password'}
 | 
						|
                                endAdornment={
 | 
						|
                                    <InputAdornment position="end">
 | 
						|
                                        <IconButton
 | 
						|
                                            aria-label="显示密码"
 | 
						|
                                            onClick={handleClickShowPassword}
 | 
						|
                                            onMouseDown={handleMouseDownPassword}
 | 
						|
                                            edge="end">
 | 
						|
                                            {showPassword ? <VisibilityOff/> : <Visibility/>}
 | 
						|
                                        </IconButton>
 | 
						|
                                    </InputAdornment>
 | 
						|
                                }
 | 
						|
                                inputProps={{
 | 
						|
                                    minLength: '6',
 | 
						|
                                    ...register('password', {required: true, minLength: 6})
 | 
						|
                                }}
 | 
						|
                            />
 | 
						|
                        </FormControl>
 | 
						|
                    </div>
 | 
						|
                    <div className='button-container'>
 | 
						|
                        <Button variant='contained' onClick={toLogin}>登录</Button>
 | 
						|
                        <Button variant='contained' type='submit' disabled={submitting}>重置</Button>
 | 
						|
                    </div>
 | 
						|
                </Box>
 | 
						|
            </Paper>
 | 
						|
        </Container>
 | 
						|
    );
 | 
						|
}
 | 
						|
 | 
						|
export default PasswordReset; |