You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractPollCreate.tsx 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* eslint-disable arrow-body-style */
  2. import React, { ComponentType, FormEvent, useCallback, useMemo, useState } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import { useDispatch, useSelector } from 'react-redux';
  5. import { createPollEvent } from '../../analytics/AnalyticsEvents';
  6. import { sendAnalytics } from '../../analytics/functions';
  7. import { IReduxState } from '../../app/types';
  8. import { getLocalParticipant } from '../../base/participants/functions';
  9. import { savePoll } from '../actions';
  10. import { hasIdenticalAnswers } from '../functions';
  11. import { IAnswerData, IPoll } from '../types';
  12. /**
  13. * The type of the React {@code Component} props of inheriting component.
  14. */
  15. type InputProps = {
  16. setCreateMode: (mode: boolean) => void;
  17. };
  18. /*
  19. * Props that will be passed by the AbstractPollCreate to its
  20. * concrete implementations (web/native).
  21. **/
  22. export type AbstractProps = InputProps & {
  23. addAnswer: (index?: number) => void;
  24. answers: Array<IAnswerData>;
  25. editingPoll: IPoll | undefined;
  26. editingPollId: string | undefined;
  27. isSubmitDisabled: boolean;
  28. onSubmit: (event?: FormEvent<HTMLFormElement>) => void;
  29. question: string;
  30. removeAnswer: (index: number) => void;
  31. setAnswer: (index: number, value: IAnswerData) => void;
  32. setQuestion: (question: string) => void;
  33. t: Function;
  34. };
  35. /**
  36. * Higher Order Component taking in a concrete PollCreate component and
  37. * augmenting it with state/behavior common to both web and native implementations.
  38. *
  39. * @param {React.AbstractComponent} Component - The concrete component.
  40. * @returns {React.AbstractComponent}
  41. */
  42. const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props: InputProps) => {
  43. const { setCreateMode } = props;
  44. const pollState = useSelector((state: IReduxState) => state['features/polls'].polls);
  45. const editingPoll: [ string, IPoll ] | null = useMemo(() => {
  46. if (!pollState) {
  47. return null;
  48. }
  49. for (const key in pollState) {
  50. if (pollState.hasOwnProperty(key) && pollState[key].editing) {
  51. return [ key, pollState[key] ];
  52. }
  53. }
  54. return null;
  55. }, [ pollState ]);
  56. const answerResults = useMemo(() => {
  57. return editingPoll
  58. ? editingPoll[1].answers
  59. : [
  60. {
  61. name: '',
  62. voters: []
  63. },
  64. {
  65. name: '',
  66. voters: []
  67. } ];
  68. }, [ editingPoll ]);
  69. const questionResult = useMemo(() => {
  70. return editingPoll ? editingPoll[1].question : '';
  71. }, [ editingPoll ]);
  72. const [ question, setQuestion ] = useState(questionResult);
  73. const [ answers, setAnswers ] = useState(answerResults);
  74. const setAnswer = useCallback((i: number, answer: IAnswerData) => {
  75. setAnswers(currentAnswers => {
  76. const newAnswers = [ ...currentAnswers ];
  77. newAnswers[i] = answer;
  78. return newAnswers;
  79. });
  80. }, [ answers ]);
  81. const addAnswer = useCallback((i?: number) => {
  82. const newAnswers: Array<IAnswerData> = [ ...answers ];
  83. sendAnalytics(createPollEvent('option.added'));
  84. newAnswers.splice(typeof i === 'number'
  85. ? i : answers.length, 0, {
  86. name: '',
  87. voters: []
  88. });
  89. setAnswers(newAnswers);
  90. }, [ answers ]);
  91. const removeAnswer = useCallback(i => {
  92. if (answers.length <= 2) {
  93. return;
  94. }
  95. const newAnswers = [ ...answers ];
  96. sendAnalytics(createPollEvent('option.removed'));
  97. newAnswers.splice(i, 1);
  98. setAnswers(newAnswers);
  99. }, [ answers ]);
  100. const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
  101. const dispatch = useDispatch();
  102. const pollId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
  103. const localParticipant = useSelector(getLocalParticipant);
  104. const onSubmit = useCallback(ev => {
  105. if (ev) {
  106. ev.preventDefault();
  107. }
  108. const filteredAnswers = answers.filter(answer => answer.name.trim().length > 0);
  109. if (filteredAnswers.length < 2) {
  110. return;
  111. }
  112. const poll = {
  113. changingVote: false,
  114. senderId: localParticipant?.id,
  115. showResults: false,
  116. lastVote: null,
  117. question,
  118. answers: filteredAnswers,
  119. saved: true,
  120. editing: false
  121. };
  122. if (editingPoll) {
  123. dispatch(savePoll(editingPoll[0], poll));
  124. } else {
  125. dispatch(savePoll(pollId, poll));
  126. }
  127. sendAnalytics(createPollEvent('created'));
  128. setCreateMode(false);
  129. }, [ conference, question, answers ]);
  130. // Check if the poll create form can be submitted i.e. if the send button should be disabled.
  131. const isSubmitDisabled
  132. = question.trim().length <= 0 // If no question is provided
  133. || answers.filter(answer => answer.name.trim().length > 0).length < 2 // If not enough options are provided
  134. || hasIdenticalAnswers(answers); // If duplicate options are provided
  135. const { t } = useTranslation();
  136. return (<Component
  137. addAnswer = { addAnswer }
  138. answers = { answers }
  139. editingPoll = { editingPoll?.[1] }
  140. editingPollId = { editingPoll?.[0] }
  141. isSubmitDisabled = { isSubmitDisabled }
  142. onSubmit = { onSubmit }
  143. question = { question }
  144. removeAnswer = { removeAnswer }
  145. setAnswer = { setAnswer }
  146. setCreateMode = { setCreateMode }
  147. setQuestion = { setQuestion }
  148. t = { t } />);
  149. };
  150. export default AbstractPollCreate;