feat: Make user settings support `UserCenterAgent`

This commit is contained in:
haitaoo 2023-03-29 15:24:18 +08:00
parent 4b1f985be7
commit b47fb3da1c
7 changed files with 285 additions and 263 deletions

View File

@ -707,6 +707,7 @@ ui:
label: Confirm New Password
settings:
page_title: Settings
goto_modify: Go to Modify
nav:
profile: Profile
notification: Notifications

View File

@ -1,18 +1,46 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { userCenterStore } from '@/stores';
import { getUcSettings } from '@/services';
import { ModifyEmail, ModifyPassword, MyLogins } from './components';
const Index = () => {
const { t } = useTranslation('translation', {
keyPrefix: 'settings.account',
});
const { agent: ucAgent } = userCenterStore();
const [accountAgent, setAccountAgent] = useState('');
const initData = () => {
if (ucAgent?.enabled) {
getUcSettings().then((resp) => {
if (
resp.account_setting_agent?.enabled &&
resp.account_setting_agent?.redirect_url
) {
setAccountAgent(resp.account_setting_agent.redirect_url);
}
});
}
};
useEffect(() => {
initData();
}, []);
return (
<>
<h3 className="mb-4">{t('heading')}</h3>
<ModifyEmail />
<ModifyPassword />
<MyLogins />
{accountAgent ? (
<a href={accountAgent}>{t('goto_modify', { keyPrefix: 'settings' })}</a>
) : null}
{!ucAgent?.enabled ? (
<>
<ModifyEmail />
<ModifyPassword />
<MyLogins />
</>
) : null}
</>
);
};

View File

