ViewAppSheet.tsx95 lines · main
1import { X } from 'lucide-react'
2import { useState } from 'react'
3import { toast } from 'sonner'
4import { Button, cn, ScrollArea, Sheet, SheetContent, SheetHeader } from 'ui'
5
6import { PrivateApp, usePrivateApps } from '../../PrivateAppsContext'
7import { PERMISSIONS, type Permission } from '../Apps.constants'
8import { DeleteAppModal } from '../DeleteAppModal'
9import { ViewAppSheetDangerZone } from './ViewAppSheetDangerZone'
10import { ViewAppSheetInfo } from './ViewAppSheetInfo'
11import { ViewAppSheetPermissions } from './ViewAppSheetPermissions'
12import { usePlatformAppDeleteMutation } from '@/data/platform-apps/platform-app-delete-mutation'
13import { usePlatformAppQuery } from '@/data/platform-apps/platform-app-query'
14
15interface ViewAppSheetProps {
16 app: PrivateApp | null
17 visible: boolean
18 onClose: () => void
19 onDeleted: () => void
20}
21
22export function ViewAppSheet({ app, visible, onClose, onDeleted }: ViewAppSheetProps) {
23 const { slug, installations, removeInstallationsByAppId } = usePrivateApps()
24 const installation = installations.find((i) => i.app_id === app?.id)
25 const isInstalled = installation !== undefined
26 const [showDeleteModal, setShowDeleteModal] = useState(false)
27
28 const { data: detail, isLoading: isLoadingDetail } = usePlatformAppQuery(
29 { slug, id: app?.id },
30 { enabled: visible && app !== null }
31 )
32
33 const { mutate: deleteApp, isPending: isDeleting } = usePlatformAppDeleteMutation({
34 onSuccess: (_, vars) => {
35 toast.success(`Deleted "${app?.name}"`)
36 removeInstallationsByAppId(vars.appId)
37 setShowDeleteModal(false)
38 onClose()
39 onDeleted()
40 },
41 })
42
43 function handleDelete() {
44 if (!app || !slug) return
45 deleteApp({ slug, appId: app.id })
46 }
47
48 const permissions: Permission[] = detail
49 ? detail.permissions
50 .map((id) => PERMISSIONS.find((p) => p.id === id))
51 .filter((p): p is Permission => p !== undefined)
52 : []
53
54 return (
55 <>
56 <Sheet
57 open={visible}
58 onOpenChange={(open) => {
59 if (!open) onClose()
60 }}
61 >
62 <SheetContent
63 showClose={false}
64 size="default"
65 className="min-w-[600px]! flex flex-col h-full gap-0"
66 >
67 <SheetHeader
68 className={cn('flex flex-row justify-between gap-x-4 items-center border-b')}
69 >
70 <p className="truncate font-medium">{app?.name}</p>
71 <Button type="text" icon={<X size={16} />} className="px-1" onClick={onClose} />
72 </SheetHeader>
73
74 <ScrollArea className="flex-1 max-h-[calc(100vh-60px)]">
75 {app && (
76 <div className="flex flex-col gap-0">
77 <ViewAppSheetInfo app={app} isInstalled={isInstalled} installation={installation} />
78 <ViewAppSheetPermissions permissions={permissions} isLoading={isLoadingDetail} />
79 <ViewAppSheetDangerZone onDelete={() => setShowDeleteModal(true)} />
80 </div>
81 )}
82 </ScrollArea>
83 </SheetContent>
84 </Sheet>
85
86 <DeleteAppModal
87 app={app}
88 visible={showDeleteModal}
89 isLoading={isDeleting}
90 onClose={() => setShowDeleteModal(false)}
91 onConfirm={handleDelete}
92 />
93 </>
94 )
95}