diff --git a/frontend/src/components/comments/commentInput.js b/frontend/src/components/comments/commentInput.js index 0a20b5214b..10fb0bb5f4 100644 --- a/frontend/src/components/comments/commentInput.js +++ b/frontend/src/components/comments/commentInput.js @@ -17,6 +17,7 @@ import { formatUserNamesToLink, markdownFromHtml } from '../../utils/htmlFromMar import { iconConfig } from './editorIconConfig'; import messages from './messages'; import { CurrentUserAvatar } from '../user/avatar'; +import * as safeStorage from '../../utils/safe_storage'; const maxFileSize = 1 * 1024 * 1024; // 1MB @@ -40,6 +41,7 @@ function CommentInputField({ const isBundle = useRef(false); const lastConvertedRef = useRef(null); const [isShowPreview, setIsShowPreview] = useState(false); + const hasMounted = useRef(false); const appendImgToComment = (url) => setComment(`${comment}\n![image](${url})\n`); const [uploadError, uploading, onDrop] = useOnDrop(appendImgToComment); @@ -139,18 +141,29 @@ function CommentInputField({ useEffect(() => { if (!sessionkey) return; - const commenEvent = sessionStorage.getItem(sessionkey); - if (commenEvent) { - setComment(commenEvent); + const saved = safeStorage.getItem(sessionkey); + if (saved) { + setComment(saved); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionkey]); useEffect(() => { if (!sessionkey) return; - sessionStorage.setItem(sessionkey, comment); + if (comment) { + safeStorage.setItem(sessionkey, comment); + } else if (hasMounted.current) { + // Only purge after mount — avoids deleting the saved draft before + // the restore effect above has had a chance to apply it. + safeStorage.removeItem(sessionkey); + } }, [comment, sessionkey]); + // Mark component as mounted after all initial effects run + useEffect(() => { + hasMounted.current = true; + }, []); + useEffect(() => { if ( comment && diff --git a/frontend/src/components/taskSelection/action.js b/frontend/src/components/taskSelection/action.js index 172669ed08..b1d632d8f1 100644 --- a/frontend/src/components/taskSelection/action.js +++ b/frontend/src/components/taskSelection/action.js @@ -7,6 +7,7 @@ import toast from 'react-hot-toast'; import { FormattedMessage, useIntl } from 'react-intl'; import messages from './messages'; +import * as safeStorage from '../../utils/safe_storage'; import { ProjectInstructions } from './instructions'; import { TasksMap } from './map'; import { HeaderLine } from '../projectDetail/header'; @@ -85,7 +86,14 @@ export function TaskMapAction({ project, tasks, activeTasks, getTasks, action, e const [disabled, setDisable] = useState(false); const [taskComment, setTaskComment] = useState(''); const [selectedStatus, setSelectedStatus] = useState(); - const [validationComments, setValidationComments] = useState({}); + const [validationComments, setValidationComments] = useState(() => { + const restored = {}; + tasksIds.forEach((id) => { + const saved = safeStorage.getItem(`tm-comment-validation-${project.projectId}-${id}`); + if (saved) restored[id] = saved; + }); + return restored; + }); const [validationStatus, setValidationStatus] = useState({}); const [historyTabChecked, setHistoryTabChecked] = useState(false); const [showMapChangesModal, setShowMapChangesModal] = useState(false); diff --git a/frontend/src/components/taskSelection/actionSidebars.js b/frontend/src/components/taskSelection/actionSidebars.js index cd6f92834a..a45166ad3f 100644 --- a/frontend/src/components/taskSelection/actionSidebars.js +++ b/frontend/src/components/taskSelection/actionSidebars.js @@ -37,6 +37,7 @@ import { submitValidationTask, } from '../../api/projects'; import ReactPlaceholder from 'react-placeholder'; +import * as safeStorage from '../../utils/safe_storage'; const CommentInputField = lazy(() => import('../comments/commentInput' /* webpackChunkName: "commentInput" */), @@ -69,7 +70,7 @@ export function CompletionTabForMapping({ const clearLockedTasks = useClearLockedTasks(); const directedFrom = localStorage.getItem('lastProjectPathname'); const { projectId } = project; - const SESSION_KEY = 'task-comment'; + const SESSION_KEY = `tm-comment-mapping-${projectId}-${tasksIds?.[0]}`; const splitTaskMutation = useMutation({ mutationFn: () => splitTask(projectId, tasksIds[0], token, locale), @@ -148,7 +149,7 @@ export function CompletionTabForMapping({ payload.status = selectedStatus; } submitTaskMutation.mutate({ url, payload }); - sessionStorage.removeItem(SESSION_KEY); + safeStorage.removeItem(SESSION_KEY); }; const invalidateProjectData = () => { @@ -390,8 +391,15 @@ export function CompletionTabForValidation({ const updateStatus = (id, newStatus) => setValidationStatus({ ...validationStatus, [id]: newStatus }); - const updateComment = (id, newComment) => + const updateComment = (id, newComment) => { setValidationComments({ ...validationComments, [id]: newComment }); + const key = `tm-comment-validation-${projectId}-${id}`; + if (newComment) { + safeStorage.setItem(key, newComment); + } else { + safeStorage.removeItem(key); + } + }; const copyCommentToTasks = (id, statusFilter) => { const comment = validationComments[id]; @@ -420,6 +428,9 @@ export function CompletionTabForValidation({ })), }; stopValidationMutation.mutate(payload); + tasksIds?.forEach((taskId) => { + safeStorage.removeItem(`tm-comment-validation-${projectId}-${taskId}`); + }); }; const onSubmitTask = () => { @@ -431,6 +442,9 @@ export function CompletionTabForValidation({ })), }; submitTaskMutation.mutate(payload); + tasksIds?.forEach((taskId) => { + safeStorage.removeItem(`tm-comment-validation-${projectId}-${taskId}`); + }); }; const navigateToTasksPage = (applyFilter = false) => { diff --git a/frontend/src/components/taskSelection/taskActivity.js b/frontend/src/components/taskSelection/taskActivity.js index 7d0cdd3e00..bc8d679e51 100644 --- a/frontend/src/components/taskSelection/taskActivity.js +++ b/frontend/src/components/taskSelection/taskActivity.js @@ -25,6 +25,7 @@ import { useTaskDetail } from '../../api/projects'; import { Alert } from '../alert'; import { MessageStatus } from '../comments/status'; import { postTaskComment } from '../../api/questionsAndComments'; +import * as safeStorage from '../../utils/safe_storage'; import './styles.scss'; @@ -36,7 +37,7 @@ const PostComment = ({ projectId, taskId, contributors, setCommentPayload }) => const token = useSelector((state) => state.auth.token); const locale = useSelector((state) => state.preferences['locale']); const [comment, setComment] = useState(''); - const SESSION_KEY = `task-comment-${taskId}`; + const SESSION_KEY = `tm-comment-history-${projectId}-${taskId}`; const saveComment = () => { if (comment) { @@ -49,7 +50,7 @@ const PostComment = ({ projectId, taskId, contributors, setCommentPayload }) => onSuccess: (res) => { setCommentPayload(res.data); setComment(''); - sessionStorage.removeItem(SESSION_KEY); + safeStorage.removeItem(SESSION_KEY); }, });