page.tsx 5.2 KB

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