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:
Arkadzi Rakouski 2023-08-18 19:37:14 +03:00 committed by GitHub
parent f2a5751f3e
commit 52d8a01a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 13 deletions

View File

@ -8,10 +8,9 @@ import { useLocalize } from '../../../context/localize'
import { Modal } from '../../Nav/Modal'
import { Menu } from './Menu'
import type { MenuItem } from './Menu/Menu'
import { hideModal, showModal } from '../../../stores/ui'
import { showModal } from '../../../stores/ui'
import { UploadModalContent } from '../UploadModalContent'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { imageProxy } from '../../../utils/imageProxy'
import { UploadedFile } from '../../../pages/types'
import { renderUploadedImage } from '../../../utils/renderUploadedImage'

View File

@ -30,7 +30,6 @@ import { UploadedFile } from '../../pages/types'
import { Figure } from './extensions/Figure'
import { Image } from '@tiptap/extension-image'
import { Figcaption } from './extensions/Figcaption'
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
type Props = {
initialContent?: string

View File

@ -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 { DEFAULT_HEADER_OFFSET } from '../../stores/router'

View File

@ -15,7 +15,7 @@ import { AudioUploader } from '../Editor/AudioUploader'
import { slugify } from '../../utils/slugify'
import { SolidSwiper } from '../_shared/SolidSwiper'
import { DropArea } from '../_shared/DropArea'
import { LayoutType, MediaItem, UploadedFile } from '../../pages/types'
import { LayoutType, MediaItem } from '../../pages/types'
import { clone } from '../../utils/clone'
import deepEqual from 'fast-deep-equal'
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'

View File

@ -9,7 +9,7 @@ type Props = {
type?: 'submit' | 'button'
loading?: boolean
disabled?: boolean
onClick?: () => void
onClick?: (event?: MouseEvent) => void
class?: string
ref?: HTMLButtonElement | ((el: HTMLButtonElement) => void)
}

View File

@ -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;
}

View 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>
)
}

View File

@ -9,7 +9,7 @@ import { useProfileForm } from '../../context/profile'
import { validateUrl } from '../../utils/validateUrl'
import { createFileUploader } from '@solid-primitives/upload'
import { useSession } from '../../context/session'
import { Button } from '../../components/_shared/Button'
import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel'
import { useSnackbar } from '../../context/snackbar'
import { useLocalize } from '../../context/localize'
import { handleFileUpload } from '../../utils/handleFileUpload'
@ -21,8 +21,8 @@ export const ProfileSettingsPage = () => {
const { t } = useLocalize()
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false)
const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false)
const {
actions: { showSnackbar }
@ -31,6 +31,7 @@ export const ProfileSettingsPage = () => {
const {
actions: { loadSession }
} = useSession()
const { form, updateFormField, submit, slugError } = useProfileForm()
const [prevForm, setPrevForm] = createStore(clone(form))
@ -45,8 +46,6 @@ export const ProfileSettingsPage = () => {
const handleSubmit = async (event: Event) => {
event.preventDefault()
setIsSubmitting(true)
try {
await submit(form)
setPrevForm(clone(form))
@ -56,7 +55,6 @@ export const ProfileSettingsPage = () => {
}
loadSession()
setIsSubmitting(false)
}
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
@ -68,6 +66,7 @@ export const ProfileSettingsPage = () => {
const result = await handleFileUpload(uploadFile)
updateFormField('userpic', result.url)
setIsUserpicUpdating(false)
setIsFloatingPanelVisible(true)
} catch (error) {
console.error('[upload avatar] error', error)
}
@ -92,6 +91,11 @@ export const ProfileSettingsPage = () => {
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
})
const handleSaveProfile = () => {
setIsFloatingPanelVisible(false)
setPrevForm(clone(form))
}
return (
<PageLayout>
<Show when={form}>
@ -107,7 +111,15 @@ export const ProfileSettingsPage = () => {
<div class="col-md-20 col-lg-18 col-xl-16">
<h1>{t('Profile settings')}</h1>
<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>
<div class="pretty-form__item">
<Userpic
@ -235,7 +247,13 @@ export const ProfileSettingsPage = () => {
</For>
</div>
<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>
</div>
</div>