feat: add login page ip detect (#2819)

* feat: add login page ip detect

* code perf
This commit is contained in:
papapatrick
2024-09-27 13:39:10 +08:00
committed by GitHub
parent efcb53cd6d
commit 691476c821
11 changed files with 255 additions and 45 deletions

View 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;

View File

@@ -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')}:&nbsp;</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']}>

View File

@@ -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 && (

View File

@@ -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']))
}
};
}

View File

@@ -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;

View File

@@ -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'
}
};