ShareLinkForm.jsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import React from 'react';
  2. import { isInteger } from 'core-js/fn/number';
  3. import { format, parse } from 'date-fns';
  4. import PropTypes from 'prop-types';
  5. import { useTranslation } from 'react-i18next';
  6. import PageContainer from '~/client/services/PageContainer';
  7. import { toastSuccess, toastError } from '~/client/util/apiNotification';
  8. import { apiv3Post } from '~/client/util/apiv3-client';
  9. import { withUnstatedContainers } from '../UnstatedUtils';
  10. class ShareLinkForm extends React.Component {
  11. constructor(props) {
  12. super(props);
  13. this.state = {
  14. expirationType: 'unlimited',
  15. numberOfDays: '7',
  16. description: '',
  17. customExpirationDate: format(new Date(), 'yyyy-MM-dd'),
  18. customExpirationTime: format(new Date(), 'HH:mm'),
  19. };
  20. this.handleChangeExpirationType = this.handleChangeExpirationType.bind(this);
  21. this.handleChangeNumberOfDays = this.handleChangeNumberOfDays.bind(this);
  22. this.handleChangeDescription = this.handleChangeDescription.bind(this);
  23. this.handleIssueShareLink = this.handleIssueShareLink.bind(this);
  24. }
  25. /**
  26. * change expirationType
  27. * @param {string} expirationType
  28. */
  29. handleChangeExpirationType(expirationType) {
  30. this.setState({ expirationType });
  31. }
  32. /**
  33. * change numberOfDays
  34. * @param {string} numberOfDays
  35. */
  36. handleChangeNumberOfDays(numberOfDays) {
  37. this.setState({ numberOfDays });
  38. }
  39. /**
  40. * change description
  41. * @param {string} description
  42. */
  43. handleChangeDescription(description) {
  44. this.setState({ description });
  45. }
  46. /**
  47. * change customExpirationDate
  48. * @param {date} customExpirationDate
  49. */
  50. handleChangeCustomExpirationDate(customExpirationDate) {
  51. this.setState({ customExpirationDate });
  52. }
  53. /**
  54. * change customExpirationTime
  55. * @param {date} customExpirationTime
  56. */
  57. handleChangeCustomExpirationTime(customExpirationTime) {
  58. this.setState({ customExpirationTime });
  59. }
  60. /**
  61. * Generate expiredAt by expirationType
  62. */
  63. generateExpired() {
  64. const { t } = this.props;
  65. const { expirationType } = this.state;
  66. let expiredAt;
  67. if (expirationType === 'unlimited') {
  68. return null;
  69. }
  70. if (expirationType === 'numberOfDays') {
  71. if (!isInteger(Number(this.state.numberOfDays))) {
  72. throw new Error(t('share_links.Invalid_Number_of_Date'));
  73. }
  74. const date = new Date();
  75. date.setDate(date.getDate() + Number(this.state.numberOfDays));
  76. expiredAt = date;
  77. }
  78. if (expirationType === 'custom') {
  79. const { customExpirationDate, customExpirationTime } = this.state;
  80. expiredAt = parse(`${customExpirationDate}T${customExpirationTime}`, "yyyy-MM-dd'T'HH:mm", new Date());
  81. }
  82. return expiredAt;
  83. }
  84. closeForm() {
  85. const { onCloseForm } = this.props;
  86. if (onCloseForm == null) {
  87. return;
  88. }
  89. onCloseForm();
  90. }
  91. async handleIssueShareLink() {
  92. const {
  93. t, pageContainer,
  94. } = this.props;
  95. const { pageId } = pageContainer.state;
  96. const { description } = this.state;
  97. let expiredAt;
  98. try {
  99. expiredAt = this.generateExpired();
  100. }
  101. catch (err) {
  102. return toastError(err);
  103. }
  104. try {
  105. await apiv3Post('/share-links/', { relatedPage: pageId, expiredAt, description });
  106. this.closeForm();
  107. toastSuccess(t('toaster.issue_share_link'));
  108. }
  109. catch (err) {
  110. toastError(err);
  111. }
  112. }
  113. renderExpirationTypeOptions() {
  114. const { expirationType } = this.state;
  115. const { t } = this.props;
  116. return (
  117. <div className="form-group row">
  118. <label htmlFor="inputDesc" className="col-md-5 col-form-label">{t('share_links.expire')}</label>
  119. <div className="col-md-7">
  120. <div className="custom-control custom-radio form-group ">
  121. <input
  122. type="radio"
  123. className="custom-control-input"
  124. id="customRadio1"
  125. name="expirationType"
  126. value="customRadio1"
  127. checked={expirationType === 'unlimited'}
  128. onChange={() => { this.handleChangeExpirationType('unlimited') }}
  129. />
  130. <label className="custom-control-label" htmlFor="customRadio1">{t('share_links.Unlimited')}</label>
  131. </div>
  132. <div className="custom-control custom-radio form-group">
  133. <input
  134. type="radio"
  135. className="custom-control-input"
  136. id="customRadio2"
  137. value="customRadio2"
  138. checked={expirationType === 'numberOfDays'}
  139. onChange={() => { this.handleChangeExpirationType('numberOfDays') }}
  140. name="expirationType"
  141. />
  142. <label className="custom-control-label" htmlFor="customRadio2">
  143. <div className="row align-items-center m-0">
  144. <input
  145. type="number"
  146. min="1"
  147. className="col-4"
  148. name="expirationType"
  149. value={this.state.numberOfDays}
  150. onFocus={() => { this.handleChangeExpirationType('numberOfDays') }}
  151. onChange={e => this.handleChangeNumberOfDays(Number(e.target.value))}
  152. />
  153. <span className="col-auto">{t('share_links.Days')}</span>
  154. </div>
  155. </label>
  156. </div>
  157. <div className="custom-control custom-radio form-group text-nowrap mb-0">
  158. <input
  159. type="radio"
  160. className="custom-control-input"
  161. id="customRadio3"
  162. name="expirationType"
  163. value="customRadio3"
  164. checked={expirationType === 'custom'}
  165. onChange={() => { this.handleChangeExpirationType('custom') }}
  166. />
  167. <label className="custom-control-label" htmlFor="customRadio3">
  168. {t('share_links.Custom')}
  169. </label>
  170. <div className="d-inline-flex flex-wrap">
  171. <input
  172. type="date"
  173. className="ml-3 mb-2"
  174. name="customExpirationDate"
  175. value={this.state.customExpirationDate}
  176. onFocus={() => { this.handleChangeExpirationType('custom') }}
  177. onChange={e => this.handleChangeCustomExpirationDate(e.target.value)}
  178. />
  179. <input
  180. type="time"
  181. className="ml-3 mb-2"
  182. name="customExpiration"
  183. value={this.state.customExpirationTime}
  184. onFocus={() => { this.handleChangeExpirationType('custom') }}
  185. onChange={e => this.handleChangeCustomExpirationTime(e.target.value)}
  186. />
  187. </div>
  188. </div>
  189. </div>
  190. </div>
  191. );
  192. }
  193. renderDescriptionForm() {
  194. const { t } = this.props;
  195. return (
  196. <div className="form-group row">
  197. <label htmlFor="inputDesc" className="col-md-5 col-form-label">{t('share_links.description')}</label>
  198. <div className="col-md-4">
  199. <input
  200. type="text"
  201. className="form-control"
  202. id="inputDesc"
  203. placeholder={t('share_links.enter_desc')}
  204. value={this.state.description}
  205. onChange={e => this.handleChangeDescription(e.target.value)}
  206. />
  207. </div>
  208. </div>
  209. );
  210. }
  211. render() {
  212. const { t } = this.props;
  213. return (
  214. <div className="share-link-form p-3">
  215. <h3 className="grw-modal-head pb-2"> { t('share_links.share_settings') }</h3>
  216. <div className=" p-3">
  217. {this.renderExpirationTypeOptions()}
  218. {this.renderDescriptionForm()}
  219. <button type="button" className="btn btn-primary d-block mx-auto px-5" onClick={this.handleIssueShareLink}>
  220. {t('share_links.Issue')}
  221. </button>
  222. </div>
  223. </div>
  224. );
  225. }
  226. }
  227. ShareLinkForm.propTypes = {
  228. t: PropTypes.func.isRequired, // i18next
  229. pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
  230. onCloseForm: PropTypes.func,
  231. };
  232. const ShareLinkFormWrapperFC = (props) => {
  233. const { t } = useTranslation();
  234. return <ShareLinkForm t={t} {...props} />;
  235. };
  236. /**
  237. * Wrapper component for using unstated
  238. */
  239. const ShareLinkFormWrapper = withUnstatedContainers(ShareLinkFormWrapperFC, [PageContainer]);
  240. export default ShareLinkFormWrapper;