webapp/src/pages/profile/profileSettings.page.tsx

333 lines
14 KiB
TypeScript
Raw Normal View History

2023-02-17 09:21:02 +00:00
import { PageLayout } from '../../components/_shared/PageLayout'
import { ProfileSettingsNavigation } from '../../components/Nav/ProfileSettingsNavigation'
import { For, createSignal, Show, onMount, onCleanup, createEffect, Switch, Match } from 'solid-js'
import deepEqual from 'fast-deep-equal'
2022-12-01 08:37:04 +00:00
import { clsx } from 'clsx'
import styles from './Settings.module.scss'
2023-02-17 09:21:02 +00:00
import { useProfileForm } from '../../context/profile'
import { validateUrl } from '../../utils/validateUrl'
import { createFileUploader } from '@solid-primitives/upload'
2023-02-17 09:21:02 +00:00
import { useSession } from '../../context/session'
import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel'
2023-02-17 09:21:02 +00:00
import { useSnackbar } from '../../context/snackbar'
import { useLocalize } from '../../context/localize'
import { createStore } from 'solid-js/store'
import { clone } from '../../utils/clone'
import SimplifiedEditor from '../../components/Editor/SimplifiedEditor'
import { GrowingTextarea } from '../../components/_shared/GrowingTextarea'
import { AuthGuard } from '../../components/AuthGuard'
import { handleImageUpload } from '../../utils/handleImageUpload'
import { SocialNetworkInput } from '../../components/_shared/SocialNetworkInput'
import { profileSocialLinks } from '../../utils/profileSocialLinks'
import { Icon } from '../../components/_shared/Icon'
import { Popover } from '../../components/_shared/Popover'
import { Image } from '../../components/_shared/Image'
import { Loading } from '../../components/_shared/Loading'
2023-11-13 16:55:32 +00:00
import { getImageUrl } from '../../utils/getImageUrl'
2023-02-10 01:19:20 +00:00
2023-02-09 17:53:11 +00:00
export const ProfileSettingsPage = () => {
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
2023-02-09 17:53:11 +00:00
const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false)
const [uploadError, setUploadError] = createSignal(false)
const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false)
2023-02-09 22:39:52 +00:00
const {
actions: { showSnackbar }
} = useSnackbar()
2023-02-09 17:53:11 +00:00
const {
actions: { loadSession }
} = useSession()
2022-12-07 08:53:41 +00:00
const { form, updateFormField, submit, slugError } = useProfileForm()
const [prevForm, setPrevForm] = createStore(clone(form))
const [social, setSocial] = createSignal(form.links)
2023-01-26 05:30:39 +00:00
const handleChangeSocial = (value: string) => {
2022-12-07 08:37:40 +00:00
if (validateUrl(value)) {
updateFormField('links', value)
setAddLinkForm(false)
} else {
setIncorrectUrl(true)
}
2022-12-01 17:16:14 +00:00
}
2023-02-09 22:39:52 +00:00
2023-02-09 17:53:11 +00:00
const handleSubmit = async (event: Event) => {
2022-12-01 17:16:14 +00:00
event.preventDefault()
2023-02-09 22:39:52 +00:00
try {
await submit(form)
setPrevForm(clone(form))
2023-02-17 09:21:02 +00:00
showSnackbar({ body: t('Profile successfully saved') })
2023-02-09 22:39:52 +00:00
} catch {
showSnackbar({ type: 'error', body: t('Error') })
}
loadSession()
2022-12-01 17:16:14 +00:00
}
2023-01-31 12:14:46 +00:00
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
const handleUploadAvatar = async () => {
selectFiles(async ([uploadFile]) => {
2023-01-31 12:14:46 +00:00
try {
setUploadError(false)
2023-02-09 17:53:11 +00:00
setIsUserpicUpdating(true)
const result = await handleImageUpload(uploadFile)
updateFormField('userpic', result.url)
2023-02-09 17:53:11 +00:00
setIsUserpicUpdating(false)
setIsFloatingPanelVisible(true)
2023-01-31 12:14:46 +00:00
} catch (error) {
setUploadError(true)
2023-01-31 12:14:46 +00:00
console.error('[upload avatar] error', error)
}
})
2022-12-02 06:12:50 +00:00
}
2023-01-28 00:31:46 +00:00
2023-02-11 11:33:32 +00:00
const [hostname, setHostname] = createSignal<string | null>(null)
2023-08-11 16:42:41 +00:00
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))
})
const handleSaveProfile = () => {
setIsFloatingPanelVisible(false)
setPrevForm(clone(form))
}
createEffect(() => {
if (!deepEqual(form, prevForm)) {
setIsFloatingPanelVisible(true)
}
})
const handleDeleteSocialLink = (link) => {
updateFormField('links', link, true)
}
createEffect(() => {
setSocial(form.links)
})
2022-11-24 21:37:43 +00:00
return (
2023-02-17 09:21:02 +00:00
<PageLayout>
<AuthGuard>
<Show when={form}>
<div class="wide-container">
<div class="row">
<div class="col-md-5">
<div class={clsx('left-navigation', styles.leftNavigation)}>
<ProfileSettingsNavigation />
</div>
2022-12-01 08:37:04 +00:00
</div>
<div class="col-md-19">
<div class="row">
<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">
<h4>{t('Userpic')}</h4>
<div class="pretty-form__item">
<div
class={clsx(styles.userpic, { [styles.hasControls]: form.userpic })}
onClick={!form.userpic && handleUploadAvatar}
>
<Switch>
<Match when={isUserpicUpdating()}>
<Loading />
</Match>
<Match when={form.userpic}>
2023-11-13 16:55:32 +00:00
<div
class={styles.userpicImage}
style={{
'background-image': `url(${getImageUrl(form.userpic, {
width: 180,
height: 180
})})`
}}
/>
<div class={styles.controls}>
<Popover content={t('Delete userpic')}>
{(triggerRef: (el) => void) => (
<button
ref={triggerRef}
class={styles.control}
onClick={() => updateFormField('userpic', '')}
>
<Icon name="close" />
</button>
)}
</Popover>
<Popover content={t('Upload userpic')}>
{(triggerRef: (el) => void) => (
<button
ref={triggerRef}
class={styles.control}
onClick={handleUploadAvatar}
>
<Icon name="user-image-black" />
</button>
)}
</Popover>
</div>
</Match>
<Match when={!form.userpic}>
<Icon name="user-image-gray" />
{t('Here you can upload your photo')}
</Match>
</Switch>
</div>
<Show when={uploadError()}>
<div class={styles.error}>{t('Upload error')}</div>
</Show>
</div>
<h4>{t('Name')}</h4>
<p class="description">
{t(
'Your name will appear on your profile page and as your signature in publications, comments and responses.'
)}
</p>
<div class="pretty-form__item">
<input
type="text"
name="username"
id="username"
placeholder={t('Name')}
onChange={(event) => updateFormField('name', event.currentTarget.value)}
value={form.name}
/>
<label for="username">{t('Name')}</label>
</div>
2023-03-23 22:05:23 +00:00
<h4>{t('Address on Discourse')}</h4>
<div class="pretty-form__item">
<div class={styles.discoursName}>
<label for="user-address">https://{hostname()}/author/</label>
<div class={styles.discoursNameField}>
<input
type="text"
name="user-address"
id="user-address"
onChange={(event) => updateFormField('slug', event.currentTarget.value)}
value={form.slug}
class="nolabel"
/>
<Show when={slugError()}>
<p class="form-message form-message--error">{t(`${slugError()}`)}</p>
</Show>
</div>
2023-03-23 22:05:23 +00:00
</div>
2022-12-01 17:16:14 +00:00
</div>
2023-03-23 22:05:23 +00:00
<h4>{t('Introduce')}</h4>
<GrowingTextarea
variant="bordered"
placeholder={t('Introduce')}
value={(value) => updateFormField('bio', value)}
initialValue={form.bio}
allowEnterKey={false}
maxLength={120}
/>
2023-03-23 22:05:23 +00:00
2023-10-16 09:54:14 +00:00
<h4>{t('About')}</h4>
<SimplifiedEditor
variant="bordered"
onlyBubbleControls={true}
smallHeight={true}
2023-10-16 09:54:14 +00:00
placeholder={t('About')}
label={t('About')}
initialContent={form.about}
onChange={(value) => updateFormField('about', value)}
/>
{/*Нет реализации полей на бэке*/}
{/*<h4>{t('How can I help/skills')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input type="text" name="skills" id="skills" />*/}
{/*</div>*/}
{/*<h4>{t('Where')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input type="text" name="location" id="location" placeholder="Откуда" />*/}
{/* <label for="location">{t('Where')}</label>*/}
{/*</div>*/}
2023-03-23 22:05:23 +00:00
{/*<h4>{t('Date of Birth')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input*/}
{/* type="date"*/}
{/* name="birthdate"*/}
{/* id="birthdate"*/}
{/* placeholder="Дата рождения"*/}
{/* class="nolabel"*/}
{/* />*/}
{/*</div>*/}
2023-03-23 22:05:23 +00:00
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
<div class={styles.multipleControlsHeader}>
<h4>{t('Social networks')}</h4>
<button
type="button"
class="button"
onClick={() => setAddLinkForm(!addLinkForm())}
>
+
</button>
2022-12-01 08:37:04 +00:00
</div>
<Show when={addLinkForm()}>
<SocialNetworkInput
isExist={false}
autofocus={true}
handleChange={(value) => handleChangeSocial(value)}
/>
<Show when={incorrectUrl()}>
<p class="form-message form-message--error">{t('It does not look like url')}</p>
</Show>
</Show>
<For each={profileSocialLinks(social())}>
{(network) => (
<SocialNetworkInput
class={styles.socialInput}
link={network.link}
network={network.name}
handleChange={(value) => handleChangeSocial(value)}
isExist={!network.isPlaceholder}
slug={form.slug}
handleDelete={() => handleDeleteSocialLink(network.link)}
/>
)}
</For>
</div>
<br />
<FloatingPanel
isVisible={isFloatingPanelVisible()}
confirmTitle={t('Save settings')}
confirmAction={handleSaveProfile}
declineTitle={t('Cancel')}
declineAction={() => setIsFloatingPanelVisible(false)}
/>
</form>
</div>
2023-03-23 22:05:23 +00:00
</div>
2022-12-01 08:37:04 +00:00
</div>
2022-11-24 21:37:43 +00:00
</div>
</div>
</Show>
</AuthGuard>
2023-02-17 09:21:02 +00:00
</PageLayout>
2022-11-24 21:37:43 +00:00
)
}
2023-02-17 09:21:02 +00:00
export const Page = ProfileSettingsPage