-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathSpaceCreator.tsx
191 lines (170 loc) · 5.83 KB
/
SpaceCreator.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import type { ChangeEvent } from 'react'
import React, { useState } from 'react'
import { Space, useW3 } from '@w3ui/react'
import Loader from '../components/Loader'
import { DID, DIDKey } from '@ucanto/interface'
import { DidIcon } from './DidIcon'
import Link from 'next/link'
import { FolderPlusIcon, InformationCircleIcon } from '@heroicons/react/24/outline'
import Tooltip from './Tooltip'
import { H3 } from './Text'
export function SpaceCreatorCreating(): JSX.Element {
return (
<div className='flex flex-col items-center space-y-4'>
<h5 className='font-epilogue'>Creating Space...</h5>
<Loader className='w-6' />
</div>
)
}
interface SpaceCreatorFormProps {
className?: string
}
export function SpaceCreatorForm({
className = ''
}: SpaceCreatorFormProps): JSX.Element {
const [{ client, accounts }] = useW3()
const [submitted, setSubmitted] = useState(false)
const [created, setCreated] = useState(false)
const [name, setName] = useState('')
const [space, setSpace] = useState<Space>()
function resetForm(): void {
setName('')
}
async function onSubmit(e: React.FormEvent<HTMLFormElement>): Promise<void> {
e.preventDefault()
if (!client) return
// TODO: account selection
const account = accounts[0]
if (!account) {
throw new Error('cannot create space, no account found, have you authorized your email?')
}
const { ok: plan } = await account.plan.get()
if (!plan) {
throw new Error('a payment plan is required on account to provision a new space.')
}
setSubmitted(true)
try {
const space = await client.createSpace(name)
const provider = (process.env.NEXT_PUBLIC_W3UP_PROVIDER || 'did:web:web3.storage') as DID<'web'>
const result = await account.provision(space.did(), { provider })
if (result.error) {
setSubmitted(false)
setCreated(false)
throw new Error(`failed provisioning space: ${space.did()} with provider: ${provider}`, { cause: result.error })
}
// MUST do this before creating recovery, as it creates necessary authorizations
await space.save()
// TODO this should have its own UX, like the CLI does, which would allow us to handle errors
const recovery = await space.createRecovery(account.did())
// TODO we are currently ignoring the result of this because we have no good way to handle errors - revamp this ASAP!
await client.capability.access.delegate({
space: space.did(),
delegations: [recovery],
})
setSpace(client.spaces().find(s => s.did() === space.did()))
setCreated(true)
resetForm()
} catch (error) {
/* eslint-disable-next-line no-console */
console.error(error)
throw new Error('failed to create space', { cause: error })
}
}
if (created && space) {
return (
<div className={className}>
<div className='max-w-3xl border border-hot-red rounded-2xl'>
<SpacePreview did={space.did()} name={space.name} capabilities={['*']} />
</div>
</div>
)
}
if (submitted) {
return (
<div className={className}>
<SpaceCreatorCreating />
</div>
)
}
return (
<div className={className}>
<form className='' onSubmit={(e: React.FormEvent<HTMLFormElement>) => { void onSubmit(e) }}>
<label className='block mb-2 uppercase text-xs text-hot-red font-epilogue m-1' htmlFor='space-name'>Name</label>
<input
id='space-name'
className='text-black py-2 px-2 rounded-xl block mb-4 border border-hot-red w-80'
placeholder='Name'
value={name}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value)
}}
required={true}
/>
<button type='submit' 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`}>
<FolderPlusIcon className='h-5 w-5 inline-block mr-1 align-middle' style={{ marginTop: -4 }} /> Create
</button>
</form>
</div>
)
}
interface SpaceCreatorProps {
className?: string
}
export function SpaceCreator({
className = ''
}: SpaceCreatorProps): JSX.Element {
const [creating, setCreating] = useState(false)
return (
<div className={`${className}`}>
{creating
? (
<SpaceCreatorForm />
)
: (
<button
className='w3ui-button py-2'
onClick={() => { setCreating(true) }}
>
Add Space
</button>
)}
</div>
)
/* eslint-enable no-nested-ternary */
}
interface SpacePreviewProps {
did: DIDKey
name?: string
capabilities: string[]
}
export function SpacePreview({ did, name, capabilities }: SpacePreviewProps) {
return (
<figure className='p-4 flex flex-row items-start gap-2 rounded'>
<Link href={`/space/${did}`} className='block'>
<DidIcon did={did} />
</Link>
<figcaption className='grow'>
<Link href={`/space/${did}`} className='block'>
<span className='font-epilogue text-lg text-hot-red font-semibold leading-5 m-0 flex items-center'>
{name ?? 'Untitled'}
<InformationCircleIcon className={`h-5 w-5 ml-2 space-preview-capability-icon`} />
<Tooltip anchorSelect={`.space-preview-capability-icon`}>
<H3>Capabilities</H3>
{capabilities.map((c, i) => (
<p key={i}>{c}</p>
))}
</Tooltip>
</span>
<span className='block font-mono text-xs truncate'>
{did}
</span>
</Link>
</figcaption>
<div>
<Link href={`/space/${did}`} className='text-sm font-semibold align-[-8px] hover:underline'>
View
</Link>
</div>
</figure>
)
}