InstallerForm.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import {
  2. FormEventHandler, memo, useCallback, useState,
  3. } from 'react';
  4. import { Lang, AllLang } from '@growi/core';
  5. import { useTranslation } from 'next-i18next';
  6. import { useRouter } from 'next/router';
  7. import { i18n as i18nConfig } from '^/config/next-i18next.config';
  8. import { apiv3Post } from '~/client/util/apiv3-client';
  9. import { toastError } from '~/client/util/toastr';
  10. import { LoadingSpinnerPulse } from './LoadingSpinnerPulse';
  11. const InstallerForm = memo((): JSX.Element => {
  12. const { t, i18n } = useTranslation();
  13. const router = useRouter();
  14. const isSupportedLang = AllLang.includes(i18n.language as Lang);
  15. const [isValidUserName, setValidUserName] = useState(true);
  16. const [isLoading, setIsLoading] = useState(false);
  17. const [currentLocale, setCurrentLocale] = useState(isSupportedLang ? i18n.language : Lang.en_US);
  18. const checkUserName = useCallback(async(event) => {
  19. const axios = require('axios').create({
  20. headers: {
  21. 'Content-Type': 'application/json',
  22. 'X-Requested-With': 'XMLHttpRequest',
  23. },
  24. responseType: 'json',
  25. });
  26. const res = await axios.get('/_api/v3/check-username', { params: { username: event.target.value } });
  27. setValidUserName(res.data.valid);
  28. }, []);
  29. const onClickLanguageItem = useCallback((locale) => {
  30. i18n.changeLanguage(locale);
  31. setCurrentLocale(locale);
  32. }, [i18n]);
  33. const submitHandler: FormEventHandler = useCallback(async(e: any) => {
  34. e.preventDefault();
  35. setIsLoading(true);
  36. const formData = e.target.elements;
  37. const {
  38. 'registerForm[username]': { value: username },
  39. 'registerForm[name]': { value: name },
  40. 'registerForm[email]': { value: email },
  41. 'registerForm[password]': { value: password },
  42. } = formData;
  43. const data = {
  44. registerForm: {
  45. username,
  46. name,
  47. email,
  48. password,
  49. 'app:globalLang': currentLocale,
  50. },
  51. };
  52. try {
  53. await apiv3Post('/installer', data);
  54. router.push('/');
  55. }
  56. catch (errs) {
  57. const err = errs[0];
  58. const code = err.code;
  59. setIsLoading(false);
  60. if (code === 'failed_to_login_after_install') {
  61. toastError(t('installer.failed_to_login_after_install'));
  62. setTimeout(() => { router.push('/login') }, 700); // Wait 700 ms to show toastr
  63. }
  64. toastError(t('installer.failed_to_install'));
  65. }
  66. }, [currentLocale, router, t]);
  67. const hasErrorClass = isValidUserName ? '' : ' has-error';
  68. const unavailableUserId = isValidUserName
  69. ? ''
  70. : <span><span className="material-symbols-outlined">block</span>{ t('installer.unavaliable_user_id') }</span>;
  71. return (
  72. <div data-testid="installerForm" className={`nologin-dialog p-3 mx-auto${hasErrorClass}`}>
  73. <div className="row">
  74. <div className="col-md-12">
  75. <p className="alert alert-success">
  76. <strong>{ t('installer.create_initial_account') }</strong><br />
  77. <small>{ t('installer.initial_account_will_be_administrator_automatically') }</small>
  78. </p>
  79. </div>
  80. </div>
  81. <div className="row">
  82. <form role="form" id="register-form" className="col-md-12" onSubmit={submitHandler}>
  83. <div className="dropdown mb-3">
  84. <div className="input-group dropdown-with-icon">
  85. <span className="input-group-text"></span><span className="material-symbols-outlined">language</span>
  86. <button
  87. type="button"
  88. className="btn btn-secondary dropdown-toggle form-control text-end rounded-end"
  89. id="dropdownLanguage"
  90. data-testid="dropdownLanguage"
  91. data-bs-toggle="dropdown"
  92. aria-haspopup="true"
  93. aria-expanded="true"
  94. >
  95. <span className="float-start">
  96. {t('meta.display_name')}
  97. </span>
  98. </button>
  99. <input
  100. type="hidden"
  101. name="registerForm[app:globalLang]"
  102. />
  103. <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
  104. {
  105. i18nConfig.locales.map((locale) => {
  106. let fixedT;
  107. if (i18n != null) {
  108. fixedT = i18n.getFixedT(locale);
  109. i18n.loadLanguages(i18nConfig.locales);
  110. }
  111. return (
  112. <button
  113. key={locale}
  114. data-testid={`dropdownLanguageMenu-${locale}`}
  115. className="dropdown-item"
  116. type="button"
  117. onClick={() => { onClickLanguageItem(locale) }}
  118. >
  119. {fixedT?.('meta.display_name')}
  120. </button>
  121. );
  122. })
  123. }
  124. </div>
  125. </div>
  126. </div>
  127. <div className={`input-group mb-3${hasErrorClass}`}>
  128. <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
  129. <input
  130. data-testid="tiUsername"
  131. type="text"
  132. className="form-control"
  133. placeholder={t('User ID')}
  134. name="registerForm[username]"
  135. // onBlur={checkUserName} // need not to check username before installation -- 2020.07.24 Yuki Takei
  136. required
  137. />
  138. </div>
  139. <p className="form-text">{ unavailableUserId }</p>
  140. <div className="input-group mb-3">
  141. <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
  142. <input
  143. data-testid="tiName"
  144. type="text"
  145. className="form-control"
  146. placeholder={t('Name')}
  147. name="registerForm[name]"
  148. required
  149. />
  150. </div>
  151. <div className="input-group mb-3">
  152. <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
  153. <input
  154. data-testid="tiEmail"
  155. type="email"
  156. className="form-control"
  157. placeholder={t('Email')}
  158. name="registerForm[email]"
  159. required
  160. />
  161. </div>
  162. <div className="input-group mb-3">
  163. <span className="input-group-text"></span> <span className="material-symbols-outlined">lock</span>
  164. <input
  165. data-testid="tiPassword"
  166. type="password"
  167. className="form-control"
  168. placeholder={t('Password')}
  169. name="registerForm[password]"
  170. required
  171. />
  172. </div>
  173. <div className="input-group mt-4 d-flex justify-content-center">
  174. <button
  175. data-testid="btnSubmit"
  176. type="submit"
  177. className="btn-fill btn btn-register"
  178. id="register"
  179. disabled={isLoading}
  180. >
  181. <div className="eff"></div>
  182. <span className="btn-label">
  183. {isLoading ? (
  184. <LoadingSpinnerPulse />
  185. ) : (
  186. <i className="icon-user-follow" />
  187. )}
  188. </span>
  189. <span className="btn-label-text">{ t('Create') }</span>
  190. </button>
  191. </div>
  192. <div>
  193. <a href="https://growi.org" className="link-growi-org">
  194. <span className="growi">GROWI</span>.<span className="org">org</span>
  195. </a>
  196. </div>
  197. </form>
  198. </div>
  199. </div>
  200. );
  201. });
  202. InstallerForm.displayName = 'InstallerForm';
  203. export default InstallerForm;