EditCategory.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import React from 'react';
  2. import { Button, Message } from 'semantic-ui-react';
  3. import { Form, Field, withFormik, FormikProps } from 'formik';
  4. import * as Yup from 'yup';
  5. import { History } from 'history';
  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 { CategoryId, Category } from '@joystream/types/forum';
  11. import { useMyAccount } from '@polkadot/joy-utils/react/hooks';
  12. import { UrlHasIdProps, CategoryCrumbs } from './utils';
  13. import { withOnlyForumSudo } from './ForumSudo';
  14. import { withForumCalls } from './calls';
  15. import { ValidationProps, withCategoryValidation } from './validation';
  16. import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
  17. import { createMock } from '@joystream/types';
  18. import { useApi } from '@polkadot/react-hooks';
  19. const buildSchema = (props: ValidationProps) => {
  20. const {
  21. categoryTitleConstraint,
  22. categoryDescriptionConstraint
  23. } = props;
  24. if (!categoryTitleConstraint || !categoryDescriptionConstraint) {
  25. throw new Error('Missing some validation constraints');
  26. }
  27. const minTitle = categoryTitleConstraint.min.toNumber();
  28. const maxTitle = categoryTitleConstraint.max.toNumber();
  29. const minDescr = categoryDescriptionConstraint.min.toNumber();
  30. const maxDescr = categoryDescriptionConstraint.max.toNumber();
  31. return Yup.object().shape({
  32. title: Yup.string()
  33. .min(minTitle, `Category title is too short. Minimum length is ${minTitle} chars.`)
  34. .max(maxTitle, `Category title is too long. Maximum length is ${maxTitle} chars.`)
  35. .required('Category title is required'),
  36. description: Yup.string()
  37. .min(minDescr, `Category description is too short. Minimum length is ${minDescr} chars.`)
  38. .max(maxDescr, `Category description is too long. Maximum length is ${maxDescr} chars.`)
  39. .required('Category description is required')
  40. });
  41. };
  42. type OuterProps = ValidationProps & {
  43. history?: History;
  44. id?: CategoryId;
  45. parentId?: CategoryId;
  46. struct?: Category;
  47. };
  48. type FormValues = {
  49. title: string;
  50. description: string;
  51. };
  52. type FormProps = OuterProps & FormikProps<FormValues>;
  53. const LabelledField = JoyForms.LabelledField<FormValues>();
  54. const LabelledText = JoyForms.LabelledText<FormValues>();
  55. const InnerForm = (props: FormProps) => {
  56. const {
  57. history,
  58. id,
  59. parentId,
  60. struct,
  61. values,
  62. dirty,
  63. isValid,
  64. isSubmitting,
  65. setSubmitting,
  66. resetForm
  67. } = props;
  68. const {
  69. title,
  70. description
  71. } = values;
  72. const onSubmit = (sendTx: () => void) => {
  73. if (isValid) sendTx();
  74. };
  75. const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
  76. setSubmitting(false);
  77. if (txResult == null) {
  78. // Tx cancelled.
  79. }
  80. };
  81. const onTxSuccess: TxCallback = (_txResult: SubmittableResult) => {
  82. setSubmitting(false);
  83. if (!history) return;
  84. // Get id of newly created category:
  85. let _id = id;
  86. if (!_id) {
  87. _txResult.events.find((event) => {
  88. const { event: { data, method } } = event;
  89. if (method === 'CategoryCreated') {
  90. _id = data.toArray()[0] as CategoryId;
  91. }
  92. return true;
  93. });
  94. }
  95. // Redirect to category view:
  96. if (_id) {
  97. history.push('/forum/categories/' + _id.toString());
  98. }
  99. };
  100. const isNew = struct === undefined;
  101. const isSubcategory = parentId !== undefined;
  102. const buildTxParams = () => {
  103. if (!isValid) return [];
  104. if (isNew) {
  105. return [
  106. createMock('Option<CategoryId>', parentId),
  107. title,
  108. description
  109. ];
  110. } else {
  111. // NOTE: currently update_category doesn't support title and description updates.
  112. return [];
  113. }
  114. };
  115. const categoryWord = isSubcategory ? 'subcategory' : 'category';
  116. const form =
  117. <Form className='ui form JoyForm EditEntityForm'>
  118. <LabelledText name='title' placeholder={`Name your ${categoryWord}`} {...props} />
  119. <LabelledField name='description' {...props}>
  120. <Field component='textarea' id='description' name='description' disabled={isSubmitting} rows={3} placeholder={`Describe your ${categoryWord}. You can use Markdown.`} />
  121. </LabelledField>
  122. <LabelledField {...props}>
  123. <TxButton
  124. type='submit'
  125. label={isNew
  126. ? `Create a ${categoryWord}`
  127. : 'Update a category'
  128. }
  129. isDisabled={!dirty || isSubmitting}
  130. params={buildTxParams()}
  131. tx={isNew
  132. ? 'forum.createCategory'
  133. : 'forum.updateCategory'
  134. }
  135. onClick={onSubmit}
  136. txFailedCb={onTxFailed}
  137. txSuccessCb={onTxSuccess}
  138. />
  139. <Button
  140. type='button'
  141. size='large'
  142. disabled={!dirty || isSubmitting}
  143. onClick={() => resetForm()}
  144. content='Reset form'
  145. />
  146. </LabelledField>
  147. </Form>;
  148. const sectionTitle = isNew
  149. ? `New ${categoryWord}`
  150. : `Edit my ${categoryWord}`;
  151. return <>
  152. <CategoryCrumbs categoryId={parentId} />
  153. <Section className='EditEntityBox' title={sectionTitle}>
  154. {form}
  155. </Section>
  156. </>;
  157. };
  158. const EditForm = withFormik<OuterProps, FormValues>({
  159. // Transform outer props into form values
  160. mapPropsToValues: (props) => {
  161. const { parentId, struct } = props;
  162. return {
  163. parentId: struct ? struct.parent_id : parentId,
  164. title: struct ? struct.title : '',
  165. description: struct ? struct.description : ''
  166. };
  167. },
  168. validationSchema: buildSchema,
  169. handleSubmit: (values) => {
  170. // do submitting things
  171. }
  172. })(InnerForm);
  173. function FormOrLoading (props: OuterProps) {
  174. const { state: { address } } = useMyAccount();
  175. const { struct } = props;
  176. if (!address || !struct) {
  177. return <em>Loading category...</em>;
  178. }
  179. if (struct.isEmpty) {
  180. return <em>Category not found</em>;
  181. }
  182. const isMyStruct = address === struct.moderator_id.toString();
  183. if (isMyStruct) {
  184. return <EditForm {...props} />;
  185. }
  186. return <Message error className='JoyMainStatus' header='You are not allowed edit this category.' />;
  187. }
  188. function withIdFromUrl (Component: React.ComponentType<OuterProps>) {
  189. return function (props: UrlHasIdProps) {
  190. const { match: { params: { id } } } = props;
  191. const { api } = useApi();
  192. try {
  193. return <Component id={api.createType('CategoryId', id)} />;
  194. } catch (err) {
  195. return <em>Invalid category ID: {id}</em>;
  196. }
  197. };
  198. }
  199. function NewSubcategoryForm (props: UrlHasIdProps & OuterProps) {
  200. const { match: { params: { id } } } = props;
  201. const { api } = useApi();
  202. try {
  203. return <EditForm {...props} parentId={api.createType('CategoryId', id)} />;
  204. } catch (err) {
  205. return <em>Invalid parent category id: {id}</em>;
  206. }
  207. }
  208. export const NewCategory = withMulti(
  209. EditForm,
  210. withOnlyForumSudo,
  211. withCategoryValidation
  212. );
  213. export const NewSubcategory = withMulti(
  214. NewSubcategoryForm,
  215. withOnlyForumSudo,
  216. withCategoryValidation
  217. );
  218. export const EditCategory = withMulti(
  219. FormOrLoading,
  220. withOnlyForumSudo,
  221. withIdFromUrl,
  222. withCategoryValidation,
  223. withForumCalls<OuterProps>(
  224. ['categoryById', { paramName: 'id', propName: 'struct' }]
  225. )
  226. );