Refactor OwnerDropdown into its own component

This commit is contained in:
Joshen Lim committed 2022-07-14 22:25:15 +08:00
1 parent d6a52397b9
commit 14458ae20d
3 files changed
+223 -195

No files matched your search

@@ -0,0 +1,211 @@
import { useContext, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { timeout } from 'lib/helpers'
import { Button, IconMoreHorizontal, IconTrash, Dropdown } from '@supabase/ui'
import { useOrganizationDetail, useStore } from 'hooks'
import { Member } from 'types'
import { API_URL } from 'lib/constants'
import { post, delete_ } from 'lib/common/fetch'
import { PageContext } from 'pages/org/[slug]/settings'
import TextConfirmModal from 'components/ui/Modals/TextConfirmModal'
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
const OwnerDropdown = observer(({ members, member }: any) => {
const { ui } = useStore()
const { mutateOrgMembers } = useOrganizationDetail(ui.selectedOrganization?.slug || '')
const PageState: any = useContext(PageContext)
const [loading, setLoading] = useState(false)
// handle modal visibility
const [ownerTransferIsVisble, setOwnerTransferIsVisble] = useState(false)
const { id: orgId, slug: orgSlug, name: orgName } = PageState.organization
// Invite is expired if older than 24hrs
function isInviteExpired(timestamp: Date) {
const inviteDate = new Date(timestamp)
const now = new Date()
var timeBetween = now.valueOf() - inviteDate.valueOf()
if (timeBetween / 1000 / 60 / 60 < 24) {
return true
}
}
async function handleMemberDelete() {
await timeout(200)
confirmAlert({
title: 'Confirm to remove',
message: `This is permanent! Are you sure you want to remove ${member.profile.primary_email}?`,
onAsyncConfirm: async () => {
setLoading(true)
const response = await delete_(`${API_URL}/organizations/${orgSlug}/members/remove`, {
member_id: member.id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to delete user: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = members.filter((x: any) => x.id !== member.id)
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Successfully removed member' })
}
},
})
}
async function handleTransfer() {
setLoading(true)
const response = await post(`${API_URL}/organizations/${orgSlug}/transfer`, {
org_id: orgId,
member_id: member.id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to transfer ownership: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
const oldOwner = updatedMembers.find((x) => x.is_owner == true)
if (oldOwner) oldOwner.is_owner = false
const newOwner = updatedMembers.find((x) => x.id == member.id)
if (newOwner) newOwner.is_owner = true
mutateOrgMembers(updatedMembers)
setOwnerTransferIsVisble(false)
ui.setNotification({ category: 'success', message: 'Successfully transfered organization' })
}
}
async function handleResendInvite(member: Member) {
setLoading(true)
const response = await post(`${API_URL}/organizations/${orgSlug}/members/invite`, {
invited_email: member.profile.primary_email,
owner_id: member.invited_id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to resend invitation: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Resent the invitation.' })
setLoading(false)
}
}
async function handleRevokeInvitation(id: number) {
setLoading(true)
const response = await delete_(
`${API_URL}/organizations/${orgSlug}/members/invite?invited_id=${id}`,
{}
)
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to revoke invitation: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Successfully revoked the invitation.' })
}
}
return (
<div className="flex items-center justify-end">
<Dropdown
side="bottom"
align="end"
overlay={
<>
{!member.invited_at && (
<Dropdown.Item onClick={() => setOwnerTransferIsVisble(!ownerTransferIsVisble)}>
<div className="flex flex-col">
<p>Make owner</p>
<p className="block opacity-50">Transfer ownership of "{orgName}"</p>
</div>
</Dropdown.Item>
)}
{member.invited_at && (
<>
<Dropdown.Item onClick={() => handleRevokeInvitation(member.invited_id)}>
<div className="flex flex-col">
<p>Cancel invitation</p>
<p className="block opacity-50">Revoke this invitation.</p>
</div>
</Dropdown.Item>
{!isInviteExpired(member.invited_at) && (
<>
<Dropdown.Seperator />
<Dropdown.Item onClick={() => handleResendInvite(member)}>
<div className="flex flex-col">
<p>Resend invitation</p>
<p className="block opacity-50">Invites expire after 24hrs.</p>
</div>
</Dropdown.Item>
</>
)}
</>
)}
{!member.invited_at && (
<>
<Dropdown.Seperator />
<Dropdown.Item icon={<IconTrash size="tiny" />} onClick={handleMemberDelete}>
Remove member
</Dropdown.Item>
</>
)}
</>
}
>
<Button
as="span"
disabled={loading}
loading={loading}
type="text"
icon={<IconMoreHorizontal />}
></Button>
</Dropdown>
<TextConfirmModal
title="Transfer organization"
visible={ownerTransferIsVisble}
confirmString={orgSlug}
loading={loading}
confirmLabel="I understand, transfer ownership"
confirmPlaceholder="Type in name of orgnization"
onCancel={() => setOwnerTransferIsVisble(!ownerTransferIsVisble)}
onConfirm={handleTransfer}
alert="Payment methods such as credit cards will also be transferred. You may want to delete credit card information first before transferring."
text={
<span>
By transferring this organization, it will be solely owned by{' '}
<span className="font-medium dark:text-white">{member.profile?.username}</span>, they
will also be able to remove you from the organization as a member
</span>
}
/>
</div>
)
})
export default OwnerDropdown
@@ -2,5 +2,12 @@ import BillingSettings from './BillingSettings/BillingSettings'
import InvoicesSettings from './InvoicesSettings/InvoicesSettings'
import InviteMemberModal from './InviteMemberModal'
import DeleteOrganizationButton from './DeleteOrganizationButton'
import OwnerDropdown from './OwnerDropdown'
export { BillingSettings, InvoicesSettings, InviteMemberModal, DeleteOrganizationButton }
export {
BillingSettings,
InvoicesSettings,
InviteMemberModal,
DeleteOrganizationButton,
OwnerDropdown,
}
+4 -194
View File
@@ -3,35 +3,32 @@ import { createContext, useEffect, useContext, useState } from 'react'
import { useRouter } from 'next/router'
import { observer, useLocalObservable } from 'mobx-react-lite'
import { toJS } from 'mobx'
import { pluckJsonSchemaFields, pluckObjectFields, timeout } from 'lib/helpers'
import { pluckJsonSchemaFields, pluckObjectFields } from 'lib/helpers'
import { AutoField } from 'uniforms-bootstrap4'
import { organizations } from 'stores/jsonSchema'
import {
Loading,
Button,
Badge,
IconMoreHorizontal,
Tabs,
Typography,
IconTrash,
Alert,
Input,
Dropdown,
IconSearch,
IconUser,
} from '@supabase/ui'
import { useOrganizationDetail, useStore, withAuth } from 'hooks'
import { Member, NextPageWithLayout, Project } from 'types'
import { NextPageWithLayout, Project } from 'types'
import { API_URL } from 'lib/constants'
import { post, delete_, patch } from 'lib/common/fetch'
import { post, patch } from 'lib/common/fetch'
import Panel from 'components/ui/Panel'
import { AccountLayoutWithoutAuth } from 'components/layouts'
import TextConfirmModal from 'components/ui/Modals/TextConfirmModal'
import {
BillingSettings,
InvoicesSettings,
OwnerDropdown,
InviteMemberModal,
DeleteOrganizationButton,
} from 'components/interfaces/Organization'
@@ -453,190 +450,3 @@ const MembersView = observer(() => {
</div>
)
})
const OwnerDropdown = observer(({ members, member }: any) => {
const { ui } = useStore()
const { mutateOrgMembers } = useOrganizationDetail(ui.selectedOrganization?.slug || '')
const PageState: any = useContext(PageContext)
const [loading, setLoading] = useState(false)
// handle modal visibility
const [ownerTransferIsVisble, setOwnerTransferIsVisble] = useState(false)
const { id: orgId, slug: orgSlug, name: orgName } = PageState.organization
async function handleMemberDelete() {
await timeout(200)
confirmAlert({
title: 'Confirm to remove',
message: `This is permanent! Are you sure you want to remove ${member.profile.primary_email}?`,
onAsyncConfirm: async () => {
setLoading(true)
const response = await delete_(`${API_URL}/organizations/${orgSlug}/members/remove`, {
member_id: member.id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to delete user: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = members.filter((x: any) => x.id !== member.id)
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Successfully removed member' })
}
},
})
}
async function handleTransfer() {
setLoading(true)
const response = await post(`${API_URL}/organizations/${orgSlug}/transfer`, {
org_id: orgId,
member_id: member.id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to transfer ownership: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
const oldOwner = updatedMembers.find((x) => x.is_owner == true)
if (oldOwner) oldOwner.is_owner = false
const newOwner = updatedMembers.find((x) => x.id == member.id)
if (newOwner) newOwner.is_owner = true
mutateOrgMembers(updatedMembers)
setOwnerTransferIsVisble(false)
ui.setNotification({ category: 'success', message: 'Successfully transfered organization' })
}
}
async function handleResendInvite(member: Member) {
setLoading(true)
const response = await post(`${API_URL}/organizations/${orgSlug}/members/invite`, {
invited_email: member.profile.primary_email,
owner_id: member.invited_id,
})
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to resend invitation: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Resent the invitation.' })
setLoading(false)
}
}
async function handleRevokeInvitation(id: number) {
setLoading(true)
const response = await delete_(
`${API_URL}/organizations/${orgSlug}/members/invite?invited_id=${id}`,
{}
)
if (response.error) {
ui.setNotification({
category: 'error',
message: `Failed to revoke invitation: ${response.error.message}`,
})
setLoading(false)
} else {
const updatedMembers = [...members]
mutateOrgMembers(updatedMembers)
ui.setNotification({ category: 'success', message: 'Successfully revoked the invitation.' })
}
}
return (
<div className="flex items-center justify-end">
<Dropdown
side="bottom"
align="end"
overlay={
<>
{!member.invited_at && (
<Dropdown.Item onClick={() => setOwnerTransferIsVisble(!ownerTransferIsVisble)}>
<div className="flex flex-col">
<p>Make owner</p>
<p className="block opacity-50">Transfer ownership of "{orgName}"</p>
</div>
</Dropdown.Item>
)}
{member.invited_at && (
<>
<Dropdown.Item onClick={() => handleRevokeInvitation(member.invited_id)}>
<div className="flex flex-col">
<p>Cancel invitation</p>
<p className="block opacity-50">Revoke this invitation.</p>
</div>
</Dropdown.Item>
{!inviteExpired(member.invited_at) && (
<>
<Dropdown.Seperator />
<Dropdown.Item onClick={() => handleResendInvite(member)}>
<div className="flex flex-col">
<p>Resend invitation</p>
<p className="block opacity-50">Invites expire after 24hrs.</p>
</div>
</Dropdown.Item>
</>
)}
</>
)}
{!member.invited_at && (
<>
<Dropdown.Seperator />
<Dropdown.Item icon={<IconTrash size="tiny" />} onClick={handleMemberDelete}>
Remove member
</Dropdown.Item>
</>
)}
</>
}
>
<Button
as="span"
disabled={loading}
loading={loading}
type="text"
icon={<IconMoreHorizontal />}
></Button>
</Dropdown>
<TextConfirmModal
title="Transfer organization"
visible={ownerTransferIsVisble}
confirmString={orgSlug}
loading={loading}
confirmLabel="I understand, transfer ownership"
confirmPlaceholder="Type in name of orgnization"
onCancel={() => setOwnerTransferIsVisble(!ownerTransferIsVisble)}
onConfirm={handleTransfer}
alert="Payment methods such as credit cards will also be transferred. You may want to delete credit card information first before transferring."
text={
<span>
By transferring this organization, it will be solely owned by{' '}
<span className="font-medium dark:text-white">{member.profile?.username}</span>, they
will also be able to remove you from the organization as a member
</span>
}
/>
</div>
)
})