diff --git a/web/app/account/account-page/index.tsx b/web/app/account/account-page/index.tsx index 30b781c606..f94087b0f0 100644 --- a/web/app/account/account-page/index.tsx +++ b/web/app/account/account-page/index.tsx @@ -14,6 +14,7 @@ import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import { updateUserProfile } from '@/service/common' import { useAppContext } from '@/context/app-context' +import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' import AppIcon from '@/app/components/base/app-icon' import { IS_CE_EDITION } from '@/config' @@ -33,6 +34,7 @@ export default function AccountPage() { const { t } = useTranslation() const { systemFeatures } = useAppContext() const { mutateUserProfile, userProfile, apps } = useAppContext() + const { isEducationAccount } = useProviderContext() const { notify } = useContext(ToastContext) const [editNameModalVisible, setEditNameModalVisible] = useState(false) const [editName, setEditName] = useState('') @@ -140,10 +142,12 @@ export default function AccountPage() {

{userProfile.name} - - - EDU - + {isEducationAccount && ( + + + EDU + + )}

{userProfile.email}

diff --git a/web/app/account/avatar.tsx b/web/app/account/avatar.tsx index c3568a66a5..bf2cb71b7f 100644 --- a/web/app/account/avatar.tsx +++ b/web/app/account/avatar.tsx @@ -9,6 +9,7 @@ import { Menu, Transition } from '@headlessui/react' import Avatar from '@/app/components/base/avatar' import { logout } from '@/service/common' import { useAppContext } from '@/context/app-context' +import { useProviderContext } from '@/context/provider-context' import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general' import PremiumBadge from '@/app/components/base/premium-badge' @@ -20,6 +21,7 @@ export default function AppSelector() { const router = useRouter() const { t } = useTranslation() const { userProfile } = useAppContext() + const { isEducationAccount } = useProviderContext() const handleLogout = async () => { await logout({ @@ -74,10 +76,12 @@ export default function AppSelector() {
{userProfile.name} - - - EDU - + {isEducationAccount && ( + + + EDU + + )}
{userProfile.email}
diff --git a/web/app/components/billing/plan/index.tsx b/web/app/components/billing/plan/index.tsx index a5454f3b11..78569b0044 100644 --- a/web/app/components/billing/plan/index.tsx +++ b/web/app/components/billing/plan/index.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' import { RiBook2Line, RiBox3Line, @@ -29,8 +30,9 @@ const PlanComp: FC = ({ loc, }) => { const { t } = useTranslation() + const router = useRouter() const { userProfile } = useAppContext() - const { plan } = useProviderContext() + const { plan, enableEducationPlan, isEducationAccount } = useProviderContext() const { type, } = plan @@ -41,6 +43,12 @@ const PlanComp: FC = ({ } = plan const [showModal, setShowModal] = React.useState(false) + const handleVerify = () => { + if (userProfile.email.endsWith('.edu')) + router.push('/education-apply') + else + setShowModal(true) + } return (
@@ -65,12 +73,12 @@ const PlanComp: FC = ({
{t(`billing.plans.${type}.for`)}
- {/* {(plan.type === Plan.sandbox || plan.type === Plan.professional) && ( */} - - {/* )} */} + {enableEducationPlan && !isEducationAccount && ( + + )} {(plan.type as any) !== SelfHostedPlan.enterprise && ( { @@ -90,10 +92,12 @@ export default function AppSelector() {
{userProfile.name} - - - EDU - + {isEducationAccount && ( + + + EDU + + )}
{userProfile.email}
diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index c2568f9219..e7d1f3c7cb 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -68,7 +68,7 @@ const Header = () => { - {enableBilling ? : } + {enableBilling ? : }
} @@ -79,7 +79,7 @@ const Header = () => {
/
- {enableBilling ? : } + {enableBilling ? : } )} { diff --git a/web/app/components/header/plan-badge/index.tsx b/web/app/components/header/plan-badge/index.tsx index d6dad3012d..b8dd094cfd 100644 --- a/web/app/components/header/plan-badge/index.tsx +++ b/web/app/components/header/plan-badge/index.tsx @@ -1,9 +1,9 @@ import { useProviderContext } from '@/context/provider-context' import type { FC } from 'react' import { useTranslation } from 'react-i18next' -// import { -// RiGraduationCapFill, -// } from '@remixicon/react' +import { + RiGraduationCapFill, +} from '@remixicon/react' import { SparklesSoft } from '../../base/icons/src/public/common' import PremiumBadge from '../../base/premium-badge' import { Plan } from '../../billing/type' @@ -16,7 +16,7 @@ type PlanBadgeProps = { } const PlanBadge: FC = ({ plan, allowHover, sandboxAsUpgrade = false, onClick }) => { - const { isFetchedPlan } = useProviderContext() + const { isFetchedPlan, isEducationWorkspace } = useProviderContext() const { t } = useTranslation() if (!isFetchedPlan) return null @@ -42,7 +42,8 @@ const PlanBadge: FC = ({ plan, allowHover, sandboxAsUpgrade = fa if (plan === Plan.professional) { return
- + + {isEducationWorkspace && } pro
diff --git a/web/app/education-apply/components/education-apply-page.tsx b/web/app/education-apply/components/education-apply-page.tsx index f8c041bc82..cce0401f18 100644 --- a/web/app/education-apply/components/education-apply-page.tsx +++ b/web/app/education-apply/components/education-apply-page.tsx @@ -13,7 +13,9 @@ import Checkbox from '@/app/components/base/checkbox' import { useEducationAdd, useEducationVerify, + useInvalidateEducationStatus, } from '@/service/use-education' +import { useProviderContext } from '@/context/provider-context' const EducationApplyAge = () => { const { t } = useTranslation() @@ -25,8 +27,16 @@ const EducationApplyAge = () => { isPending, mutateAsync: educationAdd, } = useEducationAdd({ onSuccess: () => {} }) - const [modalShow, setShowModal] = useState(undefined) + const [modalShow, setShowModal] = useState void }>(undefined) const { data } = useEducationVerify() + const { onPlanInfoChanged } = useProviderContext() + const updateEducationStatus = useInvalidateEducationStatus() + + const handleModalConfirm = () => { + setShowModal(undefined) + onPlanInfoChanged() + updateEducationStatus() + } const handleSubmit = () => { educationAdd({ @@ -38,6 +48,7 @@ const EducationApplyAge = () => { setShowModal({ title: t('education.successTitle'), desc: t('education.successContent'), + onConfirm: handleModalConfirm, }) } else { @@ -139,7 +150,7 @@ const EducationApplyAge = () => { isShow={!!modalShow} title={modalShow?.title || ''} content={modalShow?.desc} - onConfirm={() => setShowModal(undefined)} + onConfirm={modalShow?.onConfirm || (() => {})} onCancel={() => setShowModal(undefined)} /> diff --git a/web/app/education-apply/page.tsx b/web/app/education-apply/page.tsx index 2b89f5874e..7f55fd3f31 100644 --- a/web/app/education-apply/page.tsx +++ b/web/app/education-apply/page.tsx @@ -6,16 +6,14 @@ import { } from 'react' import { useRouter } from 'next/navigation' import EducationApplyAge from './components/education-apply-page' -import { IS_CE_EDITION } from '@/config' -import { useAppContext } from '@/context/app-context' -import { LicenseStatus } from '@/types/feature' +import { useProviderContext } from '@/context/provider-context' export default function EducationApply() { const router = useRouter() - const { systemFeatures } = useAppContext() + const { enableEducationPlan, isEducationAccount } = useProviderContext() const hiddenEducationApply = useMemo(() => { - return IS_CE_EDITION || (systemFeatures.license.status !== LicenseStatus.NONE) - }, [systemFeatures.license.status]) + return enableEducationPlan && isEducationAccount + }, [enableEducationPlan, isEducationAccount]) useEffect(() => { if (hiddenEducationApply) diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 11340f6acd..67c389167d 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -22,6 +22,9 @@ import { fetchCurrentPlanInfo } from '@/service/billing' import { parseCurrentPlan } from '@/app/components/billing/utils' import { defaultPlan } from '@/app/components/billing/config' import Toast from '@/app/components/base/toast' +import { + useEducationStatus, +} from '@/service/use-education' type ProviderContextState = { modelProviders: ModelProvider[] @@ -40,6 +43,9 @@ type ProviderContextState = { enableReplaceWebAppLogo: boolean modelLoadBalancingEnabled: boolean datasetOperatorEnabled: boolean + enableEducationPlan: boolean + isEducationWorkspace: boolean + isEducationAccount: boolean } const ProviderContext = createContext({ modelProviders: [], @@ -70,6 +76,9 @@ const ProviderContext = createContext({ enableReplaceWebAppLogo: false, modelLoadBalancingEnabled: false, datasetOperatorEnabled: false, + enableEducationPlan: false, + isEducationWorkspace: false, + isEducationAccount: false, }) export const useProviderContext = () => useContext(ProviderContext) @@ -97,13 +106,19 @@ export const ProviderContextProvider = ({ const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false) const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false) + const [enableEducationPlan, setEnableEducationPlan] = useState(false) + const [isEducationWorkspace, setIsEducationWorkspace] = useState(false) + const { data: isEducationAccount } = useEducationStatus(!enableEducationPlan) + const fetchPlan = async () => { const data = await fetchCurrentPlanInfo() const enabled = data.billing.enabled setEnableBilling(enabled) + setEnableEducationPlan(data.education.enabled) + setIsEducationWorkspace(data.education.activated) setEnableReplaceWebAppLogo(data.can_replace_logo) if (enabled) { - setPlan(parseCurrentPlan(data)) + setPlan(parseCurrentPlan(data) as any) setIsFetchedPlan(true) } if (data.model_load_balancing_enabled) @@ -155,6 +170,9 @@ export const ProviderContextProvider = ({ enableReplaceWebAppLogo, modelLoadBalancingEnabled, datasetOperatorEnabled, + enableEducationPlan, + isEducationWorkspace, + isEducationAccount: isEducationAccount?.result || false, }}> {children} diff --git a/web/service/use-education.ts b/web/service/use-education.ts index 225ee38207..314813d7b9 100644 --- a/web/service/use-education.ts +++ b/web/service/use-education.ts @@ -3,6 +3,7 @@ import { useMutation, useQuery, } from '@tanstack/react-query' +import { useInvalid } from './use-base' import type { EducationAddParams } from '@/app/education-apply/components/types' const NAME_SPACE = 'education' @@ -50,3 +51,18 @@ export const useEducationAutocomplete = () => { }, }) } + +export const useEducationStatus = (disable?: boolean) => { + return useQuery({ + enabled: !disable, + queryKey: [NAME_SPACE, 'education-status'], + queryFn: () => { + return get<{ result: boolean }>('/account/education') + }, + retry: false, + }) +} + +export const useInvalidateEducationStatus = () => { + return useInvalid([NAME_SPACE, 'education-status']) +}