@ -6,9 +6,9 @@ import MD5 from 'md5';
import type { FormDataType } from '@/common/interface';
import { UploadImg, Avatar, Icon } from '@/components';
import { loggedUserInfoStore } from '@/stores';
import { loggedUserInfoStore, userCenterStore } from '@/stores';
import { useToast } from '@/hooks';
import { modifyUserInfo, getLoggedUserInfo } from '@/services';
import { modifyUserInfo, getLoggedUserInfo, getUcSettings } from '@/services';
import { handleFormError } from '@/utils';
const Index: React.FC = () => {
@ -17,8 +17,10 @@ const Index: React.FC = () => {
});
const toast = useToast();
const { user, update } = loggedUserInfoStore();
const { agent: ucAgent } = userCenterStore();
const [mailHash, setMailHash] = useState('');
const [count] = useState(0);
const [profileAgent, setProfileAgent] = useState('');
const [formData, setFormData] = useState<FormDataType>({
display_name: {
@ -226,6 +228,7 @@ const Index: React.FC = () => {
};
const getProfile = () => {
console.log('getProfile@Profile');
getLoggedUserInfo().then((res) => {
formData.display_name.value = res.display_name;
formData.username.value = res.username;
@ -243,228 +246,243 @@ const Index: React.FC = () => {
}
});
};
// const refreshGravatar = () => {
// setCount((pre) => pre + 1);
// };
const initData = () => {
if (ucAgent?.enabled) {
getUcSettings().then((resp) => {
if (
resp.profile_setting_agent?.enabled &&
resp.profile_setting_agent?.redirect_url
) {
setProfileAgent(resp.profile_setting_agent.redirect_url);
}
});
} else {
getProfile();
}
};
useEffect(() => {
getProfile();
initData();
}, []);
return (
<>
<h3 className="mb-4">{t('heading')}</h3>
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="displayName" className="mb-3">
<Form.Label>{t('display_name.label')}</Form.Label>
<Form.Control
required
type="text"
value={formData.display_name.value}
isInvalid={formData.display_name.isInvalid}
onChange={(e) =>
handleChange({
display_name: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.display_name.errorMsg}
</Form.Control.Feedback>
</Form.Group>
{profileAgent ? (
<a href={profileAgent}>{t('goto_modify', { keyPrefix: 'settings' })}</a>
) : null}
{!ucAgent?.enabled ? (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="displayName" className="mb-3">
<Form.Label>{t('display_name.label')}</Form.Label>
<Form.Control
required
type="text"
value={formData.display_name.value}
isInvalid={formData.display_name.isInvalid}
onChange={(e) =>
handleChange({
display_name: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.display_name.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="userName" className="mb-3">
<Form.Label>{t('username.label')}</Form.Label>
<Form.Control
required
type="text"
value={formData.username.value}
isInvalid={formData.username.isInvalid}
onChange={(e) =>
handleChange({
username: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Text as="div">{t('username.caption')}</Form.Text>
<Form.Control.Feedback type="invalid">
{formData.username.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="userName" className="mb-3">
<Form.Label>{t('username.label')}</Form.Label>
<Form.Control
required
type="text"
value={formData.username.value}
isInvalid={formData.username.isInvalid}
onChange={(e) =>
handleChange({
username: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Text as="div">{t('username.caption')}</Form.Text>
<Form.Control.Feedback type="invalid">
{formData.username.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>{t('avatar.label')}</Form.Label>
<div className="mb-3">
<Form.Select
name="avatar.type"
value={formData.avatar.type}
onChange={handleAvatarChange}>
<option value="gravatar" key="gravatar">
{t('avatar.gravatar')}
</option>
<option value="default" key="default">
{t('avatar.default')}
</option>
<option value="custom" key="custom">
{t('avatar.custom')}
</option>
</Form.Select>
</div>
<div className="d-flex">
{formData.avatar.type === 'gravatar' && (
<Stack>
<Avatar
size="160px"
avatar={formData.avatar.gravatar}
searchStr={`s=256&d=identicon${
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
/>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.gravatar_text">
You can change image on
<a
href="https://gravatar.com"
target="_blank"
rel="noreferrer">
gravatar.com
</a>
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'custom' && (
<Stack>
<Stack direction="horizontal" className="align-items-start">
<Form.Group className="mb-3">
<Form.Label>{t('avatar.label')}</Form.Label>
<div className="mb-3">
<Form.Select
name="avatar.type"
value={formData.avatar.type}
onChange={handleAvatarChange}>
<option value="gravatar" key="gravatar">
{t('avatar.gravatar')}
</option>
<option value="default" key="default">
{t('avatar.default')}
</option>
<option value="custom" key="custom">
{t('avatar.custom')}
</option>
</Form.Select>
</div>
<div className="d-flex">
{formData.avatar.type === 'gravatar' && (
<Stack>
<Avatar
size="160px"
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-2 bg-gray-300 "
avatar={formData.avatar.gravatar}
searchStr={`s=256&d=identicon${
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
/>
<ButtonGroup vertical className="fit-content">
<UploadImg type="avatar" uploadCallback={avatarUpload}>
<Icon name="cloud-upload" />
</UploadImg>
<Button
variant="outline-secondary"
onClick={removeCustomAvatar}>
<Icon name="trash" />
</Button>
</ButtonGroup>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.gravatar_text">
You can change image on
<a
href="https://gravatar.com"
target="_blank"
rel="noreferrer">
gravatar.com
</a>
</Trans>
</Form.Text>
</Stack>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image.
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'default' && (
<Avatar size="160px" avatar="" />
)}
</div>
<Form.Control
isInvalid={formData.avatar.isInvalid}
className="d-none"
/>
<Form.Control.Feedback type="invalid">
{formData.avatar.errorMsg}
</Form.Control.Feedback>
</Form.Group>
)}
<Form.Group controlId="bio" className="mb-3">
<Form.Label>
{`${t('bio.label')} ${t('optional', {
{formData.avatar.type === 'custom' && (
<Stack>
<Stack direction="horizontal" className="align-items-start">
<Avatar
size="160px"
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-2 bg-gray-300 "
/>
<ButtonGroup vertical className="fit-content">
<UploadImg type="avatar" uploadCallback={avatarUpload}>
<Icon name="cloud-upload" />
</UploadImg>
<Button
variant="outline-secondary"
onClick={removeCustomAvatar}>
<Icon name="trash" />
</Button>
</ButtonGroup>
</Stack>
<Form.Text className="text-muted mt-1">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image.
</Trans>
</Form.Text>
</Stack>
)}
{formData.avatar.type === 'default' && (
<Avatar size="160px" avatar="" />
)}
</div>
<Form.Control
isInvalid={formData.avatar.isInvalid}
className="d-none"
/>
<Form.Control.Feedback type="invalid">
{formData.avatar.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="bio" className="mb-3">
<Form.Label>
{`${t('bio.label')} ${t('optional', {
keyPrefix: 'form',
})}`}
</Form.Label>
<Form.Control
className="font-monospace"
required
as="textarea"
rows={5}
value={formData.bio.value}
isInvalid={formData.bio.isInvalid}
onChange={(e) =>
handleChange({
bio: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.bio.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="website" className="mb-3">
<Form.Label>{`${t('website.label')} ${t('optional', {
keyPrefix: 'form',
})}`}
</Form.Label>
<Form.Control
className="font-monospace"
required
as="textarea"
rows={5}
value={formData.bio.value}
isInvalid={formData.bio.isInvalid}
onChange={(e) =>
handleChange({
bio: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.bio.errorMsg}
</Form.Control.Feedback>
</Form.Group>
})}`}</Form.Label>
<Form.Control
required
type="url"
placeholder={t('website.placeholder')}
value={formData.website.value}
isInvalid={formData.website.isInvalid}
onChange={(e) =>
handleChange({
website: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.website.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="website" className="mb-3">
<Form.Label>{`${t('website.label')} ${t('optional', {
keyPrefix: 'form',
})}`}</Form.Label>
<Form.Control
required
type="url"
placeholder={t('website.placeholder')}
value={formData.website.value}
isInvalid={formData.website.isInvalid}
onChange={(e) =>
handleChange({
website: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.website.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="email" className="mb-3">
<Form.Label>{`${t('location.label')} ${t('optional', {
keyPrefix: 'form',
})}`}</Form.Label>
<Form.Control
required
type="text"
placeholder={t('location.placeholder')}
value={formData.location.value}
isInvalid={formData.location.isInvalid}
onChange={(e) =>
handleChange({
location: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.location.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="email" className="mb-3">
<Form.Label>{`${t('location.label')} ${t('optional', {
keyPrefix: 'form',
})}`}</Form.Label>
<Form.Control
required
type="text"
placeholder={t('location.placeholder')}
value={formData.location.value}
isInvalid={formData.location.isInvalid}
onChange={(e) =>
handleChange({
location: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Control.Feedback type="invalid">
{formData.location.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Button variant="primary" type="submit">
{t('btn_name')}
</Button>
</Form>
<Button variant="primary" type="submit">
{t('btn_name')}
</Button>
</Form>
) : null}
</>
);
};

View File

@ -1,13 +1,18 @@
import React, { FC } from 'react';
import { Nav } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { NavLink } from 'react-router-dom';
import { NavLink, useMatch } from 'react-router-dom';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'settings.nav' });
const settingMatch = useMatch('/users/settings/:setting');
return (
<Nav variant="pills" className="flex-column">
<NavLink className="nav-link" to="/users/settings/profile">
<NavLink
className={({ isActive }) =>
isActive || !settingMatch ? 'nav-link active' : 'nav-link'
}
to="/users/settings/profile">
{t('profile')}
</NavLink>
<NavLink className="nav-link" to="/users/settings/notify">

View File

@ -1,62 +1,17 @@
import React, { useState, useEffect } from 'react';
import { FC, memo } from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Outlet } from 'react-router-dom';
import { usePageTags } from '@/hooks';
import type { FormDataType } from '@/common/interface';
import { getLoggedUserInfo } from '@/services';
import Nav from './components/Nav';
const Index: React.FC = () => {
const Index: FC = () => {
const { t } = useTranslation('translation', {
keyPrefix: 'settings.profile',
});
const [formData, setFormData] = useState<FormDataType>({
display_name: {
value: '',
isInvalid: false,
errorMsg: '',
},
avatar: {
value: '',
isInvalid: false,
errorMsg: '',
},
bio: {
value: '',
isInvalid: false,
errorMsg: '',
},
website: {
value: '',
isInvalid: false,
errorMsg: '',
},
location: {
value: '',
isInvalid: false,
errorMsg: '',
},
});
const getProfile = () => {
getLoggedUserInfo().then((res) => {
if (res) {
formData.display_name.value = res.display_name;
formData.bio.value = res.bio;
formData.avatar.value = res.avatar;
formData.location.value = res.location;
formData.website.value = res.website;
setFormData({ ...formData });
}
});
};
useEffect(() => {
getProfile();
}, []);
usePageTags({
title: t('settings', { keyPrefix: 'page_title' }),
});
@ -81,4 +36,4 @@ const Index: React.FC = () => {
);
};
export default React.memo(Index);
export default memo(Index);

View File

@ -2,3 +2,4 @@ export * from './admin';
export * from './common';
export * from './client';
export * from './install';
export * from './user-center';

View File

@ -17,7 +17,21 @@ export interface UcAgent {
};
}
export interface UcSettingAgent {
enabled: boolean;
redirect_url: string;
}
export interface UcSettings {
profile_setting_agent: UcSettingAgent;
account_setting_agent: UcSettingAgent;
}
export const getUcAgent = () => {
const apiUrl = `/answer/api/v1/user-center/agent`;
return request.get<UcAgent>(apiUrl);
};
export const getUcSettings = () => {
const apiUrl = `/answer/api/v1/user-center/user/settings`;
return request.get<UcSettings>(apiUrl);
};