From 5a215fcbf78a657878f0bdb93da67b1356548d54 Mon Sep 17 00:00:00 2001 From: pandeymangg Date: Fri, 6 Feb 2026 12:59:03 +0530 Subject: [PATCH] fix: code simplification and fixes the key validation in bulk contacts upload api --- apps/web/i18n.lock | 7 ++- apps/web/locales/de-DE.json | 3 +- apps/web/locales/en-US.json | 1 - apps/web/locales/es-ES.json | 3 +- apps/web/locales/fr-FR.json | 3 +- apps/web/locales/ja-JP.json | 3 +- apps/web/locales/nl-NL.json | 3 +- apps/web/locales/pt-BR.json | 3 +- apps/web/locales/pt-PT.json | 3 +- apps/web/locales/ro-RO.json | 3 +- apps/web/locales/ru-RU.json | 3 +- apps/web/locales/sv-SE.json | 3 +- apps/web/locales/zh-Hans-CN.json | 3 +- apps/web/locales/zh-Hant-TW.json | 3 +- .../types/contact-attribute-keys.ts | 14 +++++- .../management/contacts/bulk/lib/contact.ts | 17 +++++++ .../components/upload-contacts-button.tsx | 50 +++++++++++++++++-- .../ee/contacts/lib/contact-attributes.ts | 42 ++-------------- .../modules/ee/contacts/segments/actions.ts | 3 +- .../components/attribute-value-input.tsx | 15 +++--- .../segments/components/segment-filter.tsx | 3 +- 21 files changed, 117 insertions(+), 71 deletions(-) diff --git a/apps/web/i18n.lock b/apps/web/i18n.lock index 751daefde2..8f434dd1eb 100644 --- a/apps/web/i18n.lock +++ b/apps/web/i18n.lock @@ -604,6 +604,7 @@ checksums: environments/contacts/create_key: 0d385c354af8963acbe35cd646710f86 environments/contacts/create_new_attribute: c17d407dacd0b90f360f9f5e899d662f environments/contacts/create_new_attribute_description: cc19d76bb6940537bbe3461191f25d26 + environments/contacts/custom_attributes: fffc7722742d1291b102dc737cf2fc9e environments/contacts/data_type: 1ea127ba2c18d0d91fb0361cc6747e2b environments/contacts/data_type_cannot_be_changed: 22603f6193fdac3784eeef8315df70de environments/contacts/data_type_description: 800bf4935df15e6cb14269e1b60c506e @@ -619,6 +620,7 @@ checksums: environments/contacts/edit_attributes_success: 39f93b1a6f1605bc5951f4da5847bb22 environments/contacts/generate_personal_link: 9ac0865f6876d40fe858f94eae781eb8 environments/contacts/generate_personal_link_description: b9dbaf9e2d8362505b7e3cfa40f415a6 + environments/contacts/invalid_csv_column_names: dcb8534e7d4c00b9ea7bdaf389f72328 environments/contacts/invalid_date_format: 5bad9730ac5a5bacd0792098f712b1c4 environments/contacts/invalid_number_format: bd0422507385f671c3046730a6febc64 environments/contacts/no_published_link_surveys_available: 9c1abc5b21aba827443cdf87dd6c8bfe @@ -634,13 +636,16 @@ checksums: environments/contacts/search_contact: 020205a93846ab3e12c203ac4fa97c12 environments/contacts/select_a_survey: 1f49086dfb874307aae1136e88c3d514 environments/contacts/select_attribute: d93fb60eb4fbb42bf13a22f6216fbd79 + environments/contacts/select_attribute_key: 673a6683fab41b387d921841cded7e38 + environments/contacts/system_attributes: eadb6a8888c7b32c0e68881f945ae9b6 environments/contacts/unlock_contacts_description: c5572047f02b4c39e5109f9de715499d environments/contacts/unlock_contacts_title: a8b3d7db03eb404d9267fd5cdd6d5ddb + environments/contacts/upload_contacts_modal_attribute_header: 263246ad2a76f8e2f80f0ed175d7629a environments/contacts/upload_contacts_modal_attributes_description: e2cedbd4a043423002cbb2048e2145ac environments/contacts/upload_contacts_modal_attributes_new: 9829382598c681de74130440a37b560f environments/contacts/upload_contacts_modal_attributes_search_or_add: 1874839e465650d353282b43b00247a9 - environments/contacts/upload_contacts_modal_attributes_should_be_mapped_to: 693dfe5836e90b1c4c7c65b015418174 environments/contacts/upload_contacts_modal_attributes_title: 86d0ae6fea0fbb119722ed3841f8385a + environments/contacts/upload_contacts_modal_csv_column_header: f181add48fb8325efaa40579fe8c343e environments/contacts/upload_contacts_modal_description: 41566d40d25cc882aa9f82d87b4e2f03 environments/contacts/upload_contacts_modal_download_example_csv: 7a186fc4941b264452ee6c9e785769de environments/contacts/upload_contacts_modal_duplicates_description: 112ce4641088520469a83a0bd740b073 diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index d9cef5ae4a..b4742b4013 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -676,11 +676,12 @@ "system_attributes": "Systemattribute", "unlock_contacts_description": "Verwalte Kontakte und sende gezielte Umfragen", "unlock_contacts_title": "Kontakte mit einem höheren Plan freischalten", + "upload_contacts_modal_attribute_header": "Formbricks-Attribut", "upload_contacts_modal_attributes_description": "Ordne die Spalten in deiner CSV den Attributen in Formbricks zu.", "upload_contacts_modal_attributes_new": "Neues Attribut", "upload_contacts_modal_attributes_search_or_add": "Attribut suchen oder hinzufügen", - "upload_contacts_modal_attributes_should_be_mapped_to": "sollte zugeordnet werden zu", "upload_contacts_modal_attributes_title": "Attribute", + "upload_contacts_modal_csv_column_header": "CSV-Spalte", "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?", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 1248dd34d6..3b957860d3 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -680,7 +680,6 @@ "upload_contacts_modal_attributes_description": "Map the columns in your CSV to the attributes in Formbricks.", "upload_contacts_modal_attributes_new": "New attribute", "upload_contacts_modal_attributes_search_or_add": "Search or add attribute", - "upload_contacts_modal_attributes_should_be_mapped_to": "should be mapped to", "upload_contacts_modal_attributes_title": "Attributes", "upload_contacts_modal_csv_column_header": "CSV Column", "upload_contacts_modal_description": "Upload a CSV to quickly import contacts with attributes", diff --git a/apps/web/locales/es-ES.json b/apps/web/locales/es-ES.json index 370b1bba3c..76aadeddd6 100644 --- a/apps/web/locales/es-ES.json +++ b/apps/web/locales/es-ES.json @@ -676,11 +676,12 @@ "system_attributes": "Atributos del sistema", "unlock_contacts_description": "Gestiona contactos y envía encuestas dirigidas", "unlock_contacts_title": "Desbloquea contactos con un plan superior", + "upload_contacts_modal_attribute_header": "Atributo de Formbricks", "upload_contacts_modal_attributes_description": "Asigna las columnas de tu CSV a los atributos en Formbricks.", "upload_contacts_modal_attributes_new": "Nuevo atributo", "upload_contacts_modal_attributes_search_or_add": "Buscar o añadir atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "debe asignarse a", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Columna CSV", "upload_contacts_modal_description": "Sube un CSV para importar rápidamente contactos con atributos", "upload_contacts_modal_download_example_csv": "Descargar CSV de ejemplo", "upload_contacts_modal_duplicates_description": "¿Cómo deberíamos manejar si un contacto ya existe en tus contactos?", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index 9d3d6753e6..3a803e0231 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -676,11 +676,12 @@ "system_attributes": "Attributs système", "unlock_contacts_description": "Gérer les contacts et envoyer des enquêtes ciblées", "unlock_contacts_title": "Débloquez des contacts avec un plan supérieur.", + "upload_contacts_modal_attribute_header": "Attribut Formbricks", "upload_contacts_modal_attributes_description": "Mappez les colonnes de votre CSV aux attributs dans Formbricks.", "upload_contacts_modal_attributes_new": "Nouvel attribut", "upload_contacts_modal_attributes_search_or_add": "Rechercher ou ajouter un attribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "devrait être mappé à", "upload_contacts_modal_attributes_title": "Attributs", + "upload_contacts_modal_csv_column_header": "Colonne CSV", "upload_contacts_modal_description": "Téléchargez un fichier 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": "Que faire si un contact existe déjà ?", diff --git a/apps/web/locales/ja-JP.json b/apps/web/locales/ja-JP.json index 34a1be1e2b..b5b5ac42bd 100644 --- a/apps/web/locales/ja-JP.json +++ b/apps/web/locales/ja-JP.json @@ -676,11 +676,12 @@ "system_attributes": "システム属性", "unlock_contacts_description": "連絡先を管理し、特定のフォームを送信します", "unlock_contacts_title": "上位プランで連絡先をアンロック", + "upload_contacts_modal_attribute_header": "Formbricks属性", "upload_contacts_modal_attributes_description": "CSVの列をFormbricksの属性にマッピングします。", "upload_contacts_modal_attributes_new": "新しい属性", "upload_contacts_modal_attributes_search_or_add": "属性を検索または追加", - "upload_contacts_modal_attributes_should_be_mapped_to": "は以下にマッピングする必要があります", "upload_contacts_modal_attributes_title": "属性", + "upload_contacts_modal_csv_column_header": "CSV列", "upload_contacts_modal_description": "CSVをアップロードして、属性を持つ連絡先をすばやくインポート", "upload_contacts_modal_download_example_csv": "CSVの例をダウンロード", "upload_contacts_modal_duplicates_description": "連絡先がすでに存在する場合、どのように処理しますか?", diff --git a/apps/web/locales/nl-NL.json b/apps/web/locales/nl-NL.json index dc1005cb75..3f672252c2 100644 --- a/apps/web/locales/nl-NL.json +++ b/apps/web/locales/nl-NL.json @@ -676,11 +676,12 @@ "system_attributes": "Systeemkenmerken", "unlock_contacts_description": "Beheer contacten en verstuur gerichte enquêtes", "unlock_contacts_title": "Ontgrendel contacten met een hoger abonnement", + "upload_contacts_modal_attribute_header": "Formbricks attribuut", "upload_contacts_modal_attributes_description": "Wijs de kolommen in uw CSV toe aan de attributen in Formbricks.", "upload_contacts_modal_attributes_new": "Nieuw attribuut", "upload_contacts_modal_attributes_search_or_add": "Kenmerk zoeken of toevoegen", - "upload_contacts_modal_attributes_should_be_mapped_to": "in kaart moeten worden gebracht", "upload_contacts_modal_attributes_title": "Kenmerken", + "upload_contacts_modal_csv_column_header": "CSV kolom", "upload_contacts_modal_description": "Upload een CSV om snel contacten met attributen te importeren", "upload_contacts_modal_download_example_csv": "Voorbeeld-CSV downloaden", "upload_contacts_modal_duplicates_description": "Hoe moeten we omgaan als er al een contact bestaat in uw contacten?", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index 89abd4f0a6..001e32a84c 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -676,11 +676,12 @@ "system_attributes": "Atributos do sistema", "unlock_contacts_description": "Gerencie contatos e envie pesquisas direcionadas", "unlock_contacts_title": "Desbloqueie contatos com um plano superior", + "upload_contacts_modal_attribute_header": "Atributo do Formbricks", "upload_contacts_modal_attributes_description": "Mapeie as colunas do seu CSV para os atributos no Formbricks.", "upload_contacts_modal_attributes_new": "Novo atributo", "upload_contacts_modal_attributes_search_or_add": "Buscar ou adicionar atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Coluna CSV", "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?", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index d34914706b..70ffe522f4 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -676,11 +676,12 @@ "system_attributes": "Atributos do sistema", "unlock_contacts_description": "Gerir contactos e enviar inquéritos direcionados", "unlock_contacts_title": "Desbloqueie os contactos com um plano superior", + "upload_contacts_modal_attribute_header": "Atributo Formbricks", "upload_contacts_modal_attributes_description": "Mapeie as colunas no seu CSV para os atributos no Formbricks.", "upload_contacts_modal_attributes_new": "Novo atributo", "upload_contacts_modal_attributes_search_or_add": "Pesquisar ou adicionar atributo", - "upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para", "upload_contacts_modal_attributes_title": "Atributos", + "upload_contacts_modal_csv_column_header": "Coluna CSV", "upload_contacts_modal_description": "Carregue um ficheiro CSV para importar rapidamente contactos com atributos", "upload_contacts_modal_download_example_csv": "Descarregar exemplo de CSV", "upload_contacts_modal_duplicates_description": "Como devemos proceder se um contacto já existir nos seus contactos?", diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json index fb6aea19dc..788b824a9c 100644 --- a/apps/web/locales/ro-RO.json +++ b/apps/web/locales/ro-RO.json @@ -676,11 +676,12 @@ "system_attributes": "Atribute de sistem", "unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite", "unlock_contacts_title": "Deblocați contactele cu un plan superior.", + "upload_contacts_modal_attribute_header": "Atribut Formbricks", "upload_contacts_modal_attributes_description": "Mapează coloanele din CSV-ul tău la atributele din Formbricks.", "upload_contacts_modal_attributes_new": "Atribut nou", "upload_contacts_modal_attributes_search_or_add": "Căutați sau adăugați atribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "ar trebui să fie mapat către", "upload_contacts_modal_attributes_title": "Atribute", + "upload_contacts_modal_csv_column_header": "Coloană CSV", "upload_contacts_modal_description": "Încărcați un fișier CSV pentru a importa rapid contactele cu atribute.", "upload_contacts_modal_download_example_csv": "Descărcați exemplul CSV", "upload_contacts_modal_duplicates_description": "Cum ar trebui să procedăm dacă un contact există deja în agenda dumneavoastră?", diff --git a/apps/web/locales/ru-RU.json b/apps/web/locales/ru-RU.json index ec8e92cf83..3777a00624 100644 --- a/apps/web/locales/ru-RU.json +++ b/apps/web/locales/ru-RU.json @@ -676,11 +676,12 @@ "system_attributes": "Системные атрибуты", "unlock_contacts_description": "Управляйте контактами и отправляйте целевые опросы", "unlock_contacts_title": "Откройте доступ к контактам с более высоким тарифом", + "upload_contacts_modal_attribute_header": "Атрибут Formbricks", "upload_contacts_modal_attributes_description": "Сопоставьте столбцы в вашем CSV с атрибутами в Formbricks.", "upload_contacts_modal_attributes_new": "Новый атрибут", "upload_contacts_modal_attributes_search_or_add": "Найти или добавить атрибут", - "upload_contacts_modal_attributes_should_be_mapped_to": "должен быть сопоставлен с", "upload_contacts_modal_attributes_title": "Атрибуты", + "upload_contacts_modal_csv_column_header": "Столбец CSV", "upload_contacts_modal_description": "Загрузите CSV, чтобы быстро импортировать контакты с атрибутами", "upload_contacts_modal_download_example_csv": "Скачать пример CSV", "upload_contacts_modal_duplicates_description": "Как поступить, если контакт уже существует в вашей базе?", diff --git a/apps/web/locales/sv-SE.json b/apps/web/locales/sv-SE.json index 755f77e420..bbe88f82d4 100644 --- a/apps/web/locales/sv-SE.json +++ b/apps/web/locales/sv-SE.json @@ -676,11 +676,12 @@ "system_attributes": "Systemattribut", "unlock_contacts_description": "Hantera kontakter och skicka ut riktade enkäter", "unlock_contacts_title": "Lås upp kontakter med en högre plan", + "upload_contacts_modal_attribute_header": "Formbricks-attribut", "upload_contacts_modal_attributes_description": "Mappa kolumnerna i din CSV till attributen i Formbricks.", "upload_contacts_modal_attributes_new": "Nytt attribut", "upload_contacts_modal_attributes_search_or_add": "Sök eller lägg till attribut", - "upload_contacts_modal_attributes_should_be_mapped_to": "ska mappas till", "upload_contacts_modal_attributes_title": "Attribut", + "upload_contacts_modal_csv_column_header": "CSV-kolumn", "upload_contacts_modal_description": "Ladda upp en CSV för att snabbt importera kontakter med attribut", "upload_contacts_modal_download_example_csv": "Ladda ner exempel-CSV", "upload_contacts_modal_duplicates_description": "Hur ska vi hantera om en kontakt redan finns i dina kontakter?", diff --git a/apps/web/locales/zh-Hans-CN.json b/apps/web/locales/zh-Hans-CN.json index 2ace50c5fc..7e23c4bebd 100644 --- a/apps/web/locales/zh-Hans-CN.json +++ b/apps/web/locales/zh-Hans-CN.json @@ -676,11 +676,12 @@ "system_attributes": "系统属性", "unlock_contacts_description": "管理 联系人 并 发送 定向 调查", "unlock_contacts_title": "通过 更 高级 划解锁 联系人", + "upload_contacts_modal_attribute_header": "Formbricks 属性", "upload_contacts_modal_attributes_description": "将您 CSV 中的列映射到 Formbricks 中的属性。", "upload_contacts_modal_attributes_new": "新 属性", "upload_contacts_modal_attributes_search_or_add": "搜索或添加属性", - "upload_contacts_modal_attributes_should_be_mapped_to": "应该映射到", "upload_contacts_modal_attributes_title": "属性", + "upload_contacts_modal_csv_column_header": "CSV 列", "upload_contacts_modal_description": "上传 CSV,快速 导入 具有 属性 的 联系人", "upload_contacts_modal_download_example_csv": "下载 示例 CSV", "upload_contacts_modal_duplicates_description": "如果联系人已经存在,应该如何处理?", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index a9b5286029..c62473da5a 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -676,11 +676,12 @@ "system_attributes": "系統屬性", "unlock_contacts_description": "管理聯絡人並發送目標問卷", "unlock_contacts_title": "使用更高等級的方案解鎖聯絡人", + "upload_contacts_modal_attribute_header": "Formbricks 屬性", "upload_contacts_modal_attributes_description": "將 CSV 中的欄位對應到 Formbricks 中的屬性。", "upload_contacts_modal_attributes_new": "新增屬性", "upload_contacts_modal_attributes_search_or_add": "搜尋或新增屬性", - "upload_contacts_modal_attributes_should_be_mapped_to": "應對應到", "upload_contacts_modal_attributes_title": "屬性", + "upload_contacts_modal_csv_column_header": "CSV 欄位", "upload_contacts_modal_description": "上傳 CSV 以快速匯入具有屬性的聯絡人", "upload_contacts_modal_download_example_csv": "下載範例 CSV", "upload_contacts_modal_duplicates_description": "如果聯絡人已存在於您的聯絡人中,我們應該如何處理?", diff --git a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts index b97d27f1be..4c28d00a50 100644 --- a/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts +++ b/apps/web/modules/ee/contacts/api/v1/management/contact-attribute-keys/[contactAttributeKeyId]/types/contact-attribute-keys.ts @@ -1,8 +1,12 @@ import { z } from "zod"; import { ZContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; +import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; export const ZContactAttributeKeyCreateInput = z.object({ - key: z.string(), + key: z.string().refine((val) => isSafeIdentifier(val), { + message: + "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", + }), description: z.string().optional(), type: z.enum(["custom"]), dataType: ZContactAttributeDataType.optional(), @@ -14,7 +18,13 @@ export type TContactAttributeKeyCreateInput = z.infer isSafeIdentifier(val), { + message: + "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", + }) + .optional(), dataType: ZContactAttributeDataType.optional(), }); diff --git a/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts b/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts index ce4b9fffbb..63b6cf5907 100644 --- a/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts +++ b/apps/web/modules/ee/contacts/api/v2/management/contacts/bulk/lib/contact.ts @@ -4,6 +4,7 @@ import { prisma } from "@formbricks/database"; import { logger } from "@formbricks/logger"; import { TContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { Result, err, ok } from "@formbricks/types/error-handlers"; +import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error"; import { prepareAttributeColumnsForStorage } from "@/modules/ee/contacts/lib/attribute-storage"; import { detectAttributeDataType } from "@/modules/ee/contacts/lib/detect-attribute-type"; @@ -497,6 +498,22 @@ export const upsertBulkContacts = async ( }), ]); + // Validate new attribute keys are safe identifiers before proceeding + const existingKeySet = new Set(existingAttributeKeys.map((ak) => ak.key)); + const invalidNewKeys = attributeKeys.filter((key) => !existingKeySet.has(key) && !isSafeIdentifier(key)); + + if (invalidNewKeys.length > 0) { + return err({ + type: "bad_request", + details: [ + { + field: "attributes", + issue: `Invalid attribute key(s): ${invalidNewKeys.join(", ")}. Keys must only contain lowercase letters, numbers, and underscores, and must start with a letter.`, + }, + ], + }); + } + // Type Detection Phase const attributeValuesByKey = buildAttributeValuesByKey(contacts); const attributeTypeMap = determineAttributeTypes(attributeValuesByKey, existingAttributeKeys); diff --git a/apps/web/modules/ee/contacts/components/upload-contacts-button.tsx b/apps/web/modules/ee/contacts/components/upload-contacts-button.tsx index b0cc72e14a..69adc840f3 100644 --- a/apps/web/modules/ee/contacts/components/upload-contacts-button.tsx +++ b/apps/web/modules/ee/contacts/components/upload-contacts-button.tsx @@ -283,11 +283,51 @@ export const UploadContactsCSVButton = ({ // Function to download an example CSV const handleDownloadExampleCSV = () => { const exampleData = [ - { email: "user1@example.com", userId: "1001", first_name: "John", last_name: "Doe" }, - { email: "user2@example.com", userId: "1002", first_name: "Jane", last_name: "Smith" }, - { email: "user3@example.com", userId: "1003", first_name: "Mark", last_name: "Jones" }, - { email: "user4@example.com", userId: "1004", first_name: "Emily", last_name: "Brown" }, - { email: "user5@example.com", userId: "1005", first_name: "David", last_name: "Wilson" }, + { + email: "user1@example.com", + userId: "1001", + first_name: "John", + last_name: "Doe", + age: "28", + plan: "premium", + signup_date: "2024-01-15", + }, + { + email: "user2@example.com", + userId: "1002", + first_name: "Jane", + last_name: "Smith", + age: "34", + plan: "free", + signup_date: "2024-02-20", + }, + { + email: "user3@example.com", + userId: "1003", + first_name: "Mark", + last_name: "Jones", + age: "45", + plan: "enterprise", + signup_date: "2023-11-08", + }, + { + email: "user4@example.com", + userId: "1004", + first_name: "Emily", + last_name: "Brown", + age: "22", + plan: "premium", + signup_date: "2024-03-01", + }, + { + email: "user5@example.com", + userId: "1005", + first_name: "David", + last_name: "Wilson", + age: "31", + plan: "free", + signup_date: "2024-01-28", + }, ]; const headers = Object.keys(exampleData[0]); diff --git a/apps/web/modules/ee/contacts/lib/contact-attributes.ts b/apps/web/modules/ee/contacts/lib/contact-attributes.ts index 96ebbf61a6..2c2512a3e2 100644 --- a/apps/web/modules/ee/contacts/lib/contact-attributes.ts +++ b/apps/web/modules/ee/contacts/lib/contact-attributes.ts @@ -3,7 +3,6 @@ import { cache as reactCache } from "react"; import { prisma } from "@formbricks/database"; import { ZId, ZString } from "@formbricks/types/common"; import { TContactAttributes } from "@formbricks/types/contact-attribute"; -import { TContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { DatabaseError } from "@formbricks/types/errors"; import { ZUserEmail } from "@formbricks/types/user"; import { validateInputs } from "@/lib/utils/validate"; @@ -132,55 +131,24 @@ export const hasUserIdAttribute = reactCache( ); export const getDistinctAttributeValues = reactCache( - async (attributeKeyId: string, dataType: TContactAttributeDataType, limit: number = 50) => { + async (attributeKeyId: string, limit: number = 50): Promise => { validateInputs([attributeKeyId, ZId]); try { - // Determine which column to query based on data type - let selectField: "value" | "valueNumber" | "valueDate"; - if (dataType === "number") { - selectField = "valueNumber"; - } else if (dataType === "date") { - selectField = "valueDate"; - } else { - selectField = "value"; - } - - // Build where clause - only filter by attributeKeyId, we'll filter nulls in JS const results = await prisma.contactAttribute.findMany({ where: { attributeKeyId, + value: { not: "" }, }, select: { value: true, - valueNumber: true, - valueDate: true, }, - distinct: [selectField as any], + distinct: ["value"], take: limit * 2, // Get more than needed to account for filtering - orderBy: { [selectField]: "asc" }, + orderBy: { value: "asc" }, }); - // Extract and filter values based on data type - let values: any[]; - if (dataType === "number") { - values = results - .map((r) => r.valueNumber) - .filter((v) => v !== null && v !== undefined) - .slice(0, limit); - } else if (dataType === "date") { - values = results - .map((r) => r.valueDate) - .filter((v) => v !== null && v !== undefined) - .slice(0, limit); - } else { - values = results - .map((r) => r.value) - .filter((v) => v !== null && v !== undefined && v !== "") - .slice(0, limit); - } - - return values; + return results.map((r) => r.value); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { throw new DatabaseError(error.message); diff --git a/apps/web/modules/ee/contacts/segments/actions.ts b/apps/web/modules/ee/contacts/segments/actions.ts index ef5ac3a12b..f46f55f451 100644 --- a/apps/web/modules/ee/contacts/segments/actions.ts +++ b/apps/web/modules/ee/contacts/segments/actions.ts @@ -315,7 +315,6 @@ export const resetSegmentFiltersAction = authenticatedActionClient.schema(ZReset const ZGetDistinctAttributeValuesAction = z.object({ environmentId: ZId, attributeKeyId: ZId, - dataType: z.enum(["string", "number", "date"]), }); export const getDistinctAttributeValuesAction = authenticatedActionClient @@ -337,5 +336,5 @@ export const getDistinctAttributeValuesAction = authenticatedActionClient ], }); - return await getDistinctAttributeValues(parsedInput.attributeKeyId, parsedInput.dataType); + return await getDistinctAttributeValues(parsedInput.attributeKeyId); }); diff --git a/apps/web/modules/ee/contacts/segments/components/attribute-value-input.tsx b/apps/web/modules/ee/contacts/segments/components/attribute-value-input.tsx index ffb6b7990c..4618d56fc7 100644 --- a/apps/web/modules/ee/contacts/segments/components/attribute-value-input.tsx +++ b/apps/web/modules/ee/contacts/segments/components/attribute-value-input.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { TContactAttributeDataType } from "@formbricks/types/contact-attribute-key"; import { cn } from "@/lib/cn"; import { InputCombobox, TComboboxOption } from "@/modules/ui/components/input-combo-box"; import { getDistinctAttributeValuesAction } from "../actions"; @@ -9,9 +8,8 @@ import { getDistinctAttributeValuesAction } from "../actions"; interface AttributeValueInputProps { attributeKeyId: string; environmentId: string; - dataType: TContactAttributeDataType; - value: string | number; - onChange: (value: string | number) => void; + value: string; + onChange: (value: string) => void; disabled?: boolean; className?: string; valueError?: string; @@ -20,7 +18,6 @@ interface AttributeValueInputProps { export const AttributeValueInput = ({ attributeKeyId, environmentId, - dataType, value, onChange, disabled, @@ -45,14 +42,14 @@ export const AttributeValueInput = ({ const result = await getDistinctAttributeValuesAction({ environmentId, attributeKeyId, - dataType, }); if (!isCancelled && result?.data) { const comboboxOptions: TComboboxOption[] = result.data.map((val) => ({ - label: String(val), + label: val, value: val, })); + setOptions(comboboxOptions); } } catch (error) { @@ -71,7 +68,7 @@ export const AttributeValueInput = ({ return () => { isCancelled = true; }; - }, [environmentId, attributeKeyId, dataType]); + }, [environmentId, attributeKeyId]); const emptyDropdownText = useMemo(() => { if (loading) return "Loading values..."; @@ -85,7 +82,7 @@ export const AttributeValueInput = ({ id={`attribute-value-${attributeKeyId}`} options={options} value={value} - onChangeValue={(newValue) => onChange(newValue as string | number)} + onChangeValue={(newValue) => onChange(newValue as string)} withInput={true} showSearch={true} clearable={true} diff --git a/apps/web/modules/ee/contacts/segments/components/segment-filter.tsx b/apps/web/modules/ee/contacts/segments/components/segment-filter.tsx index c23a148a5f..e0a106b78d 100644 --- a/apps/web/modules/ee/contacts/segments/components/segment-filter.tsx +++ b/apps/web/modules/ee/contacts/segments/components/segment-filter.tsx @@ -315,8 +315,7 @@ function AttributeSegmentFilter({ { updateValueInLocalSurvey(resource.id, newValue); }}