ajout app
This commit is contained in:
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Reference in New Issue
Block a user