From 3c705a608437498892ddde0e735c76947590464a Mon Sep 17 00:00:00 2001 From: ireic Date: Tue, 24 Dec 2019 16:39:03 +0100 Subject: [PATCH] Improved code styling --- client/src/App/AppStyles.js | 8 -- client/src/App/Routes.jsx | 6 +- client/src/App/Toast/Styles.js | 17 +-- client/src/App/Toast/index.jsx | 9 +- client/src/App/assets/fonts/jira.svg | 3 + client/src/App/assets/fonts/jira.ttf | Bin 8328 -> 8848 bytes client/src/App/assets/fonts/jira.woff | Bin 8404 -> 8924 bytes client/src/App/index.jsx | 2 +- client/src/{App => Auth}/Authenticate.jsx | 21 ++-- client/src/Project/Board/Filters/Styles.js | 2 +- client/src/Project/Board/Filters/index.jsx | 20 ++-- client/src/Project/Board/Header/index.jsx | 2 + .../src/Project/Board/Lists/Issue/Styles.js | 2 +- .../src/Project/Board/Lists/Issue/index.jsx | 1 + client/src/Project/Board/Lists/index.jsx | 35 ++++--- client/src/Project/Board/index.jsx | 8 +- client/src/Project/IssueCreateForm/index.jsx | 5 +- .../IssueDetails/Comments/BodyForm/index.jsx | 8 +- .../IssueDetails/Comments/Comment/index.jsx | 4 + .../Comments/Create/ProTip/index.jsx | 5 +- .../IssueDetails/Comments/Create/index.jsx | 1 + .../Project/IssueDetails/Comments/index.jsx | 5 +- .../src/Project/IssueDetails/Dates/index.jsx | 1 + client/src/Project/IssueDetails/Delete.jsx | 5 +- .../IssueDetails/Description/index.jsx | 26 ++--- .../Project/IssueDetails/Feedback/index.jsx | 9 +- .../Project/IssueDetails/Priority/index.jsx | 4 +- .../src/Project/IssueDetails/Status/index.jsx | 3 +- .../src/Project/IssueDetails/Title/index.jsx | 1 + .../Project/IssueDetails/Tracking/index.jsx | 97 +++++++++--------- .../src/Project/IssueDetails/Type/index.jsx | 3 +- .../src/Project/IssueDetails/Users/index.jsx | 3 +- client/src/Project/IssueDetails/index.jsx | 5 +- client/src/Project/NavbarLeft/Styles.js | 2 +- client/src/Project/NavbarLeft/index.jsx | 1 + client/src/Project/Sidebar/Styles.js | 8 +- client/src/Project/Sidebar/index.jsx | 11 +- client/src/Project/index.jsx | 28 ++--- client/src/shared/components/Avatar/index.jsx | 22 ++-- client/src/shared/components/Button/Styles.js | 34 +++--- client/src/shared/components/Button/index.jsx | 62 +++++------ .../shared/components/ConfirmModal/Styles.js | 18 +--- .../shared/components/ConfirmModal/index.jsx | 37 ++----- .../src/shared/components/CopyLinkButton.jsx | 3 +- .../components/DatePicker/DateSection.jsx | 59 ++++++----- .../shared/components/DatePicker/Styles.js | 31 +++--- .../components/DatePicker/TimeSection.jsx | 50 ++++----- .../shared/components/DatePicker/index.jsx | 15 +-- client/src/shared/components/Form/Field.jsx | 5 +- client/src/shared/components/Form/index.jsx | 1 + client/src/shared/components/Icon/Styles.js | 2 +- client/src/shared/components/Icon/index.jsx | 11 +- client/src/shared/components/Input/Styles.js | 77 +++++++------- client/src/shared/components/Input/index.jsx | 20 ++-- .../components/IssuePriorityIcon/Styles.js | 2 +- .../components/IssuePriorityIcon/index.jsx | 1 + client/src/shared/components/Modal/Styles.js | 2 +- client/src/shared/components/Modal/index.jsx | 1 + .../src/shared/components/PageError/Styles.js | 3 +- .../shared/components/PageLoader/index.jsx | 1 + .../src/shared/components/Select/Dropdown.jsx | 33 +++--- client/src/shared/components/Select/index.jsx | 1 + .../components/TextEditedContent/index.jsx | 3 +- .../shared/components/TextEditor/index.jsx | 18 ++-- .../src/shared/components/Textarea/Styles.js | 13 ++- .../src/shared/components/Textarea/index.jsx | 2 +- .../src/shared/components/Tooltip/index.jsx | 1 + client/src/shared/hooks/api.js | 90 ---------------- client/src/shared/hooks/api/index.js | 11 ++ client/src/shared/hooks/api/mutation.js | 42 ++++++++ client/src/shared/hooks/api/query.js | 92 +++++++++++++++++ client/src/shared/hooks/mergeState.js | 18 ++++ client/src/shared/hooks/onEscapeKeyDown.js | 2 + client/src/shared/hooks/onOutsideClick.js | 16 +-- client/src/shared/utils/authToken.js | 2 + client/src/shared/utils/browser.js | 18 ++++ client/src/shared/utils/clipboard.js | 8 -- client/src/shared/utils/dom.js | 3 - client/src/shared/utils/html.js | 5 - client/src/shared/utils/styles.js | 42 ++++---- client/src/shared/utils/toast.js | 6 +- 81 files changed, 671 insertions(+), 583 deletions(-) delete mode 100644 client/src/App/AppStyles.js rename client/src/{App => Auth}/Authenticate.jsx (64%) delete mode 100644 client/src/shared/hooks/api.js create mode 100644 client/src/shared/hooks/api/index.js create mode 100644 client/src/shared/hooks/api/mutation.js create mode 100644 client/src/shared/hooks/api/query.js create mode 100644 client/src/shared/hooks/mergeState.js create mode 100644 client/src/shared/utils/browser.js delete mode 100644 client/src/shared/utils/clipboard.js delete mode 100644 client/src/shared/utils/dom.js delete mode 100644 client/src/shared/utils/html.js diff --git a/client/src/App/AppStyles.js b/client/src/App/AppStyles.js deleted file mode 100644 index 4bdbdfd..0000000 --- a/client/src/App/AppStyles.js +++ /dev/null @@ -1,8 +0,0 @@ -import styled from 'styled-components'; -import { sizes } from 'shared/utils/styles'; - -export const Main = styled.main` - position: relative; - width: 100%; - padding-left: ${sizes.appNavBarLeftWidth}px; -`; diff --git a/client/src/App/Routes.jsx b/client/src/App/Routes.jsx index a94f506..33539fa 100644 --- a/client/src/App/Routes.jsx +++ b/client/src/App/Routes.jsx @@ -1,10 +1,10 @@ import React from 'react'; import { Router, Switch, Route, Redirect } from 'react-router-dom'; -import history from 'browserHistory'; -import PageError from 'shared/components/PageError'; +import history from 'browserHistory'; import Project from 'Project'; -import Authenticate from './Authenticate'; +import Authenticate from 'Auth/Authenticate'; +import PageError from 'shared/components/PageError'; const Routes = () => ( diff --git a/client/src/App/Toast/Styles.js b/client/src/App/Toast/Styles.js index dd9b966..554fbc6 100644 --- a/client/src/App/Toast/Styles.js +++ b/client/src/App/Toast/Styles.js @@ -1,6 +1,7 @@ import styled from 'styled-components'; import { color, font, mixin, zIndexValues } from 'shared/utils/styles'; +import { Icon } from 'shared/components'; export const Container = styled.div` z-index: ${zIndexValues.modal + 1}; @@ -33,15 +34,15 @@ export const StyledToast = styled.div` opacity: 1; right: 0; } +`; - i { - position: absolute; - top: 13px; - right: 14px; - font-size: 22px; - cursor: pointer; - color: #fff; - } +export const CloseIcon = styled(Icon)` + position: absolute; + top: 13px; + right: 14px; + font-size: 22px; + cursor: pointer; + color: #fff; `; export const Title = styled.div` diff --git a/client/src/App/Toast/index.jsx b/client/src/App/Toast/index.jsx index e61b161..f344502 100644 --- a/client/src/App/Toast/index.jsx +++ b/client/src/App/Toast/index.jsx @@ -3,15 +3,14 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group'; import pubsub from 'sweet-pubsub'; import { uniqueId } from 'lodash'; -import { Icon } from 'shared/components'; -import { Container, StyledToast, Title, Message } from './Styles'; +import { Container, StyledToast, CloseIcon, Title, Message } from './Styles'; const Toast = () => { const [toasts, setToasts] = useState([]); useEffect(() => { const addToast = ({ type = 'success', title, message, duration = 5 }) => { - const id = uniqueId(); + const id = uniqueId('toast-'); setToasts(currentToasts => [...currentToasts, { id, type, title, message }]); @@ -19,7 +18,9 @@ const Toast = () => { setTimeout(() => removeToast(id), duration * 1000); } }; + pubsub.on('toast', addToast); + return () => { pubsub.off('toast', addToast); }; @@ -35,7 +36,7 @@ const Toast = () => { {toasts.map(toast => ( removeToast(toast.id)}> - + {toast.title && {toast.title}} {toast.message && {toast.message}} diff --git a/client/src/App/assets/fonts/jira.svg b/client/src/App/assets/fonts/jira.svg index 8a9a873..085a0ec 100755 --- a/client/src/App/assets/fonts/jira.svg +++ b/client/src/App/assets/fonts/jira.svg @@ -36,4 +36,7 @@ + + + \ No newline at end of file diff --git a/client/src/App/assets/fonts/jira.ttf b/client/src/App/assets/fonts/jira.ttf index a42e6da04cd34feb9ebed7f16acd617be0724800..50ea2eb25ed3aa6f7c032e534ed40dd85aa4823e 100755 GIT binary patch delta 800 zcmYjOK}-`-5S`gywoB-?{HCQ@goSNun`|SPLW>CqA<>JPNYM7CQV{8ZLK85liKdun z;t}O)2qzEV4HFaN!EondEC-Jp^k7Ihs3+6KnXN=N`8)r;ng8CO`7b8duhszppaa%G zz{Q)l2D72Cnj_{Tt(mz>sTwPOegl9#gl{jDR;m;`33JpI79Twro&M7xTmvx2%JZfB zvBilHK;Jvc=gUO+_ry2CM}(8*%4+TTQg9ETpEO{7ad~dC^z?DH-QU4Y4EE zU;w6IhK@xOan&EhsEl;QOdPVZ`H^wV$>CIb05g~yPEo!Wt=1;W8!i~dXryZ>o4<_K z>1HU)0?{^0Mp!Um#aSYUnPT^#l+j374tCd-GoWb5uyF!LSQ?7mEUKy+YWFzwxT4lc zRI{jTdzt38heg5ml$zWBRsV_FVZ#X9-rqe~^QzBKHmP*hwF9@6VWjb_5O!cz6qH|J z0h~`$|JJAzj~exFJVR z=mF08eLl_C9#Y)~SJ(NWJi}?_CmUkSimN<9%dbXaWP#(zHUpSOvL_B5$CYe|$1Ow5 z2%jPZwh6c4IV$fRo}}Xew8>JFn$IuLCd-80YLb27&bbd$`}aa)zoEm&G<;1xxqBKn m4tqy1xzS@j3sAB^YlqjymU$%@d>cFrnN&$4klg)YZsK3XNR+TmEwz{tSBz|GLWz|3IaAFOZ0FT`d56xjpB3CX#M1>#|s7Bes~$^iKx>50V! zKw1FE2hkkqIhAQk&t^3P`4tQdG9DSJi7Dbq1<4EyvIl_jW*I;M_Cw5%fczgozDh=J zNkwHIuQ&sP9LT(uocv_R#N<=3i69ffDQ#oWq=3TFA}d@4CP=rZO{`52lZsSIPcXaByRHUo9~<1`F19|yO;K_M4sLp z9|r`w68q38&UHZRbNtlnbCKG>bhW$)fE7yI=7^TQzMCyCaqSav?>}_S&OLif+7|$Q zgkyN}PopwZegqJw5-4y)=f(mR5=k2(p5pLqv+tGa@(O8piLZ0Kn)h$dElgA0jrbu6 zS2@<6ue`06SIF4l561=0KH|g9a=sd!}6y*<*2xTL8;?HIJ6s!3+EB+GK3 zLue?pQq9{%TC*f=id1t`a8@)$r{?B=UCp4T1x;~x_Etsp3CW}`UEV#Xl@NqB0tj`O zVnyi}7yxH8M!vu&H3kd)wM;tUK|j(gG-4DZc@i^{ZsU=vX{w{zisDQ8oCb5FgtClT zVv4e1^NDOFsrY!#<8paj9Rb;Ci0ZcCr1X;;V$?{;e1V2X4n@fUN0GbaFoW}CPa@j3 zqjEz$Y8hgdxg^HW&76jCls?#em9`*glBHMmx;zX`vP>$iDY-G+`#7=lATaV9I$d0a zZ|N8J+oyFwAH!~StQgstV8{BxBtEWAfQtv^?(!}h;JT1;=jn&t=Jm=1oej#3t$p*Bd0~CKfO-FlGQHLO@tN?9$@& z#A1-x9w46siUrbhD${^sM;I7nJV1Eq*{tS_)Wj49200#}8Z!_UPbx^x015)dT!4HP z5N1Eb{3s*0qyi`gvZDorEAx28bMljc>ip#100lsbU6MCt?#NB709q_>0OTuxu@>XA zyu{p8pjZIV@+1(>usD0AAiuZ-sALOV9W#){%=~IH52HP!%w!)%Z$_ETGZ;%{ltCT` z;ycBT-{bjhzA|vL00kHrZYfR!`3y`?R#7l#RGb{6&@%arf(si+IfLS6JH>g70O~YE A { useEffect(() => { const createGuestAccount = async () => { - if (!getStoredAuthToken()) { - try { - const { authToken } = await api.post('/authentication/guest'); - storeAuthToken(authToken); - history.push('/'); - } catch (error) { - toast.error(error); - } + try { + const { authToken } = await api.post('/authentication/guest'); + storeAuthToken(authToken); + history.push('/'); + } catch (error) { + toast.error(error); } }; - createGuestAccount(); + + if (!getStoredAuthToken()) { + createGuestAccount(); + } }, [history]); return ; diff --git a/client/src/Project/Board/Filters/Styles.js b/client/src/Project/Board/Filters/Styles.js index 65de5f4..b5162c6 100644 --- a/client/src/Project/Board/Filters/Styles.js +++ b/client/src/Project/Board/Filters/Styles.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { InputDebounced, Avatar, Button } from 'shared/components'; import { color, font, mixin } from 'shared/utils/styles'; +import { InputDebounced, Avatar, Button } from 'shared/components'; export const Filters = styled.div` display: flex; diff --git a/client/src/Project/Board/Filters/index.jsx b/client/src/Project/Board/Filters/index.jsx index db76dc4..16a7a4d 100644 --- a/client/src/Project/Board/Filters/index.jsx +++ b/client/src/Project/Board/Filters/index.jsx @@ -16,14 +16,12 @@ const propTypes = { projectUsers: PropTypes.array.isRequired, defaultFilters: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, - setFilters: PropTypes.func.isRequired, + mergeFilters: PropTypes.func.isRequired, }; -const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters }) => { +const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, mergeFilters }) => { const { searchQuery, userIds, myOnly, recent } = filters; - const setFiltersMerge = newFilters => setFilters({ ...filters, ...newFilters }); - const areFiltersCleared = !searchQuery && userIds.length === 0 && !myOnly && !recent; return ( @@ -31,7 +29,7 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters setFiltersMerge({ searchQuery: value })} + onChange={value => mergeFilters({ searchQuery: value })} /> {projectUsers.map(user => ( @@ -39,27 +37,27 @@ const ProjectBoardFilters = ({ projectUsers, defaultFilters, filters, setFilters setFiltersMerge({ userIds: xor(userIds, [user.id]) })} + onClick={() => mergeFilters({ userIds: xor(userIds, [user.id]) })} /> ))} setFiltersMerge({ myOnly: !myOnly })} + onClick={() => mergeFilters({ myOnly: !myOnly })} > Only My Issues setFiltersMerge({ recent: !recent })} + onClick={() => mergeFilters({ recent: !recent })} > Recently Updated {!areFiltersCleared && ( - setFilters(defaultFilters)}>Clear all + mergeFilters(defaultFilters)}>Clear all )} ); diff --git a/client/src/Project/Board/Header/index.jsx b/client/src/Project/Board/Header/index.jsx index bf22666..7416dc5 100644 --- a/client/src/Project/Board/Header/index.jsx +++ b/client/src/Project/Board/Header/index.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { CopyLinkButton } from 'shared/components'; + import { Breadcrumbs, Divider, Header, BoardName } from './Styles'; const propTypes = { @@ -17,6 +18,7 @@ const ProjectBoardHeader = ({ projectName }) => ( / Kanban Board +
Kanban board diff --git a/client/src/Project/Board/Lists/Issue/Styles.js b/client/src/Project/Board/Lists/Issue/Styles.js index b1a6c99..33ac15d 100644 --- a/client/src/Project/Board/Lists/Issue/Styles.js +++ b/client/src/Project/Board/Lists/Issue/Styles.js @@ -1,8 +1,8 @@ import styled, { css } from 'styled-components'; import { Link } from 'react-router-dom'; -import { Avatar } from 'shared/components'; import { color, font, mixin } from 'shared/utils/styles'; +import { Avatar } from 'shared/components'; export const IssueLink = styled(Link)` display: block; diff --git a/client/src/Project/Board/Lists/Issue/index.jsx b/client/src/Project/Board/Lists/Issue/index.jsx index 6d5493f..122aad6 100644 --- a/client/src/Project/Board/Lists/Issue/index.jsx +++ b/client/src/Project/Board/Lists/Issue/index.jsx @@ -4,6 +4,7 @@ import { useRouteMatch } from 'react-router-dom'; import { Draggable } from 'react-beautiful-dnd'; import { IssueTypeIcon, IssuePriorityIcon } from 'shared/components'; + import { IssueLink, Issue, Title, Bottom, Assignees, AssigneeAvatar } from './Styles'; const propTypes = { diff --git a/client/src/Project/Board/Lists/index.jsx b/client/src/Project/Board/Lists/index.jsx index f338a15..c3f7a5c 100644 --- a/client/src/Project/Board/Lists/index.jsx +++ b/client/src/Project/Board/Lists/index.jsx @@ -8,6 +8,7 @@ import api from 'shared/utils/api'; import useApi from 'shared/hooks/api'; import { moveItemWithinArray, insertItemIntoArray } from 'shared/utils/javascript'; import { IssueStatus, IssueStatusCopy } from 'shared/constants/issues'; + import Issue from './Issue'; import { Lists, List, Title, IssuesCount, Issues } from './Styles'; @@ -21,13 +22,8 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { const [{ data: currentUserData }] = useApi.get('/currentUser'); const currentUserId = get(currentUserData, 'currentUser.id'); - const filteredIssues = filterIssues(project.issues, filters, currentUserId); - const handleIssueDrop = async ({ draggableId, destination, source }) => { - if (!destination) return; - const isSameList = destination.droppableId === source.droppableId; - const isSamePosition = destination.index === source.index; - if (isSameList && isSamePosition) return; + if (!isPositionChanged(source, destination)) return; const issueId = Number(draggableId); @@ -35,7 +31,7 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { url: `/issues/${issueId}`, updatedFields: { status: destination.droppableId, - listPosition: calculateListPosition(project.issues, destination, isSameList, issueId), + listPosition: calculateListPosition(project.issues, destination, source, issueId), }, currentFields: project.issues.find(({ id }) => id === issueId), setLocalData: fields => updateLocalIssuesArray(issueId, fields), @@ -43,21 +39,17 @@ const ProjectBoardLists = ({ project, filters, updateLocalIssuesArray }) => { }; const renderList = status => { + const filteredIssues = filterIssues(project.issues, filters, currentUserId); const filteredListIssues = getSortedListIssues(filteredIssues, status); const allListIssues = getSortedListIssues(project.issues, status); - const issuesCount = - allListIssues.length !== filteredListIssues.length - ? `${filteredListIssues.length} of ${allListIssues.length}` - : allListIssues.length; - return ( {provided => ( {`${IssueStatusCopy[status]} `} - <IssuesCount>{issuesCount}</IssuesCount> + <IssuesCount>{formatIssuesCount(allListIssues, filteredListIssues)}</IssuesCount> {filteredListIssues.map((issue, index) => ( @@ -102,6 +94,20 @@ const filterIssues = (projectIssues, filters, currentUserId) => { const getSortedListIssues = (issues, status) => issues.filter(issue => issue.status === status).sort((a, b) => a.listPosition - b.listPosition); +const formatIssuesCount = (allListIssues, filteredListIssues) => { + if (allListIssues.length !== filteredListIssues.length) { + return `${filteredListIssues.length} of ${allListIssues.length}`; + } + return allListIssues.length; +}; + +const isPositionChanged = (destination, source) => { + if (!destination) return false; + const isSameList = destination.droppableId === source.droppableId; + const isSamePosition = destination.index === source.index; + return !isSameList || !isSamePosition; +}; + const calculateListPosition = (...args) => { const { prevIssue, nextIssue } = getAfterDropPrevNextIssue(...args); let position; @@ -118,9 +124,10 @@ const calculateListPosition = (...args) => { return position; }; -const getAfterDropPrevNextIssue = (allIssues, destination, isSameList, droppedIssueId) => { +const getAfterDropPrevNextIssue = (allIssues, destination, source, droppedIssueId) => { const destinationIssues = getSortedListIssues(allIssues, destination.droppableId); const droppedIssue = allIssues.find(issue => issue.id === droppedIssueId); + const isSameList = destination.droppableId === source.droppableId; const afterDropDestinationIssues = isSameList ? moveItemWithinArray(destinationIssues, droppedIssue, destination.index) diff --git a/client/src/Project/Board/index.jsx b/client/src/Project/Board/index.jsx index 67aeca3..a786813 100644 --- a/client/src/Project/Board/index.jsx +++ b/client/src/Project/Board/index.jsx @@ -1,6 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; +import useMergeState from 'shared/hooks/mergeState'; + import Header from './Header'; import Filters from './Filters'; import Lists from './Lists'; @@ -18,7 +20,7 @@ const defaultFilters = { }; const ProjectBoard = ({ project, updateLocalIssuesArray }) => { - const [filters, setFilters] = useState(defaultFilters); + const [filters, mergeFilters] = useMergeState(defaultFilters); return ( <>
@@ -26,7 +28,7 @@ const ProjectBoard = ({ project, updateLocalIssuesArray }) => { projectUsers={project.users} defaultFilters={defaultFilters} filters={filters} - setFilters={setFilters} + mergeFilters={mergeFilters} /> diff --git a/client/src/Project/IssueCreateForm/index.jsx b/client/src/Project/IssueCreateForm/index.jsx index eb2c352..0bdd79f 100644 --- a/client/src/Project/IssueCreateForm/index.jsx +++ b/client/src/Project/IssueCreateForm/index.jsx @@ -11,6 +11,7 @@ import { import toast from 'shared/utils/toast'; import useApi from 'shared/hooks/api'; import { Form, IssueTypeIcon, Icon, Avatar, IssuePriorityIcon } from 'shared/components'; + import { FormHeading, FormElement, @@ -152,10 +153,10 @@ const ProjectIssueCreateForm = ({ project, fetchProject, modalClose }) => { renderValue={renderPriority} /> - + Create Issue - + Cancel diff --git a/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx b/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx index d96a728..fea70f7 100644 --- a/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx +++ b/client/src/Project/IssueDetails/Comments/BodyForm/index.jsx @@ -2,6 +2,7 @@ import React, { useRef } from 'react'; import PropTypes from 'prop-types'; import { Textarea } from 'shared/components'; + import { Actions, FormButton } from './Styles'; const propTypes = { @@ -20,6 +21,7 @@ const ProjectBoardIssueDetailsCommentsBodyForm = ({ onCancel, }) => { const $textareaRef = useRef(); + return ( <>