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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. >
  99. <span className='cursor-pointer text-text-accent-secondary'>{t('login.dontHave')}</span>
  100. </Tooltip>
  101. </label>
  102. <div className="mt-1">
  103. <Input
  104. id="invitation_code"
  105. value={state.invitation_code}
  106. type="text"
  107. placeholder={t('login.invitationCodePlaceholder') || ''}
  108. onChange={(e) => {
  109. dispatch({ type: 'invitation_code', value: e.target.value.trim() })
  110. }}
  111. />
  112. </div>
  113. </div>
  114. <div className='mb-5'>
  115. <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary">
  116. {t('login.interfaceLanguage')}
  117. </label>
  118. <div className="mt-1">
  119. <SimpleSelect
  120. defaultValue={LanguagesSupported[0]}
  121. items={languages.filter(item => item.supported)}
  122. onSelect={(item) => {
  123. dispatch({ type: 'interface_language', value: item.value as typeof LanguagesSupported[number] })
  124. }}
  125. />
  126. </div>
  127. </div>
  128. <div className='mb-4'>
  129. <label htmlFor="timezone" className="system-md-semibold text-text-tertiary">
  130. {t('login.timezone')}
  131. </label>
  132. <div className="mt-1">
  133. <SimpleSelect
  134. defaultValue={state.timezone}
  135. items={timezones}
  136. onSelect={(item) => {
  137. dispatch({ type: 'timezone', value: item.value as typeof state.timezone })
  138. }}
  139. />
  140. </div>
  141. </div>
  142. <div>
  143. <Button
  144. variant='primary'
  145. className='w-full'
  146. disabled={state.formState === 'processing'}
  147. onClick={() => {
  148. dispatch({ type: 'formState', value: 'processing' })
  149. }}
  150. >
  151. {t('login.go')}
  152. </Button>
  153. </div>
  154. <div className="system-xs-regular mt-2 block w-full text-text-tertiary">
  155. {t('login.license.tip')}
  156. &nbsp;
  157. <Link
  158. className='system-xs-medium text-text-accent-secondary'
  159. target='_blank' rel='noopener noreferrer'
  160. href={docLink('/policies/agreement/README')}
  161. >{t('login.license.link')}</Link>
  162. </div>
  163. </div>
  164. </div>
  165. </>
  166. )
  167. }
  168. export default OneMoreStep