2024-06-24 17:50:27 +00:00
|
|
|
import { UploadFile, createFileUploader } from '@solid-primitives/upload'
|
2023-11-23 18:15:06 +00:00
|
|
|
import { clsx } from 'clsx'
|
|
|
|
import deepEqual from 'fast-deep-equal'
|
2024-04-04 06:01:26 +00:00
|
|
|
import {
|
|
|
|
For,
|
|
|
|
Match,
|
|
|
|
Show,
|
|
|
|
Switch,
|
|
|
|
createEffect,
|
|
|
|
createSignal,
|
|
|
|
lazy,
|
2024-04-04 06:02:34 +00:00
|
|
|
on,
|
2024-04-04 06:01:26 +00:00
|
|
|
onCleanup,
|
2024-06-26 08:22:05 +00:00
|
|
|
onMount
|
2024-04-04 06:02:34 +00:00
|
|
|
} from 'solid-js'
|
2023-11-23 18:15:06 +00:00
|
|
|
import { createStore } from 'solid-js/store'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { useLocalize } from '~/context/localize'
|
|
|
|
import { useProfile } from '~/context/profile'
|
|
|
|
import { useSession } from '~/context/session'
|
|
|
|
import { useSnackbar, useUI } from '~/context/ui'
|
|
|
|
import { InputMaybe, ProfileInput } from '~/graphql/schema/core.gen'
|
2024-07-13 12:25:25 +00:00
|
|
|
import { getImageUrl } from '~/lib/getThumbUrl'
|
2024-07-05 14:08:12 +00:00
|
|
|
import { handleImageUpload } from '~/lib/handleImageUpload'
|
|
|
|
import { profileSocialLinks } from '~/lib/profileSocialLinks'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { clone } from '~/utils/clone'
|
2024-07-13 11:42:53 +00:00
|
|
|
import { validateUrl } from '~/utils/validate'
|
2024-07-06 01:25:10 +00:00
|
|
|
import { Modal } from '../../Nav/Modal'
|
|
|
|
import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation'
|
|
|
|
import { Button } from '../../_shared/Button'
|
|
|
|
import { Icon } from '../../_shared/Icon'
|
|
|
|
import { ImageCropper } from '../../_shared/ImageCropper'
|
|
|
|
import { Loading } from '../../_shared/Loading'
|
|
|
|
import { Popover } from '../../_shared/Popover'
|
|
|
|
import { SocialNetworkInput } from '../../_shared/SocialNetworkInput'
|
2024-07-03 21:25:03 +00:00
|
|
|
import styles from './Settings.module.scss'
|
2023-11-23 18:15:06 +00:00
|
|
|
|
2024-07-04 07:51:15 +00:00
|
|
|
const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor'))
|
|
|
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
2023-11-23 18:15:06 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
function filterNulls(arr: InputMaybe<string>[]): string[] {
|
|
|
|
return arr.filter((item): item is string => item !== null && item !== undefined)
|
|
|
|
}
|
|
|
|
|
2023-11-23 18:15:06 +00:00
|
|
|
export const ProfileSettings = () => {
|
|
|
|
const { t } = useLocalize()
|
2024-05-12 23:36:46 +00:00
|
|
|
const [prevForm, setPrevForm] = createStore<ProfileInput>({})
|
2023-11-23 18:15:06 +00:00
|
|
|
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
2024-04-01 04:16:53 +00:00
|
|
|
const [isSaving, setIsSaving] = createSignal(false)
|
2024-06-24 17:50:27 +00:00
|
|
|
const [social, setSocial] = createSignal<string[]>([])
|
2023-11-23 18:15:06 +00:00
|
|
|
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
|
|
|
|
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
|
|
|
const [isUserpicUpdating, setIsUserpicUpdating] = createSignal(false)
|
2024-06-24 17:50:27 +00:00
|
|
|
const [userpicFile, setUserpicFile] = createSignal<UploadFile>()
|
2023-11-23 18:15:06 +00:00
|
|
|
const [uploadError, setUploadError] = createSignal(false)
|
|
|
|
const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false)
|
|
|
|
const [hostname, setHostname] = createSignal<string | null>(null)
|
|
|
|
const [slugError, setSlugError] = createSignal<string>()
|
|
|
|
const [nameError, setNameError] = createSignal<string>()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { form, submit, updateFormField, setForm } = useProfile()
|
2024-02-04 17:40:15 +00:00
|
|
|
const { showSnackbar } = useSnackbar()
|
2024-06-05 16:11:48 +00:00
|
|
|
const { loadSession, session } = useSession()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { showConfirm } = useUI()
|
2024-05-12 23:36:46 +00:00
|
|
|
const [clearAbout, setClearAbout] = createSignal(false)
|
2024-06-24 17:50:27 +00:00
|
|
|
const { showModal, hideModal } = useUI()
|
2023-11-23 18:15:06 +00:00
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
|
|
|
setPrevForm(form)
|
2024-06-24 17:50:27 +00:00
|
|
|
const soc: string[] = filterNulls(form.links || [])
|
|
|
|
setSocial(soc)
|
2023-11-23 18:15:06 +00:00
|
|
|
setIsFormInitialized(true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
let slugInputRef: HTMLInputElement | null
|
|
|
|
let nameInputRef: HTMLInputElement | null
|
2023-11-23 18:15:06 +00:00
|
|
|
|
|
|
|
const handleChangeSocial = (value: string) => {
|
|
|
|
if (validateUrl(value)) {
|
|
|
|
updateFormField('links', value)
|
|
|
|
setAddLinkForm(false)
|
|
|
|
} else {
|
|
|
|
setIncorrectUrl(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleSubmit = async (event: MouseEvent | undefined) => {
|
|
|
|
event?.preventDefault()
|
2024-04-01 04:16:53 +00:00
|
|
|
setIsSaving(true)
|
2024-06-24 17:50:27 +00:00
|
|
|
if (nameInputRef?.value.length === 0) {
|
2023-11-23 18:15:06 +00:00
|
|
|
setNameError(t('Required'))
|
2024-06-24 17:50:27 +00:00
|
|
|
nameInputRef?.focus()
|
2024-04-01 04:16:53 +00:00
|
|
|
setIsSaving(false)
|
2023-11-23 18:15:06 +00:00
|
|
|
return
|
|
|
|
}
|
2024-06-24 17:50:27 +00:00
|
|
|
if (slugInputRef?.value.length === 0) {
|
2023-11-23 18:15:06 +00:00
|
|
|
setSlugError(t('Required'))
|
2024-06-24 17:50:27 +00:00
|
|
|
slugInputRef?.focus()
|
2024-04-01 04:16:53 +00:00
|
|
|
setIsSaving(false)
|
2023-11-23 18:15:06 +00:00
|
|
|
return
|
|
|
|
}
|
2024-04-01 04:16:53 +00:00
|
|
|
|
2023-11-23 18:15:06 +00:00
|
|
|
try {
|
|
|
|
await submit(form)
|
|
|
|
setPrevForm(clone(form))
|
|
|
|
showSnackbar({ body: t('Profile successfully saved') })
|
|
|
|
} catch (error) {
|
2024-06-24 17:50:27 +00:00
|
|
|
if (error?.toString().search('duplicate_slug')) {
|
2023-11-23 18:15:06 +00:00
|
|
|
setSlugError(t('The address is already taken'))
|
2024-06-24 17:50:27 +00:00
|
|
|
slugInputRef?.focus()
|
2023-11-23 18:15:06 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
showSnackbar({ type: 'error', body: t('Error') })
|
2024-04-01 04:16:53 +00:00
|
|
|
} finally {
|
|
|
|
setIsSaving(false)
|
2023-11-23 18:15:06 +00:00
|
|
|
}
|
2023-12-20 16:54:20 +00:00
|
|
|
|
2024-06-05 16:11:48 +00:00
|
|
|
setTimeout(loadSession, 5000) // renews author's profile
|
2023-11-23 18:15:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const handleCancel = async () => {
|
|
|
|
const isConfirmed = await showConfirm({
|
|
|
|
confirmBody: t('Do you really want to reset all changes?'),
|
|
|
|
confirmButtonVariant: 'primary',
|
2024-06-26 08:22:05 +00:00
|
|
|
declineButtonVariant: 'secondary'
|
2023-11-23 18:15:06 +00:00
|
|
|
})
|
|
|
|
if (isConfirmed) {
|
2024-05-12 23:36:46 +00:00
|
|
|
setClearAbout(true)
|
2023-11-23 18:15:06 +00:00
|
|
|
setForm(clone(prevForm))
|
2024-05-12 23:36:46 +00:00
|
|
|
setClearAbout(false)
|
2023-11-23 18:15:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-25 12:41:25 +00:00
|
|
|
const handleCropAvatar = () => {
|
|
|
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
|
|
|
|
|
|
|
selectFiles(([uploadFile]) => {
|
2024-06-24 17:50:27 +00:00
|
|
|
setUserpicFile(uploadFile as UploadFile)
|
2024-01-25 12:41:25 +00:00
|
|
|
|
|
|
|
showModal('cropImage')
|
2023-11-23 18:15:06 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleUploadAvatar = async (uploadFile: UploadFile) => {
|
2024-01-25 12:41:25 +00:00
|
|
|
try {
|
|
|
|
setUploadError(false)
|
|
|
|
setIsUserpicUpdating(true)
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const result = await handleImageUpload(uploadFile, session()?.access_token || '')
|
2024-03-18 11:55:07 +00:00
|
|
|
updateFormField('pic', result.url)
|
2024-01-25 12:41:25 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
setUserpicFile(undefined)
|
2024-01-25 12:41:25 +00:00
|
|
|
setIsUserpicUpdating(false)
|
|
|
|
} catch (error) {
|
|
|
|
setUploadError(true)
|
|
|
|
console.error('[upload avatar] error', error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-23 18:15:06 +00:00
|
|
|
onMount(() => {
|
|
|
|
setHostname(window?.location.host)
|
|
|
|
|
|
|
|
// eslint-disable-next-line unicorn/consistent-function-scoping
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
2023-11-23 18:15:06 +00:00
|
|
|
if (!deepEqual(form, prevForm)) {
|
|
|
|
event.returnValue = t(
|
2024-06-26 08:22:05 +00:00
|
|
|
'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?'
|
2023-11-23 18:15:06 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
|
|
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
|
|
|
})
|
|
|
|
|
2024-04-04 06:01:26 +00:00
|
|
|
createEffect(
|
|
|
|
on(
|
|
|
|
() => deepEqual(form, prevForm),
|
|
|
|
() => {
|
2024-05-12 23:36:46 +00:00
|
|
|
if (Object.keys(prevForm).length > 0) {
|
|
|
|
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
|
|
|
}
|
2024-06-26 08:22:05 +00:00
|
|
|
}
|
|
|
|
)
|
2024-04-04 06:01:26 +00:00
|
|
|
)
|
2024-05-12 23:36:46 +00:00
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const handleDeleteSocialLink = (link: string) => {
|
2023-11-23 18:15:06 +00:00
|
|
|
updateFormField('links', link, true)
|
|
|
|
}
|
|
|
|
|
2024-07-13 11:42:53 +00:00
|
|
|
const slugUpdate = (ev: InputEvent) => {
|
|
|
|
const input = (ev.target || ev.currentTarget) as HTMLInputElement
|
|
|
|
const value = input.value
|
|
|
|
const newValue = value.startsWith('@') || value.startsWith('!') ? value.substring(1) : value
|
|
|
|
input.value = newValue
|
|
|
|
updateFormField('slug', newValue)
|
|
|
|
}
|
|
|
|
|
2023-11-23 18:15:06 +00:00
|
|
|
return (
|
|
|
|
<Show when={Object.keys(form).length > 0 && isFormInitialized()} fallback={<Loading />}>
|
|
|
|
<>
|
|
|
|
<div class="wide-container">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-md-5">
|
|
|
|
<div class={clsx('left-navigation', styles.leftNavigation)}>
|
|
|
|
<ProfileSettingsNavigation />
|
|
|
|
</div>
|
|
|
|
</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>
|
2024-04-22 13:59:39 +00:00
|
|
|
<form enctype="multipart/form-data" autocomplete="off">
|
2023-11-23 18:15:06 +00:00
|
|
|
<h4>{t('Userpic')}</h4>
|
|
|
|
<div class="pretty-form__item">
|
|
|
|
<div
|
2023-12-13 23:56:44 +00:00
|
|
|
class={clsx(styles.userpic, { [styles.hasControls]: form.pic })}
|
2024-01-25 12:41:25 +00:00
|
|
|
onClick={handleCropAvatar}
|
2023-11-23 18:15:06 +00:00
|
|
|
>
|
|
|
|
<Switch>
|
|
|
|
<Match when={isUserpicUpdating()}>
|
|
|
|
<Loading />
|
|
|
|
</Match>
|
2023-12-13 23:56:44 +00:00
|
|
|
<Match when={form.pic}>
|
2023-11-23 18:15:06 +00:00
|
|
|
<div
|
|
|
|
class={styles.userpicImage}
|
|
|
|
style={{
|
2024-06-24 17:50:27 +00:00
|
|
|
'background-image': `url(${getImageUrl(form.pic || '', {
|
2023-11-23 18:15:06 +00:00
|
|
|
width: 180,
|
2024-06-26 08:22:05 +00:00
|
|
|
height: 180
|
|
|
|
})})`
|
2023-11-23 18:15:06 +00:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<div class={styles.controls}>
|
|
|
|
<Popover content={t('Delete userpic')}>
|
2024-06-24 17:50:27 +00:00
|
|
|
{(triggerRef: (el: HTMLElement) => void) => (
|
2023-11-23 18:15:06 +00:00
|
|
|
<button
|
|
|
|
ref={triggerRef}
|
|
|
|
class={styles.control}
|
2023-12-13 23:56:44 +00:00
|
|
|
onClick={() => updateFormField('pic', '')}
|
2023-11-23 18:15:06 +00:00
|
|
|
>
|
|
|
|
<Icon name="close" />
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</Popover>
|
2024-01-25 12:41:25 +00:00
|
|
|
|
|
|
|
{/* @@TODO inspect popover below. onClick causes page refreshing */}
|
|
|
|
{/* <Popover content={t('Upload userpic')}>
|
2024-06-24 17:50:27 +00:00
|
|
|
{(triggerRef: (el: HTMLElement) => void) => (
|
2023-11-23 18:15:06 +00:00
|
|
|
<button
|
|
|
|
ref={triggerRef}
|
|
|
|
class={styles.control}
|
2024-01-25 12:41:25 +00:00
|
|
|
onClick={() => handleCropAvatar()}
|
2023-11-23 18:15:06 +00:00
|
|
|
>
|
|
|
|
<Icon name="user-image-black" />
|
|
|
|
</button>
|
|
|
|
)}
|
2024-01-25 12:41:25 +00:00
|
|
|
</Popover> */}
|
2023-11-23 18:15:06 +00:00
|
|
|
</div>
|
|
|
|
</Match>
|
2023-12-13 23:56:44 +00:00
|
|
|
<Match when={!form.pic}>
|
2023-11-23 18:15:06 +00:00
|
|
|
<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(
|
2024-06-26 08:22:05 +00:00
|
|
|
'Your name will appear on your profile page and as your signature in publications, comments and responses.'
|
2023-11-23 18:15:06 +00:00
|
|
|
)}
|
|
|
|
</p>
|
|
|
|
<div class="pretty-form__item">
|
|
|
|
<input
|
|
|
|
type="text"
|
2024-04-22 13:59:39 +00:00
|
|
|
name="nameOfUser"
|
|
|
|
id="nameOfUser"
|
|
|
|
data-lpignore="true"
|
2024-04-22 13:47:37 +00:00
|
|
|
autocomplete="one-time-code"
|
2023-11-23 18:15:06 +00:00
|
|
|
placeholder={t('Name')}
|
|
|
|
onInput={(event) => updateFormField('name', event.currentTarget.value)}
|
2024-06-24 17:50:27 +00:00
|
|
|
value={form.name || ''}
|
|
|
|
ref={(el) => (nameInputRef = el)}
|
2023-11-23 18:15:06 +00:00
|
|
|
/>
|
2024-04-22 13:59:39 +00:00
|
|
|
<label for="nameOfUser">{t('Name')}</label>
|
2023-11-23 18:15:06 +00:00
|
|
|
<Show when={nameError()}>
|
|
|
|
<div
|
|
|
|
style={{ position: 'absolute', 'margin-top': '-4px' }}
|
|
|
|
class="form-message form-message--error"
|
|
|
|
>
|
|
|
|
{t(`${nameError()}`)}
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
<h4>{t('Address on Discours')}</h4>
|
2023-11-23 18:15:06 +00:00
|
|
|
<div class="pretty-form__item">
|
|
|
|
<div class={styles.discoursName}>
|
2024-07-13 11:42:53 +00:00
|
|
|
<label for="user-address">{hostname()}/@</label>
|
2023-11-23 18:15:06 +00:00
|
|
|
<div class={styles.discoursNameField}>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
name="user-address"
|
|
|
|
id="user-address"
|
2024-04-22 13:59:39 +00:00
|
|
|
data-lpignore="true"
|
2024-04-22 13:47:37 +00:00
|
|
|
autocomplete="one-time-code2"
|
2024-07-13 11:42:53 +00:00
|
|
|
onInput={slugUpdate}
|
2024-06-24 17:50:27 +00:00
|
|
|
value={form.slug || ''}
|
|
|
|
ref={(el) => (slugInputRef = el)}
|
2023-11-23 18:15:06 +00:00
|
|
|
class="nolabel"
|
|
|
|
/>
|
|
|
|
<Show when={slugError()}>
|
|
|
|
<p class="form-message form-message--error">{t(`${slugError()}`)}</p>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<h4>{t('Introduce')}</h4>
|
|
|
|
<GrowingTextarea
|
|
|
|
variant="bordered"
|
|
|
|
placeholder={t('Introduce')}
|
|
|
|
value={(value) => updateFormField('bio', value)}
|
|
|
|
initialValue={form.bio || ''}
|
|
|
|
allowEnterKey={false}
|
|
|
|
maxLength={120}
|
|
|
|
/>
|
|
|
|
|
2024-07-03 17:38:43 +00:00
|
|
|
<h4>{t('About the author')}</h4>
|
2023-11-23 18:15:06 +00:00
|
|
|
<SimplifiedEditor
|
2024-05-12 23:36:46 +00:00
|
|
|
resetToInitial={clearAbout()}
|
|
|
|
noLimits={true}
|
2023-11-23 18:15:06 +00:00
|
|
|
variant="bordered"
|
|
|
|
onlyBubbleControls={true}
|
|
|
|
smallHeight={true}
|
2024-07-03 17:38:43 +00:00
|
|
|
placeholder={t('About the author')}
|
|
|
|
label={t('About the author')}
|
2023-11-23 18:15:06 +00:00
|
|
|
initialContent={form.about || ''}
|
|
|
|
autoFocus={false}
|
|
|
|
onChange={(value) => updateFormField('about', value)}
|
|
|
|
/>
|
|
|
|
<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>
|
|
|
|
</div>
|
|
|
|
<Show when={addLinkForm()}>
|
|
|
|
<SocialNetworkInput
|
|
|
|
isExist={false}
|
|
|
|
autofocus={true}
|
|
|
|
handleInput={(value) => handleChangeSocial(value)}
|
|
|
|
/>
|
|
|
|
<Show when={incorrectUrl()}>
|
|
|
|
<p class="form-message form-message--error">{t('It does not look like url')}</p>
|
|
|
|
</Show>
|
|
|
|
</Show>
|
2023-11-27 21:44:58 +00:00
|
|
|
<Show when={social()}>
|
|
|
|
<For each={profileSocialLinks(social())}>
|
|
|
|
{(network) => (
|
|
|
|
<SocialNetworkInput
|
|
|
|
class={styles.socialInput}
|
|
|
|
link={network.link}
|
|
|
|
network={network.name}
|
|
|
|
handleInput={(value) => handleChangeSocial(value)}
|
|
|
|
isExist={!network.isPlaceholder}
|
2024-06-24 17:50:27 +00:00
|
|
|
slug={form.slug || ''}
|
2023-11-27 21:44:58 +00:00
|
|
|
handleDelete={() => handleDeleteSocialLink(network.link)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
</Show>
|
2023-11-23 18:15:06 +00:00
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<Show when={isFloatingPanelVisible()}>
|
|
|
|
<div class={styles.formActions}>
|
|
|
|
<div class="wide-container">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-md-19 offset-md-5">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-md-20 col-lg-18 col-xl-16">
|
|
|
|
<div class={styles.content}>
|
2023-11-27 21:44:58 +00:00
|
|
|
<Button
|
|
|
|
class={styles.cancel}
|
|
|
|
variant="light"
|
2023-11-29 22:12:06 +00:00
|
|
|
value={
|
|
|
|
<>
|
|
|
|
<span class={styles.cancelLabel}>{t('Cancel changes')}</span>
|
|
|
|
<span class={styles.cancelLabelMobile}>{t('Cancel')}</span>
|
|
|
|
</>
|
|
|
|
}
|
2023-11-27 21:44:58 +00:00
|
|
|
onClick={handleCancel}
|
|
|
|
/>
|
2024-04-01 04:16:53 +00:00
|
|
|
<Button
|
|
|
|
onClick={handleSubmit}
|
|
|
|
variant="primary"
|
|
|
|
disabled={isSaving()}
|
|
|
|
value={isSaving() ? t('Saving...') : t('Save settings')}
|
|
|
|
/>
|
2023-11-23 18:15:06 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Show>
|
2024-06-24 17:50:27 +00:00
|
|
|
<Modal variant="medium" name="cropImage" onClose={() => setUserpicFile(undefined)}>
|
2024-01-25 12:41:25 +00:00
|
|
|
<h2>{t('Crop image')}</h2>
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
<Show when={Boolean(userpicFile())}>
|
2024-01-25 12:41:25 +00:00
|
|
|
<ImageCropper
|
2024-06-24 17:50:27 +00:00
|
|
|
uploadFile={userpicFile() as UploadFile}
|
2024-01-25 12:41:25 +00:00
|
|
|
onSave={(data) => {
|
|
|
|
handleUploadAvatar(data)
|
|
|
|
|
|
|
|
hideModal()
|
|
|
|
}}
|
|
|
|
onDecline={() => hideModal()}
|
|
|
|
/>
|
|
|
|
</Show>
|
|
|
|
</Modal>
|
2023-11-23 18:15:06 +00:00
|
|
|
</>
|
|
|
|
</Show>
|
|
|
|
)
|
|
|
|
}
|