mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-21 10:31:13 -06:00
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:
@@ -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>
|
||||
|
||||
@@ -580,6 +580,8 @@
|
||||
"contact_deleted_successfully": "Kontakt erfolgreich gelöscht",
|
||||
"contact_not_found": "Kein solcher Kontakt gefunden",
|
||||
"contacts_table_refresh": "Kontakte aktualisieren",
|
||||
"contacts_table_refresh_error": "Beim Aktualisieren der Kontakte ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
|
||||
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
|
||||
"error_fetching_next_page_of_people_data": "Fehler beim Abrufen der nächsten Seite mit Kontaktdaten",
|
||||
"error_fetching_people_data": "Fehler beim Abrufen der Kontaktdaten",
|
||||
"fetching_user": "Benutzer wird geladen",
|
||||
@@ -601,6 +603,7 @@
|
||||
"upload_contacts_modal_attributes_should_be_mapped_to": "sollte zugeordnet werden zu",
|
||||
"upload_contacts_modal_attributes_title": "Attribute",
|
||||
"upload_contacts_modal_description": "Lade eine CSV hoch, um Kontakte mit Attributen schnell zu importieren",
|
||||
"upload_contacts_modal_download_example_csv": "Beispiel-CSV herunterladen",
|
||||
"upload_contacts_modal_duplicates_description": "Wie sollen wir vorgehen, wenn ein Kontakt bereits existiert?",
|
||||
"upload_contacts_modal_duplicates_overwrite_description": "Überschreibt die bestehenden Kontakte",
|
||||
"upload_contacts_modal_duplicates_overwrite_title": "Überschreiben",
|
||||
|
||||
@@ -580,6 +580,8 @@
|
||||
"contact_deleted_successfully": "Contact deleted successfully",
|
||||
"contact_not_found": "No such contact found",
|
||||
"contacts_table_refresh": "Refresh contacts",
|
||||
"contacts_table_refresh_error": "Something went wrong while refreshing contacts, please try again",
|
||||
"contacts_table_refresh_success": "Contacts refreshed successfully",
|
||||
"error_fetching_next_page_of_people_data": "Error fetching next page of contacts data",
|
||||
"error_fetching_people_data": "Error fetching contacts data",
|
||||
"fetching_user": "Fetching user",
|
||||
@@ -601,6 +603,7 @@
|
||||
"upload_contacts_modal_attributes_should_be_mapped_to": "should be mapped to",
|
||||
"upload_contacts_modal_attributes_title": "Attributes",
|
||||
"upload_contacts_modal_description": "Upload a CSV to quickly import contacts with attributes",
|
||||
"upload_contacts_modal_download_example_csv": "Download example CSV",
|
||||
"upload_contacts_modal_duplicates_description": "How should we handle if a contact already exists in your contacts?",
|
||||
"upload_contacts_modal_duplicates_overwrite_description": "Overwrites the existing contacts",
|
||||
"upload_contacts_modal_duplicates_overwrite_title": "Overwrite",
|
||||
|
||||
@@ -580,6 +580,8 @@
|
||||
"contact_deleted_successfully": "Contact supprimé avec succès",
|
||||
"contact_not_found": "Aucun contact trouvé",
|
||||
"contacts_table_refresh": "Rafraîchir les contacts",
|
||||
"contacts_table_refresh_error": "Une erreur s'est produite lors de la mise à jour des contacts. Veuillez réessayer.",
|
||||
"contacts_table_refresh_success": "Contacts rafraîchis avec succès",
|
||||
"error_fetching_next_page_of_people_data": "Erreur lors de la récupération de la page suivante des données de contacts",
|
||||
"error_fetching_people_data": "Erreur lors de la récupération des données de contacts",
|
||||
"fetching_user": "Récupération de l'utilisateur",
|
||||
@@ -601,6 +603,7 @@
|
||||
"upload_contacts_modal_attributes_should_be_mapped_to": "devrait être mappé à",
|
||||
"upload_contacts_modal_attributes_title": "Attributs",
|
||||
"upload_contacts_modal_description": "Téléchargez un CSV pour importer rapidement des contacts avec des attributs.",
|
||||
"upload_contacts_modal_download_example_csv": "Télécharger un exemple de CSV",
|
||||
"upload_contacts_modal_duplicates_description": "Comment devrions-nous procéder si un contact existe déjà dans vos contacts ?",
|
||||
"upload_contacts_modal_duplicates_overwrite_description": "Écrase les contacts existants",
|
||||
"upload_contacts_modal_duplicates_overwrite_title": "Sélectionner",
|
||||
|
||||
@@ -580,6 +580,8 @@
|
||||
"contact_deleted_successfully": "Contato excluído com sucesso",
|
||||
"contact_not_found": "Nenhum contato encontrado",
|
||||
"contacts_table_refresh": "Atualizar contatos",
|
||||
"contacts_table_refresh_error": "Ocorreu um erro ao atualizar os contatos. Por favor, tente novamente.",
|
||||
"contacts_table_refresh_success": "Contatos atualizados com sucesso",
|
||||
"error_fetching_next_page_of_people_data": "Erro ao buscar a próxima página de dados de contatos",
|
||||
"error_fetching_people_data": "Erro ao buscar os dados de contatos",
|
||||
"fetching_user": "Carregando usuário",
|
||||
@@ -601,6 +603,7 @@
|
||||
"upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para",
|
||||
"upload_contacts_modal_attributes_title": "Atributos",
|
||||
"upload_contacts_modal_description": "Faça upload de um CSV para importar contatos com atributos rapidamente",
|
||||
"upload_contacts_modal_download_example_csv": "Baixar exemplo de CSV",
|
||||
"upload_contacts_modal_duplicates_description": "O que devemos fazer se um contato já existir nos seus contatos?",
|
||||
"upload_contacts_modal_duplicates_overwrite_description": "Sobrescreve os contatos existentes",
|
||||
"upload_contacts_modal_duplicates_overwrite_title": "Sobrescrever",
|
||||
|
||||
Reference in New Issue
Block a user