ajout app

This commit is contained in:
2024-04-17 20:22:30 +02:00
parent cc017cfc5e
commit f9d05a2fd3
8025 changed files with 729805 additions and 0 deletions

View File

@ -0,0 +1,142 @@
import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Button,
FormControl,
FormErrorMessage,
Input,
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 OrganizationAPI, { Organization } from '@/client/api/organization'
export type OrganizationDeleteProps = {
open: boolean
organization: Organization
onClose?: () => void
}
type FormValues = {
name: string
}
const OrganizationDelete = ({
open,
organization,
onClose,
}: OrganizationDeleteProps) => {
const navigate = useNavigate()
const { mutate } = useSWRConfig()
const [isModalOpen, setIsModalOpen] = useState(false)
const formSchema = Yup.object().shape({
name: Yup.string()
.required('Confirmation is required')
.oneOf([organization.name], 'Invalid organization name'),
})
useEffect(() => {
setIsModalOpen(open)
}, [open])
const handleSubmit = useCallback(
async (_: FormValues, { setSubmitting }: FormikHelpers<FormValues>) => {
setSubmitting(true)
try {
await OrganizationAPI.delete(organization.id)
navigate('/organization')
mutate('/organizations')
onClose?.()
} finally {
setSubmitting(false)
}
},
[organization.id, navigate, mutate, onClose],
)
return (
<Modal
isOpen={isModalOpen}
onClose={() => onClose?.()}
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Delete Organization</ModalHeader>
<ModalCloseButton />
<Formik
initialValues={{ name: '' }}
validationSchema={formSchema}
validateOnBlur={false}
onSubmit={handleSubmit}
>
{({ errors, touched, isSubmitting }) => (
<Form>
<ModalBody>
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
<span>
Are you sure you would like to delete this organization?
</span>
<span>
Please type <b>{organization.name}</b> to confirm.
</span>
<Field name="name">
{({ field }: FieldAttributes<FieldProps>) => (
<FormControl
isInvalid={errors.name && touched.name ? true : false}
>
<Input {...field} disabled={isSubmitting} />
<FormErrorMessage>{errors.name}</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
type="submit"
variant="solid"
colorScheme="red"
isLoading={isSubmitting}
>
Delete Permanently
</Button>
</div>
</ModalFooter>
</Form>
)}
</Formik>
</ModalContent>
</Modal>
)
}
export default OrganizationDelete

View File

@ -0,0 +1,140 @@
import { useCallback, useEffect, useState } from 'react'
import {
Button,
FormControl,
FormErrorMessage,
Input,
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 OrganizationAPI, { Organization } from '@/client/api/organization'
export type OrganizationEditNameProps = {
open: boolean
organization: Organization
onClose?: () => void
}
type FormValues = {
name: string
}
const OrganizationEditName = ({
open,
organization,
onClose,
}: OrganizationEditNameProps) => {
const { mutate } = useSWRConfig()
const [isModalOpen, setIsModalOpen] = useState(false)
const formSchema = Yup.object().shape({
name: Yup.string().required('Name is required').max(255),
})
useEffect(() => {
setIsModalOpen(open)
}, [open])
const handleSubmit = useCallback(
async (
{ name }: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>,
) => {
setSubmitting(true)
try {
const result = await OrganizationAPI.updateName(organization.id, {
name,
})
mutate(`/organizations/${organization.id}`, result)
setSubmitting(false)
onClose?.()
} finally {
setSubmitting(false)
}
},
[organization.id, onClose, mutate],
)
return (
<Modal
isOpen={isModalOpen}
onClose={() => onClose?.()}
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Edit Name</ModalHeader>
<ModalCloseButton />
<Formik
enableReinitialize={true}
initialValues={{ name: organization.name }}
validationSchema={formSchema}
validateOnBlur={false}
onSubmit={handleSubmit}
>
{({ errors, touched, isSubmitting }) => (
<Form>
<ModalBody>
<Field name="name">
{({ field }: FieldAttributes<FieldProps>) => (
<FormControl
isInvalid={errors.name && touched.name ? true : false}
>
<Input
{...field}
placeholder="Name"
disabled={isSubmitting}
autoFocus
/>
<FormErrorMessage>{errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
</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
type="submit"
variant="solid"
colorScheme="blue"
isLoading={isSubmitting}
>
Save
</Button>
</div>
</ModalFooter>
</Form>
)}
</Formik>
</ModalContent>
</Modal>
)
}
export default OrganizationEditName

View File

@ -0,0 +1,158 @@
import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Button,
FormControl,
FormErrorMessage,
FormLabel,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Textarea,
} 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 InvitationAPI from '@/client/api/invitation'
import EmailTokenizer from '@/components/common/email-tokenizer'
import parseEmailList from '@/helpers/parse-email-list'
export type OrganizationInviteMembersProps = {
open: boolean
id: string
onClose?: () => void
}
type FormValues = {
emails: string
}
const OrganizationInviteMembers = ({
open,
id,
onClose,
}: OrganizationInviteMembersProps) => {
const navigate = useNavigate()
const { mutate } = useSWRConfig()
const [isModalOpen, setIsModalOpen] = useState(false)
const formSchema = Yup.object().shape({
emails: Yup.string().required('Email(s) are required'),
})
useEffect(() => {
setIsModalOpen(open)
}, [open])
const handleSubmit = useCallback(
async (
{ emails }: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>,
) => {
setSubmitting(true)
try {
await InvitationAPI.create({
organizationId: id,
emails: parseEmailList(emails),
})
mutate(
`/invitations/get_outgoing?${new URLSearchParams({
organization_id: id,
})}`,
)
navigate(`/organization/${id}/invitation`)
setSubmitting(false)
onClose?.()
} finally {
setSubmitting(false)
}
},
[id, navigate, onClose, mutate],
)
return (
<Modal
isOpen={isModalOpen}
onClose={() => onClose?.()}
size="3xl"
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Invite Members</ModalHeader>
<ModalCloseButton />
<Formik
enableReinitialize={true}
initialValues={{ emails: '' }}
validationSchema={formSchema}
validateOnBlur={false}
onSubmit={handleSubmit}
>
{({ values, errors, touched, isSubmitting }) => (
<Form>
<ModalBody>
<div className={cx('flex', 'flex-col', 'gap-1.5')}>
<Field name="emails">
{({ field }: FieldAttributes<FieldProps>) => (
<FormControl
isInvalid={
errors.emails && touched.emails ? true : false
}
>
<FormLabel>Comma separated emails:</FormLabel>
<Textarea
{...field}
className={cx('h-[120px]')}
placeholder="alice@example.com, david@example.com"
disabled={isSubmitting}
/>
<FormErrorMessage>{errors.emails}</FormErrorMessage>
</FormControl>
)}
</Field>
<EmailTokenizer value={values.emails} />
</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
type="submit"
variant="solid"
colorScheme="blue"
isLoading={isSubmitting}
>
Invite
</Button>
</div>
</ModalFooter>
</Form>
)}
</Formik>
</ModalContent>
</Modal>
)
}
export default OrganizationInviteMembers

View File

@ -0,0 +1,83 @@
import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react'
import cx from 'classnames'
import OrganizationAPI from '@/client/api/organization'
export type OrganizationLeaveProps = {
open: boolean
id: string
onClose?: () => void
}
const OrganizationLeave = ({ open, id, onClose }: OrganizationLeaveProps) => {
const navigate = useNavigate()
const [isModalOpen, setIsModalOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
setIsModalOpen(open)
}, [open])
const handleConfirmation = useCallback(async () => {
setIsLoading(true)
try {
await OrganizationAPI.leave(id)
navigate('/organization')
onClose?.()
} finally {
setIsLoading(false)
}
}, [id, navigate, onClose])
return (
<Modal
isOpen={isModalOpen}
onClose={() => onClose?.()}
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Leave Organization</ModalHeader>
<ModalCloseButton />
<ModalBody>
<span>Are you sure you would like to leave this organization?</span>
</ModalBody>
<ModalFooter>
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
<Button
type="button"
variant="outline"
colorScheme="blue"
disabled={isLoading}
onClick={() => onClose?.()}
>
Cancel
</Button>
<Button
type="submit"
variant="solid"
colorScheme="red"
disabled={isLoading}
isLoading={isLoading}
onClick={() => handleConfirmation()}
>
Leave
</Button>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}
export default OrganizationLeave

View File

@ -0,0 +1,92 @@
import { useCallback, useState } from 'react'
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react'
import cx from 'classnames'
import OrganizationAPI, { Organization } from '@/client/api/organization'
import { User } from '@/client/idp/user'
import userToString from '@/helpers/user-to-string'
export type OrganizationRemoveMemberProps = {
organization: Organization
user: User
isOpen: boolean
onClose?: () => void
onCompleted?: () => void
}
const OrganizationRemoveMember = ({
organization,
user,
isOpen,
onCompleted,
onClose,
}: OrganizationRemoveMemberProps) => {
const [loading, setLoading] = useState(false)
const handleRemoveMember = useCallback(async () => {
try {
setLoading(true)
await OrganizationAPI.removeMember(organization.id, {
userId: user.id,
})
onCompleted?.()
onClose?.()
} finally {
setLoading(false)
}
}, [organization, user, onClose, onCompleted])
return (
<Modal
isOpen={isOpen}
onClose={() => onClose?.()}
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Remove Member</ModalHeader>
<ModalCloseButton />
<ModalBody>
<div>
Are you sure you would like to remove member{' '}
<span className={cx('font-bold')}>{userToString(user)}</span> from
organization{' '}
<span className={cx('font-bold')}>{organization.name}</span>?
</div>
</ModalBody>
<ModalFooter>
<div className={cx('flex', 'flex-row', 'items-center', 'gap-1')}>
<Button
type="button"
variant="outline"
colorScheme="blue"
disabled={loading}
onClick={() => onClose?.()}
>
Cancel
</Button>
<Button
type="submit"
variant="solid"
colorScheme="red"
isLoading={loading}
onClick={handleRemoveMember}
>
Remove
</Button>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}
export default OrganizationRemoveMember

View File

@ -0,0 +1,18 @@
import { Badge } from '@chakra-ui/react'
import { InvitationStatus } from '@/client/api/invitation'
type OrganizationStatusProps = {
value: InvitationStatus
}
const OrganizationStatus = ({ value }: OrganizationStatusProps) => {
let colorScheme
if (value === 'accepted') {
colorScheme = 'green'
} else if (value === 'declined') {
colorScheme = 'red'
}
return <Badge colorScheme={colorScheme}>{value}</Badge>
}
export default OrganizationStatus