ShareLinkForm.tsx 7.6 KB

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