add floating panel for profile settings (#186)
* add floating panel for profile settings * resolve conversation * don't show beforeunload message after save profile form --------- Co-authored-by: ilya-bkv <i.yablokov@ccmp.me>
This commit is contained in:
parent
f2a5751f3e
commit
52d8a01a50
|
@ -8,10 +8,9 @@ import { useLocalize } from '../../../context/localize'
|
||||||
import { Modal } from '../../Nav/Modal'
|
import { Modal } from '../../Nav/Modal'
|
||||||
import { Menu } from './Menu'
|
import { Menu } from './Menu'
|
||||||
import type { MenuItem } from './Menu/Menu'
|
import type { MenuItem } from './Menu/Menu'
|
||||||
import { hideModal, showModal } from '../../../stores/ui'
|
import { showModal } from '../../../stores/ui'
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
import { UploadModalContent } from '../UploadModalContent'
|
||||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
import { imageProxy } from '../../../utils/imageProxy'
|
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import { UploadedFile } from '../../pages/types'
|
||||||
import { Figure } from './extensions/Figure'
|
import { Figure } from './extensions/Figure'
|
||||||
import { Image } from '@tiptap/extension-image'
|
import { Image } from '@tiptap/extension-image'
|
||||||
import { Figcaption } from './extensions/Figcaption'
|
import { Figcaption } from './extensions/Figcaption'
|
||||||
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialContent?: string
|
initialContent?: string
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { For, Show, createSignal, createEffect, on, onMount } from 'solid-js'
|
import { For, Show, createSignal, createEffect, on } from 'solid-js'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { AudioUploader } from '../Editor/AudioUploader'
|
||||||
import { slugify } from '../../utils/slugify'
|
import { slugify } from '../../utils/slugify'
|
||||||
import { SolidSwiper } from '../_shared/SolidSwiper'
|
import { SolidSwiper } from '../_shared/SolidSwiper'
|
||||||
import { DropArea } from '../_shared/DropArea'
|
import { DropArea } from '../_shared/DropArea'
|
||||||
import { LayoutType, MediaItem, UploadedFile } from '../../pages/types'
|
import { LayoutType, MediaItem } from '../../pages/types'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
import deepEqual from 'fast-deep-equal'
|
import deepEqual from 'fast-deep-equal'
|
||||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||||
|
|
|
@ -9,7 +9,7 @@ type Props = {
|
||||||
type?: 'submit' | 'button'
|
type?: 'submit' | 'button'
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onClick?: () => void
|
onClick?: (event?: MouseEvent) => void
|
||||||
class?: string
|
class?: string
|
||||||
ref?: HTMLButtonElement | ((el: HTMLButtonElement) => void)
|
ref?: HTMLButtonElement | ((el: HTMLButtonElement) => void)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
.PanelWrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
max-width: 430px;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 14px;
|
||||||
|
|
||||||
|
background-color: var(--background-color);
|
||||||
|
border: 2px solid black;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(sm) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
input {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.PanelWrapperVisible {
|
||||||
|
display: flex;
|
||||||
|
}
|
35
src/components/_shared/FloatingPanel/FloatingPanel.tsx
Normal file
35
src/components/_shared/FloatingPanel/FloatingPanel.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
import { Button } from '../Button'
|
||||||
|
|
||||||
|
import styles from './FloatingPanel.module.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isVisible: boolean
|
||||||
|
confirmTitle: string
|
||||||
|
confirmAction: () => void
|
||||||
|
declineTitle: string
|
||||||
|
declineAction: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(styles.PanelWrapper, {
|
||||||
|
[styles.PanelWrapperVisible]: props.isVisible
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="L"
|
||||||
|
variant="bordered"
|
||||||
|
value={props.declineTitle}
|
||||||
|
onClick={() => {
|
||||||
|
props.declineAction()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button type="submit" size="L" value={props.confirmTitle} onClick={props.confirmAction} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import { useProfileForm } from '../../context/profile'
|
||||||
import { validateUrl } from '../../utils/validateUrl'
|
import { validateUrl } from '../../utils/validateUrl'
|
||||||
import { createFileUploader } from '@solid-primitives/upload'
|
import { createFileUploader } from '@solid-primitives/upload'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { Button } from '../../components/_shared/Button'
|
import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { handleFileUpload } from '../../utils/handleFileUpload'
|
import { handleFileUpload } from '../../utils/handleFileUpload'
|
||||||
|
@ -21,8 +21,8 @@ export const ProfileSettingsPage = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
|
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
|
||||||
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
|
||||||
const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false)
|
const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false)
|
||||||
|
const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar }
|
||||||
|
@ -31,6 +31,7 @@ export const ProfileSettingsPage = () => {
|
||||||
const {
|
const {
|
||||||
actions: { loadSession }
|
actions: { loadSession }
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { form, updateFormField, submit, slugError } = useProfileForm()
|
const { form, updateFormField, submit, slugError } = useProfileForm()
|
||||||
const [prevForm, setPrevForm] = createStore(clone(form))
|
const [prevForm, setPrevForm] = createStore(clone(form))
|
||||||
|
|
||||||
|
@ -45,8 +46,6 @@ export const ProfileSettingsPage = () => {
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setIsSubmitting(true)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await submit(form)
|
await submit(form)
|
||||||
setPrevForm(clone(form))
|
setPrevForm(clone(form))
|
||||||
|
@ -56,7 +55,6 @@ export const ProfileSettingsPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSession()
|
loadSession()
|
||||||
setIsSubmitting(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
||||||
|
@ -68,6 +66,7 @@ export const ProfileSettingsPage = () => {
|
||||||
const result = await handleFileUpload(uploadFile)
|
const result = await handleFileUpload(uploadFile)
|
||||||
updateFormField('userpic', result.url)
|
updateFormField('userpic', result.url)
|
||||||
setIsUserpicUpdating(false)
|
setIsUserpicUpdating(false)
|
||||||
|
setIsFloatingPanelVisible(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[upload avatar] error', error)
|
console.error('[upload avatar] error', error)
|
||||||
}
|
}
|
||||||
|
@ -92,6 +91,11 @@ export const ProfileSettingsPage = () => {
|
||||||
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleSaveProfile = () => {
|
||||||
|
setIsFloatingPanelVisible(false)
|
||||||
|
setPrevForm(clone(form))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout>
|
<PageLayout>
|
||||||
<Show when={form}>
|
<Show when={form}>
|
||||||
|
@ -107,7 +111,15 @@ export const ProfileSettingsPage = () => {
|
||||||
<div class="col-md-20 col-lg-18 col-xl-16">
|
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||||
<h1>{t('Profile settings')}</h1>
|
<h1>{t('Profile settings')}</h1>
|
||||||
<p class="description">{t('Here you can customize your profile the way you want.')}</p>
|
<p class="description">{t('Here you can customize your profile the way you want.')}</p>
|
||||||
<form onSubmit={handleSubmit} enctype="multipart/form-data">
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onChange={() => {
|
||||||
|
if (!deepEqual(form, prevForm)) {
|
||||||
|
setIsFloatingPanelVisible(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
>
|
||||||
<h4>{t('Userpic')}</h4>
|
<h4>{t('Userpic')}</h4>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<Userpic
|
<Userpic
|
||||||
|
@ -235,7 +247,13 @@ export const ProfileSettingsPage = () => {
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Button type="submit" size="L" value={t('Save settings')} loading={isSubmitting()} />
|
<FloatingPanel
|
||||||
|
isVisible={isFloatingPanelVisible()}
|
||||||
|
confirmTitle={t('Save settings')}
|
||||||
|
confirmAction={handleSaveProfile}
|
||||||
|
declineTitle={t('Cancel')}
|
||||||
|
declineAction={() => setIsFloatingPanelVisible(false)}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user