Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

✨ Allow adding account to workspace from handle #183

Merged
merged 1 commit into from
Sep 11, 2024
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
93 changes: 71 additions & 22 deletions components/workspace/ItemCreator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ActionButton } from '@/common/buttons'
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
import { toast } from 'react-toastify'
import { buildItemsSummary, groupSubjects } from './utils'
import { getDidFromHandleInBatch } from '@/lib/identity'
import { ArrowPathIcon } from '@heroicons/react/24/solid'

interface WorkspaceItemCreatorProps {
onCancel?: () => void
Expand All @@ -16,32 +18,69 @@ const WorkspaceItemCreator: React.FC<WorkspaceItemCreatorProps> = ({
size = 'lg',
}) => {
const addItemsMutation = useWorkspaceAddItemsMutation()
const [isAdding, setIsAdding] = React.useState(false)

const handleSubmit = (
const handleSubmit = async (
event: React.FormEvent<HTMLFormElement> & { target: HTMLFormElement },
) => {
event.preventDefault()
event.stopPropagation()
const formData = new FormData(event.currentTarget)
const items = formData.get('items') as string
const itemList = items
.split(',')
.map((item) => item.trim())
.filter((item) => item.startsWith('did:') || item.startsWith('at://'))
const groupedItems = groupSubjects(itemList)
setIsAdding(true)

addItemsMutation.mutate(itemList, {
onSuccess: () => {
const addedItemsSummary = buildItemsSummary(groupedItems)
toast.success(`Added ${addedItemsSummary} to the list.`)
event.target.reset()
onCancel?.()
},
onError: () => {
toast.error('Failed to add items to the list.')
},
})
return false
try {
const formData = new FormData(event.currentTarget)
const items = formData.get('items') as string
const isPossiblyHandle = (item) => item.includes('.')
const itemList = items
.split(',')
.map((item) => item.trim())
.filter(
(item) =>
item.startsWith('did:') ||
item.startsWith('at://') ||
isPossiblyHandle(item),
)

const handleList = itemList.filter(isPossiblyHandle)

// If there are handles in the list, we need to resolve them to DIDs and replace the handles with dids before placing them in the workspace
if (handleList.length > 0) {
const handleToDid = await getDidFromHandleInBatch(handleList)
Object.keys(handleToDid).forEach((handle) => {
// If we couldn't find the did, we don't want to replace the handle in the list
if (handleToDid[handle] === null) {
return
}
const handleIndex = itemList.indexOf(handle)
if (handleIndex !== -1) {
itemList[handleIndex] = handleToDid[handle]
}
})
}

const groupedItems = groupSubjects(itemList)

await addItemsMutation.mutateAsync(itemList, {
onSuccess: () => {
const addedItemsSummary = buildItemsSummary(groupedItems)
toast.success(`Added ${addedItemsSummary} to workspace.`)
event.target.reset()
onCancel?.()
},
onError: () => {
toast.error('Failed to add items to workspace.')
},
})

setIsAdding(false)
return false
} catch (error) {
console.error(error)
setIsAdding(false)
toast.error(
`Failed to add items to workspace. ${(error as Error).message}`,
)
}
}

return (
Expand All @@ -51,6 +90,7 @@ const WorkspaceItemCreator: React.FC<WorkspaceItemCreatorProps> = ({
>
<Input
autoFocus
disabled={isAdding}
name="items"
onKeyDown={(e) => {
if (e.key === 'Escape' && !!onCancel) {
Expand All @@ -61,8 +101,17 @@ const WorkspaceItemCreator: React.FC<WorkspaceItemCreatorProps> = ({
placeholder="Enter DID/AT-URI items separated by commas"
className={`block ${size === 'sm' ? 'p-1' : 'p-2'} w-full`}
/>
<ActionButton type="submit" appearance="outlined" size={size}>
<PlusIcon className={size === 'lg' ? 'h-5 w-5' : 'h-3 w-3'} />
<ActionButton
type="submit"
appearance="outlined"
size={size}
disabled={isAdding}
>
{isAdding ? (
<ArrowPathIcon className={size === 'lg' ? 'h-5 w-5' : 'h-3 w-3'} />
) : (
<PlusIcon className={size === 'lg' ? 'h-5 w-5' : 'h-3 w-3'} />
)}
</ActionButton>
{!!onCancel && (
<ActionButton
Expand Down
16 changes: 16 additions & 0 deletions lib/identity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { globalAgent } from './client'
import { PLC_DIRECTORY_URL } from './constants'
import { chunkArray } from './util'

export const getDidFromHandle = async (
handle: string,
Expand All @@ -12,6 +13,21 @@ export const getDidFromHandle = async (
}
}

export const getDidFromHandleInBatch = async (handles: string[]) => {
const handleToDid: Record<string, string | null> = {}

for (const handleChunk of chunkArray(handles, 50)) {
await Promise.all(
handleChunk.map(async (handle) => {
const did = await getDidFromHandle(handle)
handleToDid[handle] = did
}),
)
}

return handleToDid
}

export const resolveDidDocData = async function (
did: string,
signal?: AbortSignal,
Expand Down
Loading