InstallerForm.tsx 7.6 KB

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