diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 0696b6b7fbe..bed30fa3c3e 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -39,6 +39,8 @@ export const iconPaths = { 'common/inviteLight': () => import('./icons/common/inviteLight.svg'), 'common/language/en': () => import('./icons/common/language/en.svg'), 'common/language/zh': () => import('./icons/common/language/zh.svg'), + 'common/language/China': () => import('./icons/common/language/China.svg'), + 'common/language/America': () => import('./icons/common/language/America.svg'), 'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'), 'common/line': () => import('./icons/common/line.svg'), 'common/lineChange': () => import('./icons/common/lineChange.svg'), diff --git a/packages/web/components/common/Icon/icons/common/language/America.svg b/packages/web/components/common/Icon/icons/common/language/America.svg new file mode 100644 index 00000000000..2e63b188f07 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/language/America.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/language/China.svg b/packages/web/components/common/Icon/icons/common/language/China.svg new file mode 100644 index 00000000000..8b01b208972 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/language/China.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/web/i18n/en/login.json b/packages/web/i18n/en/login.json index a8297cf2f6e..7959b85b116 100644 --- a/packages/web/i18n/en/login.json +++ b/packages/web/i18n/en/login.json @@ -1,13 +1,16 @@ { + "Chinese_ip_tip": "It is detected that you are a mainland Chinese IP, click to jump to visit the mainland China version.", "Login": "Login", "forget_password": "Find password", "login_failed": "Login failed", "login_success": "Login successful", + "no_remind": "Don't remind again", "password_condition": "Password maximum 60 characters", "policy_tip": "By useing, you agree to our", "privacy": "Privacy policy", + "redirect": "Jump", "register": "Register", "root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW", "terms": "Terms", "use_root_login": "Log in as root user" -} \ No newline at end of file +} diff --git a/packages/web/i18n/zh/login.json b/packages/web/i18n/zh/login.json index c66304e06c8..4b4e36ff2aa 100644 --- a/packages/web/i18n/zh/login.json +++ b/packages/web/i18n/zh/login.json @@ -9,5 +9,8 @@ "register": "注册账号", "root_password_placeholder": "root 用户密码为环境变量 DEFAULT_ROOT_PSW 的值", "terms": "服务协议", - "use_root_login": "使用 root 用户登录" -} \ No newline at end of file + "use_root_login": "使用 root 用户登录", + "redirect": "跳转", + "no_remind": "不再提醒", + "Chinese_ip_tip": "检测到您是中国大陆 IP,点击跳转访问中国大陆版。" +} diff --git a/projects/app/src/components/Select/I18nLngSelector.tsx b/projects/app/src/components/Select/I18nLngSelector.tsx new file mode 100644 index 00000000000..42c34a3550b --- /dev/null +++ b/projects/app/src/components/Select/I18nLngSelector.tsx @@ -0,0 +1,40 @@ +import { langMap } from '@/web/common/utils/i18n'; +import { Avatar, Box, Flex } from '@chakra-ui/react'; +import MySelect from '@fastgpt/web/components/common/MySelect'; +import { useI18nLng } from '@fastgpt/web/hooks/useI18n'; +import { useTranslation } from 'next-i18next'; +import { useMemo } from 'react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; + +const I18nLngSelector = () => { + const { i18n } = useTranslation(); + const { onChangeLng } = useI18nLng(); + + const list = useMemo(() => { + return Object.entries(langMap).map(([key, lang]) => ({ + label: ( + + + {lang.label} + + ), + value: key + })); + }, []); + + return ( + { + const lang = val; + onChangeLng(lang); + }} + /> + ); +}; + +export default I18nLngSelector; diff --git a/projects/app/src/pages/account/components/Individuation.tsx b/projects/app/src/pages/account/components/Individuation.tsx index 2055900fb0f..425e73f47f9 100644 --- a/projects/app/src/pages/account/components/Individuation.tsx +++ b/projects/app/src/pages/account/components/Individuation.tsx @@ -7,18 +7,13 @@ import { UserType } from '@fastgpt/global/support/user/type'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useForm } from 'react-hook-form'; import { UserUpdateParams } from '@/types/user'; -import { langMap } from '@/web/common/utils/i18n'; -import { useRouter } from 'next/router'; -import { useI18nLng } from '@fastgpt/web/hooks/useI18n'; - -import MySelect from '@fastgpt/web/components/common/MySelect'; import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect'; +import I18nLngSelector from '@/components/Select/I18nLngSelector'; const Individuation = () => { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const { userInfo, updateUserInfo } = useUserStore(); const { toast } = useToast(); - const { onChangeLng } = useI18nLng(); const { reset } = useForm({ defaultValues: userInfo as UserType @@ -49,17 +44,7 @@ const Individuation = () => { {t('common:user.Language')}:  - ({ - label: lang.label, - value: key - }))} - onchange={(val: any) => { - const lang = val; - onChangeLng(lang); - }} - /> + diff --git a/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx b/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx index 7323dbc8c8b..ea83bc4b2c6 100644 --- a/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx +++ b/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx @@ -9,6 +9,8 @@ import { useRouter } from 'next/router'; import { Dispatch, useRef } from 'react'; import { useTranslation } from 'next-i18next'; import Divider from '@/pages/app/detail/components/WorkflowComponents/Flow/components/Divider'; +import I18nLngSelector from '@/components/Select/I18nLngSelector'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8); interface Props { @@ -24,6 +26,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => { const { lastRoute = '/app/list' } = router.query as { lastRoute: string }; const state = useRef(nanoid()); const redirectUri = `${location.origin}/login/provider`; + const { isPc } = useSystem(); const oAuthList = [ ...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat @@ -72,22 +75,25 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => { return ( - - - {'icon'} + + + + {'icon'} + + + {feConfigs?.systemTitle} + - - {feConfigs?.systemTitle} - + {!isPc && } {children} {show_oauth && ( diff --git a/projects/app/src/pages/login/index.tsx b/projects/app/src/pages/login/index.tsx index f719aeb6f6e..3274c623a89 100644 --- a/projects/app/src/pages/login/index.tsx +++ b/projects/app/src/pages/login/index.tsx @@ -1,5 +1,15 @@ import React, { useState, useCallback, useEffect } from 'react'; -import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react'; +import { + Box, + Button, + Center, + Drawer, + DrawerCloseButton, + DrawerContent, + DrawerOverlay, + Flex, + useDisclosure +} from '@chakra-ui/react'; import { LoginPageTypeEnum } from '@/web/support/user/login/constants'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import type { ResLogin } from '@/global/support/api/userRes.d'; @@ -12,22 +22,31 @@ import { serviceSideProps } from '@/web/common/utils/i18n'; import { clearToken, setToken } from '@/web/support/user/auth'; import Script from 'next/script'; import Loading from '@fastgpt/web/components/common/MyLoading'; -import { useMount } from 'ahooks'; -import { t } from 'i18next'; +import { useLocalStorageState, useMount } from 'ahooks'; +import { useTranslation } from 'next-i18next'; +import I18nLngSelector from '@/components/Select/I18nLngSelector'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; const RegisterForm = dynamic(() => import('./components/RegisterForm')); const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm')); const WechatForm = dynamic(() => import('./components/LoginForm/WechatForm')); const CommunityModal = dynamic(() => import('@/components/CommunityModal')); -const Login = () => { +const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => { const router = useRouter(); + const { t } = useTranslation(); const { lastRoute = '' } = router.query as { lastRoute: string }; const { feConfigs } = useSystemStore(); const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>(); const { setUserInfo } = useUserStore(); const { setLastChatId, setLastChatAppId } = useChatStore(); const { isOpen, onOpen, onClose } = useDisclosure(); + const { isPc } = useSystem(); + const { + isOpen: isOpenRedirect, + onOpen: onOpenRedirect, + onClose: onCloseRedirect + } = useDisclosure(); const loginSuccess = useCallback( (res: ResLogin) => { @@ -69,6 +88,25 @@ const Login = () => { router.prefetch('/app/list'); }); + const [showRedirect, setShowRedirect] = useLocalStorageState('showRedirect', { + defaultValue: true + }); + + const checkIpInChina = useCallback(() => { + const onSuccess = (res: any) => { + if (!res.country.iso_code) { + return; + } + + const country = res.country.iso_code.toLowerCase(); + if (country === 'cn') { + onOpenRedirect(); + } + }; + const onError = (e: any) => console.log(e); + geoip2 && geoip2.country(onSuccess, onError); + }, [onOpenRedirect]); + return ( <> {feConfigs.googleClientVerKey && ( @@ -76,6 +114,15 @@ const Login = () => { src={`https://www.recaptcha.net/recaptcha/api.js?render=${feConfigs.googleClientVerKey}`} > )} + + {ChineseRedirectUrl && showRedirect && ( + + )} + { h={'100%'} px={[0, '10vw']} > + {isPc && ( + + + + )} { {isOpen && } + {showRedirect && ( + router.push(ChineseRedirectUrl)} + disableDrawer={() => setShowRedirect(false)} + /> + )} ); }; +function RedirectDrawer({ + isOpen, + onClose, + disableDrawer, + onRedirect +}: { + isOpen: boolean; + onClose: () => void; + disableDrawer: () => void; + onRedirect: () => void; +}) { + const { t } = useTranslation(); + return ( + + + + + + + + {t('login:Chinese_ip_tip')} + + + {t('login:no_remind')} + + + + + + + ); +} + export async function getServerSideProps(context: any) { return { - props: { ...(await serviceSideProps(context, ['app', 'user', 'login'])) } + props: { + ChineseRedirectUrl: process.env.CHINESE_IP_REDIRECT_URL, + ...(await serviceSideProps(context, ['app', 'user', 'login'])) + } }; } diff --git a/projects/app/src/types/index.d.ts b/projects/app/src/types/index.d.ts index f96bb1827e1..f388be3508a 100644 --- a/projects/app/src/types/index.d.ts +++ b/projects/app/src/types/index.d.ts @@ -22,7 +22,7 @@ export type RequestPaging = { pageNum: number; pageSize: number; [key]: any }; declare global { var qaQueueLen: number; var vectorQueueLen: number; - + var geoip2: any; interface Window { grecaptcha: any; QRCode: any; diff --git a/projects/app/src/web/common/utils/i18n.ts b/projects/app/src/web/common/utils/i18n.ts index 3e2d2102ec6..27f2ab75572 100644 --- a/projects/app/src/web/common/utils/i18n.ts +++ b/projects/app/src/web/common/utils/i18n.ts @@ -7,12 +7,14 @@ export enum LangEnum { } export const langMap = { [LangEnum.en]: { - label: 'English', - icon: 'common/language/en' + label: 'English(US)', + icon: 'common/language/en', + avatar: 'common/language/America' }, [LangEnum.zh]: { label: '简体中文', - icon: 'common/language/zh' + icon: 'common/language/zh', + avatar: 'common/language/China' } };