Commit d1c71eeb by light5980 Committed by GitHub

fix: login ui (#7009)

* fix: login ui

* delet doc

* fix: login en policy tip

* perf: word break

---------

Co-authored-by: archer <545436317@qq.com>
parent bd140f71
......@@ -78,6 +78,7 @@ export type FastGPTFeConfigsType = {
concatMd?: string;
docUrl?: string;
loginGuideDocUrl?: string;
openAPIDocUrl?: string;
submitPluginRequestUrl?: string;
appTemplateCourse?: string;
......
......@@ -1060,7 +1060,7 @@
"support.user.login.Provider error": "Login Error, Please Try Again",
"support.user.login.Username": "Username",
"support.user.login.Wechat": "WeChat Login",
"support.user.login.can_not_login": "Cannot log in? Click here to contact us",
"support.user.login.can_not_login": "Cannot log in?",
"support.user.login.error": "Login Error",
"support.user.login.security_failed": "Security Verification Failed",
"support.user.login.wx_qr_login": "WeChat QR Code Login",
......
......@@ -8,7 +8,7 @@
"no_remind": "Don't remind again",
"password_condition": "Password must be 4–60 characters",
"password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"policy_tip": "By using it, you have read and agree to\n<div>our<termsLink> Terms </termsLink>&<privacyLink> Privacy Policy</privacyLink></div>",
"policy_tip": "By using it, you have read and agree to <lineBreak/>our<termsLink> Terms </termsLink>&<privacyLink> Privacy Policy</privacyLink>",
"privacy_policy": "Privacy Policy",
"redirect": "Jump",
"register": "Register",
......
......@@ -1060,7 +1060,7 @@
"support.user.login.Provider error": "登录异常,请重试",
"support.user.login.Username": "用户名",
"support.user.login.Wechat": "微信登录",
"support.user.login.can_not_login": "无法登录,点击联系",
"support.user.login.can_not_login": "无法登录",
"support.user.login.error": "登录异常",
"support.user.login.security_failed": "安全校验失败",
"support.user.login.wx_qr_login": "微信扫码登录",
......
......@@ -8,7 +8,7 @@
"no_remind": "不再提醒",
"password_condition": "密码最多 60 位",
"password_tip": "密码至少 8 位,且至少包含两种组合:数字、字母或特殊字符",
"policy_tip": "使用即代表您已阅读并同意<termsLink> 服务协议 </termsLink>和<privacyLink> 隐私协议</privacyLink>",
"policy_tip": "使用即代表您已经阅读并且同意<lineBreak/>我们的<termsLink> 服务协议 </termsLink> & <privacyLink> 隐私协议</privacyLink>",
"privacy_policy": "隐私政策",
"redirect": "跳转",
"register": "注册账号",
......
......@@ -1049,7 +1049,7 @@
"support.user.login.Provider error": "登入錯誤,請重試",
"support.user.login.Username": "使用者名稱",
"support.user.login.Wechat": "微信登入",
"support.user.login.can_not_login": "無法登入,點選聯絡我們",
"support.user.login.can_not_login": "無法登入",
"support.user.login.error": "登入錯誤",
"support.user.login.security_failed": "安全驗證失敗",
"support.user.login.wx_qr_login": "微信 QR Code 登入",
......
......@@ -8,7 +8,7 @@
"no_remind": "不再提醒",
"password_condition": "密碼最多 60 個字元",
"password_tip": "密碼至少 8 位,且至少包含兩種組合:數字、字母或特殊字元",
"policy_tip": "使用即代表您已閱讀並同意<termsLink> 服務協議 </termsLink>和<privacyLink> 隱私協議</privacyLink>",
"policy_tip": "使用即代表您已經閱讀並且同意<lineBreak/>我們的<termsLink> 服務協議 </termsLink> & <privacyLink> 隱私協議</privacyLink>",
"privacy_policy": "隱私權政策",
"redirect": "跳轉",
"register": "註冊帳號",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -6,8 +6,6 @@ import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import { useRouter } from 'next/router';
import { type Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { checkIsWecomTerminal } from '@fastgpt/global/support/user/login/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
......@@ -36,7 +34,6 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
const rootLogin = router.query.rootLogin === '1';
const { setLoginStore, feConfigs } = useSystemStore();
const { isPc } = useSystem();
const { lastRoute = '/dashboard/agent', lastTmbId = '' } = router.query as {
lastRoute: string;
......@@ -200,13 +197,23 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
]);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'} justifyContent={['space-between', 'center']}>
<Flex alignItems={'center'} pr="4">
<Flex
flexDirection={'column'}
h={'100%'}
alignItems={['center', 'stretch']}
justifyContent={['center', 'flex-start']}
>
<Flex
alignItems={'center'}
justifyContent={['flex-start', 'center']}
w={['fit-content', '100%']}
alignSelf={['flex-start', 'auto']}
>
<Flex alignItems={'center'} pr={['0', '4']} w={'fit-content'} justifyContent={'flex-start'}>
<Flex
w={['42px', '56px']}
h={['42px', '56px']}
bg={'myGray.25'}
bg={'white'}
borderRadius={['semilg', 'lg']}
borderWidth={['1px', '1.5px']}
borderColor={'myGray.200'}
......@@ -219,14 +226,15 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
{feConfigs?.systemTitle}
</Box>
</Flex>
{!isPc && <I18nLngSelector />}
</Flex>
{children}
<Box w={'100%'} mt={[8, 0]}>
{children}
</Box>
{show_oauth && (
<Box mt={['80px', 9]}>
<Box mt={8} w={'100%'}>
<Box flex={1} />
<Flex position={'relative'} mb={5} alignItems={'center'}>
<Flex position={'relative'} mb={4} alignItems={'center'}>
<Box h={'1px'} flex={'1'} bg={'myGray.250'} />
<Box px={3} color={'myGray.500'} fontSize={'mini'}>
or
......
......@@ -7,13 +7,13 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import FormLayout from './FormLayout';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import PolicyTip from './PolicyTip';
import { useSearchParams } from 'next/navigation';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { useRouter } from 'next/router';
import { useMount } from 'ahooks';
import type { LangEnum } from '@fastgpt/global/common/i18n/type';
import type { LoginSuccessResponseType } from '@fastgpt/global/openapi/support/user/account/login/api';
import PolicyTip from './PolicyTip';
type LoginSuccessHandler = (res: LoginSuccessResponseType) => void | Promise<void>;
......@@ -108,7 +108,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
return (
<FormLayout setPageType={setPageType} pageType={LoginPageTypeEnum.passwordLogin}>
<Box
mt={8}
mt={[0, 8]}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey && !requesting) {
handleSubmit(onclickLogin)();
......@@ -117,7 +117,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
>
<FormControl isInvalid={!!errors.username}>
<Input
bg={'myGray.50'}
bg={'white'}
size={'lg'}
placeholder={placeholder}
{...register('username', {
......@@ -125,9 +125,9 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
})}
></Input>
</FormControl>
<FormControl mt={7} isInvalid={!!errors.password}>
<FormControl mt={6} isInvalid={!!errors.password}>
<Input
bg={'myGray.50'}
bg={'white'}
size={'lg'}
type={'password'}
placeholder={
......@@ -144,14 +144,12 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
})}
></Input>
</FormControl>
<PolicyTip isCenter={false} />
<PolicyTip />
<Button
type="submit"
my={[5, 7]}
mt={6}
w={'100%'}
size={['md', 'md']}
h={[10, 10]}
size={'lg'}
fontWeight={['medium', 'medium']}
colorScheme="blue"
isLoading={requesting}
......@@ -161,10 +159,14 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
</Button>
<Flex
mt={6}
align={'center'}
justifyContent={['flex-end', 'center']}
justifyContent={'center'}
gap={0}
color={'primary.700'}
fontWeight={'medium'}
h={'16px'}
lineHeight={'16px'}
>
{feConfigs?.find_password_method && feConfigs.find_password_method.length > 0 && (
<Box
......@@ -177,17 +179,18 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
</Box>
)}
{feConfigs?.register_method && feConfigs.register_method.length > 0 && (
<Flex alignItems={'center'}>
<Box mx={3} h={'12px'} w={'1px'} bg={'myGray.250'}></Box>
<>
<Box display={['block', 'block']} mx={3} h={'12px'} w={'1px'} bg={'myGray.250'}></Box>
<Box
cursor={'pointer'}
_hover={{ textDecoration: 'underline' }}
onClick={() => setPageType('register')}
fontSize="mini"
lineHeight="16px"
>
{t('login:register')}
</Box>
</Flex>
</>
)}
</Flex>
</Box>
......
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Box, Flex, Link } from '@chakra-ui/react';
import React from 'react';
import { Trans } from 'next-i18next';
import { Box, Link } from '@chakra-ui/react';
import React, { useEffect, useRef, useState } from 'react';
import { Trans, useTranslation } from 'next-i18next';
const PolicyTip = ({ isCenter }: { isCenter: boolean }) => {
const PolicyTip = () => {
const { feConfigs } = useSystemStore();
const { i18n } = useTranslation();
const tipRef = useRef<HTMLDivElement>(null);
const measureRef = useRef<HTMLSpanElement>(null);
const [isMultiline, setIsMultiline] = useState(false);
useEffect(() => {
const tip = tipRef.current;
const measure = measureRef.current;
if (!tip || !measure) return;
// 先按单行版本测量宽度;放不下时再使用文案中的 lineBreak 设计断点。
const updateMultiline = () => {
const tipWidth = tip.getBoundingClientRect().width;
const singleLineWidth = measure.scrollWidth;
setIsMultiline(singleLineWidth > tipWidth + 1);
};
const timer = setTimeout(updateMultiline, 0);
const observer = new ResizeObserver(updateMultiline);
observer.observe(tip);
observer.observe(measure);
return () => {
clearTimeout(timer);
observer.disconnect();
};
}, [i18n.language, feConfigs?.docUrl]);
return (
<>
{feConfigs?.docUrl && (
<Box
display={isCenter ? 'block' : 'flex'}
textAlign={isCenter ? 'center' : 'left'}
mt={7}
ref={tipRef}
display={'block'}
position={'relative'}
textAlign={isMultiline ? 'center' : 'left'}
mt={6}
fontSize={'mini'}
lineHeight={'16px'}
color={'myGray.400'}
whiteSpace={'pre-wrap'}
>
<Trans
i18nKey="login:policy_tip"
components={{
div: <Flex justifyContent={'center'} />,
lineBreak: isMultiline ? <br /> : <span />,
termsLink: (
<Link
href={getDocPath('/guide/version/cloud/terms')}
......@@ -38,6 +69,24 @@ const PolicyTip = ({ isCenter }: { isCenter: boolean }) => {
)
}}
/>
<Box
ref={measureRef}
as={'span'}
position={'absolute'}
visibility={'hidden'}
pointerEvents={'none'}
whiteSpace={'nowrap'}
aria-hidden
>
<Trans
i18nKey="login:policy_tip"
components={{
lineBreak: <span />,
termsLink: <Link />,
privacyLink: <Link />
}}
/>
</Box>
</Box>
)}
</>
......
......@@ -88,7 +88,7 @@ const WechatForm = ({ setPageType, loginSuccess }: Props) => {
</Center>
)}
</Box>
<PolicyTip isCenter />
<PolicyTip />
</Box>
</FormLayout>
);
......
......@@ -3,7 +3,6 @@ import { Box, Flex } from '@chakra-ui/react';
import { LoginContainer } from '@/pageComponents/login';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { type LoginSuccessResponseType } from '@fastgpt/global/openapi/support/user/account/login/api';
type LoginModalProps = {
......@@ -17,10 +16,12 @@ const LoginModal = ({ onSuccess }: LoginModalProps) => {
<Flex
alignItems={'center'}
justifyContent={'center'}
bg={['white', `url(${getWebReqUrl('/icon/login-bg.svg')}) no-repeat`]}
backgroundSize={['cover', 'cover']}
bg={'white'}
userSelect={'none'}
h={'100%'}
minH={'100vh'}
px={0}
pt={0}
pb={0}
>
{/* Language selector - login page */}
{isPc && (
......@@ -30,21 +31,48 @@ const LoginModal = ({ onSuccess }: LoginModalProps) => {
)}
<Flex
flexDirection={'column'}
w={['100%', '560px']}
h={['100%', '690px']}
bg={[`url(${getWebReqUrl('/icon/login-bg-phone.svg')}) no-repeat`, 'white']}
backgroundSize={'cover'}
px={['8', '90px']}
py={['38px', '90px']}
borderRadius={[0, '16px']}
boxShadow={[
'',
'0px 32px 64px -12px rgba(19, 51, 107, 0.20), 0px 0px 1px 0px rgba(19, 51, 107, 0.20)'
]}
position="relative"
alignItems={'center'}
justifyContent={'center'}
w={'100%'}
maxW={['100%', '1328px']}
h={'100vh'}
minH={['100vh', '720px']}
bg={['transparent', 'white']}
borderRadius={[0, '24px']}
overflow={'hidden'}
>
<LoginContainer onSuccess={onSuccess} />
<Box
position={'absolute'}
top={['-190px', '-100px']}
left={'50%'}
w={['900px', '1230px']}
h={['590px', '510px']}
transform={'translateX(-50%)'}
pointerEvents={'none'}
bgImage={'url(/icon/login-gradient-bg.svg)'}
bgRepeat={'no-repeat'}
bgPosition={'center top'}
bgSize={'100% 100%'}
/>
<Flex
flexDirection={'column'}
w={['100%', '560px']}
h={['100%', 'auto']}
bg={['transparent', 'white']}
px={['8', '90px']}
py={['38px', '90px']}
borderRadius={[0, '16px']}
boxShadow={[
'',
'0px 16px 40px rgba(30, 64, 175, 0.10), 0px 1px 3px rgba(15, 23, 42, 0.06)'
]}
position="relative"
zIndex={1}
>
<LoginContainer onSuccess={onSuccess} />
</Flex>
</Flex>
</Flex>
);
......
......@@ -16,9 +16,15 @@ type LoginFormPanelProps = {
pageType: `${LoginPageTypeEnum}`;
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
loginSuccess: LoginSuccessHandler;
reserveLoginGuideSpace?: boolean;
};
const LoginFormPanel = ({ pageType, setPageType, loginSuccess }: LoginFormPanelProps) => {
const LoginFormPanel = ({
pageType,
setPageType,
loginSuccess,
reserveLoginGuideSpace
}: LoginFormPanelProps) => {
const DynamicComponent = useMemo(() => {
if (!pageType) return null;
......@@ -36,7 +42,7 @@ const LoginFormPanel = ({ pageType, setPageType, loginSuccess }: LoginFormPanelP
}, [pageType, setPageType, loginSuccess]);
return (
<Box w={['100%', '380px']} flex={['', '1 0 0']}>
<Box w={['100%', '380px']} flex={['0 0 auto', reserveLoginGuideSpace ? '1 0 0' : '0 0 auto']}>
{pageType && DynamicComponent ? DynamicComponent : <Loading fixed={false} />}
</Box>
);
......
......@@ -10,6 +10,8 @@ import ChineseRedirectModal from './components/ChineseRedirectModal';
import CookieConsentModal from './components/CookieConsentModal';
import LoginFormPanel from './components/LoginFormPanel';
import type { LoginSuccessResponseType } from '@fastgpt/global/openapi/support/user/account/login/api';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
type LoginSuccessHandler = (res: LoginSuccessResponseType) => void | Promise<void>;
......@@ -26,6 +28,8 @@ export const LoginContainer = ({
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const { resetChatCache } = useChatStore();
const { isPc } = useSystem();
const loginGuideDocUrl = feConfigs?.loginGuideDocUrl?.trim();
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>(LoginPageTypeEnum.passwordLogin);
const [showCommunityModal, setShowCommunityModal] = useState(false);
......@@ -57,35 +61,57 @@ export const LoginContainer = ({
my={['', pageType === LoginPageTypeEnum.wechat ? '-15px' : '']}
position="relative"
w="full"
flex={'1 0 0'}
flex={['1 0 0', '0 0 auto']}
flexDirection={'column'}
justifyContent={['center', 'flex-start']}
>
{!isPc && (
<Box mb={8} alignSelf={'flex-start'}>
<I18nLngSelector />
</Box>
)}
{/* main content area */}
<LoginFormPanel pageType={pageType} setPageType={setPageType} loginSuccess={loginSuccess} />
<LoginFormPanel
pageType={pageType}
setPageType={setPageType}
loginSuccess={loginSuccess}
reserveLoginGuideSpace={
pageType === LoginPageTypeEnum.passwordLogin &&
!!(loginGuideDocUrl || feConfigs?.concatMd)
}
/>
{/* custom content */}
{children}
{/* help link for login */}
{feConfigs?.concatMd && (
<Box
mt={[9, '6']}
color={'primary.700'}
fontSize={'mini'}
fontWeight={'medium'}
cursor={'pointer'}
textAlign={'center'}
onClick={() => setShowCommunityModal(true)}
>
{t('common:support.user.login.can_not_login')}
</Box>
)}
{pageType === LoginPageTypeEnum.passwordLogin &&
(loginGuideDocUrl || feConfigs?.concatMd) && (
<Box
mt={[8, 8]}
color={'primary.700'}
fontSize={'mini'}
fontWeight={'medium'}
lineHeight={'16px'}
cursor={'pointer'}
textAlign={'center'}
onClick={() => {
if (loginGuideDocUrl) {
window.open(loginGuideDocUrl, '_blank', 'noopener,noreferrer');
} else {
setShowCommunityModal(true);
}
}}
>
{t('common:support.user.login.can_not_login')}
</Box>
)}
</Flex>
<CookieConsentModal />
<ChineseRedirectModal />
{/* Community modal */}
{showCommunityModal && <CommunityModal onClose={() => setShowCommunityModal(false)} />}
</>
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment