Skip to content

Commit 370d6c2

Browse files
Merge pull request #452 from BinaryStudioAcademy/task/OV-444-add-warning-that-progress-not-saved-on-studio-page
OV-444: + add unsaved warning
2 parents 264640c + 981a292 commit 370d6c2

File tree

5 files changed

+93
-13
lines changed

5 files changed

+93
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { UnsavedWarningContent } from './unsaved-warning-content.js';
12
export { WarningContent } from './warning-content.js';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
Button,
3+
Flex,
4+
Heading,
5+
Text,
6+
} from '~/bundles/common/components/components.js';
7+
8+
type Properties = {
9+
onCancel: () => void;
10+
onSubmit: () => void;
11+
};
12+
13+
const UnsavedWarningContent: React.FC<Properties> = ({
14+
onCancel,
15+
onSubmit,
16+
}) => {
17+
return (
18+
<>
19+
<Heading variant="H3" color="typography.900" mb="20px">
20+
Notice before you leave the page
21+
</Heading>
22+
23+
<Text as="p" color="typography.600" mb="20px">
24+
If you leave now all your unsaved changes will be lost. Please
25+
save draft or submit to render before leaving.
26+
</Text>
27+
28+
<Flex gap="10px" justify="end">
29+
<Button label="Cancel" w="auto" onClick={onCancel} />
30+
<Button
31+
label="Leave Anyway"
32+
w="auto"
33+
variant="secondaryOutlined"
34+
onClick={onSubmit}
35+
/>
36+
</Flex>
37+
</>
38+
);
39+
};
40+
41+
export { UnsavedWarningContent };

frontend/src/bundles/studio/components/warning-modal/warning-modal.tsx

+6-7
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,22 @@ import {
66
ModalOverlay,
77
} from '~/bundles/common/components/components.js';
88

9-
import { WarningContent } from './components/components.js';
10-
119
type Properties = {
1210
isOpen: boolean;
1311
onClose: () => void;
14-
onSubmit: () => void;
1512
};
1613

17-
const WarningModal: React.FC<Properties> = ({ isOpen, onClose, onSubmit }) => {
14+
const WarningModal: React.FC<React.PropsWithChildren<Properties>> = ({
15+
isOpen,
16+
onClose,
17+
children,
18+
}) => {
1819
return (
1920
<BaseModal isOpen={isOpen} onClose={onClose} size="xl">
2021
<ModalOverlay />
2122
<ModalContent>
2223
<ModalCloseButton />
23-
<ModalBody p="40px">
24-
<WarningContent onCancel={onClose} onSubmit={onSubmit} />
25-
</ModalBody>
24+
<ModalBody p="40px">{children}</ModalBody>
2625
</ModalContent>
2726
</BaseModal>
2827
);

frontend/src/bundles/studio/pages/studio.tsx

+42-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type PlayerRef } from '@remotion/player';
2+
import { useBlocker } from 'react-router-dom';
23

34
import { AudioPlayer } from '~/bundles/common/components/audio-player/audio-player.js';
45
import {
@@ -30,6 +31,10 @@ import {
3031
} from '~/bundles/common/hooks/hooks.js';
3132
import { IconName } from '~/bundles/common/icons/icons.js';
3233
import { notificationService } from '~/bundles/common/services/services.js';
34+
import {
35+
UnsavedWarningContent,
36+
WarningContent,
37+
} from '~/bundles/studio/components/warning-modal/components/components.js';
3338

3439
import {
3540
PlayerControls,
@@ -81,6 +86,8 @@ const Studio: React.FC = () => {
8186
scriptPlayer,
8287
isVideoScriptsGenerationReady,
8388
isVideoScriptsGenerationPending,
89+
isDraftSaved,
90+
isSubmitToRender,
8491
} = useAppSelector(({ studio }) => studio);
8592

8693
const playerReference = useRef<PlayerRef>(null);
@@ -131,7 +138,6 @@ const Studio: React.FC = () => {
131138
message: NotificationMessage.VIDEO_SUBMITTED,
132139
title: NotificationTitle.VIDEO_SUBMITTED,
133140
});
134-
navigate(AppRoute.ROOT);
135141
})
136142
.catch(() => {
137143
notificationService.error({
@@ -140,7 +146,7 @@ const Studio: React.FC = () => {
140146
title: NotificationTitle.VIDEO_SUBMIT_FAILED,
141147
});
142148
});
143-
}, [dispatch, navigate, scenes, scripts]);
149+
}, [dispatch, scenes, scripts]);
144150

145151
const handleSubmit = useCallback(() => {
146152
if (scenesExceedScripts(scenes, scripts)) {
@@ -242,6 +248,26 @@ const Studio: React.FC = () => {
242248

243249
const { isPlaying, url } = scriptPlayer;
244250

251+
const blocker = useBlocker(
252+
({ currentLocation, nextLocation }) =>
253+
!(isDraftSaved || isSubmitToRender) &&
254+
currentLocation.pathname !== nextLocation.pathname,
255+
);
256+
257+
const handleCloseUnsavedChangesModal = useCallback(() => {
258+
blocker.reset?.();
259+
}, [blocker]);
260+
261+
const handleSubmitUnsavedChangesModal = useCallback(() => {
262+
blocker.proceed?.();
263+
}, [blocker]);
264+
265+
useEffect(() => {
266+
if (isSubmitToRender) {
267+
navigate(AppRoute.ROOT);
268+
}
269+
}, [navigate, isSubmitToRender]);
270+
245271
return (
246272
<>
247273
<Overlay isOpen={isVideoScriptsGenerationPending}>
@@ -256,11 +282,21 @@ const Studio: React.FC = () => {
256282
overflowY={'hidden'}
257283
className={styles['scrollableContainer']}
258284
>
285+
<WarningModal isOpen={isModalOpen} onClose={handleCloseModal}>
286+
<WarningContent
287+
onCancel={handleCloseModal}
288+
onSubmit={handleConfirmSubmit}
289+
/>
290+
</WarningModal>
259291
<WarningModal
260-
isOpen={isModalOpen}
261-
onClose={handleCloseModal}
262-
onSubmit={handleConfirmSubmit}
263-
/>
292+
isOpen={blocker.state === 'blocked'}
293+
onClose={handleCloseUnsavedChangesModal}
294+
>
295+
<UnsavedWarningContent
296+
onCancel={handleCloseUnsavedChangesModal}
297+
onSubmit={handleSubmitUnsavedChangesModal}
298+
/>
299+
</WarningModal>
264300
<Header
265301
center={
266302
<Button

frontend/src/bundles/studio/store/slice.ts

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type State = {
9595
videoSize: VideoPreviewT;
9696
videoName: string;
9797
isDraftSaved: boolean;
98+
isSubmitToRender: boolean;
9899
videoId: string | null;
99100
voices: Voice[];
100101
templates: {
@@ -126,6 +127,7 @@ const initialState: State = {
126127
videoSize: VideoPreview.LANDSCAPE,
127128
videoName: 'Untitled Video',
128129
isDraftSaved: true,
130+
isSubmitToRender: false,
129131
videoId: null,
130132
voices: [],
131133
templates: {
@@ -623,6 +625,7 @@ const { reducer, actions, name } = createSlice({
623625
state.dataStatus = DataStatus.PENDING;
624626
});
625627
builder.addCase(renderAvatar.fulfilled, (state) => {
628+
state.isSubmitToRender = true;
626629
state.dataStatus = DataStatus.FULFILLED;
627630
});
628631
builder.addCase(renderAvatar.rejected, (state) => {

0 commit comments

Comments
 (0)