diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 77f3689a..b4d1dcc0 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -709,6 +709,7 @@ ui: label: Confirm New Password settings: page_title: Settings + goto_modify: Go to Modify nav: profile: Profile notification: Notifications diff --git a/ui/src/components/Editor/Editor.tsx b/ui/src/components/Editor/Editor.tsx index 520a5ac6..a1ac16a9 100644 --- a/ui/src/components/Editor/Editor.tsx +++ b/ui/src/components/Editor/Editor.tsx @@ -94,7 +94,7 @@ const Editor = ({ return; } if (editor.getValue() !== value) { - editor.setValue(value); + editor.setValue(value || ''); } }, [editor, value]); diff --git a/ui/src/components/Header/components/NavItems/index.tsx b/ui/src/components/Header/components/NavItems/index.tsx index 392f814b..be7b6f34 100644 --- a/ui/src/components/Header/components/NavItems/index.tsx +++ b/ui/src/components/Header/components/NavItems/index.tsx @@ -6,6 +6,7 @@ import { NavLink, useNavigate } from 'react-router-dom'; import type * as Type from '@/common/interface'; import { Avatar, Icon } from '@/components'; import { floppyNavigation } from '@/utils'; +import { userCenterStore } from '@/stores'; interface Props { redDot: Type.NotificationStatus | undefined; @@ -15,13 +16,14 @@ interface Props { const Index: FC = ({ redDot, userInfo, logOut }) => { const { t } = useTranslation(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const navigate = useNavigate(); + const { agent: ucAgent } = userCenterStore(); const handleLinkClick = (evt) => { if (floppyNavigation.shouldProcessLinkClick(evt)) { evt.preventDefault(); - const { href } = evt.currentTarget; - const { pathname } = new URL(href); - navigate(pathname); + const href = evt.currentTarget.getAttribute('href'); + navigate(href); } }; return ( @@ -90,6 +92,32 @@ const Index: FC = ({ redDot, userInfo, logOut }) => { + {/* Dropdown for user center agent info */} + {ucAgent?.enabled && ucAgent?.agent_info ? ( + + + + + + + + {ucAgent.agent_info.name} + + + {ucAgent.agent_info.control_center?.map((ctrl) => { + return ( + + {ctrl.label} + + ); + })} + + + ) : null} ); }; diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 3ed3f66c..297ac5b9 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -20,7 +20,7 @@ import { import classnames from 'classnames'; -import { floppyNavigation } from '@/utils'; +import { floppyNavigation, userCenter } from '@/utils'; import { loggedUserInfoStore, siteInfoStore, @@ -29,6 +29,7 @@ import { themeSettingStore, } from '@/stores'; import { logout, useQueryNotificationStatus } from '@/services'; +import { RouteAlias } from '@/router/alias'; import NavItems from './components/NavItems'; @@ -46,6 +47,9 @@ const Header: FC = () => { const brandingInfo = brandingStore((state) => state.branding); const loginSetting = loginSettingStore((state) => state.login); const { data: redDot } = useQueryNotificationStatus(); + /** + * Automatically append `tag` information when creating a question + */ const tagMatch = useMatch('/tags/:slugName'); let askUrl = '/questions/ask'; if (tagMatch && tagMatch.params.slugName) { @@ -70,16 +74,14 @@ const Header: FC = () => { window.location.replace(window.location.href); }; const onLoginClick = (evt) => { - if (location.pathname === '/users/login') { + if (location.pathname === RouteAlias.login) { evt.preventDefault(); window.location.reload(); return; } if (floppyNavigation.shouldProcessLinkClick(evt)) { evt.preventDefault(); - floppyNavigation.navigateToLogin((loginPath) => { - navigate(loginPath, { replace: true }); - }); + floppyNavigation.navigateToLogin(); } }; @@ -152,7 +154,7 @@ const Header: FC = () => { 'link-primary': navbarStyle !== 'theme-colored', })} onClick={onLoginClick} - href="/users/login"> + href={userCenter.getLoginUrl()}> {t('btns.login')} {loginSetting.allow_new_registrations && ( @@ -160,7 +162,7 @@ const Header: FC = () => { variant={ navbarStyle === 'theme-colored' ? 'light' : 'primary' } - href="/users/register"> + href={userCenter.getSignUpUrl()}> {t('btns.signup')} )} @@ -240,7 +242,7 @@ const Header: FC = () => { 'link-primary': navbarStyle !== 'theme-colored', })} onClick={onLoginClick} - href="/users/login"> + href={userCenter.getLoginUrl()}> {t('btns.login')} {loginSetting.allow_new_registrations && ( @@ -248,7 +250,7 @@ const Header: FC = () => { variant={ navbarStyle === 'theme-colored' ? 'light' : 'primary' } - href="/users/register"> + href={userCenter.getSignUpUrl()}> {t('btns.signup')} )} diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx index 6c245d24..0dc5e209 100644 --- a/ui/src/components/SchemaForm/index.tsx +++ b/ui/src/components/SchemaForm/index.tsx @@ -348,7 +348,7 @@ const SchemaForm: ForwardRefRenderFunction = ( useImperativeHandle(ref, () => ({ validator, })); - console.log('uiSchema: ', uiSchema); + return (
{keys.map((key) => { diff --git a/ui/src/hooks/useLoginRedirect/index.tsx b/ui/src/hooks/useLoginRedirect/index.tsx index 925765de..8629d6c5 100644 --- a/ui/src/hooks/useLoginRedirect/index.tsx +++ b/ui/src/hooks/useLoginRedirect/index.tsx @@ -11,8 +11,11 @@ const Index = () => { const loginRedirect = () => { const redirect = Storage.get(REDIRECT_PATH_STORAGE_KEY) || RouteAlias.home; Storage.remove(REDIRECT_PATH_STORAGE_KEY); - floppyNavigation.navigate(redirect, () => { - navigate(redirect, { replace: true }); + floppyNavigation.navigate(redirect, { + handler: navigate, + options: { + replace: true, + }, }); }; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index 8f409fbc..f5d8b3a1 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -22,7 +22,7 @@ import { useToast, } from '@/hooks'; import { useQueryUsers, addUser, updateUserPassword } from '@/services'; -import { loggedUserInfoStore } from '@/stores'; +import { loggedUserInfoStore, userCenterStore } from '@/stores'; import { formatCount } from '@/utils'; const UserFilterKeys: Type.UserFilterBy[] = [ @@ -49,6 +49,7 @@ const Users: FC = () => { const curPage = Number(urlSearchParams.get('page') || '1'); const curQuery = urlSearchParams.get('query') || ''; const currentUser = loggedUserInfoStore((state) => state.user); + const { agent: ucAgent } = userCenterStore(); const Toast = useToast(); const { data, @@ -149,12 +150,14 @@ const Users: FC = () => { sortKey="filter" i18nKeyPrefix="admin.users" /> - + {!ucAgent?.enabled ? ( + + ) : null} { - handleAction('password', user)}> - {t('set_new_password')} - + {!ucAgent?.enabled ? ( + handleAction('password', user)}> + {t('set_new_password')} + + ) : null} handleAction('status', user)}> {t('change_status')} diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx index 6f12a519..533b59f7 100644 --- a/ui/src/pages/Questions/Ask/index.tsx +++ b/ui/src/pages/Questions/Ask/index.tsx @@ -302,7 +302,7 @@ const Ask = () => { const handleSelectedRevision = (e) => { const index = e.target.value; const revision = revisions[index]; - formData.content.value = revision.content.content; + formData.content.value = revision.content?.content || ''; setImmData({ ...formData }); setFormData({ ...formData }); }; diff --git a/ui/src/pages/Questions/index.tsx b/ui/src/pages/Questions/index.tsx index a55c7873..075b74d8 100644 --- a/ui/src/pages/Questions/index.tsx +++ b/ui/src/pages/Questions/index.tsx @@ -8,6 +8,7 @@ import { FollowingTags, QuestionList, HotQuestions } from '@/components'; import { siteInfoStore, loggedUserInfoStore } from '@/stores'; import { useQuestionList } from '@/services'; import * as Type from '@/common/interface'; +import { userCenter } from '@/utils'; const Questions: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'question' }); @@ -43,7 +44,7 @@ const Questions: FC = () => { /> - {!loggedUser.access_token && ( + {!loggedUser.username && (
@@ -52,10 +53,12 @@ const Questions: FC = () => { })}

{siteInfo.description}

- + {t('login', { keyPrefix: 'btns' })} - + {t('signup', { keyPrefix: 'btns' })}
diff --git a/ui/src/pages/Users/Login/index.tsx b/ui/src/pages/Users/Login/index.tsx index 4e843bbd..8834888e 100644 --- a/ui/src/pages/Users/Login/index.tsx +++ b/ui/src/pages/Users/Login/index.tsx @@ -94,8 +94,9 @@ const Index: React.FC = () => { const handleLoginRedirect = () => { const redirect = Storage.get(REDIRECT_PATH_STORAGE_KEY) || RouteAlias.home; Storage.remove(REDIRECT_PATH_STORAGE_KEY); - floppyNavigation.navigate(redirect, () => { - navigate(redirect, { replace: true }); + floppyNavigation.navigate(redirect, { + handler: navigate, + options: { replace: true }, }); }; @@ -168,6 +169,9 @@ const Index: React.FC = () => { usePageTags({ title: t('login', { keyPrefix: 'page_title' }), }); + if (!guard.loginAgent().ok) { + return null; + } return ( diff --git a/ui/src/pages/Users/Personal/components/UserInfo/index.tsx b/ui/src/pages/Users/Personal/components/UserInfo/index.tsx index 3ff91bbf..5125d7f4 100644 --- a/ui/src/pages/Users/Personal/components/UserInfo/index.tsx +++ b/ui/src/pages/Users/Personal/components/UserInfo/index.tsx @@ -1,10 +1,15 @@ -import { FC, memo } from 'react'; +import { FC, memo, useEffect, useState } from 'react'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +import classnames from 'classnames'; + import { Avatar, Icon } from '@/components'; import type { UserInfoRes } from '@/common/interface'; +import { getUcBranding, UcBrandingEntry } from '@/services'; +import { userCenterStore } from '@/stores'; +import { base64ToSvg } from '@/utils'; interface Props { data: UserInfoRes; @@ -12,6 +17,22 @@ interface Props { const Index: FC = ({ data }) => { const { t } = useTranslation('translation', { keyPrefix: 'personal' }); + const { agent: ucAgent } = userCenterStore(); + const [ucBranding, setUcBranding] = useState([]); + + const initData = () => { + if (ucAgent?.enabled && data?.username) { + getUcBranding(data.username).then((resp) => { + if (resp.enabled && Array.isArray(resp.personal_branding)) { + setUcBranding(resp.personal_branding); + } + }); + } + }; + + useEffect(() => { + initData(); + }, [data?.username]); if (!data?.username) { return null; } @@ -65,27 +86,54 @@ const Index: FC = ({ data }) => {
- {data.location && ( -
- - {data.location} -
- )} - - {data.website && ( - - )} + {!ucAgent?.enabled ? ( + <> + {data.location && ( +
+ + {data.location} +
+ )} + {data.website && ( + + )} + + ) : null} + {ucBranding.map((b, i) => { + return ( +
0, + })}> + {b.icon ? ( + + ) : null} + + {b.label} + +
+ ); + })}
diff --git a/ui/src/pages/Users/Personal/index.tsx b/ui/src/pages/Users/Personal/index.tsx index 662e0e7a..45ce56bf 100644 --- a/ui/src/pages/Users/Personal/index.tsx +++ b/ui/src/pages/Users/Personal/index.tsx @@ -47,14 +47,16 @@ const Personal: FC = () => { }, tabName, ); + const { count = 0, list = [] } = listData?.[tabName] || {}; + let pageTitle = ''; if (userInfo?.username) { pageTitle = `${userInfo?.display_name} (${userInfo?.username})`; } - const { count = 0, list = [] } = listData?.[tabName] || {}; usePageTags({ title: pageTitle, }); + return ( diff --git a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx index 51b90d25..bc15d8ec 100644 --- a/ui/src/pages/Users/Register/components/SignUpForm/index.tsx +++ b/ui/src/pages/Users/Register/components/SignUpForm/index.tsx @@ -12,7 +12,7 @@ import { useLegalTos, useLegalPrivacy, } from '@/services'; -import userStore from '@/stores/loggedUserInfoStore'; +import userStore from '@/stores/loggedUserInfo'; import { handleFormError } from '@/utils'; interface Props { diff --git a/ui/src/pages/Users/Register/index.tsx b/ui/src/pages/Users/Register/index.tsx index b6200e23..5839ed11 100644 --- a/ui/src/pages/Users/Register/index.tsx +++ b/ui/src/pages/Users/Register/index.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { usePageTags } from '@/hooks'; import { Unactivate, WelcomeTitle } from '@/components'; import { PluginOauth } from '@/plugins'; +import { guard } from '@/utils'; import SignUpForm from './components/SignUpForm'; @@ -17,6 +18,9 @@ const Index: React.FC = () => { usePageTags({ title: t('sign_up', { keyPrefix: 'page_title' }), }); + if (!guard.singUpAgent().ok) { + return null; + } return ( diff --git a/ui/src/pages/Users/Settings/Account/index.tsx b/ui/src/pages/Users/Settings/Account/index.tsx index 4571a90c..aee15788 100644 --- a/ui/src/pages/Users/Settings/Account/index.tsx +++ b/ui/src/pages/Users/Settings/Account/index.tsx @@ -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 ( <>

{t('heading')}

- - - + {accountAgent ? ( + {t('goto_modify', { keyPrefix: 'settings' })} + ) : null} + {!ucAgent?.enabled ? ( + <> + + + + + ) : null} ); }; diff --git a/ui/src/pages/Users/Settings/Profile/index.tsx b/ui/src/pages/Users/Settings/Profile/index.tsx index 96eb3e3c..6fb3e02f 100644 --- a/ui/src/pages/Users/Settings/Profile/index.tsx +++ b/ui/src/pages/Users/Settings/Profile/index.tsx @@ -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({ 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 ( <>

{t('heading')}

- - - {t('display_name.label')} - - handleChange({ - display_name: { - value: e.target.value, - isInvalid: false, - errorMsg: '', - }, - }) - } - /> - - {formData.display_name.errorMsg} - - + {profileAgent ? ( + {t('goto_modify', { keyPrefix: 'settings' })} + ) : null} + {!ucAgent?.enabled ? ( + + + {t('display_name.label')} + + handleChange({ + display_name: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }) + } + /> + + {formData.display_name.errorMsg} + + - - {t('username.label')} - - handleChange({ - username: { - value: e.target.value, - isInvalid: false, - errorMsg: '', - }, - }) - } - /> - {t('username.caption')} - - {formData.username.errorMsg} - - + + {t('username.label')} + + handleChange({ + username: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }) + } + /> + {t('username.caption')} + + {formData.username.errorMsg} + + - - {t('avatar.label')} -
- - - - - -
-
- {formData.avatar.type === 'gravatar' && ( - - 0 ? `&t=${new Date().valueOf()}` : '' - }`} - className="me-3 rounded" - /> - - - You can change image on - - gravatar.com - - - - - )} - - {formData.avatar.type === 'custom' && ( - - + + {t('avatar.label')} +
+ + + + + +
+
+ {formData.avatar.type === 'gravatar' && ( + 0 ? `&t=${new Date().valueOf()}` : '' + }`} + className="me-3 rounded" /> - - - - - - + + + You can change image on + + gravatar.com + + + - - - You can upload your image. - - - - )} - {formData.avatar.type === 'default' && ( - - )} -
- - - {formData.avatar.errorMsg} - -
+ )} - - - {`${t('bio.label')} ${t('optional', { + {formData.avatar.type === 'custom' && ( + + + + + + + + + + + + + You can upload your image. + + + + )} + {formData.avatar.type === 'default' && ( + + )} +
+ + + {formData.avatar.errorMsg} + +
+ + + + {`${t('bio.label')} ${t('optional', { + keyPrefix: 'form', + })}`} + + + handleChange({ + bio: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }) + } + /> + + {formData.bio.errorMsg} + + + + + {`${t('website.label')} ${t('optional', { keyPrefix: 'form', - })}`} - - - handleChange({ - bio: { - value: e.target.value, - isInvalid: false, - errorMsg: '', - }, - }) - } - /> - - {formData.bio.errorMsg} - - + })}`} + + handleChange({ + website: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }) + } + /> + + {formData.website.errorMsg} + + - - {`${t('website.label')} ${t('optional', { - keyPrefix: 'form', - })}`} - - handleChange({ - website: { - value: e.target.value, - isInvalid: false, - errorMsg: '', - }, - }) - } - /> - - {formData.website.errorMsg} - - + + {`${t('location.label')} ${t('optional', { + keyPrefix: 'form', + })}`} + + handleChange({ + location: { + value: e.target.value, + isInvalid: false, + errorMsg: '', + }, + }) + } + /> + + {formData.location.errorMsg} + + - - {`${t('location.label')} ${t('optional', { - keyPrefix: 'form', - })}`} - - handleChange({ - location: { - value: e.target.value, - isInvalid: false, - errorMsg: '', - }, - }) - } - /> - - {formData.location.errorMsg} - - - - - + + + ) : null} ); }; diff --git a/ui/src/pages/Users/Settings/components/Nav/index.tsx b/ui/src/pages/Users/Settings/components/Nav/index.tsx index 5d1a0809..6f65dcfe 100644 --- a/ui/src/pages/Users/Settings/components/Nav/index.tsx +++ b/ui/src/pages/Users/Settings/components/Nav/index.tsx @@ -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 (