From b5708d26cd30ea17002762048d3c68c6d7a54f39 Mon Sep 17 00:00:00 2001 From: Arkadzi Rakouski Date: Sun, 13 Aug 2023 18:51:02 +0300 Subject: [PATCH] add useconfirm for req actions & fixes for toc (#155) * add useconfirm for req actions & fixes for toc * add interval for editor * revert editor interval * refactor by review comments * add sticky pos for table of contents * refactor toc for editor * add debounce * refactor by review comments * Merge remote-tracking branch 'origin/main' into fix/useconfirm_n_tableofcont # Conflicts: # package.json # src/components/Article/Comment.tsx # src/pages/profile/profileSettings.page.tsx --------- Co-authored-by: bniwredyc --- package.json | 3 +- public/locales/en/translation.json | 2 ++ public/locales/ru/translation.json | 2 ++ src/components/Article/Article.module.scss | 33 +++++++++++-------- src/components/Article/Comment.tsx | 2 +- src/components/Article/FullArticle.tsx | 4 +-- src/components/Author/Userpic/Userpic.tsx | 1 - src/components/Editor/Editor.tsx | 3 +- src/components/Editor/Prosemirror.scss | 28 +++++++++++----- .../Nav/ConfirmModal/ConfirmModal.module.scss | 8 ++--- src/components/Nav/Header.tsx | 3 +- .../TableOfContents.module.scss | 30 +++++++---------- .../TableOfContents/TableOfContents.tsx | 27 +++++++++++---- src/components/Views/Edit.tsx | 26 ++++++++++----- .../Views/PublishSettings/PublishSettings.tsx | 2 +- .../GrowingTextarea/GrowingTextarea.tsx | 3 +- src/context/editor.tsx | 22 ++++++------- src/context/profile.tsx | 6 ++-- src/pages/profile/profileSettings.page.tsx | 28 +++++++++++++--- 19 files changed, 144 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 855840f8..b3a96ab9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@hocuspocus/provider": "2.0.6", - "fast-deep-equal": "3.1.3", "form-data": "4.0.0", "i18next": "22.4.15", "mailgun.js": "8.2.1", @@ -101,6 +100,7 @@ "cookie-signature": "1.2.1", "cosmiconfig-toml-loader": "1.0.0", "cross-env": "7.0.3", + "debounce": "1.2.1", "eslint": "8.40.0", "eslint-config-stylelint": "18.0.0", "eslint-import-resolver-typescript": "3.5.5", @@ -111,6 +111,7 @@ "eslint-plugin-solid": "0.12.1", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", + "fast-deep-equal": "3.1.3", "graphql": "16.6.0", "graphql-tag": "2.12.6", "graphql-ws": "5.12.1", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index a5a3ed40..c3e06da6 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -302,6 +302,8 @@ "Topic is supported by": "Topic is supported by", "Topics": "Topics", "Topics which supported by author": "Topics which supported by author", + "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?", + "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?", "Try to find another way": "Try to find another way", "Unfollow": "Unfollow", "Unfollow the topic": "Unfollow the topic", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 5612625c..b3e49787 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -319,6 +319,8 @@ "Topic is supported by": "Тему поддерживают", "Topics": "Темы", "Topics which supported by author": "Автор поддерживает темы", + "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "В настройках публикации есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", + "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", "Try to find another way": "Попробуйте найти по-другому", "Unfollow": "Отписаться", "Unfollow the topic": "Отписаться от темы", diff --git a/src/components/Article/Article.module.scss b/src/components/Article/Article.module.scss index 342db76f..42d0b498 100644 --- a/src/components/Article/Article.module.scss +++ b/src/components/Article/Article.module.scss @@ -1,11 +1,13 @@ h1 { @include font-size(4rem); + line-height: 1.1; margin-top: 0.5em; } h2 { @include font-size(4rem); + line-height: 1.1; } @@ -44,7 +46,7 @@ img { margin: 3.2rem 0; position: relative; - &:before { + &::before { background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNiAyMSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wIDEwLjUwMDFWMjFIMTAuNzA2MVYxMC41MDAxSDQuNTg4MzNDNC41ODgzMyA3LjE4NjU4IDcuMzI3NTggNC41MDAwOCAxMC43MDYxIDQuNTAwMDhWMEM0Ljc5Mjk3IDAgMCA0LjcwMDczIDAgMTAuNDk5OVYxMC41MDAxWk0yNiA0LjUwMDA4VjBDMjAuMDg2OSAwIDE1LjI5MzkgNC43MDA3MyAxNS4yOTM5IDEwLjQ5OTlWMjAuOTk5OUgyNlYxMC41MDA1SDE5Ljg4MjJDMTkuODgyNCA3LjE4NyAyMi42MjE3IDQuNTAwNSAyNiA0LjUwMDVWNC41MDAwOFoiIGZpbGw9ImJsYWNrIi8+PC9zdmc+') no-repeat; content: ''; @@ -60,17 +62,19 @@ img { blockquote[data-type='quote'], ta-quotation { @include font-size(1.4rem); + border: solid #000; border-width: 0 0 0 2px; display: block; font-weight: 500; line-height: 1.6; - margin: 1.6rem 0 0 calc(-8.33333% - 2px); - padding: 0 0 0 8.33333%; + margin: 1.6rem 0 0 calc(-8.3333% - 2px); + padding: 0 0 0 8.3333%; &[data-float='left'], &[data-float='right'] { @include font-size(2.2rem); + line-height: 1.4; } @@ -84,7 +88,7 @@ img { } } - &:before { + &::before { display: none; } } @@ -95,13 +99,15 @@ img { ta-border-sub { background: #f1f2f3; display: block; + @include font-size(1.4rem); + margin: 3.2rem 0; padding: 3.2rem; @include media-breakpoint-up(md) { - margin: 3.2rem -8.33333%; - padding: 3.2rem 8.33333%; + margin: 3.2rem -8.3333%; + padding: 3.2rem 8.3333%; } p:last-child { @@ -144,7 +150,7 @@ img { } @include media-breakpoint-up(md) { - margin: 0 8.33333% 3.2rem -16.66666%; + margin: 0 8.3333% 3.2rem -16.6666%; } } @@ -154,7 +160,7 @@ img { } @include media-breakpoint-up(md) { - margin: 0 -16.66666% 3.2rem 8.33333%; + margin: 0 -16.6666% 3.2rem 8.3333%; } } @@ -168,13 +174,13 @@ img { h2 { @include media-breakpoint-up(xl) { - margin-left: -16.6666666666%; + margin-left: -16.6666%; } } :global(.img-align-left) { float: left; - margin: 1em 8.333333333% 0.5em 0; + margin: 1em 8.3333% 0.5em 0; } :global(.width-30) { @@ -187,18 +193,18 @@ img { :global(.img-align-left.width-50) { @include media-breakpoint-up(xl) { - margin-left: -16.6666666666%; + margin-left: -16.6666%; } } :global(.img-align-right) { float: right; - margin: 1em 0 0.5em 8.333333333%; + margin: 1em 0 0.5em 8.3333%; } :global(.img-align-right.width-50) { @include media-breakpoint-up(xl) { - margin-right: -16.6666666666%; + margin-right: -16.6666%; } } @@ -498,6 +504,7 @@ img { button { @include font-size(1.5rem); + border-radius: 0.8rem; margin-right: 1.2rem; padding: 0.9rem 1.2rem; diff --git a/src/components/Article/Comment.tsx b/src/components/Article/Comment.tsx index c8cc6028..6cfaf59b 100644 --- a/src/components/Article/Comment.tsx +++ b/src/components/Article/Comment.tsx @@ -17,7 +17,6 @@ import { useSnackbar } from '../../context/snackbar' import { useConfirm } from '../../context/confirm' import { Author, Reaction, ReactionKind } from '../../graphql/types.gen' - import { router } from '../../stores/router' import styles from './Comment.module.scss' @@ -48,6 +47,7 @@ export const Comment = (props: Props) => { const { actions: { showConfirm } } = useConfirm() + const { actions: { showSnackbar } } = useSnackbar() diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 61940471..886119e0 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -132,7 +132,7 @@ export const FullArticle = (props: Props) => { <> {props.article.title}
-
+
{/*TODO: Check styles.shoutTopic*/} @@ -212,7 +212,7 @@ export const FullArticle = (props: Props) => {
- +
diff --git a/src/components/Author/Userpic/Userpic.tsx b/src/components/Author/Userpic/Userpic.tsx index 57c205bd..077109a5 100644 --- a/src/components/Author/Userpic/Userpic.tsx +++ b/src/components/Author/Userpic/Userpic.tsx @@ -1,5 +1,4 @@ import { Show } from 'solid-js' -import type { Author, User } from '../../../graphql/types.gen' import styles from './Userpic.module.scss' import { clsx } from 'clsx' import { imageProxy } from '../../../utils/imageProxy' diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 56bb2514..7d8daf88 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -62,6 +62,7 @@ const providers: Record = {} export const Editor = (props: Props) => { const { t } = useLocalize() const { user } = useSession() + const [isCommonMarkup, setIsCommonMarkup] = createSignal(false) const docName = `shout-${props.shoutId}` @@ -247,7 +248,7 @@ export const Editor = (props: Props) => { <>
(editorElRef.current = el)} id="editorBody" /> - + = 768px) { padding-left: calc(21.9% + 3px); max-width: 72.7%; } - @media (min-width: 1200px) { + + @media (width >= 1200px) { padding-left: calc(21.5% + 3px); max-width: 64.9%; } @@ -38,32 +39,35 @@ .articleEditor figure, .articleEditor .uploadedImage, .articleEditor article[data-type='incut'] { - @media (min-width: 768px) { + @media (width >= 768px) { margin-left: calc(21.9% + 3px) !important; max-width: 73.6%; } - @media (min-width: 1200px) { + + @media (width >= 1200px) { margin-left: calc(21.4% + 3px) !important; max-width: 65.3%; } } .articleEditor h2 { - @media (min-width: 768px) { + @media (width >= 768px) { padding-left: calc(21.9% + 2px); max-width: 72.7%; } - @media (min-width: 1200px) { + + @media (width >= 1200px) { padding-left: 21.5%; max-width: 87.1%; } } .articleEditor h3 { - @media (min-width: 768px) { + @media (width >= 768px) { padding-left: calc(21.9% + 2px); } - @media (min-width: 1200px) { + + @media (width >= 1200px) { padding-left: 21.5%; max-width: 87.1%; } @@ -73,7 +77,7 @@ .articleEditor * h2, .articleEditor * h3, .articleEditor * h4 { - @media (min-width: 768px) { + @media (width >= 768px) { padding-left: unset; max-width: unset; } @@ -183,6 +187,7 @@ mark.highlight { &[data-type='quote'] { @include font-size(1.4rem); + border: solid #000; border-width: 0 0 0 2px; margin: 1.6rem 0; @@ -204,7 +209,9 @@ mark.highlight { &[data-type='punchline'] { border: solid #000; border-width: 2px 0; + @include font-size(3.2rem); + font-weight: 700; line-height: 1.2; margin: 1em 0; @@ -213,6 +220,7 @@ mark.highlight { &[data-float='left'], &[data-float='right'] { @include font-size(2.2rem); + line-height: 1.4; } @@ -230,7 +238,9 @@ mark.highlight { .ProseMirror article[data-type='incut'] { background: #f1f2f3; + @include font-size(1.4rem); + margin: 1em -1rem; padding: 2em 2rem; transition: background 0.3s ease-in-out; diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss index 266b4adb..deb8f946 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss +++ b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss @@ -19,7 +19,6 @@ .confirmModalActions { display: flex; justify-content: space-between; - margin-top: 16px; } @@ -27,26 +26,23 @@ display: block; width: 100%; margin-right: 12px; - font-weight: 700; - margin-top: 32px; padding: 1.6rem !important; border: 1px solid black; &:hover { - background-color: rgba(0, 0, 0, 0.08); + background-color: rgb(0 0 0 / 8%); } } .confirmModalButtonPrimary { margin-right: 0; - background-color: black; color: white; border: none; &:hover { - background-color: rgba(0, 0, 0, 0.6); + background-color: rgb(0 0 0 / 60%); } } diff --git a/src/components/Nav/Header.tsx b/src/components/Nav/Header.tsx index 58135664..614597da 100644 --- a/src/components/Nav/Header.tsx +++ b/src/components/Nav/Header.tsx @@ -1,7 +1,6 @@ import { Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js' -import { getPagePath } from '@nanostores/router' +import { getPagePath, redirectPage } from '@nanostores/router' import { clsx } from 'clsx' -import { redirectPage } from '@nanostores/router' import { Modal } from './Modal' import { AuthModal } from './AuthModal' diff --git a/src/components/TableOfContents/TableOfContents.module.scss b/src/components/TableOfContents/TableOfContents.module.scss index 56ab8ace..c6f310a9 100644 --- a/src/components/TableOfContents/TableOfContents.module.scss +++ b/src/components/TableOfContents/TableOfContents.module.scss @@ -1,28 +1,26 @@ .TableOfContentsFixedWrapper { - position: fixed; - top: 150px; - right: 20px; - + position: absolute; + top: 0; + right: 0; width: 281px; + min-height: 100%; } .TableOfContentsFixedWrapperLefted { right: auto; - left: 20px; + left: 70px; } .TableOfContentsContainer { - position: absolute; - right: 0; - top: 0; - + position: sticky; + top: 150px; + right: 20px; display: flex; width: 100%; height: auto; padding: 20px; flex-direction: column; align-items: flex-start; - background-color: transparent; } @@ -34,7 +32,6 @@ .TableOfContentsHeading { margin: 0; - color: #000; font-size: 22px; font-style: normal; @@ -46,20 +43,17 @@ position: absolute; right: 20px; top: 10px; - display: flex; align-items: center; justify-content: center; - width: 40px; height: 40px; - background: transparent; border: none; cursor: pointer; &:hover { - box-shadow: 0px 0px 1px 1px rgba(0, 0, 0, 0.3); + box-shadow: 0 0 1px 1px rgb(0 0 0 / 30%); } } @@ -70,18 +64,16 @@ .TableOfContentsHeadingsList { position: relative; - display: flex; flex-direction: column; list-style-type: none; - margin: 0; padding: 0 38px 0 0; + width: 100%; } .TableOfContentsHeadingsItem { margin-top: 20px; - color: #000; font-size: 14px; font-style: normal; @@ -91,7 +83,7 @@ letter-spacing: -0.14px; &:hover { - transform: scale(1.05); + color: rgb(0 0 0 / 50%); } } diff --git a/src/components/TableOfContents/TableOfContents.tsx b/src/components/TableOfContents/TableOfContents.tsx index a0b36dd2..7a3793cd 100644 --- a/src/components/TableOfContents/TableOfContents.tsx +++ b/src/components/TableOfContents/TableOfContents.tsx @@ -1,10 +1,12 @@ -import { onMount, For, Show, createSignal } from 'solid-js' +import { For, Show, createSignal, createEffect, on } from 'solid-js' import { clsx } from 'clsx' import { DEFAULT_HEADER_OFFSET } from '../../stores/router' import { useLocalize } from '../../context/localize' +import { debounce } from 'debounce' + import { Icon } from '../_shared/Icon' import styles from './TableOfContents.module.scss' @@ -12,6 +14,7 @@ import styles from './TableOfContents.module.scss' interface Props { variant: 'article' | 'editor' parentSelector: string + body: string } const scrollToHeader = (element) => { @@ -30,21 +33,33 @@ export const TableOfContents = (props: Props) => { const [headings, setHeadings] = createSignal([]) const [areHeadingsLoaded, setAreHeadingsLoaded] = createSignal(false) - const [isVisible, setIsVisible] = createSignal(true) + const [isVisible, setIsVisible] = createSignal(props.variant === 'article') const toggleIsVisible = () => { setIsVisible((visible) => !visible) } - onMount(() => { + const updateHeadings = () => { const { parentSelector } = props + // eslint-disable-next-line unicorn/prefer-spread setHeadings(Array.from(document.querySelector(parentSelector).querySelectorAll('h2, h3, h4'))) - setAreHeadingsLoaded(true) - }) + } + + const debouncedUpdateHeadings = debounce(updateHeadings, 500) + createEffect( + on( + () => props.body, + () => debouncedUpdateHeadings() + ) + ) return ( - + 2 : headings().length > 1) + } + >
{ }) } - const [prevForm, setPrevForm] = createSignal(clone(form)) + const [prevForm, setPrevForm] = createStore(clone(form)) const [saving, setSaving] = createSignal(false) const mediaItems: Accessor = createMemo(() => { @@ -94,6 +90,20 @@ export const EditView = (props: Props) => { }) }) + onMount(() => { + // eslint-disable-next-line unicorn/consistent-function-scoping + const handleBeforeUnload = (event) => { + if (!deepEqual(prevForm, form)) { + event.returnValue = t( + `There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?` + ) + } + } + + window.addEventListener('beforeunload', handleBeforeUnload) + onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) + }) + const handleTitleInputChange = (value) => { setForm('title', value) setForm('slug', slugify(value)) @@ -174,7 +184,7 @@ export const EditView = (props: Props) => { const autoSaveRecursive = () => { autoSaveTimeOutId = setTimeout(async () => { - const hasChanges = !deepEqual(form, prevForm()) + const hasChanges = !deepEqual(form, prevForm) if (hasChanges) { setSaving(true) if (props.shout.visibility === 'owner') { diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index f2f6904d..61ab1d73 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' import styles from './PublishSettings.module.scss' -import { createEffect, createSignal, onMount, Show } from 'solid-js' +import { createSignal, onMount, Show } from 'solid-js' import { TopicSelect, UploadModalContent } from '../../Editor' import { Button } from '../../_shared/Button' import { hideModal, showModal } from '../../../stores/ui' diff --git a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx index d5e70faf..452cb032 100644 --- a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx +++ b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx @@ -1,7 +1,6 @@ import { clsx } from 'clsx' import styles from './GrowingTextarea.module.scss' -import { createSignal, Show, Switch } from 'solid-js' -import { style } from 'solid-js/web' +import { createSignal, Show } from 'solid-js' type Props = { class?: string diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 3cd0efca..28316a7d 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -65,6 +65,17 @@ const topic2topicInput = (topic: Topic): TopicInput => { } } +const saveDraftToLocalStorage = (formToSave: ShoutForm) => { + localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) +} +const getDraftFromLocalStorage = (shoutId: number) => { + return JSON.parse(localStorage.getItem(`shout-${shoutId}`)) +} + +const removeDraftFromLocalStorage = (shoutId: number) => { + localStorage.removeItem(`shout-${shoutId}`) +} + export const EditorProvider = (props: { children: JSX.Element }) => { const { t } = useLocalize() @@ -164,17 +175,6 @@ export const EditorProvider = (props: { children: JSX.Element }) => { await updateShout(draftForm, { publish: false }) } - const saveDraftToLocalStorage = (formToSave: ShoutForm) => { - localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) - } - const getDraftFromLocalStorage = (shoutId: number) => { - return JSON.parse(localStorage.getItem(`shout-${shoutId}`)) - } - - const removeDraftFromLocalStorage = (shoutId: number) => { - localStorage.removeItem(`shout-${shoutId}`) - } - const publishShout = async (formToPublish: ShoutForm) => { if (isEditorPanelVisible()) { toggleEditorPanel() diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 0e4a8cbf..6588c354 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -34,14 +34,16 @@ const useProfileForm = () => { if (!currentSlug()) return try { await loadAuthor({ slug: currentSlug() }) - setForm({ + const updatedFormValues = { name: currentAuthor()?.name, slug: currentAuthor()?.slug, bio: currentAuthor()?.bio, about: currentAuthor()?.about, userpic: currentAuthor()?.userpic, links: currentAuthor()?.links - }) + } + + setForm(updatedFormValues) } catch (error) { console.error(error) } diff --git a/src/pages/profile/profileSettings.page.tsx b/src/pages/profile/profileSettings.page.tsx index 2a8ebf68..b2ff4231 100644 --- a/src/pages/profile/profileSettings.page.tsx +++ b/src/pages/profile/profileSettings.page.tsx @@ -1,8 +1,10 @@ import { PageLayout } from '../../components/_shared/PageLayout' import { Icon } from '../../components/_shared/Icon' import ProfileSettingsNavigation from '../../components/Discours/ProfileSettingsNavigation' -import { For, createSignal, Show, onMount } from 'solid-js' +import { For, createSignal, Show, onMount, onCleanup } from 'solid-js' +import deepEqual from 'fast-deep-equal' import { clsx } from 'clsx' + import styles from './Settings.module.scss' import { useProfileForm } from '../../context/profile' import { validateUrl } from '../../utils/validateUrl' @@ -13,6 +15,8 @@ import { useSnackbar } from '../../context/snackbar' import { useLocalize } from '../../context/localize' import { handleFileUpload } from '../../utils/handleFileUpload' import { Userpic } from '../../components/Author/Userpic' +import { createStore } from 'solid-js/store' +import { clone } from '../../utils/clone' export const ProfileSettingsPage = () => { const { t } = useLocalize() @@ -24,11 +28,12 @@ export const ProfileSettingsPage = () => { const { actions: { showSnackbar } } = useSnackbar() - const { actions: { loadSession } } = useSession() + const { form, updateFormField, submit, slugError } = useProfileForm() + const [prevForm, setPrevForm] = createStore(clone(form)) const handleChangeSocial = (value: string) => { if (validateUrl(value)) { @@ -45,6 +50,7 @@ export const ProfileSettingsPage = () => { try { await submit(form) + setPrevForm(clone(form)) showSnackbar({ body: t('Profile successfully saved') }) } catch { showSnackbar({ type: 'error', body: t('Error') }) @@ -70,9 +76,23 @@ export const ProfileSettingsPage = () => { } const [hostname, setHostname] = createSignal(null) - onMount(() => setHostname(window?.location.host)) - console.log('!!! form:', form) + onMount(() => { + setHostname(window?.location.host) + + // eslint-disable-next-line unicorn/consistent-function-scoping + const handleBeforeUnload = (event) => { + if (!deepEqual(form, prevForm)) { + event.returnValue = t( + 'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?' + ) + } + } + + window.addEventListener('beforeunload', handleBeforeUnload) + onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) + }) + return (