ShareLinkForm.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import React, { FC, useState, useCallback } from 'react';
  2. import { isInteger } from 'core-js/fn/number';
  3. import {
  4. format, parse, addDays, set,
  5. } from 'date-fns';
  6. import { useTranslation } from 'next-i18next';
  7. import { apiv3Post } from '~/client/util/apiv3-client';
  8. import { toastSuccess, toastError } from '~/client/util/toastr';
  9. import { useCurrentPageId } from '~/stores/page';
  10. const ExpirationType = {
  11. UNLIMITED: 'unlimited',
  12. CUSTOM: 'custom',
  13. NUMBER_OF_DAYS: 'numberOfDays',
  14. } as const;
  15. type ExpirationType = typeof ExpirationType[keyof typeof ExpirationType];
  16. type Props = {
  17. onCloseForm: () => void,
  18. }
  19. export const ShareLinkForm: FC<Props> = (props: Props) => {
  20. const { t } = useTranslation();
  21. const { onCloseForm } = props;
  22. const [expirationType, setExpirationType] = useState<ExpirationType>(ExpirationType.UNLIMITED);
  23. const [numberOfDays, setNumberOfDays] = useState<number>(7);
  24. const [description, setDescription] = useState<string>('');
  25. const [customExpirationDate, setCustomExpirationDate] = useState<Date>(new Date());
  26. const [customExpirationTime, setCustomExpirationTime] = useState<Date>(new Date());
  27. const { data: currentPageId } = useCurrentPageId();
  28. const handleChangeExpirationType = useCallback((expirationType: ExpirationType) => {
  29. setExpirationType(expirationType);
  30. }, []);
  31. const handleChangeNumberOfDays = useCallback((numberOfDays: number) => {
  32. setNumberOfDays(numberOfDays);
  33. }, []);
  34. const handleChangeDescription = useCallback((description: string) => {
  35. setDescription(description);
  36. }, []);
  37. const handleChangeCustomExpirationDate = useCallback((customExpirationDate: string) => {
  38. const parsedDate = parse(customExpirationDate, 'yyyy-MM-dd', new Date());
  39. setCustomExpirationDate(parsedDate);
  40. }, []);
  41. const handleChangeCustomExpirationTime = useCallback((customExpirationTime: string) => {
  42. const parsedTime = parse(customExpirationTime, 'HH:mm', new Date());
  43. setCustomExpirationTime(parsedTime);
  44. }, []);
  45. const generateExpired = useCallback(() => {
  46. if (expirationType === ExpirationType.UNLIMITED) {
  47. return null;
  48. }
  49. if (expirationType === ExpirationType.NUMBER_OF_DAYS) {
  50. if (!isInteger(Number(numberOfDays))) {
  51. throw new Error(t('share_links.Invalid_Number_of_Date'));
  52. }
  53. return addDays(new Date(), numberOfDays);
  54. }
  55. if (expirationType === ExpirationType.CUSTOM) {
  56. return set(customExpirationDate, { hours: customExpirationTime.getHours(), minutes: customExpirationTime.getMinutes() });
  57. }
  58. }, [t, customExpirationTime, customExpirationDate, expirationType, numberOfDays]);
  59. const closeForm = useCallback(() => {
  60. if (onCloseForm == null) {
  61. return;
  62. }
  63. onCloseForm();
  64. }, [onCloseForm]);
  65. const handleIssueShareLink = useCallback(async() => {
  66. let expiredAt;
  67. try {
  68. expiredAt = generateExpired();
  69. }
  70. catch (err) {
  71. return toastError(err);
  72. }
  73. try {
  74. await apiv3Post('/share-links/', { relatedPage: currentPageId, expiredAt, description });
  75. closeForm();
  76. toastSuccess(t('toaster.issue_share_link'));
  77. }
  78. catch (err) {
  79. toastError(err);
  80. }
  81. }, [t, currentPageId, description, closeForm, generateExpired]);
  82. return (
  83. <div className="share-link-form p-3">
  84. <h3 className="grw-modal-head pb-2"> { t('share_links.share_settings') }</h3>
  85. <div className=" p-3">
  86. {/* ExpirationTypeOptions */}
  87. <div className="form-group row">
  88. <label htmlFor="inputDesc" className="col-md-5 col-form-label">{t('share_links.expire')}</label>
  89. <div className="col-md-7">
  90. <div className="custom-control custom-radio form-group ">
  91. <input
  92. type="radio"
  93. className="custom-control-input"
  94. id="customRadio1"
  95. name="expirationType"
  96. value="customRadio1"
  97. checked={expirationType === ExpirationType.UNLIMITED}
  98. onChange={() => { handleChangeExpirationType(ExpirationType.UNLIMITED) }}
  99. />
  100. <label className="custom-control-label" htmlFor="customRadio1">{t('share_links.Unlimited')}</label>
  101. </div>
  102. <div className="custom-control custom-radio form-group">
  103. <input
  104. type="radio"
  105. className="custom-control-input"
  106. id="customRadio2"
  107. value="customRadio2"
  108. checked={expirationType === ExpirationType.NUMBER_OF_DAYS}
  109. onChange={() => { handleChangeExpirationType(ExpirationType.NUMBER_OF_DAYS) }}
  110. name="expirationType"
  111. />
  112. <label className="custom-control-label" htmlFor="customRadio2">
  113. <div className="row align-items-center m-0">
  114. <input
  115. type="number"
  116. min="1"
  117. className="col-4"
  118. name="expirationType"
  119. value={numberOfDays}
  120. onFocus={() => { handleChangeExpirationType(ExpirationType.NUMBER_OF_DAYS) }}
  121. onChange={e => handleChangeNumberOfDays(Number(e.target.value))}
  122. />
  123. <span className="col-auto">{t('share_links.Days')}</span>
  124. </div>
  125. </label>
  126. </div>
  127. <div className="custom-control custom-radio form-group text-nowrap mb-0">
  128. <input
  129. type="radio"
  130. className="custom-control-input"
  131. id="customRadio3"
  132. name="expirationType"
  133. value="customRadio3"
  134. checked={expirationType === ExpirationType.CUSTOM}
  135. onChange={() => { handleChangeExpirationType(ExpirationType.CUSTOM) }}
  136. />
  137. <label className="custom-control-label" htmlFor="customRadio3">
  138. {t('share_links.Custom')}
  139. </label>
  140. <div className="d-inline-flex flex-wrap">
  141. <input
  142. type="date"
  143. className="ml-3 mb-2"
  144. name="customExpirationDate"
  145. value={format(customExpirationDate, 'yyyy-MM-dd')}
  146. onFocus={() => { handleChangeExpirationType(ExpirationType.CUSTOM) }}
  147. onChange={e => handleChangeCustomExpirationDate(e.target.value)}
  148. />
  149. <input
  150. type="time"
  151. className="ml-3 mb-2"
  152. name="customExpiration"
  153. value={format(customExpirationTime, 'HH:mm')}
  154. onFocus={() => { handleChangeExpirationType(ExpirationType.CUSTOM) }}
  155. onChange={e => handleChangeCustomExpirationTime(e.target.value)}
  156. />
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. {/* DescriptionForm */}
  162. <div className="form-group row">
  163. <label htmlFor="inputDesc" className="col-md-5 col-form-label">{t('share_links.description')}</label>
  164. <div className="col-md-4">
  165. <input
  166. type="text"
  167. className="form-control"
  168. id="inputDesc"
  169. placeholder={t('share_links.enter_desc')}
  170. value={description}
  171. onChange={e => handleChangeDescription(e.target.value)}
  172. />
  173. </div>
  174. </div>
  175. <button type="button" className="btn btn-primary d-block mx-auto px-5" onClick={handleIssueShareLink} data-testid="btn-sharelink-issue">
  176. {t('share_links.Issue')}
  177. </button>
  178. </div>
  179. </div>
  180. );
  181. };