Skip to content

Commit

Permalink
feat(platform): Added the feature for deleting a secret (#674)
Browse files Browse the repository at this point in the history
  • Loading branch information
poswalsameer authored Jan 30, 2025
1 parent 5060fe7 commit 37e7960
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 88 deletions.
46 changes: 30 additions & 16 deletions apps/platform/src/app/(main)/project/[project]/@secret/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ import SecretLoader from '@/components/dashboard/secret/secretLoader'
import { Button } from '@/components/ui/button'
import {
createSecretOpenAtom,
deleteSecretOpenAtom,
secretsOfProjectAtom,
selectedProjectAtom
selectedProjectAtom,
selectedSecretAtom
} from '@/store'
import ConfirmDeleteSecret from '@/components/dashboard/secret/confirmDeleteSecret'
import SecretCard from '@/components/dashboard/secret/secretCard'

extend(relativeTime)

function SecretPage(): React.JSX.Element {
const setIsCreateSecretOpen = useSetAtom(createSecretOpenAtom)
const isDeleteSecretOpen = useAtomValue(deleteSecretOpenAtom)
const selectedSecret = useAtomValue(selectedSecretAtom)
const [secrets, setSecrets] = useAtom(secretsOfProjectAtom)
const [isLoading, setIsLoading] = useState<boolean>(true)
const selectedProject = useAtomValue(selectedProjectAtom)
Expand Down Expand Up @@ -104,21 +109,30 @@ function SecretPage(): React.JSX.Element {
</Button>
</div>
) : (
<ScrollArea className="mb-4 h-fit w-full">
<Accordion
className="flex h-fit w-full flex-col gap-4"
collapsible
type="single"
>
{secrets.map((secret) => (
<SecretCard
isDecrypted={isDecrypted}
key={secret.secret.id}
secretData={secret}
/>
))}
</Accordion>
</ScrollArea>
<div
className={`flex h-full w-full flex-col items-center justify-start gap-y-8 p-3 text-white ${isDeleteSecretOpen ? 'inert' : ''} `}
>
<ScrollArea className="mb-4 h-fit w-full">
<Accordion
className="flex h-fit w-full flex-col gap-4"
collapsible
type="single"
>
{secrets.map((secret) => (
<SecretCard
isDecrypted={isDecrypted}
key={secret.id}
secretData={secret}
/>
))}
</Accordion>
</ScrollArea>

{/* Delete secret alert dialog */}
{isDeleteSecretOpen && selectedSecret ? (
<ConfirmDeleteSecret />
) : null}
</div>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useCallback, useEffect } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import { TrashSVG } from '@public/svg/shared'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import {
deleteSecretOpenAtom,
secretsOfProjectAtom,
selectedSecretAtom
} from '@/store'
import ControllerInstance from '@/lib/controller-instance'

function ConfirmDeleteSecret() {
const selectedSecret = useAtomValue(selectedSecretAtom)
const setSelectedSecret = useSetAtom(selectedSecretAtom)
const [isDeleteSecretOpen, setIsDeleteSecretOpen] =
useAtom(deleteSecretOpenAtom)
const setSecrets = useSetAtom(secretsOfProjectAtom)

const handleClose = useCallback(() => {
setIsDeleteSecretOpen(false)
}, [setIsDeleteSecretOpen])

const deleteSecret = useCallback(async () => {
if (selectedSecret === null) {
toast.error('No secret selected', {
description: (
<p className="text-xs text-red-300">
No secret selected. Please select a secret.
</p>
)
})
return
}

const secretSlug = selectedSecret.secret.slug

const { success, error } =
await ControllerInstance.getInstance().secretController.deleteSecret(
{ secretSlug },
{}
)

if (success) {
toast.success('Secret deleted successfully', {
description: (
<p className="text-xs text-emerald-300">
The secret has been deleted.
</p>
)
})

// Remove the secret from the store
setSecrets((prevSecrets) =>
prevSecrets.filter(({ secret }) => secret.slug !== secretSlug)
)

setSelectedSecret(null)
}
if (error) {
toast.error('Something went wrong!', {
description: (
<p className="text-xs text-red-300">
Something went wrong while deleting the secret. Check console for
more info.
</p>
)
})
// eslint-disable-next-line no-console -- we need to log the error
console.error(error)
}

handleClose()
}, [setSecrets, selectedSecret, handleClose])

//Cleaning the pointer events for the context menu after closing the alert dialog
const cleanup = useCallback(() => {
document.body.style.pointerEvents = ''
document.documentElement.style.pointerEvents = ''
}, [])

useEffect(() => {
if (!isDeleteSecretOpen) {
cleanup()
}
return () => cleanup()
}, [isDeleteSecretOpen, cleanup])

return (
<AlertDialog
aria-hidden={!isDeleteSecretOpen}
onOpenChange={handleClose}
open={isDeleteSecretOpen}
>
<AlertDialogContent className="rounded-lg border border-white/25 bg-[#18181B] ">
<AlertDialogHeader>
<div className="flex items-center gap-x-3">
<TrashSVG />
<AlertDialogTitle className="text-lg font-semibold">
Do you really want to delete {selectedSecret?.secret.name}?
</AlertDialogTitle>
</div>
<AlertDialogDescription className="text-sm font-normal leading-5 text-[#71717A]">
This action cannot be undone. This will permanently delete your
secret and remove your secret data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
className="rounded-md bg-[#F4F4F5] text-black hover:bg-[#F4F4F5]/80 hover:text-black"
onClick={handleClose}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction
className="rounded-md bg-[#DC2626] text-white hover:bg-[#DC2626]/80"
onClick={deleteSecret}
>
Yes, delete {selectedSecret?.secret.name}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}

export default ConfirmDeleteSecret
174 changes: 102 additions & 72 deletions apps/platform/src/components/dashboard/secret/secretCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ import {
TableHeader,
TableRow
} from '@/components/ui/table'
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger
} from '@/components/ui/context-menu'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from '@/components/ui/tooltip'
import { deleteSecretOpenAtom, selectedSecretAtom } from '@/store'
import { useSetAtom } from 'jotai'

interface SecretCardProps {
secretData: Secret
Expand All @@ -32,79 +40,101 @@ export default function SecretCard({
}: SecretCardProps) {
const { secret, values } = secretData

const setSelectedSecret = useSetAtom(selectedSecretAtom)
const setIsDeleteSecretOpen = useSetAtom(deleteSecretOpenAtom)

const handleDeleteClick = () => {
setSelectedSecret(secretData)
setIsDeleteSecretOpen(true)
}

return (
<AccordionItem
className="rounded-xl bg-white/5 px-5"
key={secret.id}
value={secret.id}
>
<AccordionTrigger
className="hover:no-underline"
rightChildren={
<div className="text-xs text-white/50">
{dayjs(secret.updatedAt).toNow(true)} ago by{' '}
<span className="text-white">{secret.lastUpdatedBy.name}</span>
</div>
}
<ContextMenu>
<AccordionItem
className="rounded-xl bg-white/5 px-5"
key={secret.id}
value={secret.id}
>
<div className="flex gap-x-5">
<div className="flex items-center gap-x-4">
{/* <SecretLogoSVG /> */}
{secret.name}
</div>
{secret.note ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<NoteIconSVG className="w-7" />
</TooltipTrigger>
<TooltipContent className="border-white/20 bg-white/10 text-white backdrop-blur-xl">
<p>{secret.note}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : null}
</div>
</AccordionTrigger>
<AccordionContent>
<Table className="h-full w-full">
<TableHeader className="h-[3.125rem] w-full ">
<TableRow className="h-full w-full bg-white/10 ">
<TableHead className="h-full w-[10.25rem] rounded-tl-xl text-base font-bold text-white/50">
Environment
</TableHead>
<TableHead className="h-full text-base font-normal text-white/50">
Value
</TableHead>
<TableHead className="h-full rounded-tr-xl text-base font-normal text-white/50">
Version
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{values.map((value) => {
return (
<TableRow
className="h-[3.125rem] w-full hover:bg-white/5"
key={value.environment.id}
>
<TableCell className="h-full w-[10.25rem] text-base">
{value.environment.name}
</TableCell>
<TableCell className="h-full text-base">
{isDecrypted
? value.value
: value.value.replace(/./g, '*').substring(0, 20)}
</TableCell>
<TableCell className="h-full px-8 py-4 text-base">
{value.version}
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</AccordionContent>
</AccordionItem>
<ContextMenuTrigger>
<AccordionTrigger
className="hover:no-underline"
rightChildren={
<div className="text-xs text-white/50">
{dayjs(secret.updatedAt).toNow(true)} ago by{' '}
<span className="text-white">{secret.lastUpdatedBy.name}</span>
</div>
}
>
<div className="flex gap-x-5">
<div className="flex items-center gap-x-4">
{/* <SecretLogoSVG /> */}
{secret.name}
</div>
{secret.note ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<NoteIconSVG className="w-7" />
</TooltipTrigger>
<TooltipContent className="border-white/20 bg-white/10 text-white backdrop-blur-xl">
<p>{secret.note}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : null}
</div>
</AccordionTrigger>
</ContextMenuTrigger>
<AccordionContent>
<Table className="h-full w-full">
<TableHeader className="h-[3.125rem] w-full">
<TableRow className="h-[3.125rem] w-full hover:bg-[#232424]">
<TableHead className="h-full w-[10.25rem] border-2 border-white/30 text-base font-bold text-white">
Environment
</TableHead>
<TableHead className="h-full border-2 border-white/30 text-base font-normal text-white">
Value
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{values.map((value) => {
return (
<TableRow
className="h-[3.125rem] w-full hover:cursor-pointer hover:bg-[#232424]"
key={value.environment.id}
>
<TableCell className="h-full w-[10.25rem] border-2 border-white/30 text-base font-bold text-white">
{value.environment.name}
</TableCell>
<TableCell className="h-full border-2 border-white/30 text-base font-normal text-white">
{isDecrypted
? value.value
: value.value.replace(/./g, '*').substring(0, 20)}
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</AccordionContent>
</AccordionItem>
<ContextMenuContent className="flex h-[6.375rem] w-[15.938rem] flex-col items-center justify-center rounded-lg bg-[#3F3F46]">
<ContextMenuItem className="h-[33%] w-[15.938rem] border-b-[0.025rem] border-white/65 text-xs font-semibold tracking-wide">
Show Version History
</ContextMenuItem>
<ContextMenuItem
className="h-[33%] w-[15.938rem] text-xs font-semibold tracking-wide"
>
Edit
</ContextMenuItem>
<ContextMenuItem
className="h-[33%] w-[15.938rem] text-xs font-semibold tracking-wide"
onSelect={handleDeleteClick}
>
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}

0 comments on commit 37e7960

Please sign in to comment.