InstallerForm.tsx 7.8 KB

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