Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

page.tsx 5.2KB

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