Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

reusing dialog #240

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { useState } from 'react'
import { MoreHorizontal, Trash2 } from 'lucide-react'
import { pageStyles } from '../page.styles'
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from '@repo/ui/dropdown-menu'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '@repo/ui/dialog'
import { Button } from '@repo/ui/button'
import { usePathname } from 'next/navigation'
import { useDeleteApiToken, useDeletePersonalAccessToken } from '@/lib/graphql-hooks/tokens'
import { useNotification } from '@/hooks/useNotification'
import { ConfirmationDialog } from '@repo/ui/confirmation-dialog'
import { Button } from '@repo/ui/button'

type TokenActionProps = {
tokenId: string
Expand All @@ -18,43 +18,42 @@ const ICON_SIZE = 12

export const TokenAction = ({ tokenId }: TokenActionProps) => {
const { actionIcon } = pageStyles()
const { mutateAsync: deletePersonalToken, error: isError } = useDeletePersonalAccessToken()
const { mutateAsync: deleteApiToken, error } = useDeleteApiToken()
const { mutateAsync: deletePersonalToken } = useDeletePersonalAccessToken()
const { mutateAsync: deleteApiToken } = useDeleteApiToken()
const { successNotification, errorNotification } = useNotification()
const path = usePathname()
const isOrg = path.includes('/organization-settings')

const [menuOpen, setMenuOpen] = useState(false) // Track dropdown menu state
const [dialogOpen, setDialogOpen] = useState(false) // Track dialog state
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)

const handleDeleteToken = async () => {
try {
isOrg ? await deleteApiToken({ deleteAPITokenId: tokenId }) : await deletePersonalToken({ deletePersonalAccessTokenId: tokenId })

successNotification({
title: 'Token deleted successfully',
})
} catch (error) {
} catch {
errorNotification({
title: 'There was a problem deleting this token, please try again',
})
setDialogOpen(false)
setMenuOpen(false)
} finally {
setIsDeleteDialogOpen(false)
}
}

return (
<>
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenu>
<DropdownMenuTrigger className="cursor-pointer justify-self-end" asChild>
<MoreHorizontal className={actionIcon()} />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-10">
<DropdownMenuGroup>
<DropdownMenuItem
asChild
onClick={() => {
setMenuOpen(false)
setDialogOpen(true)
onSelect={(e) => {
e.preventDefault()
setIsDeleteDialogOpen(true)
}}
>
<div className="flex items-center cursor-pointer">
Expand All @@ -65,23 +64,13 @@ export const TokenAction = ({ tokenId }: TokenActionProps) => {
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>

<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Deletion</DialogTitle>
<DialogDescription>Are you sure you want to delete this token? This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button variant="filled" onClick={handleDeleteToken}>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<ConfirmationDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteToken}
title="Confirm Deletion"
description={`This action cannot be undone, this will permanently remove the ${isOrg ? 'API Token' : 'Personal Token'} from the organization.`}
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,95 +2,79 @@
import { Panel, PanelHeader } from '@repo/ui/panel'
import { useSession } from 'next-auth/react'
import { Button } from '@repo/ui/button'
import {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogAction,
AlertDialogCancel,
} from '@repo/ui/alert-dialog'
import { useRouter } from 'next/navigation'
import { useNotification } from '@/hooks/useNotification'
import { useUserHasOrganizationDeletePermissions } from '@/lib/authz/utils'
import { useDeleteOrganization, useGetOrganizationNameById } from '@/lib/graphql-hooks/organization'
import { useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { ConfirmationDialog } from '@repo/ui/confirmation-dialog'

const OrganizationDelete = () => {
const { successNotification, errorNotification } = useNotification()
const { push } = useRouter()
const queryClient = useQueryClient()

const { mutateAsync: deleteOrganization } = useDeleteOrganization()
const { data: sessionData, update } = useSession()
const currentOrgId = sessionData?.user.activeOrganizationId

const { data: org } = useGetOrganizationNameById(currentOrgId)

const { data, isLoading, error } = useUserHasOrganizationDeletePermissions(sessionData)

const [isDialogOpen, setIsDialogOpen] = useState(false)

const clickHandler = async () => {
const response = await deleteOrganization({
deleteOrganizationId: currentOrgId,
})
try {
const response = await deleteOrganization({
deleteOrganizationId: currentOrgId,
})

if (response.extensions && sessionData) {
await update({
...sessionData,
user: {
...sessionData.user,
accessToken: response.extensions.auth.access_token,
organization: response.extensions.auth.authorized_organization,
refreshToken: response.extensions.auth.refresh_token,
},
})
}

if (response.extensions && sessionData) {
await update({
...sessionData,
user: {
...sessionData.user,
accessToken: response.extensions.auth.access_token,
organization: response.extensions.auth.authorized_organization,
refreshToken: response.extensions.auth.refresh_token,
},
successNotification({
title: 'Organization successfully deleted',
})
}
successNotification({
title: 'Organization successfully deleted',
})

requestAnimationFrame(() => {
queryClient?.invalidateQueries()
})
push('/organization')
requestAnimationFrame(() => {
queryClient?.invalidateQueries()
})
push('/organization')
} catch (err) {
errorNotification({
title: 'Failed to delete organization',
})
}
}

if (error || !data?.allowed) {
return null
}

return (
<Panel>
<PanelHeader heading="Delete organization" noBorder />
<Panel align="start" destructive>
<p className="red">Deleting your organization is irreversible.</p>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="redOutline" type="button" loading={isLoading}>
Delete this organization
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your organization <b>({org?.organization?.displayName})</b> and remove your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="outline">Cancel</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="filled" onClick={clickHandler}>
Delete organization
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<p className="text-red-600">Deleting your organization is irreversible.</p>
<Button variant="redOutline" type="button" loading={isLoading} onClick={() => setIsDialogOpen(true)}>
Delete this organization
</Button>

<ConfirmationDialog
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
onConfirm={clickHandler}
title="Are you absolutely sure?"
description="This action cannot be undone. This will permanently delete your organization and remove your data from our servers."
/>
</Panel>
</Panel>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
AlertDialogCancel,
} from '@repo/ui/alert-dialog'
import { Button } from '@repo/ui/button'
import React from 'react'
import React, { useState } from 'react'
import { Form, FormControl, FormField, FormItem } from '@repo/ui/form'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@repo/ui/select'
import { useForm } from 'react-hook-form'
Expand All @@ -28,6 +28,7 @@ import { useQueryClient } from '@tanstack/react-query'
import { OrgMembershipRole } from '@repo/codegen/src/schema'
import { useRemoveUserFromOrg, useUpdateUserRoleInOrg } from '@/lib/graphql-hooks/members'
import { useGetUserProfile } from '@/lib/graphql-hooks/user'
import { ConfirmationDialog } from '@repo/ui/confirmation-dialog'

type MemberActionsProps = {
memberId: string
Expand All @@ -37,6 +38,7 @@ type MemberActionsProps = {
const ICON_SIZE = 12

export const MemberActions = ({ memberId, memberRole }: MemberActionsProps) => {
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false)
const { actionIcon, roleRow, buttonRow } = pageStyles()
const { mutateAsync: deleteMember } = useRemoveUserFromOrg()
const { data: sessionData } = useSession()
Expand Down Expand Up @@ -128,29 +130,15 @@ export const MemberActions = ({ memberId, memberRole }: MemberActionsProps) => {
e.preventDefault()
}}
>
<AlertDialog>
<AlertDialogTrigger asChild>
<div style={{ display: 'flex' }}>
<Trash2 width={ICON_SIZE} /> &nbsp; Remove Member
</div>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>This action cannot be undone, this will permanently remove the member from the organization.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="outline">Cancel</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="filled" onClick={handleDeleteMember}>
Remove Member
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<div className="flex" onClick={() => setShowDeleteConfirmation(true)}>
<Trash2 width={ICON_SIZE} /> &nbsp; Remove Member
</div>
<ConfirmationDialog
open={showDeleteConfirmation}
onOpenChange={setShowDeleteConfirmation}
onConfirm={handleDeleteMember}
description="This action cannot be undone, this will permanently remove the member from the organization."
/>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
'use client'

import { MoreHorizontal, RotateCw, Trash2 } from 'lucide-react'
import { MoreHorizontal, Trash2 } from 'lucide-react'
import { useNotification } from '@/hooks/useNotification'
import { pageStyles } from '../page.styles'
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuTrigger } from '@repo/ui/dropdown-menu'
import { useDeleteSubscriber } from '@/lib/graphql-hooks/subscribes'
import { useState } from 'react'
import { ConfirmationDialog } from '@repo/ui/confirmation-dialog'

type SubscriberActionsProps = {
subscriberEmail: string
}

const ICON_SIZE = 12

export const SubscriberActions = ({ subscriberEmail: subscriberEmail }: SubscriberActionsProps) => {
export const SubscriberActions = ({ subscriberEmail }: SubscriberActionsProps) => {
const { actionIcon } = pageStyles()
const { mutateAsync: deleteSubscriber } = useDeleteSubscriber()
const { successNotification, errorNotification } = useNotification()

const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)

const handleDeleteSubscriber = async () => {
await deleteSubscriber({ deleteSubscriberEmail: subscriberEmail })
try {
await deleteSubscriber({ deleteSubscriberEmail: subscriberEmail })
successNotification({
title: 'Subscriber deleted successfully',
})
Expand All @@ -27,20 +32,39 @@ export const SubscriberActions = ({ subscriberEmail: subscriberEmail }: Subscrib
title: 'There was a problem deleting the subscriber, please try again',
variant: 'destructive',
})
} finally {
setIsDeleteDialogOpen(false)
}
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<MoreHorizontal className={actionIcon()} />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-10">
<DropdownMenuGroup>
<DropdownMenuItem onSelect={handleDeleteSubscriber}>
<Trash2 width={ICON_SIZE} /> Delete Subscriber
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<MoreHorizontal className={actionIcon()} />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-10">
<DropdownMenuGroup>
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault()
setIsDeleteDialogOpen(true)
}}
>
<Trash2 width={ICON_SIZE} /> Delete Subscriber
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>

{/* Delete Subscriber Confirmation Dialog */}
<ConfirmationDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onConfirm={handleDeleteSubscriber}
title="Are you absolutely sure?"
description="This action cannot be undone. This will permanently remove the subscriber from the system."
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const PendingActions = () => {
const noData = pendingActions.length === 0 && approvalsWaitingFor.length === 0

return (
<Card className="shadow-md rounded-lg border flex-1">
<Card className=" rounded-lg border flex-1">
<CardTitle className="text-lg font-semibold">Pending Actions</CardTitle>
<CardContent>
{noData ? (
Expand Down
Loading