fix: adds example CSV and other fixes on the contacts page (#4493)

Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Anshuman Pandey
2024-12-18 17:32:21 +05:30
committed by GitHub
parent 3b90f085b1
commit 7b11ef9b40
10 changed files with 99 additions and 40 deletions
@@ -24,7 +24,7 @@ interface ContactDataViewProps {
itemsPerPage: number;
isReadOnly: boolean;
hasMore: boolean;
refreshContacts: () => void;
refreshContacts: () => Promise<void>;
}
export const ContactDataView = ({
@@ -42,7 +42,7 @@ interface ContactsTableProps {
searchValue: string;
setSearchValue: (value: string) => void;
isReadOnly: boolean;
refreshContacts: () => void;
refreshContacts: () => Promise<void>;
}
export const ContactsTable = ({
@@ -210,6 +210,29 @@ export const UploadContactsCSVButton = ({
}
}, [error]);
// Function to download an example CSV
const handleDownloadExampleCSV = () => {
const exampleData = [
{ email: "user1@example.com", userId: "1001", firstName: "John", lastName: "Doe" },
{ email: "user2@example.com", userId: "1002", firstName: "Jane", lastName: "Smith" },
{ email: "user3@example.com", userId: "1003", firstName: "Mark", lastName: "Jones" },
{ email: "user4@example.com", userId: "1004", firstName: "Emily", lastName: "Brown" },
{ email: "user5@example.com", userId: "1005", firstName: "David", lastName: "Wilson" },
];
const headers = Object.keys(exampleData[0]);
const csvRows = [headers.join(","), ...exampleData.map((row) => headers.map((h) => row[h]).join(","))];
const csvContent = "data:text/csv;charset=utf-8," + csvRows.join("\n");
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "example.csv");
document.body.appendChild(link); // Required for Firefox
link.click();
document.body.removeChild(link);
};
return (
<>
<Button size="sm" onClick={() => setOpen(true)}>
@@ -222,7 +245,7 @@ export const UploadContactsCSVButton = ({
noPadding
closeOnOutsideClick={false}
className="overflow-auto"
size="xxl"
size="xl"
hideCloseButton>
<div className="sticky top-0 flex h-full flex-col rounded-lg">
<button
@@ -262,40 +285,53 @@ export const UploadContactsCSVButton = ({
) : null}
<div className="flex flex-col gap-8 px-6 py-4">
<div className="no-scrollbar max-h-[400px] overflow-auto rounded-md border-2 border-dashed border-slate-300 bg-slate-50 p-4">
{!csvResponse.length ? (
<label
htmlFor="file"
className={cn(
"relative flex cursor-pointer flex-col items-center justify-center rounded-lg hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800"
)}
// onDragOver={(e) => handleDragOver(e)}
// onDrop={(e) => handleDrop(e)}>
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<ArrowUpFromLineIcon className="h-6 text-slate-500" />
<p className={cn("mt-2 text-center text-sm text-slate-500")}>
<span className="font-semibold">{t("common.upload_input_description")}</span>
</p>
<input
type="file"
id={"file"}
name={"file"}
accept=".csv"
className="hidden"
onChange={handleFileUpload}
/>
<div className="flex flex-col gap-2">
<div className="no-scrollbar max-h-[400px] overflow-auto rounded-md border-2 border-dashed border-slate-300 bg-slate-50 p-4">
{!csvResponse.length ? (
<div>
<label
htmlFor="file"
className={cn(
"relative flex cursor-pointer flex-col items-center justify-center rounded-lg hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800"
)}
// onDragOver={(e) => handleDragOver(e)}
// onDrop={(e) => handleDrop(e)}>
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<ArrowUpFromLineIcon className="h-6 text-slate-500" />
<p className={cn("mt-2 text-center text-sm text-slate-500")}>
<span className="font-semibold">{t("common.upload_input_description")}</span>
</p>
<input
type="file"
id={"file"}
name={"file"}
accept=".csv"
className="hidden"
onChange={handleFileUpload}
/>
</div>
</label>
</div>
</label>
) : (
<div className="flex flex-col items-center gap-8">
<h3 className="font-medium text-slate-500">
{t("environments.contacts.upload_contacts_modal_preview")}
</h3>
<div className="h-[300px] w-full overflow-auto rounded-md border border-slate-300">
<CsvTable data={[...csvResponse.slice(0, 11)]} />
) : (
<div className="flex flex-col items-center gap-8">
<h3 className="font-medium text-slate-500">
{t("environments.contacts.upload_contacts_modal_preview")}
</h3>
<div className="h-[300px] w-full overflow-auto rounded-md border border-slate-300">
<CsvTable data={[...csvResponse.slice(0, 11)]} />
</div>
</div>
</div>
)}
</div>
{!csvResponse.length && (
<p>
<a
onClick={handleDownloadExampleCSV}
className="cursor-pointer text-right text-sm text-slate-500">
{t("environments.contacts.upload_contacts_modal_download_example_csv")}{" "}
</a>
</p>
)}
</div>
@@ -2,6 +2,7 @@ import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { Table } from "@tanstack/react-table";
import { MoveVerticalIcon, RefreshCcwIcon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { SelectedRowSettings } from "./selected-row-settings";
@@ -13,7 +14,7 @@ interface DataTableToolbarProps<T> {
deleteRows: (rowIds: string[]) => void;
type: "response" | "contact";
deleteAction: (id: string) => Promise<void>;
refreshContacts?: () => void;
refreshContacts?: () => Promise<void>;
}
export const DataTableToolbar = <T,>({
@@ -41,9 +42,19 @@ export const DataTableToolbar = <T,>({
tooltipContent={t("environments.contacts.contacts_table_refresh")}
shouldRender={true}>
<div
onClick={() => refreshContacts?.()}
onClick={async () => {
if (refreshContacts) {
try {
await refreshContacts();
toast.success(t("environments.contacts.contacts_table_refresh_success"));
} catch (err) {
console.error(err);
toast.error(t("environments.contacts.contacts_table_refresh_error"));
}
}
}}
className="cursor-pointer rounded-md border bg-white hover:border-slate-400">
<RefreshCcwIcon strokeWidth={1.5} className="m-1 h-6 w-6 p-0.5" />
<RefreshCcwIcon strokeWidth={1.5} className={cn("m-1 h-6 w-6 p-0.5")} />
</div>
</TooltipRenderer>
) : null}
@@ -30,7 +30,7 @@ interface DialogContentProps
const sizeClassName = {
md: "sm:max-w-xl",
lg: "sm:max-w-[820px]",
xl: "sm:max-w-[960px]",
xl: "sm:max-w-[960px] sm:max-h-[640px]",
xxl: "sm:max-w-[1240px] sm:max-h-[760px]",
};
@@ -69,7 +69,7 @@ export const StylingTabs = <T extends string | number>({
className="sr-only"
/>
<span className="text-slate-900">{option.label}</span>
<div>{option.icon}</div>
{option.icon && <div>{option.icon}</div>}
</label>
))}
</div>