This commit is contained in:
2024-04-21 14:42:52 +02:00
parent 4b69674ede
commit 8a25f53c99
10700 changed files with 55767 additions and 14201 deletions

View File

@ -0,0 +1,121 @@
import { ChangeEvent, useCallback, useRef, useState } from 'react'
import { IconButton, Image } from '@chakra-ui/react'
import cx from 'classnames'
import { IconEdit } from '@/lib'
export type EditPictureUploadProps = {
name: string
initialValue?: string
disabled: boolean
onChange: (event: any) => void
}
const EditPictureUpload = ({
name,
initialValue,
onChange,
disabled,
}: EditPictureUploadProps) => {
const [src, setSrc] = useState<string>()
const hiddenInput = useRef<HTMLInputElement>(null)
const handleFileChange = useCallback(
(changeEvent: ChangeEvent<HTMLInputElement>) => {
if (!changeEvent.target.files || changeEvent.target.files.length === 0) {
return
}
const file = changeEvent.target.files.item(0)
if (!file) {
return
}
const reader = new FileReader()
reader.onload = (readerEvent: ProgressEvent<FileReader>) => {
if (
readerEvent.target?.result &&
typeof readerEvent.target.result === 'string'
) {
setSrc(readerEvent.target.result)
}
}
reader.readAsDataURL(file)
onChange?.(changeEvent)
},
[onChange],
)
const handleEdit = useCallback(() => {
if (!disabled && hiddenInput.current) {
hiddenInput.current.click()
}
}, [disabled, hiddenInput])
return (
<div className={cx('flex', 'flex-col', 'items-center', 'gap-1')}>
<div
className={cx(
'flex',
'items-center',
'justify-center',
'rounded',
'border',
'border-dashed',
'border-blue-600',
'dark:border-blue-200',
)}
>
{src || initialValue ? (
<div className={cx('relative', 'w-[400px]', 'h-[160px]')}>
<Image
src={src || initialValue || ''}
className={cx(
'rounded',
'w-[400px]',
'h-[160px]',
'object-cover',
)}
alt="Account picture"
/>
<IconButton
icon={<IconEdit />}
variant="solid-gray"
className={cx(
'top-[10px]',
'right-[5px]',
'absolute',
'z-[1000]',
)}
aria-label=""
disabled={disabled}
onClick={handleEdit}
/>
</div>
) : (
<div
className={cx(
'flex',
'items-center',
'justify-center',
'cursor-pointer',
'w-[400px]',
'h-[160px]',
)}
onClick={handleEdit}
>
<span className={cx('text-blue-600', 'dark:text-blue-200')}>
Browse
</span>
</div>
)}
</div>
<input
ref={hiddenInput}
className={cx('hidden')}
type="file"
name={name}
onChange={handleFileChange}
/>
</div>
)
}
export default EditPictureUpload

View File

@ -0,0 +1,186 @@
import { useCallback, useEffect, useState } from 'react'
import {
Button,
FormControl,
FormErrorMessage,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react'
import { useSWRConfig } from 'swr'
import {
Field,
FieldAttributes,
FieldProps,
Form,
Formik,
FormikHelpers,
} from 'formik'
import * as Yup from 'yup'
import cx from 'classnames'
import UserAPI, { User } from '@/client/idp/user'
import EditPictureUpload from './edit-picture-upload'
export type AccountEditPictureProps = {
open: boolean
user: User
onClose?: () => void
}
type FormValues = {
picture: any
}
const AccountEditPicture = ({
open,
user,
onClose,
}: AccountEditPictureProps) => {
const { mutate } = useSWRConfig()
const [isModalOpen, setIsModalOpen] = useState(false)
const [deletionInProgress, setDeletionInProgress] = useState(false)
const formSchema = Yup.object().shape({
picture: Yup.mixed()
.required()
.test(
'fileSize',
'Image is too big, should be less than 3 MB',
(value: any) => value === null || (value && value.size <= 3000000),
)
.test(
'fileType',
'Unsupported file format',
(value: any) =>
value === null ||
(value &&
['image/jpg', 'image/jpeg', 'image/gif', 'image/png'].includes(
value.type,
)),
),
})
useEffect(() => {
setIsModalOpen(open)
}, [open])
const handleSubmit = useCallback(
async (
{ picture }: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>,
) => {
setSubmitting(true)
try {
const result = await UserAPI.updatePicture(picture)
mutate(`/user`, result)
setSubmitting(false)
onClose?.()
} finally {
setSubmitting(false)
}
},
[onClose, mutate],
)
const handleDelete = useCallback(async () => {
try {
setDeletionInProgress(true)
const result = await UserAPI.deletePicture()
mutate(`/user`, result)
onClose?.()
} finally {
setDeletionInProgress(false)
}
}, [onClose, mutate])
return (
<Modal
isOpen={isModalOpen}
onClose={() => onClose?.()}
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Picture</ModalHeader>
<ModalCloseButton />
<Formik
enableReinitialize={true}
initialValues={{
picture: user.picture,
}}
validationSchema={formSchema}
validateOnBlur={false}
onSubmit={handleSubmit}
>
{({ errors, touched, isSubmitting, setFieldValue, values }) => (
<Form>
<ModalBody>
<div
className={cx('flex', 'flex-col', 'items-center', 'gap-1')}
>
<Field name="picture">
{({ field }: FieldAttributes<FieldProps>) => (
<FormControl
isInvalid={
errors.picture && touched.picture ? true : false
}
>
<EditPictureUpload
{...field}
initialValue={user.picture}
disabled={isSubmitting}
onChange={(e) =>
setFieldValue('picture', e.target.files[0])
}
/>
<FormErrorMessage>{errors.picture}</FormErrorMessage>
</FormControl>
)}
</Field>
</div>
</ModalBody>
<ModalFooter>
<div
className={cx('flex', 'flex-row', 'items-center', 'gap-1')}
>
<Button
type="button"
variant="outline"
colorScheme="blue"
disabled={isSubmitting}
onClick={() => onClose?.()}
>
Cancel
</Button>
<Button
variant="outline"
colorScheme="red"
isLoading={deletionInProgress}
disabled={!user.picture}
onClick={handleDelete}
>
Delete
</Button>
<Button
type="submit"
variant="solid"
colorScheme="blue"
disabled={isSubmitting || values.picture === user.picture}
isLoading={isSubmitting}
>
Save
</Button>
</div>
</ModalFooter>
</Form>
)}
</Formik>
</ModalContent>
</Modal>
)
}
export default AccountEditPicture