From fa4c28db981f7a2ce07b769e16755e8a9a2caf7e Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 23 Dec 2025 16:19:46 +0800 Subject: [PATCH] modal --- .../global/common/system/config/constants.ts | 6 +- .../web/common/file/hooks/useUploadAvatar.tsx | 17 +- packages/web/i18n/en/common.json | 4 + packages/web/i18n/zh-CN/common.json | 4 + packages/web/i18n/zh-Hant/common.json | 4 + .../app/public/imgs/system/snowflakeLeft.svg | 3 + .../app/public/imgs/system/snowflakeRight.svg | 3 + projects/app/src/components/Layout/index.tsx | 4 + .../support/activity/ActivityAdModal.tsx | 188 ++++++++++++++++++ projects/app/src/web/common/system/api.ts | 5 + 10 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 projects/app/public/imgs/system/snowflakeLeft.svg create mode 100644 projects/app/public/imgs/system/snowflakeRight.svg create mode 100644 projects/app/src/components/support/activity/ActivityAdModal.tsx diff --git a/packages/global/common/system/config/constants.ts b/packages/global/common/system/config/constants.ts index a47f11040..f679e3c9c 100644 --- a/packages/global/common/system/config/constants.ts +++ b/packages/global/common/system/config/constants.ts @@ -3,7 +3,8 @@ export enum SystemConfigsTypeEnum { fastgptPro = 'fastgptPro', systemMsgModal = 'systemMsgModal', license = 'license', - operationalAd = 'operationalAd' + operationalAd = 'operationalAd', + activityAd = 'activityAd' } export const SystemConfigsTypeMap = { @@ -21,5 +22,8 @@ export const SystemConfigsTypeMap = { }, [SystemConfigsTypeEnum.operationalAd]: { label: 'operationalAd' + }, + [SystemConfigsTypeEnum.activityAd]: { + label: 'activityAd' } }; diff --git a/packages/web/common/file/hooks/useUploadAvatar.tsx b/packages/web/common/file/hooks/useUploadAvatar.tsx index b6df4c008..19a4a710e 100644 --- a/packages/web/common/file/hooks/useUploadAvatar.tsx +++ b/packages/web/common/file/hooks/useUploadAvatar.tsx @@ -8,7 +8,17 @@ import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; export const useUploadAvatar = ( api: (params: { filename: string }) => Promise, - { onSuccess }: { onSuccess?: (avatar: string) => void } = {} + { + onSuccess, + maxW = 300, + maxH = 300, + maxSize = 1024 * 500 // 500KB + }: { + onSuccess?: (avatar: string) => void; + maxW?: number; + maxH?: number; + maxSize?: number; + } = {} ) => { const { toast } = useToast(); const { t } = useTranslation(); @@ -32,8 +42,9 @@ export const useUploadAvatar = ( const compressed = base64ToFile( await compressBase64Img({ base64Img: await fileToBase64(file), - maxW: 300, - maxH: 300 + maxW, + maxH, + maxSize }), file.name ); diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 6c87d1c45..1b02b6fdb 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -98,6 +98,10 @@ "Warning": "Warning", "Website": "Website", "action_confirm": "Confirm", + "activity_ad.desc": "You can get up to 1.66 million additional points by recharging, and you can get new points at the old price of the annual package.", + "activity_ad.join_now": "Participate now", + "activity_ad.later": "wait a moment", + "activity_ad.title": "Double Festival Gift · Limited Time Upgrade", "add_new": "add_new", "add_new_param": "Add new param", "add_success": "Added Successfully", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 591463c43..4a1b09e9d 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -99,6 +99,10 @@ "Warning": "警告", "Website": "网站", "action_confirm": "操作确认", + "activity_ad.desc": "充值最高可得额外166万积分,年费套餐旧价享新量", + "activity_ad.join_now": "立即参与", + "activity_ad.later": "稍等一会儿", + "activity_ad.title": "双节献礼 · 限时升级", "add_new": "新增", "add_new_param": "新增参数", "add_success": "添加成功", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 9dfcb9544..13dc3e94e 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -98,6 +98,10 @@ "Warning": "警告", "Website": "網站", "action_confirm": "確認", + "activity_ad.desc": "充值最高可得額外166萬積分,年費套餐舊價享新量", + "activity_ad.join_now": "立即參與", + "activity_ad.later": "稍等一會兒", + "activity_ad.title": "雙節獻禮 · 限時升級", "add_new": "新增", "add_new_param": "新增參數", "add_success": "新增成功", diff --git a/projects/app/public/imgs/system/snowflakeLeft.svg b/projects/app/public/imgs/system/snowflakeLeft.svg new file mode 100644 index 000000000..4129ce536 --- /dev/null +++ b/projects/app/public/imgs/system/snowflakeLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/app/public/imgs/system/snowflakeRight.svg b/projects/app/public/imgs/system/snowflakeRight.svg new file mode 100644 index 000000000..77d22b34b --- /dev/null +++ b/projects/app/public/imgs/system/snowflakeRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 3d98f7fc8..81aa309e4 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -40,6 +40,9 @@ const ManualCopyModal = dynamic( () => import('@fastgpt/web/hooks/useCopyData').then((mod) => mod.ManualCopyModal), { ssr: false } ); +const ActivityAdModal = dynamic(() => import('@/components/support/activity/ActivityAdModal'), { + ssr: false +}); const pcUnShowLayoutRoute: Record = { '/': true, @@ -187,6 +190,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { )} + ); diff --git a/projects/app/src/components/support/activity/ActivityAdModal.tsx b/projects/app/src/components/support/activity/ActivityAdModal.tsx new file mode 100644 index 000000000..7b3ef538a --- /dev/null +++ b/projects/app/src/components/support/activity/ActivityAdModal.tsx @@ -0,0 +1,188 @@ +import React, { useCallback, useMemo } from 'react'; +import { useDisclosure } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import { getActivityAd } from '@/web/common/system/api'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { + Box, + Flex, + Button, + Image, + Modal, + ModalOverlay, + ModalContent, + ModalCloseButton +} from '@chakra-ui/react'; + +// LocalStorage key for tracking closed ads +const CLOSED_AD_KEY = 'activity_ad_closed'; +const CLOSED_AD_DURATION = 24 * 60 * 60 * 1000; // 24 hours + +const ActivityAdModal = () => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const { t } = useTranslation(); + + // Check if ad was recently closed + const shouldShowAd = useMemo(() => { + const closedData = localStorage.getItem(CLOSED_AD_KEY); + if (!closedData) return true; + + try { + const { timestamp } = JSON.parse(closedData); + const now = Date.now(); + // Show if 24 hours passed + return now - timestamp > CLOSED_AD_DURATION; + } catch { + return true; + } + }, []); + + const { data } = useRequest2( + async () => { + return getActivityAd(); + }, + { + manual: false, + onSuccess(res) { + if (res?.activityAdImage && shouldShowAd) { + onOpen(); + } + } + } + ); + + const handleClose = useCallback(() => { + if (data?.id) { + localStorage.setItem(CLOSED_AD_KEY, JSON.stringify({ timestamp: Date.now(), adId: data.id })); + } + onClose(); + }, [data?.id, onClose]); + + const handleJoin = useCallback(() => { + if (data?.activityAdLink) { + window.open(data.activityAdLink, '_blank'); + } + // handleClose(); + }, [data?.activityAdLink, handleClose]); + + if (!data?.activityAdImage) { + return null; + } + + return isOpen ? ( + + + + + + + {/* Activity Image */} + + Activity + {/* Gradient overlay for smooth transition */} + + + + + {t('common:activity_ad.title')} + + + + {t('common:activity_ad.desc')} + + + + + + + + + + + + ) : null; +}; + +export default React.memo(ActivityAdModal); diff --git a/projects/app/src/web/common/system/api.ts b/projects/app/src/web/common/system/api.ts index b8f8a57e8..b5c62bcfd 100644 --- a/projects/app/src/web/common/system/api.ts +++ b/projects/app/src/web/common/system/api.ts @@ -28,3 +28,8 @@ export const getOperationalAd = () => GET<{ id: string; operationalAdImage: string; operationalAdLink: string }>( '/proApi/support/user/inform/getOperationalAd' ); + +export const getActivityAd = () => + GET<{ id: string; activityAdImage: string; activityAdLink: string }>( + '/proApi/support/user/inform/getActivityAd' + );