270 lines
9.3 KiB
TypeScript
270 lines
9.3 KiB
TypeScript
import { Dialog, DialogContent, DialogTitle, InputAdornment, TextField, Tooltip } from '@mui/material';
|
|
import { useForm, Controller } from 'react-hook-form';
|
|
import { Button } from '@mui/material';
|
|
import { useUserStore } from '../store';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import { isObjectNull } from '@/utils/is-null';
|
|
import { useLayoutStore } from '@/modules/layout/store';
|
|
import UploadOutlined from '@ant-design/icons/UploadOutlined';
|
|
import PandaPNG from '@/assets/panda.png';
|
|
import { FileUpload } from '../module/FileUpload';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Edit, UserCog } from 'lucide-react';
|
|
import { toast } from 'react-toastify';
|
|
import { useAdminStore } from '../admin/store/admin-store';
|
|
import { IconButton } from '@kevisual/components/button/index.tsx';
|
|
export const CheckUserExistModal = () => {
|
|
const { t } = useTranslation();
|
|
const userStore = useUserStore(
|
|
useShallow((state) => {
|
|
return {
|
|
showCheckUserExist: state.showCheckUserExist,
|
|
setShowCheckUserExist: state.setShowCheckUserExist,
|
|
};
|
|
}),
|
|
);
|
|
const adminStore = useAdminStore(
|
|
useShallow((state) => {
|
|
return {
|
|
checkUserExist: state.checkUserExist,
|
|
};
|
|
}),
|
|
);
|
|
const onClose = () => {
|
|
userStore.setShowCheckUserExist(false);
|
|
};
|
|
const [username, setUsername] = useState('@');
|
|
|
|
const onCheck = async () => {
|
|
const res = await adminStore.checkUserExist(username);
|
|
console.log(res);
|
|
if (res === false) {
|
|
toast.info('当前用户名可以修改,点击右上角关注公众号联系客服修改。', {
|
|
autoClose: 20000,
|
|
});
|
|
// toast.success(t('Check success'));
|
|
} else {
|
|
toast.error('当前用户名已存在,请更换其他用户名。');
|
|
}
|
|
};
|
|
return (
|
|
<Dialog open={userStore.showCheckUserExist} onClose={onClose}>
|
|
<DialogTitle>{t('Tips')}</DialogTitle>
|
|
<DialogContent>
|
|
<div className='flex flex-col pt-4 gap-6 w-[400px]'>
|
|
<div className='text-sm text-secondary'>{t('Check username introduction')}</div>
|
|
<TextField label='username' value={username} onChange={(e) => setUsername(e.target.value)} />
|
|
<Button variant='contained' color='primary' onClick={onCheck}>
|
|
{t('Submit')}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
export const ChangePasswordModal = () => {
|
|
const { t } = useTranslation();
|
|
const userStore = useUserStore(
|
|
useShallow((state) => {
|
|
return {
|
|
showChangePassword: state.showChangePassword,
|
|
setShowChangePassword: state.setShowChangePassword,
|
|
updateSelf: state.updateSelf,
|
|
};
|
|
}),
|
|
);
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const onSubmit = async () => {
|
|
if (newPassword !== confirmPassword) {
|
|
toast.error('两次密码不一致');
|
|
return;
|
|
}
|
|
const res = await userStore.updateSelf({
|
|
password: newPassword,
|
|
});
|
|
if (res.code === 200) {
|
|
onClose();
|
|
}
|
|
};
|
|
const onClose = () => {
|
|
setNewPassword('');
|
|
setConfirmPassword('');
|
|
userStore.setShowChangePassword(false);
|
|
};
|
|
return (
|
|
<Dialog open={userStore.showChangePassword} onClose={onClose}>
|
|
<DialogTitle>{t('Change Password')}</DialogTitle>
|
|
<DialogContent>
|
|
<div className='flex flex-col w-[400px] p-4 gap-4 pt-2'>
|
|
{/* <TextField label='oldPassword' /> */}
|
|
<TextField label='New Password' type='password' value={newPassword} onChange={(e) => setNewPassword(e.target.value)} />
|
|
<TextField label='Confirm Password' type='password' value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} />
|
|
<div className='flex justify-end gap-2 '>
|
|
<Button variant='contained' color='primary' onClick={onSubmit}>
|
|
{t('Submit')}
|
|
</Button>
|
|
<Button variant='contained' color='primary' onClick={onClose}>
|
|
{t('Cancel')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
export const Profile = () => {
|
|
const { t } = useTranslation();
|
|
const { control, handleSubmit, setValue, reset, formState, getValues } = useForm({
|
|
defaultValues: {
|
|
username: '',
|
|
avatar: '',
|
|
description: '',
|
|
},
|
|
});
|
|
const ref = useRef<any>(null);
|
|
const userStore = useUserStore(
|
|
useShallow((state) => {
|
|
return {
|
|
showEdit: state.showEdit,
|
|
setShowEdit: state.setShowEdit,
|
|
formData: state.formData,
|
|
updateData: state.updateData,
|
|
setFormData: state.setFormData,
|
|
updateSelf: state.updateSelf,
|
|
setShowCheckUserExist: state.setShowCheckUserExist,
|
|
setShowChangePassword: state.setShowChangePassword,
|
|
};
|
|
}),
|
|
);
|
|
const [avatar, setAvatar] = useState<string>('');
|
|
const layoutStore = useLayoutStore(
|
|
useShallow((state) => {
|
|
return {
|
|
me: state.me,
|
|
setMe: state.setMe,
|
|
};
|
|
}),
|
|
);
|
|
useEffect(() => {
|
|
const fromData = layoutStore.me;
|
|
if (isObjectNull(fromData)) {
|
|
reset({
|
|
username: '',
|
|
avatar: '',
|
|
description: '',
|
|
});
|
|
} else {
|
|
reset({
|
|
username: fromData.username,
|
|
avatar: fromData.avatar || '',
|
|
description: fromData.description || '',
|
|
});
|
|
}
|
|
setAvatar(fromData.avatar || '');
|
|
}, [layoutStore.me, setValue]);
|
|
const onChange = (path: string) => {
|
|
const url = path + '?t=' + new Date().getTime();
|
|
setAvatar(url);
|
|
setValue('avatar', url);
|
|
const values = getValues();
|
|
onFinish(values);
|
|
};
|
|
const onFinish = async (values) => {
|
|
const newMe = await userStore.updateSelf(values);
|
|
if (newMe) {
|
|
layoutStore.setMe(newMe);
|
|
}
|
|
};
|
|
return (
|
|
<div className='w-full h-full bg-amber-50 p-4 text-primary'>
|
|
<div className=' shadow-lg p-4 bg-white rounded-lg'>
|
|
<div className='text-2xl'>{t('Profile')}</div>
|
|
<div className='text-sm text-secondary'>{t('Edit your profile')}</div>
|
|
<div className='flex gap-4'>
|
|
<div className='w-[600px] p-4 border mt-2 '>
|
|
<div className='w-full my-4'>
|
|
{avatar && <img className='w-20 h-20 mx-auto rounded-full' src={avatar} alt='avatar' />}
|
|
{!avatar && <img className='w-20 h-20 mx-auto rounded-full' src={PandaPNG} alt='avatar' />}
|
|
</div>
|
|
<form onSubmit={handleSubmit(onFinish)} className='flex flex-col gap-4'>
|
|
<Controller
|
|
name='username'
|
|
control={control}
|
|
render={({ field }) => (
|
|
<TextField
|
|
{...field}
|
|
label={t('Name')}
|
|
className='w-full border rounded-lg p-2'
|
|
disabled
|
|
slotProps={{
|
|
input: {
|
|
endAdornment: (
|
|
<Tooltip title={t('Apply Change Name')}>
|
|
<InputAdornment position='end'>
|
|
<Edit
|
|
className='w-4 h-4 cursor-pointer text-primary'
|
|
onClick={() => {
|
|
console.log('onClick edit');
|
|
userStore.setShowCheckUserExist(true);
|
|
}}
|
|
/>
|
|
</InputAdornment>
|
|
</Tooltip>
|
|
),
|
|
onKeyDown: (event) => {
|
|
if (event.key === 'Enter') {
|
|
// setSearch(field.value);
|
|
}
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
<Controller
|
|
name='avatar'
|
|
control={control}
|
|
render={({ field }) => (
|
|
<TextField
|
|
{...field}
|
|
label={t('Avatar')}
|
|
slotProps={{
|
|
input: {
|
|
endAdornment: (
|
|
<div>
|
|
<UploadOutlined
|
|
onClick={() => {
|
|
ref.current?.open?.();
|
|
}}
|
|
/>
|
|
<FileUpload ref={ref} onChange={onChange} />
|
|
</div>
|
|
),
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label={t('Description')} multiline rows={4} />} />
|
|
<Button className='' type='submit' variant='contained'>
|
|
{t('Save')}
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
<div className='mt-2'>
|
|
<Tooltip title={t('Change Password')}>
|
|
<IconButton className='' onClick={() => userStore.setShowChangePassword(true)}>
|
|
<UserCog size={16} />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
<CheckUserExistModal />
|
|
<ChangePasswordModal />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|