oneMoreStep.tsx 6.0 KB

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