oneMoreStep.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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 Button from '@/app/components/base/button'
  8. import Tooltip from '@/app/components/base/tooltip/index'
  9. import { SimpleSelect } from '@/app/components/base/select'
  10. import { timezones } from '@/utils/timezone'
  11. import { languageMaps, languages } from '@/utils/language'
  12. import { oneMoreStep } from '@/service/common'
  13. import Toast from '@/app/components/base/toast'
  14. type IState = {
  15. formState: 'processing' | 'error' | 'success' | 'initial'
  16. invitation_code: string
  17. interface_language: string
  18. timezone: string
  19. }
  20. const reducer = (state: IState, action: any) => {
  21. switch (action.type) {
  22. case 'invitation_code':
  23. return { ...state, invitation_code: action.value }
  24. case 'interface_language':
  25. return { ...state, interface_language: action.value }
  26. case 'timezone':
  27. return { ...state, timezone: action.value }
  28. case 'formState':
  29. return { ...state, formState: action.value }
  30. case 'failed':
  31. return {
  32. formState: 'initial',
  33. invitation_code: '',
  34. interface_language: 'en-US',
  35. timezone: 'Asia/Shanghai',
  36. }
  37. default:
  38. throw new Error('Unknown action.')
  39. }
  40. }
  41. const OneMoreStep = () => {
  42. const { t } = useTranslation()
  43. const router = useRouter()
  44. const [state, dispatch] = useReducer(reducer, {
  45. formState: 'initial',
  46. invitation_code: '',
  47. interface_language: 'en-US',
  48. timezone: 'Asia/Shanghai',
  49. })
  50. const { data, error } = useSWR(state.formState === 'processing'
  51. ? {
  52. url: '/account/init',
  53. body: {
  54. invitation_code: state.invitation_code,
  55. interface_language: state.interface_language,
  56. timezone: state.timezone,
  57. },
  58. }
  59. : null, oneMoreStep)
  60. useEffect(() => {
  61. if (error && error.status === 400) {
  62. Toast.notify({ type: 'error', message: t('login.invalidInvitationCode') })
  63. dispatch({ type: 'failed', payload: null })
  64. }
  65. if (data)
  66. router.push('/apps')
  67. }, [data, error])
  68. return (
  69. <>
  70. <div className="w-full mx-auto">
  71. <h2 className="text-[32px] font-bold text-gray-900">{t('login.oneMoreStep')}</h2>
  72. <p className='mt-1 text-sm text-gray-600 '>{t('login.createSample')}</p>
  73. </div>
  74. <div className="w-full mx-auto mt-6">
  75. <div className="bg-white">
  76. <div className="mb-5">
  77. <label className="my-2 flex items-center justify-between text-sm font-medium text-gray-900">
  78. {t('login.invitationCode')}
  79. <Tooltip
  80. clickable
  81. selector='dont-have'
  82. htmlContent={
  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-primary-600'>
  86. <a href="mailto:request-invitation@langgenius.ai">request-invitation@langgenius.ai</a>
  87. </div>
  88. </div>
  89. }
  90. >
  91. <span className='cursor-pointer text-primary-600'>{t('login.donthave')}</span>
  92. </Tooltip>
  93. </label>
  94. <div className="mt-1">
  95. <input
  96. id="invitation_code"
  97. value={state.invitation_code}
  98. type="text"
  99. placeholder={t('login.invitationCodePlaceholder') || ''}
  100. 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'}
  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 flex items-center justify-between text-sm font-medium text-gray-900">
  109. {t('login.interfaceLanguage')}
  110. </label>
  111. <div className="relative mt-1 rounded-md shadow-sm">
  112. <SimpleSelect
  113. defaultValue={languageMaps.en}
  114. items={languages}
  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="block text-sm font-medium text-gray-700">
  123. {t('login.timezone')}
  124. </label>
  125. <div className="relative mt-1 rounded-md shadow-sm">
  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. type='primary'
  138. className='w-full !fone-medium !text-sm'
  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-hull mt-2 text-xs text-gray-600">
  148. {t('login.license.tip')}
  149. &nbsp;
  150. <Link
  151. className='text-primary-600'
  152. target={'_blank'}
  153. href='https://docs.dify.ai/community/open-source'
  154. >{t('login.license.link')}</Link>
  155. </div>
  156. </div>
  157. </div>
  158. </>
  159. )
  160. }
  161. export default OneMoreStep