Skip to content

Commit

Permalink
[front/components/vaults] - feature: add UI for creating vaults with …
Browse files Browse the repository at this point in the history
…member selection

 - Implement a new modal component to create vaults and manage vault members
 - Provide form inputs for vault name and search-filterable member list with add/remove actions
  • Loading branch information
Jules authored and Jules committed Aug 12, 2024
1 parent 7a401ad commit 52e3566
Showing 1 changed file with 201 additions and 0 deletions.
201 changes: 201 additions & 0 deletions front/components/vaults/CreateVaultModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {
Button,
DataTable,
Icon,
Input,
Modal,
Page,
PlusIcon,
Searchbar,
} from "@dust-tt/sparkle";
import type { UserType, WorkspaceType } from "@dust-tt/types";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import type { CellContext, ColumnDef } from "@tanstack/react-table";
import { MinusIcon } from "lucide-react";
import React, { useCallback, useContext, useMemo, useState } from "react";

import { SendNotificationsContext } from "@app/components/sparkle/Notification";

type RowData = {
icon: string;
name: string;
userId: string;
onClick?: () => void;
onMoreClick?: () => void;
};

type Info = CellContext<RowData, unknown>;

interface CreateVaultModalProps {
owner: WorkspaceType;
isOpen: boolean;
onClose: () => void;
onSave: () => void;
allUsers: UserType[];
}

function getTableRows(allUsers: UserType[]): RowData[] {
return allUsers.map((user) => {
return {
icon: user.image ?? "",
name: user.fullName,
userId: user.sId,
};
});
}

function getTableColumns(
selectedMembers: string[],
handleMemberToggle: (member: string) => void
): ColumnDef<RowData, unknown>[] {
return [
{
header: "Name",
accessorKey: "name",
id: "name",
cell: (info: Info) => (
<DataTable.Cell avatarUrl={info.row.original.icon}>
{info.row.original.name}
</DataTable.Cell>
),
},
{
id: "action",
cell: (info: Info) => {
const isSelected = selectedMembers.includes(info.row.original.userId);
return (
<Button
label={isSelected ? "Remove" : "Add"}
onClick={() => handleMemberToggle(info.row.original.userId)}
variant={isSelected ? "tertiary" : "secondary"}
size="sm"
icon={isSelected ? MinusIcon : PlusIcon}
/>
);
},
},
];
}

export function CreateVaultModal({
owner,
isOpen,
onClose,
onSave,
allUsers,
}: CreateVaultModalProps) {
const [vaultName, setVaultName] = useState<string | null>(null);
const [selectedMembers, setSelectedMembers] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState<string>("");
const [isSaving, setIsSaving] = useState(false);

const sendNotification = useContext(SendNotificationsContext);

const handleMemberToggle = useCallback((member: string) => {
setSelectedMembers((prevSelectedMembers) => {
const isCurrentlySelected = prevSelectedMembers.includes(member);
if (isCurrentlySelected) {
return prevSelectedMembers.filter((m) => m !== member);
} else {
return [...prevSelectedMembers, member];
}
});
}, []);

const createVault = async () => {
setIsSaving(true);
try {
const res = await fetch(`/api/w/${owner.sId}/vaults`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: vaultName,
memberIds: selectedMembers,
}),
});

if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error?.message || "Failed to create vault");
}
onSave();
onClose();
return await res.json();
} finally {
setIsSaving(false);
}
};

const rows = useMemo(
() => getTableRows(allUsers),
[allUsers]
);

const columns = useMemo(
() => getTableColumns(selectedMembers, handleMemberToggle),
[selectedMembers, handleMemberToggle]
);

return (
<Modal
isOpen={isOpen}
onClose={onClose}
title="Create a Vault"
saveLabel="Create"
variant="side-md"
hasChanged={!!vaultName && selectedMembers.length > 0}
isSaving={isSaving}
onSave={async () => {
try {
await createVault();
sendNotification({
type: "success",
title: "Successfully created vault",
description: "Vault was successfully created.",
});
} catch (e) {
sendNotification({
type: "error",
title: "Failed to create vault",
description:
"An unexpected error occurred while creating the vault.",
});
}
}}
>
<Page.Vertical gap="md">
<div className="mb-4 flex w-full max-w-xl flex-col gap-y-2 p-4">
<Page.SectionHeader title="Name" />
<Input
placeholder="Vault's name"
value={vaultName}
name="vaultName"
size="sm"
onChange={(value) => setVaultName(value)}
/>
<div className="flex gap-1 text-xs text-element-700">
<Icon visual={InformationCircleIcon} size="xs" />
<span>Vault name must be unique</span>
</div>
</div>
<div className="flex w-full flex-col gap-y-4 border-t p-4">
<Page.SectionHeader title="Vault members" />
<Searchbar
name="search"
placeholder="Search members"
value={searchTerm}
onChange={setSearchTerm}
/>
<DataTable
data={rows}
columns={columns}
filterColumn="name"
filter={searchTerm}
/>
</div>
</Page.Vertical>
</Modal>
);
}

0 comments on commit 52e3566

Please sign in to comment.