oneMoreStep.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use client'
  2. import React, { useEffect, useReducer } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import Link from 'next/link'
  5. import useSWR from 'swr'
  6. import { useRouter, useSearchParams } from 'next/navigation'
  7. import Input from '../components/base/input'
  8. import Button from '@/app/components/base/button'
  9. import Tooltip from '@/app/components/base/tooltip'
  10. import { SimpleSelect } from '@/app/components/base/select'
  11. import { timezones } from '@/utils/timezone'
  12. import { LanguagesSupported, languages } from '@/i18n/language'
  13. import { oneMoreStep } from '@/service/common'
  14. import Toast from '@/app/components/base/toast'
  15. type IState = {
  16. formState: 'processing' | 'error' | 'success' | 'initial'
  17. invitation_code: string
  18. interface_language: string
  19. timezone: string
  20. }
  21. const reducer = (state: IState, action: any) => {
  22. switch (action.type) {
  23. case 'invitation_code':
  24. return { ...state, invitation_code: action.value }
  25. case 'interface_language':
  26. return { ...state, interface_language: action.value }
  27. case 'timezone':
  28. return { ...state, timezone: action.value }
  29. case 'formState':
  30. return { ...state, formState: action.value }
  31. case 'failed':
  32. return {
  33. formState: 'initial',
  34. invitation_code: '',
  35. interface_language: 'en-US',
  36. timezone: 'Asia/Shanghai',
  37. }
  38. default:
  39. throw new Error('Unknown action.')
  40. }
  41. }
  42. const OneMoreStep = () => {
  43. const { t } = useTranslation()
  44. const router = useRouter()
  45. const searchParams = useSearchParams()
  46. const [state, dispatch] = useReducer(reducer, {
  47. formState: 'initial',
  48. invitation_code: searchParams.get('invitation_code') || '',
  49. interface_language: 'en-US',
  50. timezone: 'Asia/Shanghai',
  51. })
  52. const { data, error } = useSWR(state.formState === 'processing'
  53. ? {
  54. url: '/account/init',
  55. body: {
  56. invitation_code: state.invitation_code,
  57. interface_language: state.interface_language,
  58. timezone: state.timezone,
  59. },
  60. }
  61. : null, oneMoreStep)
  62. useEffect(() => {
  63. if (error && error.status === 400) {
  64. Toast.notify({ type: 'error', message: t('login.invalidInvitationCode') })
  65. dispatch({ type: 'failed', payload: null })
  66. }
  67. if (data)
  68. router.push('/apps')
  69. }, [data, error])
  70. return (
  71. <>
  72. <div className="w-full mx-auto">
  73. <h2 className="title-4xl-semi-bold text-text-secondary">{t('login.oneMoreStep')}</h2>
  74. <p className='mt-1 body-md-regular text-text-tertiary'>{t('login.createSample')}</p>
  75. </div>
  76. <div className="w-full mx-auto mt-6">
  77. <div className="bg-white">
  78. <div className="mb-5">
  79. <label className="my-2 flex items-center justify-between system-md-semibold text-text-secondary">
  80. {t('login.invitationCode')}
  81. <Tooltip
  82. popupContent={
  83. <div className='w-[256px] text-xs font-medium'>
  84. <div className='font-medium'>{t('login.sendUsMail')}</div>
  85. <div className='text-xs font-medium cursor-pointer text-text-accent-secondary'>
  86. <a href="mailto:request-invitation@langgenius.ai">request-invitation@langgenius.ai</a>
  87. </div>
  88. </div>
  89. }
  90. needsDelay
  91. >
  92. <span className='cursor-pointer text-text-accent-secondary'>{t('login.dontHave')}</span>
  93. </Tooltip>
  94. </label>
  95. <div className="mt-1">
  96. <Input
  97. id="invitation_code"
  98. value={state.invitation_code}
  99. type="text"
  100. placeholder={t('login.invitationCodePlaceholder') || ''}
  101. onChange={(e) => {
  102. dispatch({ type: 'invitation_code', value: e.target.value.trim() })
  103. }}
  104. />
  105. </div>
  106. </div>
  107. <div className='mb-5'>
  108. <label htmlFor="name" className="my-2 system-md-semibold text-text-secondary">
  109. {t('login.interfaceLanguage')}
  110. </label>
  111. <div className="mt-1">
  112. <SimpleSelect
  113. defaultValue={LanguagesSupported[0]}
  114. items={languages.filter(item => item.supported)}
  115. onSelect={(item) => {
  116. dispatch({ type: 'interface_language', value: item.value })
  117. }}
  118. />
  119. </div>
  120. </div>
  121. <div className='mb-4'>
  122. <label htmlFor="timezone" className="system-md-semibold text-text-tertiary">
  123. {t('login.timezone')}
  124. </label>
  125. <div className="mt-1">
  126. <SimpleSelect
  127. defaultValue={state.timezone}
  128. items={timezones}
  129. onSelect={(item) => {
  130. dispatch({ type: 'timezone', value: item.value })
  131. }}
  132. />
  133. </div>
  134. </div>
  135. <div>
  136. <Button
  137. variant='primary'
  138. className='w-full'
  139. disabled={state.formState === 'processing'}
  140. onClick={() => {
  141. dispatch({ type: 'formState', value: 'processing' })
  142. }}
  143. >
  144. {t('login.go')}
  145. </Button>
  146. </div>
  147. <div className="block w-full mt-2 system-xs-regular text-text-tertiary">
  148. {t('login.license.tip')}
  149. &nbsp;
  150. <Link
  151. className='system-xs-medium text-text-accent-secondary'
  152. target='_blank' rel='noopener noreferrer'
  153. href={'https://docs.dify.ai/user-agreement/open-source'}
  154. >{t('login.license.link')}</Link>
  155. </div>
  156. </div>
  157. </div>
  158. </>
  159. )
  160. }
  161. export default OneMoreStep