CompleteUserRegistrationForm.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import type React from 'react';
  2. import { useCallback, useEffect, useState } from 'react';
  3. import { useRouter } from 'next/router';
  4. import { useTranslation } from 'next-i18next';
  5. import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
  6. import { UserActivationErrorCode } from '~/interfaces/errors/user-activation';
  7. import { RegistrationMode } from '~/interfaces/registration-mode';
  8. import { toastError } from '../util/toastr';
  9. import { CompleteUserRegistration } from './CompleteUserRegistration';
  10. import styles from './CompleteUserRegistrationForm.module.scss';
  11. const moduleClass = styles['complete-user-registration-form'] ?? '';
  12. interface Props {
  13. email: string;
  14. token: string;
  15. errorCode?: UserActivationErrorCode;
  16. registrationMode: RegistrationMode;
  17. isEmailAuthenticationEnabled: boolean;
  18. }
  19. export const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
  20. const { t } = useTranslation();
  21. const {
  22. email,
  23. token,
  24. errorCode,
  25. registrationMode,
  26. isEmailAuthenticationEnabled,
  27. } = props;
  28. const forceDisableForm = errorCode != null || !isEmailAuthenticationEnabled;
  29. const [usernameAvailable, setUsernameAvailable] = useState(true);
  30. const [username, setUsername] = useState('');
  31. const [name, setName] = useState('');
  32. const [password, setPassword] = useState('');
  33. const [disableForm, setDisableForm] = useState(forceDisableForm);
  34. const [isSuccessToRagistration, setIsSuccessToRagistration] = useState(false);
  35. const router = useRouter();
  36. useEffect(() => {
  37. const delayDebounceFn = setTimeout(async () => {
  38. try {
  39. const { data } = await apiv3Get('/check-username', { username });
  40. if (data.ok) {
  41. setUsernameAvailable(data.valid);
  42. }
  43. } catch (error) {
  44. toastError(error);
  45. }
  46. }, 500);
  47. return () => clearTimeout(delayDebounceFn);
  48. }, [username]);
  49. const handleSubmitRegistration = useCallback(
  50. async (e) => {
  51. e.preventDefault();
  52. setDisableForm(true);
  53. try {
  54. const res = await apiv3Post('/complete-registration', {
  55. username,
  56. name,
  57. password,
  58. token,
  59. });
  60. setIsSuccessToRagistration(true);
  61. const { redirectTo } = res.data;
  62. if (redirectTo != null) {
  63. router.push(redirectTo);
  64. }
  65. } catch (err) {
  66. toastError(err);
  67. setDisableForm(false);
  68. setIsSuccessToRagistration(false);
  69. }
  70. },
  71. [username, name, password, token, router],
  72. );
  73. if (
  74. isSuccessToRagistration &&
  75. registrationMode === RegistrationMode.RESTRICTED
  76. ) {
  77. return <CompleteUserRegistration />;
  78. }
  79. return (
  80. <>
  81. <div
  82. className={`${moduleClass} nologin-dialog mx-auto rounded-4 rounded-top-0`}
  83. id="nologin-dialog"
  84. >
  85. <div className="row mx-0">
  86. <div className="col-12 px-4">
  87. {errorCode != null &&
  88. errorCode === UserActivationErrorCode.TOKEN_NOT_FOUND && (
  89. <p className="alert alert-danger">
  90. <span>Token not found</span>
  91. </p>
  92. )}
  93. {errorCode != null &&
  94. errorCode ===
  95. UserActivationErrorCode.USER_REGISTRATION_ORDER_IS_NOT_APPROPRIATE && (
  96. <p className="alert alert-danger">
  97. <span>{t('message.incorrect_token_or_expired_url')}</span>
  98. </p>
  99. )}
  100. {!isEmailAuthenticationEnabled && (
  101. <p className="alert alert-danger">
  102. <span>{t('message.email_authentication_is_not_enabled')}</span>
  103. </p>
  104. )}
  105. <form onSubmit={handleSubmitRegistration} id="registration-form">
  106. <input type="hidden" name="token" value={token} />
  107. <div className="input-group">
  108. <span className="p-2 text-white opacity-75">
  109. <span className="material-symbols-outlined">mail</span>
  110. </span>
  111. <input
  112. type="text"
  113. className="form-control rounded"
  114. placeholder={t('Email')}
  115. disabled
  116. value={email}
  117. />
  118. </div>
  119. <div className="input-group" id="input-group-username">
  120. <span className="p-2 text-white opacity-75">
  121. <span className="material-symbols-outlined">person</span>
  122. </span>
  123. <input
  124. type="text"
  125. className="form-control rounded"
  126. placeholder={t('User ID')}
  127. name="username"
  128. onChange={(e) => setUsername(e.target.value)}
  129. required
  130. disabled={forceDisableForm || disableForm}
  131. />
  132. </div>
  133. {!usernameAvailable && (
  134. <p className="form-text text-red">
  135. <span id="help-block-username">
  136. <span className="p-2 text-white opacity-75">
  137. <span className="material-symbols-outlined">block</span>
  138. </span>
  139. {t('installer.unavaliable_user_id')}
  140. </span>
  141. </p>
  142. )}
  143. <div className="input-group">
  144. <span className="p-2 text-white opacity-75">
  145. <span className="material-symbols-outlined">sell</span>
  146. </span>
  147. <input
  148. type="text"
  149. className="form-control rounded"
  150. placeholder={t('Name')}
  151. name="name"
  152. value={name}
  153. onChange={(e) => setName(e.target.value)}
  154. required
  155. disabled={forceDisableForm || disableForm}
  156. />
  157. </div>
  158. <div className="input-group">
  159. <span className="p-2 text-white opacity-75">
  160. <span className="material-symbols-outlined">lock</span>
  161. </span>
  162. <input
  163. type="password"
  164. className="form-control rounded"
  165. placeholder={t('Password')}
  166. name="password"
  167. value={password}
  168. onChange={(e) => setPassword(e.target.value)}
  169. required
  170. disabled={forceDisableForm || disableForm}
  171. />
  172. </div>
  173. <div className="input-group justify-content-center mt-4">
  174. <button
  175. type="submit"
  176. disabled={forceDisableForm || disableForm}
  177. className="btn btn-secondary btn-register col-6 mx-auto d-flex"
  178. >
  179. <span>
  180. <span className="material-symbols-outlined">
  181. person_add
  182. </span>
  183. </span>
  184. <span className="flex-grow-1">{t('Create')}</span>
  185. </button>
  186. </div>
  187. <div className="input-group mt-5 d-flex">
  188. <a href="https://growi.org" className="link-growi-org">
  189. <span className="growi">GROWI</span>
  190. <span className="org">.org</span>
  191. </a>
  192. </div>
  193. </form>
  194. </div>
  195. </div>
  196. </div>
  197. </>
  198. );
  199. };