-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpage.tsx
156 lines (137 loc) · 6.44 KB
/
page.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'use client'
import { MouseEventHandler, useState } from 'react'
import { Dialog } from '@headlessui/react'
import { TrashIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline'
import { H2 } from '@/components/Text'
import { useW3, UploadGetSuccess, SpaceDID, CARLink } from '@w3ui/react'
import useSWR, { useSWRConfig } from 'swr'
import { UnknownLink, parse as parseLink } from 'multiformats/link'
import DefaultLoader from '@/components/Loader'
import Link from 'next/link'
import CopyIcon from '@/components/CopyIcon'
import { Breadcrumbs } from '@/components/Breadcrumbs'
import { useRouter } from 'next/navigation'
import { createUploadsListKey } from '@/cache'
import { logAndCaptureError } from '@/sentry'
import { ipfsGatewayURL } from '@/components/services'
interface PageProps {
params: {
did: string
cid: string
}
}
export default function ItemPage ({ params }: PageProps): JSX.Element {
const [{ client, spaces }] = useW3()
const spaceDID = decodeURIComponent(params.did)
const space = spaces.find(s => s.did() === spaceDID)
const root = parseLink(params.cid)
const uploadKey = `/space/${spaceDID}/upload/${root}`
const upload = useSWR<UploadGetSuccess|undefined>(uploadKey, {
fetcher: async () => {
if (!client || !space) return
if (client.currentSpace()?.did() !== space.did()) {
await client.setCurrentSpace(space.did())
}
return await client.capability.upload.get(root)
},
onError: logAndCaptureError
})
const [isRemoveConfirmModalOpen, setRemoveConfirmModalOpen] = useState(false)
const router = useRouter()
const { mutate } = useSWRConfig()
if (!space) {
return <h1>Space not found</h1>
}
const handleRemove = async () => {
await client?.remove(root, { shards: true })
setRemoveConfirmModalOpen(false)
// ensure list data is fresh
mutate(createUploadsListKey(space.did()))
// navigate to list (this page no longer exists)
router.replace(`/space/${spaceDID}`)
}
const url = ipfsGatewayURL(root)
return (
<div>
<Breadcrumbs space={space.did()} root={root} />
<div className='border border-hot-red rounded-2xl bg-white p-5 max-w-4xl'>
<H2>Root CID</H2>
<div className="pb-5 font-mono text-sm overflow-hidden no-wrap text-ellipsis">
{root.toString()}
<CopyIcon text={root.toString()} />
</div>
<H2>URL</H2>
<div className="pb-5 overflow-hidden no-wrap text-ellipsis">
<a href={url} className="font-mono text-sm underline m-0 p-0" target="_blank">{url}</a>
<CopyIcon text={url} />
</div>
<H2>Shards</H2>
<div className='pb-5'>
{upload.isLoading
? <DefaultLoader className='w-5 h-5 inline-block' />
: upload.data?.shards?.map(link => <Shard space={space.did()} root={root} shard={link} key={link.toString()} />)}
</div>
<button onClick={e => { e.preventDefault(); setRemoveConfirmModalOpen(true) }} className={`inline-block bg-hot-red border border-hot-red hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 rounded-full whitespace-nowrap`}>
<TrashIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> Remove
</button>
<RemoveConfirmModal
isOpen={isRemoveConfirmModalOpen}
root={root}
shards={upload.data?.shards ?? []}
onConfirm={handleRemove}
onCancel={() => setRemoveConfirmModalOpen(false)}
/>
</div>
</div>
)
}
function Shard ({ space, root, shard }: { space: SpaceDID, root: UnknownLink, shard: CARLink }) {
return (
<div>
<Link href={`/space/${space}/root/${root}/shard/${shard}`} className='font-mono text-sm overflow-hidden no-wrap text-ellipsis underline'>{shard.toString()}</Link>
<CopyIcon text={shard.toString()} />
</div>
)
}
interface RemoveConfirmModalProps {
isOpen: boolean
root: UnknownLink
shards: UnknownLink[]
onConfirm: () => void
onCancel: () => void
}
function RemoveConfirmModal ({ isOpen, root, shards, onConfirm, onCancel }: RemoveConfirmModalProps) {
const [confirmed, setConfirmed] = useState(false)
const displayShards = shards.slice(0, 10)
return (
<Dialog open={isOpen} onClose={() => { setConfirmed(false); onCancel() }} className='relative z-50'>
<div className='fixed inset-0 flex w-screen items-center justify-center bg-black/70 text-white' aria-hidden='true'>
<Dialog.Panel className='bg-hot-red p-10 shadow-lg rounded-2xl font-epilogue'>
<Dialog.Title className='text-lg uppercase text-center my-3'>
<ExclamationTriangleIcon className='h-10 w-10 inline-block' /><br/>
Confirm remove
</Dialog.Title>
<Dialog.Description className='py-2'>
Are you sure you want to remove <span className='font-mono font-bold text-sm'>{root.toString()}</span>?
</Dialog.Description>
<p className='py-2'>The following shards will be removed:</p>
<ul className='py-2 list-disc pl-6'>
{displayShards.map(s => <li key={s.toString()} className='font-mono text-sm'>{s.toString()}</li>)}
</ul>
{displayShards.length < shards.length ? <p className='py-2'>...and {shards.length - displayShards.length} more.</p> : null}
<p className='pt-2 pb-5'>
Any uploads using the same shards as those listed above <em>will</em> be corrputed. This cannot be undone.
</p>
<div className='py-2 text-center'>
<button onClick={e => { e.preventDefault(); setConfirmed(true); onConfirm() }} className={`inline-block bg-hot-red-light border border-white hover:bg-white hover:text-hot-red font-epilogue text-hot-red uppercase text-sm px-6 py-2 mr-3 rounded-full whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
<TrashIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{marginTop: -4}} /> {confirmed ? 'Removing...' : 'Remove'}
</button>
<button onClick={e => { e.preventDefault(); setConfirmed(false); onCancel() }} className={`inline-block bg-hot-red border border-white hover:bg-white hover:text-hot-red font-epilogue text-white uppercase text-sm px-6 py-2 mr-3 rounded-full whitespace-nowrap ${confirmed ? 'opacity-50' : 'hover:outline'}`} disabled={confirmed}>
Cancel
</button>
</div>
</Dialog.Panel>
</div>
</Dialog>
)
}