mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
feat: add login page ip detect (#2819)
* feat: add login page ip detect * code perf
This commit is contained in:
40
projects/app/src/components/Select/I18nLngSelector.tsx
Normal file
40
projects/app/src/components/Select/I18nLngSelector.tsx
Normal file
@@ -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: (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
|
||||
<Box>{lang.label}</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: key
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
_hover={{
|
||||
bg: 'myGray.200'
|
||||
}}
|
||||
value={i18n.language}
|
||||
list={list}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
onChangeLng(lang);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default I18nLngSelector;
|
@@ -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<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
@@ -49,17 +44,7 @@ const Individuation = () => {
|
||||
<Flex alignItems={'center'} w={['85%', '350px']}>
|
||||
<Box flex={'0 0 80px'}>{t('common:user.Language')}: </Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: lang.label,
|
||||
value: key
|
||||
}))}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
onChangeLng(lang);
|
||||
}}
|
||||
/>
|
||||
<I18nLngSelector />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '350px']}>
|
||||
|
@@ -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 (
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex
|
||||
w={['48px', '56px']}
|
||||
h={['48px', '56px']}
|
||||
bg={'myGray.25'}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1.5px'}
|
||||
borderColor={'borderColor.base'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
|
||||
<Flex alignItems={'center'} justify={'space-between'}>
|
||||
<Flex>
|
||||
<Flex
|
||||
w={['48px', '56px']}
|
||||
h={['48px', '56px']}
|
||||
bg={'myGray.25'}
|
||||
borderRadius={'xl'}
|
||||
borderWidth={'1.5px'}
|
||||
borderColor={'borderColor.base'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
|
||||
</Flex>
|
||||
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
|
||||
{feConfigs?.systemTitle}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
|
||||
{feConfigs?.systemTitle}
|
||||
</Box>
|
||||
{!isPc && <I18nLngSelector />}
|
||||
</Flex>
|
||||
{children}
|
||||
{show_oauth && (
|
||||
|
@@ -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<boolean>('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}`}
|
||||
></Script>
|
||||
)}
|
||||
|
||||
{ChineseRedirectUrl && showRedirect && (
|
||||
<Script
|
||||
src="//geoip-js.com/js/apis/geoip2/v2.1/geoip2.js"
|
||||
type="text/javascript"
|
||||
onLoad={checkIpInChina}
|
||||
></Script>
|
||||
)}
|
||||
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
@@ -85,6 +132,11 @@ const Login = () => {
|
||||
h={'100%'}
|
||||
px={[0, '10vw']}
|
||||
>
|
||||
{isPc && (
|
||||
<Box position={'absolute'} top={'24px'} right={'50px'}>
|
||||
<I18nLngSelector />
|
||||
</Box>
|
||||
)}
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
w={['100%', 'auto']}
|
||||
@@ -123,13 +175,66 @@ const Login = () => {
|
||||
|
||||
{isOpen && <CommunityModal onClose={onClose} />}
|
||||
</Flex>
|
||||
{showRedirect && (
|
||||
<RedirectDrawer
|
||||
isOpen={isOpenRedirect}
|
||||
onClose={onCloseRedirect}
|
||||
onRedirect={() => router.push(ChineseRedirectUrl)}
|
||||
disableDrawer={() => setShowRedirect(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function RedirectDrawer({
|
||||
isOpen,
|
||||
onClose,
|
||||
disableDrawer,
|
||||
onRedirect
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
disableDrawer: () => void;
|
||||
onRedirect: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Drawer placement="bottom" size={'xs'} isOpen={isOpen} onClose={onClose}>
|
||||
<DrawerOverlay backgroundColor={'rgba(0,0,0,0.2)'} />
|
||||
<DrawerContent py={'1.75rem'} px={'3rem'}>
|
||||
<DrawerCloseButton size={'sm'} />
|
||||
<Flex align={'center'} justify={'space-between'}>
|
||||
<Box>
|
||||
<Box color={'myGray.900'} fontWeight={'500'} fontSize={'1rem'}>
|
||||
{t('login:Chinese_ip_tip')}
|
||||
</Box>
|
||||
<Box
|
||||
color={'primary.700'}
|
||||
fontWeight={'500'}
|
||||
fontSize={'1rem'}
|
||||
textDecorationLine={'underline'}
|
||||
cursor={'pointer'}
|
||||
onClick={disableDrawer}
|
||||
>
|
||||
{t('login:no_remind')}
|
||||
</Box>
|
||||
</Box>
|
||||
<Button ml={'0.75rem'} onClick={onRedirect}>
|
||||
{t('login:redirect')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
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']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
2
projects/app/src/types/index.d.ts
vendored
2
projects/app/src/types/index.d.ts
vendored
@@ -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;
|
||||
|
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user