You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

oneMoreStep.tsx 6.1KB

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