From 358b70821a79653cf52056c271f26aa48a3f039b Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 17 Mar 2025 11:43:16 +0800 Subject: [PATCH] feat: add education apply --- web/app/(commonLayout)/apps/Apps.tsx | 9 ++++----- web/app/(commonLayout)/apps/page.tsx | 13 +++++++++++++ web/app/components/billing/plan/index.tsx | 10 +++++++--- .../education-apply/components/constants.ts | 2 ++ .../components/education-apply-page.tsx | 19 +++++++++++++------ web/context/modal-context.tsx | 9 ++++++--- web/i18n/en-US/education.ts | 4 ++-- web/i18n/ja-JP/education.ts | 6 ++++++ web/i18n/zh-Hans/education.ts | 8 ++++---- 9 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 web/app/education-apply/components/constants.ts diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 96cbaece61..1d1d1766dc 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useRouter, - useSearchParams, } from 'next/navigation' import useSWRInfinite from 'swr/infinite' import { useTranslation } from 'react-i18next' @@ -30,6 +29,7 @@ import TagManagementModal from '@/app/components/base/tag-management' import TagFilter from '@/app/components/base/tag-management/filter' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { useModalContextSelector } from '@/context/modal-context' +import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/components/constants' const getKey = ( pageIndex: number, @@ -136,14 +136,13 @@ const Apps = () => { setQuery(prev => ({ ...prev, isCreatedByMe: newValue })) }, [isCreatedByMe, setQuery]) - const searchParams = useSearchParams() - const action = searchParams.get('action') const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal) + const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) useEffect(() => { - if (action === 'getEducationVerify') + if (educationVerifying === 'yes') setShowAccountSettingModal({ payload: 'billing' }) - }, [action, setShowAccountSettingModal]) + }, [setShowAccountSettingModal, educationVerifying]) return ( <> diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index 972aabc8bc..11167bee6c 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -1,15 +1,28 @@ 'use client' +import { useEffect } from 'react' import { useContextSelector } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiDiscordFill, RiGithubFill } from '@remixicon/react' import Link from 'next/link' +import { useSearchParams } from 'next/navigation' import style from '../list.module.css' import Apps from './Apps' import AppContext from '@/context/app-context' import { LicenseStatus } from '@/types/feature' +import { + EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, + EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION, +} from '@/app/education-apply/components/constants' const AppList = () => { const { t } = useTranslation() + const searchParams = useSearchParams() + const educationVerifyAction = searchParams.get('action') + + useEffect(() => { + if (educationVerifyAction === EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION) + localStorage.setItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, 'yes') + }, [educationVerifyAction]) const systemFeatures = useContextSelector(AppContext, v => v.systemFeatures) return ( diff --git a/web/app/components/billing/plan/index.tsx b/web/app/components/billing/plan/index.tsx index 78569b0044..bdea0066bc 100644 --- a/web/app/components/billing/plan/index.tsx +++ b/web/app/components/billing/plan/index.tsx @@ -21,6 +21,7 @@ import { useAppContext } from '@/context/app-context' import Button from '@/app/components/base/button' import UsageInfo from '@/app/components/billing/usage-info' import VerifyStateModal from '@/app/education-apply/components/verify-state-modal' +import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/components/constants' type Props = { loc: string @@ -44,10 +45,13 @@ const PlanComp: FC = ({ const [showModal, setShowModal] = React.useState(false) const handleVerify = () => { - if (userProfile.email.endsWith('.edu')) + if (userProfile.email.endsWith('.edu')) { + localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) router.push('/education-apply') - else + } + else { setShowModal(true) + } } return (
@@ -119,7 +123,7 @@ const PlanComp: FC = ({ email={userProfile.email} isShow={showModal} title={t('education.rejectTitle')} - content={t('education.rejectContent2')} + content={t('education.rejectContent')} onConfirm={() => setShowModal(false)} onCancel={() => setShowModal(false)} /> diff --git a/web/app/education-apply/components/constants.ts b/web/app/education-apply/components/constants.ts new file mode 100644 index 0000000000..8a2c364861 --- /dev/null +++ b/web/app/education-apply/components/constants.ts @@ -0,0 +1,2 @@ +export const EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION = 'getEducationVerifyA' +export const EDUCATION_VERIFYING_LOCALSTORAGE_ITEM = 'educationVerifying' diff --git a/web/app/education-apply/components/education-apply-page.tsx b/web/app/education-apply/components/education-apply-page.tsx index cce0401f18..7c85e51edc 100644 --- a/web/app/education-apply/components/education-apply-page.tsx +++ b/web/app/education-apply/components/education-apply-page.tsx @@ -4,6 +4,7 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' import UserInfo from './user-info' import SearchInput from './search-input' import RoleSelector from './role-selector' @@ -16,6 +17,8 @@ import { useInvalidateEducationStatus, } from '@/service/use-education' import { useProviderContext } from '@/context/provider-context' +import { useToastContext } from '@/app/components/base/toast' +import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/components/constants' const EducationApplyAge = () => { const { t } = useTranslation() @@ -31,11 +34,15 @@ const EducationApplyAge = () => { const { data } = useEducationVerify() const { onPlanInfoChanged } = useProviderContext() const updateEducationStatus = useInvalidateEducationStatus() + const { notify } = useToastContext() + const router = useRouter() const handleModalConfirm = () => { setShowModal(undefined) onPlanInfoChanged() updateEducationStatus() + localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) + router.replace('/') } const handleSubmit = () => { @@ -52,9 +59,9 @@ const EducationApplyAge = () => { }) } else { - setShowModal({ - title: t('education.rejectTitle'), - desc: t('education.rejectContent'), + notify({ + type: 'error', + message: t('education.submitError'), }) } }) @@ -113,9 +120,9 @@ const EducationApplyAge = () => {
{t('education.form.terms.desc.front')}  - {t('education.form.terms.desc.termsOfService')}  + {t('education.form.terms.desc.termsOfService')}  {t('education.form.terms.desc.and')}  - {t('education.form.terms.desc.privacyPolicy')}  + {t('education.form.terms.desc.privacyPolicy')}  {t('education.form.terms.desc.end')}
@@ -151,7 +158,7 @@ const EducationApplyAge = () => { title={modalShow?.title || ''} content={modalShow?.desc} onConfirm={modalShow?.onConfirm || (() => {})} - onCancel={() => setShowModal(undefined)} + onCancel={modalShow?.onConfirm || (() => {})} />
) diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 9683a88a99..76d8ed7752 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -17,6 +17,9 @@ import type { ModelLoadBalancingConfigEntry, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { + EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, +} from '@/app/education-apply/components/constants' import Pricing from '@/app/components/billing/pricing' import type { ModerationConfig, PromptVariable } from '@/models/debug' @@ -121,10 +124,10 @@ export const ModalContextProvider = ({ const [showPricingModal, setShowPricingModal] = useState(searchParams.get('show-pricing') === '1') const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false) const handleCancelAccountSettingModal = () => { - const action = searchParams.get('action') + const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) - if (action === 'getEducationVerify') - router.push(location.pathname, { forceOptimisticNavigation: true } as any) + if (educationVerifying === 'yes') + localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) setShowAccountSettingModal(null) if (showAccountSettingModal?.onCancelCallback) showAccountSettingModal?.onCancelCallback() diff --git a/web/i18n/en-US/education.ts b/web/i18n/en-US/education.ts index 2ef7c2b50a..5b0d1d1745 100644 --- a/web/i18n/en-US/education.ts +++ b/web/i18n/en-US/education.ts @@ -35,12 +35,12 @@ const translation = { }, }, submit: 'Submit', + submitError: 'Form submission failed. Please try again later.', learn: 'Learn how to get education verified', successTitle: 'You Have Got Dify Education Verified', successContent: 'We have issued a 50% discount coupon for the Dify Professional plan to your account. The coupon is valid for one year, please use it within the validity period.', rejectTitle: 'Your Dify Education Verified Has Been Rejected', - rejectContent: 'We’re sorry, but You are now eligible for Education Verified status because you disagreed with our Terms & Agreements. Please review the terms of these clauses and reapply if eligible.', - rejectContent2: 'Unfortunately you are not eligible for a Education Verified status and receive an exclusive 50% coupon for the Dify Professional Plan if you use this email address.', + rejectContent: 'Unfortunately, you are not eligible for Education Verified status and therefore cannot receive the exclusive 50% coupon for the Dify Professional Plan if you use this email address.', emailLabel: 'Your current email', } diff --git a/web/i18n/ja-JP/education.ts b/web/i18n/ja-JP/education.ts index 2e5ad73860..d51bac817d 100644 --- a/web/i18n/ja-JP/education.ts +++ b/web/i18n/ja-JP/education.ts @@ -35,7 +35,13 @@ const translation = { }, }, submit: '送信', + submitError: 'フォームの送信に失敗しました。しばらくしてから再度ご提出ください。', learn: '教育認証の取得方法はこちら', + successTitle: 'Dify教育認証を取得しました!', + successContent: 'お客様のアカウントに Difyプロフェッショナルプランの50%割引クーポン を発行しました。有効期間は 1年間 ですので、期限内にご利用ください。', + rejectTitle: 'Dify教育認証が拒否されました', + rejectContent: '申し訳ございませんが、このメールアドレスでは 教育認証 の資格を取得できず、Difyプロフェッショナルプランの50%割引クーポン を受け取ることはできません。', + emailLabel: '現在のメールアドレス', } export default translation diff --git a/web/i18n/zh-Hans/education.ts b/web/i18n/zh-Hans/education.ts index 7087d365e5..ca4d2cb3cc 100644 --- a/web/i18n/zh-Hans/education.ts +++ b/web/i18n/zh-Hans/education.ts @@ -35,12 +35,12 @@ const translation = { }, }, submit: '提交', + submitError: '提交表单失败,请稍后重新提交问卷。', learn: '了解如何获取教育版认证', - successTitle: '您已获得 Dify 教育版认证', - successContent: '我们已向您的账户发放了 Dify Professional 计划的 50% 折扣券。该券有效期为一年,请在有效期内使用。', + successTitle: '您已成功获得 Dify 教育版认证!', + successContent: '我们已向您的账户发放 Dify Professional 版 50% 折扣优惠券。该优惠券有效期为一年,请在有效期内使用。', rejectTitle: '您的 Dify 教育版认证已被拒绝', - rejectContent: '很抱歉,由于您不同意我们的条款与协议,您现在不符合教育版认证的资格。请查看这些条款的内容,如果符合资格,请重新申请。', - rejectContent2: '很抱歉,由于您的邮箱地址不符合教育版认证的资格,也无法获得 Dify Professional 计划的 50% 独家优惠券。', + rejectContent: '非常遗憾,您无法使用此电子邮件以获得教育版认证资格,也无法领取 Dify Professional 版的 50% 独家优惠券。', emailLabel: '您当前的邮箱', }