page.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use client'
  2. import { useTranslation } from 'react-i18next'
  3. import { useDocLink } from '@/context/i18n'
  4. import { useCallback, useState } from 'react'
  5. import Link from 'next/link'
  6. import { useContext } from 'use-context-selector'
  7. import { useRouter, useSearchParams } from 'next/navigation'
  8. import useSWR from 'swr'
  9. import { RiAccountCircleLine } from '@remixicon/react'
  10. import Input from '@/app/components/base/input'
  11. import { SimpleSelect } from '@/app/components/base/select'
  12. import Button from '@/app/components/base/button'
  13. import { timezones } from '@/utils/timezone'
  14. import { LanguagesSupported, languages } from '@/i18n-config/language'
  15. import I18n from '@/context/i18n'
  16. import { activateMember, invitationCheck } from '@/service/common'
  17. import Loading from '@/app/components/base/loading'
  18. import Toast from '@/app/components/base/toast'
  19. import { noop } from 'lodash-es'
  20. import { useGlobalPublicStore } from '@/context/global-public-context'
  21. import { resolvePostLoginRedirect } from '../utils/post-login-redirect'
  22. export default function InviteSettingsPage() {
  23. const { t } = useTranslation()
  24. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  25. const docLink = useDocLink()
  26. const router = useRouter()
  27. const searchParams = useSearchParams()
  28. const token = decodeURIComponent(searchParams.get('invite_token') as string)
  29. const { setLocaleOnClient } = useContext(I18n)
  30. const [name, setName] = useState('')
  31. const [language, setLanguage] = useState(LanguagesSupported[0])
  32. const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'America/Los_Angeles')
  33. const checkParams = {
  34. url: '/activate/check',
  35. params: {
  36. token,
  37. },
  38. }
  39. const { data: checkRes, mutate: recheck } = useSWR(checkParams, invitationCheck, {
  40. revalidateOnFocus: false,
  41. })
  42. const handleActivate = useCallback(async () => {
  43. try {
  44. if (!name) {
  45. Toast.notify({ type: 'error', message: t('login.enterYourName') })
  46. return
  47. }
  48. const res = await activateMember({
  49. url: '/activate',
  50. body: {
  51. token,
  52. name,
  53. interface_language: language,
  54. timezone,
  55. },
  56. })
  57. if (res.result === 'success') {
  58. localStorage.setItem('console_token', res.data.access_token)
  59. localStorage.setItem('refresh_token', res.data.refresh_token)
  60. await setLocaleOnClient(language, false)
  61. const redirectUrl = resolvePostLoginRedirect(searchParams)
  62. router.replace(redirectUrl || '/apps')
  63. }
  64. }
  65. catch {
  66. recheck()
  67. }
  68. }, [language, name, recheck, setLocaleOnClient, timezone, token, router, t])
  69. if (!checkRes)
  70. return <Loading />
  71. if (!checkRes.is_valid) {
  72. return <div className="flex flex-col md:w-[400px]">
  73. <div className="mx-auto w-full">
  74. <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle text-2xl font-bold shadow-lg">🤷‍♂️</div>
  75. <h2 className="title-4xl-semi-bold text-text-primary">{t('login.invalid')}</h2>
  76. </div>
  77. <div className="mx-auto mt-6 w-full">
  78. <Button variant='primary' className='w-full !text-sm'>
  79. <a href="https://dify.ai">{t('login.explore')}</a>
  80. </Button>
  81. </div>
  82. </div>
  83. }
  84. return <div className='flex flex-col gap-3'>
  85. <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'>
  86. <RiAccountCircleLine className='h-6 w-6 text-2xl text-text-accent-light-mode-only' />
  87. </div>
  88. <div className='pb-4 pt-2'>
  89. <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.setYourAccount')}</h2>
  90. </div>
  91. <form onSubmit={noop}>
  92. <div className='mb-5'>
  93. <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary">
  94. {t('login.name')}
  95. </label>
  96. <div className="mt-1">
  97. <Input
  98. id="name"
  99. type="text"
  100. value={name}
  101. onChange={e => setName(e.target.value)}
  102. placeholder={t('login.namePlaceholder') || ''}
  103. onKeyDown={(e) => {
  104. if (e.key === 'Enter') {
  105. e.preventDefault()
  106. e.stopPropagation()
  107. handleActivate()
  108. }
  109. }}
  110. />
  111. </div>
  112. </div>
  113. <div className='mb-5'>
  114. <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary">
  115. {t('login.interfaceLanguage')}
  116. </label>
  117. <div className="mt-1">
  118. <SimpleSelect
  119. defaultValue={LanguagesSupported[0]}
  120. items={languages.filter(item => item.supported)}
  121. onSelect={(item) => {
  122. setLanguage(item.value as string)
  123. }}
  124. />
  125. </div>
  126. </div>
  127. {/* timezone */}
  128. <div className='mb-5'>
  129. <label htmlFor="timezone" className="system-md-semibold text-text-secondary">
  130. {t('login.timezone')}
  131. </label>
  132. <div className="mt-1">
  133. <SimpleSelect
  134. defaultValue={timezone}
  135. items={timezones}
  136. onSelect={(item) => {
  137. setTimezone(item.value as string)
  138. }}
  139. />
  140. </div>
  141. </div>
  142. <div>
  143. <Button
  144. variant='primary'
  145. className='w-full'
  146. onClick={handleActivate}
  147. >
  148. {`${t('login.join')} ${checkRes?.data?.workspace_name}`}
  149. </Button>
  150. </div>
  151. </form>
  152. {/* zlwu12 20250918 注释 - 注释掉启动 Dify 社区版之前,请阅读 GitHub 上的开源协议相关内容
  153. {!systemFeatures.branding.enabled && <div className="system-xs-regular mt-2 block w-full text-text-tertiary">
  154. {t('login.license.tip')}
  155. &nbsp;
  156. <Link
  157. className='system-xs-medium text-text-accent-secondary'
  158. target='_blank' rel='noopener noreferrer'
  159. href={docLink('/policies/open-source')}
  160. >{t('login.license.link')}</Link>
  161. </div>}
  162. */}
  163. </div>
  164. }