education-apply-page.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. 'use client'
  2. import {
  3. useMemo,
  4. useState,
  5. } from 'react'
  6. import { useTranslation } from 'react-i18next'
  7. import { RiExternalLinkLine } from '@remixicon/react'
  8. import {
  9. useRouter,
  10. useSearchParams,
  11. } from 'next/navigation'
  12. import UserInfo from './user-info'
  13. import SearchInput from './search-input'
  14. import RoleSelector from './role-selector'
  15. import Confirm from './verify-state-modal'
  16. import Button from '@/app/components/base/button'
  17. import Checkbox from '@/app/components/base/checkbox'
  18. import {
  19. useEducationAdd,
  20. useInvalidateEducationStatus,
  21. } from '@/service/use-education'
  22. import { useProviderContext } from '@/context/provider-context'
  23. import { useToastContext } from '@/app/components/base/toast'
  24. import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants'
  25. import { getLocaleOnClient } from '@/i18n'
  26. const EducationApplyAge = () => {
  27. const { t } = useTranslation()
  28. const locale = getLocaleOnClient()
  29. const [schoolName, setSchoolName] = useState('')
  30. const [role, setRole] = useState('Student')
  31. const [ageChecked, setAgeChecked] = useState(false)
  32. const [inSchoolChecked, setInSchoolChecked] = useState(false)
  33. const {
  34. isPending,
  35. mutateAsync: educationAdd,
  36. } = useEducationAdd({ onSuccess: () => {} })
  37. const [modalShow, setShowModal] = useState<undefined | { title: string; desc: string; onConfirm?: () => void }>(undefined)
  38. const { onPlanInfoChanged } = useProviderContext()
  39. const updateEducationStatus = useInvalidateEducationStatus()
  40. const { notify } = useToastContext()
  41. const router = useRouter()
  42. const docLink = useMemo(() => {
  43. if (locale === 'zh-Hans')
  44. return 'https://docs.dify.ai/zh-hans/getting-started/dify-for-education'
  45. if (locale === 'ja-JP')
  46. return 'https://docs.dify.ai/ja-jp/getting-started/dify-for-education'
  47. return 'https://docs.dify.ai/getting-started/dify-for-education'
  48. }, [locale])
  49. const handleModalConfirm = () => {
  50. setShowModal(undefined)
  51. onPlanInfoChanged()
  52. updateEducationStatus()
  53. localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM)
  54. router.replace('/')
  55. }
  56. const searchParams = useSearchParams()
  57. const token = searchParams.get('token')
  58. const handleSubmit = () => {
  59. educationAdd({
  60. token: token || '',
  61. role,
  62. institution: schoolName,
  63. }).then((res) => {
  64. if (res.message === 'success') {
  65. setShowModal({
  66. title: t('education.successTitle'),
  67. desc: t('education.successContent'),
  68. onConfirm: handleModalConfirm,
  69. })
  70. }
  71. else {
  72. notify({
  73. type: 'error',
  74. message: t('education.submitError'),
  75. })
  76. }
  77. })
  78. }
  79. return (
  80. <div className='fixed inset-0 z-[31] overflow-y-auto bg-background-body p-6'>
  81. <div className='mx-auto w-full max-w-[1408px] rounded-2xl border border-effects-highlight bg-background-default-subtle'>
  82. <div
  83. className="h-[349px] w-full overflow-hidden rounded-t-2xl bg-cover bg-center bg-no-repeat"
  84. style={{
  85. backgroundImage: 'url(/education/bg.png)',
  86. }}
  87. >
  88. </div>
  89. <div className='mt-[-349px] flex h-[88px] items-center justify-between px-8 py-6'>
  90. <img
  91. src='/logo/logo-site-dark.png'
  92. alt='dify logo'
  93. className='h-10'
  94. />
  95. </div>
  96. <div className='mx-auto max-w-[720px] px-8 pb-[180px]'>
  97. <div className='mb-2 flex h-[192px] flex-col justify-end pb-4 pt-3 text-text-primary-on-surface'>
  98. <div className='title-5xl-bold mb-2 shadow-xs'>{t('education.toVerified')}</div>
  99. <div className='system-md-medium shadow-xs'>
  100. {t('education.toVerifiedTip.front')}&nbsp;
  101. <span className='system-md-semibold underline'>{t('education.toVerifiedTip.coupon')}</span>&nbsp;
  102. {t('education.toVerifiedTip.end')}
  103. </div>
  104. </div>
  105. <div className='mb-7'>
  106. <UserInfo />
  107. </div>
  108. <div className='mb-7'>
  109. <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'>
  110. {t('education.form.schoolName.title')}
  111. </div>
  112. <SearchInput
  113. value={schoolName}
  114. onChange={setSchoolName}
  115. />
  116. </div>
  117. <div className='mb-7'>
  118. <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'>
  119. {t('education.form.schoolRole.title')}
  120. </div>
  121. <RoleSelector
  122. value={role}
  123. onChange={setRole}
  124. />
  125. </div>
  126. <div className='mb-7'>
  127. <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'>
  128. {t('education.form.terms.title')}
  129. </div>
  130. <div className='system-md-regular mb-1 text-text-tertiary'>
  131. {t('education.form.terms.desc.front')}&nbsp;
  132. <a href='https://dify.ai/terms' target='_blank' className='text-text-secondary hover:underline'>{t('education.form.terms.desc.termsOfService')}</a>&nbsp;
  133. {t('education.form.terms.desc.and')}&nbsp;
  134. <a href='https://dify.ai/privacy' target='_blank' className='text-text-secondary hover:underline'>{t('education.form.terms.desc.privacyPolicy')}</a>
  135. {t('education.form.terms.desc.end')}
  136. </div>
  137. <div className='system-md-regular py-2 text-text-primary'>
  138. <div className='mb-2 flex'>
  139. <Checkbox
  140. className='mr-2 shrink-0'
  141. checked={ageChecked}
  142. onCheck={() => setAgeChecked(!ageChecked)}
  143. />
  144. {t('education.form.terms.option.age')}
  145. </div>
  146. <div className='flex'>
  147. <Checkbox
  148. className='mr-2 shrink-0'
  149. checked={inSchoolChecked}
  150. onCheck={() => setInSchoolChecked(!inSchoolChecked)}
  151. />
  152. {t('education.form.terms.option.inSchool')}
  153. </div>
  154. </div>
  155. </div>
  156. <Button
  157. variant='primary'
  158. disabled={!ageChecked || !inSchoolChecked || !schoolName || !role || isPending}
  159. onClick={handleSubmit}
  160. >
  161. {t('education.submit')}
  162. </Button>
  163. <div className='mb-4 mt-5 h-[1px] bg-gradient-to-r from-[rgba(16,24,40,0.08)]'></div>
  164. <a
  165. className='system-xs-regular flex items-center text-text-accent'
  166. href={docLink}
  167. target='_blank'
  168. >
  169. {t('education.learn')}
  170. <RiExternalLinkLine className='ml-1 h-3 w-3' />
  171. </a>
  172. </div>
  173. </div>
  174. <Confirm
  175. isShow={!!modalShow}
  176. title={modalShow?.title || ''}
  177. content={modalShow?.desc}
  178. onConfirm={modalShow?.onConfirm || (() => {})}
  179. onCancel={modalShow?.onConfirm || (() => {})}
  180. />
  181. </div>
  182. )
  183. }
  184. export default EducationApplyAge