EditReply.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import React from 'react';
  2. import { Button, Message } from 'semantic-ui-react';
  3. import styled from 'styled-components';
  4. import { Form, Field, withFormik, FormikProps } from 'formik';
  5. import * as Yup from 'yup';
  6. import { TxButton, Section } from '@polkadot/joy-utils/react/components';
  7. import { SubmittableResult } from '@polkadot/api';
  8. import { withMulti } from '@polkadot/react-api/hoc';
  9. import * as JoyForms from '@polkadot/joy-utils/react/components/forms';
  10. import { PostId, ThreadId } from '@joystream/types/common';
  11. import { Post } from '@joystream/types/forum';
  12. import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards';
  13. import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
  14. import { withForumCalls } from './calls';
  15. import { ValidationProps, withReplyValidation } from './validation';
  16. import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
  17. const buildSchema = (props: ValidationProps) => {
  18. const {
  19. postTextConstraint
  20. } = props;
  21. if (!postTextConstraint) {
  22. throw new Error('Missing some validation constraints');
  23. }
  24. const minText = postTextConstraint.min.toNumber();
  25. const maxText = postTextConstraint.max.toNumber();
  26. return Yup.object().shape({
  27. text: Yup.string()
  28. .min(minText, `Reply text is too short. Minimum length is ${minText} chars.`)
  29. .max(maxText, `Reply text is too long. Maximum length is ${maxText} chars.`)
  30. .required('Text is required')
  31. });
  32. };
  33. const FormActionsContainer = styled.div`
  34. display: flex;
  35. justify-content: space-between;
  36. `;
  37. type OuterProps = ValidationProps & {
  38. id?: PostId;
  39. struct?: Post;
  40. threadId: ThreadId;
  41. quotedPost?: Post | null;
  42. onEditSuccess?: () => void;
  43. onEditCancel?: () => void;
  44. };
  45. type FormValues = {
  46. text: string;
  47. };
  48. type FormProps = OuterProps & FormikProps<FormValues>;
  49. const LabelledField = JoyForms.LabelledField<FormValues>();
  50. const InnerForm = (props: FormProps) => {
  51. const {
  52. id,
  53. threadId,
  54. struct,
  55. values,
  56. dirty,
  57. isValid,
  58. isSubmitting,
  59. setSubmitting,
  60. resetForm,
  61. onEditSuccess,
  62. onEditCancel
  63. } = props;
  64. const {
  65. text
  66. } = values;
  67. const isNew = struct === undefined;
  68. const onSubmit = (sendTx: () => void) => {
  69. if (isValid) sendTx();
  70. };
  71. const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
  72. setSubmitting(false);
  73. if (txResult == null) {
  74. // Tx cancelled.
  75. }
  76. };
  77. const onTxSuccess: TxCallback = (_txResult: SubmittableResult) => {
  78. setSubmitting(false);
  79. resetForm();
  80. if (!isNew && onEditSuccess) {
  81. onEditSuccess();
  82. }
  83. };
  84. const buildTxParams = () => {
  85. if (!isValid) return [];
  86. if (!id) {
  87. return [threadId, text];
  88. } else {
  89. return [id, text];
  90. }
  91. };
  92. const form =
  93. <Form className='ui form JoyForm EditEntityForm'>
  94. <LabelledField name='text' {...props}>
  95. <Field component='textarea' id='text' name='text' disabled={isSubmitting} rows={5} placeholder='Type here. You can use Markdown.' />
  96. </LabelledField>
  97. <LabelledField {...props}>
  98. <FormActionsContainer>
  99. <div style={{ display: 'flex' }}>
  100. <TxButton
  101. type='submit'
  102. label={isNew
  103. ? 'Post a reply'
  104. : 'Update a reply'
  105. }
  106. isDisabled={!dirty || isSubmitting}
  107. params={buildTxParams()}
  108. tx={isNew
  109. ? 'forum.addPost'
  110. : 'forum.editPostText'
  111. }
  112. onClick={onSubmit}
  113. txFailedCb={onTxFailed}
  114. txSuccessCb={onTxSuccess}
  115. />
  116. <Button
  117. type='button'
  118. size='large'
  119. disabled={!dirty || isSubmitting}
  120. onClick={() => resetForm()}
  121. content='Reset form'
  122. />
  123. </div>
  124. {
  125. !isNew && (
  126. <Button
  127. type='button'
  128. size='large'
  129. disabled={isSubmitting}
  130. content='Cancel edit'
  131. onClick={() => onEditCancel && onEditCancel()}
  132. />
  133. )
  134. }
  135. </FormActionsContainer>
  136. </LabelledField>
  137. </Form>;
  138. const sectionTitle = isNew
  139. ? 'New reply'
  140. : `Edit my reply #${struct?.nr_in_thread.toString() || ''}`;
  141. return (
  142. <Section className='EditEntityBox' title={sectionTitle}>
  143. {form}
  144. </Section>
  145. );
  146. };
  147. const getQuotedPostString = (post: Post) => {
  148. const lines = post.current_text.split('\n');
  149. return lines.reduce((acc, line) => {
  150. return `${acc}> ${line}\n`;
  151. }, '');
  152. };
  153. const EditForm = withFormik<OuterProps, FormValues>({
  154. // Transform outer props into form values
  155. mapPropsToValues: (props) => {
  156. const { struct, quotedPost } = props;
  157. return {
  158. text: struct
  159. ? struct.current_text
  160. : quotedPost
  161. ? getQuotedPostString(quotedPost)
  162. : ''
  163. };
  164. },
  165. validationSchema: buildSchema,
  166. handleSubmit: (values) => {
  167. // do submitting things
  168. }
  169. })(InnerForm);
  170. function FormOrLoading (props: OuterProps) {
  171. const { state: { address } } = useMyAccount();
  172. const { struct } = props;
  173. if (!address || !struct) {
  174. return <em>Loading reply...</em>;
  175. }
  176. if (struct.isEmpty) {
  177. return <em>Reply not found</em>;
  178. }
  179. const isMyStruct = address === struct.author_id.toString();
  180. if (isMyStruct) {
  181. return <EditForm {...props} threadId={struct.thread_id} />;
  182. }
  183. return <Message error className='JoyMainStatus' header='You are not allowed edit this reply.' />;
  184. }
  185. export const NewReply = withMulti(
  186. EditForm,
  187. withOnlyMembers,
  188. withReplyValidation
  189. );
  190. export const EditReply = withMulti(
  191. FormOrLoading,
  192. withOnlyMembers,
  193. withReplyValidation,
  194. withForumCalls<OuterProps>(
  195. ['postById', { paramName: 'id', propName: 'struct' }]
  196. )
  197. );