diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connector-row-dropdown.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connector-row-dropdown.tsx index 0c490fd73d..49669b1ae7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connector-row-dropdown.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connector-row-dropdown.tsx @@ -13,13 +13,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { TConnectorWithMappings } from "@formbricks/types/connector"; import { DeleteDialog } from "@/modules/ui/components/delete-dialog"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/modules/ui/components/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -28,7 +21,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/modules/ui/components/dropdown-menu"; -import { CsvImportSection } from "./csv-import-section"; +import { CsvImportModal } from "./csv-import-modal"; interface ConnectorRowDropdownProps { connector: TConnectorWithMappings; @@ -164,19 +157,12 @@ export function ConnectorRowDropdown({ /> {connector.type === "csv" && ( - - - - {t("environments.unify.import_csv_data")} - {t("environments.unify.upload_csv_data_description")} - - setIsCsvImportDialogOpen(false)} - /> - - + )} ); diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connectors-page-client.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connectors-page-client.tsx index 84c10c7352..02e3626abf 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connectors-page-client.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/connectors-page-client.tsx @@ -18,6 +18,7 @@ import { UnifyConfigNavigation } from "../../components/UnifyConfigNavigation"; import { TFieldMapping, TUnifySurvey } from "../types"; import { ConnectorsTable } from "./connectors-table"; import { CreateConnectorModal } from "./create-connector-modal"; +import { CsvImportModal } from "./csv-import-modal"; import { EditConnectorModal } from "./edit-connector-modal"; interface ConnectorsSectionProps { @@ -35,6 +36,7 @@ export function ConnectorsSection({ const router = useRouter(); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingConnector, setEditingConnector] = useState(null); + const [csvImportConnector, setCsvImportConnector] = useState(null); const handleCreateConnector = async (data: { name: string; @@ -179,7 +181,24 @@ export function ConnectorsSection({ onOpenChange={(open) => !open && setEditingConnector(null)} onUpdateConnector={handleUpdateConnector} surveys={initialSurveys} + onOpenCsvImport={() => { + if (editingConnector) { + setCsvImportConnector(editingConnector); + } + }} /> + + {csvImportConnector && ( + !open && setCsvImportConnector(null)} + connectorId={csvImportConnector.id} + environmentId={csvImportConnector.environmentId} + onOpenEditConnector={() => { + setEditingConnector(csvImportConnector); + }} + /> + )} ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-modal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-modal.tsx new file mode 100644 index 0000000000..959984f6a1 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-modal.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { Loader2Icon } from "lucide-react"; +import { useCallback, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button } from "@/modules/ui/components/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/modules/ui/components/dialog"; +import { CsvImportHandle, CsvImportSection, CsvImportState } from "./csv-import-section"; + +interface CsvImportModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + connectorId: string; + environmentId: string; + onOpenEditConnector?: () => void; +} + +export function CsvImportModal({ + open, + onOpenChange, + connectorId, + environmentId, + onOpenEditConnector, +}: CsvImportModalProps) { + const { t } = useTranslation(); + const importHandleRef = useRef(null); + const [importState, setImportState] = useState({ + rowCount: 0, + isImporting: false, + hasData: false, + }); + + const handleStateChange = useCallback((state: CsvImportState) => { + setImportState(state); + }, []); + + return ( + + + + {t("environments.unify.import_csv_data")} + {t("environments.unify.upload_csv_data_description")} + + onOpenChange(false)} + onStateChange={handleStateChange} + handleRef={importHandleRef} + renderFooter={false} + /> + + {onOpenEditConnector && ( + + )} + + + + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-section.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-section.tsx index 909eff82ee..130b750382 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-section.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/csv-import-section.tsx @@ -2,7 +2,7 @@ import { parse } from "csv-parse/sync"; import { ArrowUpFromLineIcon, Loader2Icon } from "lucide-react"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { validateCsvFile } from "@/app/(app)/environments/[environmentId]/workspace/unify/sources/utils"; @@ -13,13 +13,33 @@ import { Badge } from "@/modules/ui/components/badge"; import { Button } from "@/modules/ui/components/button"; import { createFeedbackCSVDataSchema } from "../types"; +export interface CsvImportState { + rowCount: number; + isImporting: boolean; + hasData: boolean; +} + +export interface CsvImportHandle { + import: () => Promise; +} + interface CsvImportSectionProps { connectorId: string; environmentId: string; onImportComplete?: () => void; + onStateChange?: (state: CsvImportState) => void; + handleRef?: React.MutableRefObject; + renderFooter?: boolean; } -export function CsvImportSection({ connectorId, environmentId, onImportComplete }: CsvImportSectionProps) { +export function CsvImportSection({ + connectorId, + environmentId, + onImportComplete, + onStateChange, + handleRef, + renderFooter = true, +}: CsvImportSectionProps) { const { t } = useTranslation(); const [csvFile, setCsvFile] = useState(null); const [rowCount, setRowCount] = useState(0); @@ -99,6 +119,17 @@ export function CsvImportSection({ connectorId, environmentId, onImportComplete } }; + const handleImportRef = useRef(handleImport); + handleImportRef.current = handleImport; + + if (handleRef) { + handleRef.current = { import: () => handleImportRef.current() }; + } + + useEffect(() => { + onStateChange?.({ rowCount, isImporting, hasData: parsedData.length > 0 }); + }, [rowCount, isImporting, parsedData.length, onStateChange]); + const handleClear = () => { setCsvFile(null); setParsedData([]); @@ -108,9 +139,9 @@ export function CsvImportSection({ connectorId, environmentId, onImportComplete return (
-
-

{t("environments.unify.csv_import_duplicate_warning")}

-
+ + {t("environments.unify.csv_import_duplicate_warning")} + {csvError && ( @@ -130,16 +161,18 @@ export function CsvImportSection({ connectorId, environmentId, onImportComplete
- + {renderFooter && ( + + )} ) : (
diff --git a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/edit-connector-modal.tsx b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/edit-connector-modal.tsx index 08931aec0b..d76fca067f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/edit-connector-modal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/workspace/unify/sources/components/edit-connector-modal.tsx @@ -38,6 +38,7 @@ interface EditConnectorModalProps { fieldMappings?: TFieldMapping[]; }) => Promise; surveys: TUnifySurvey[]; + onOpenCsvImport?: () => void; } const getConnectorIcon = (type: TConnectorType) => { @@ -79,6 +80,7 @@ export const EditConnectorModal = ({ onOpenChange, onUpdateConnector, surveys, + onOpenCsvImport, }: EditConnectorModalProps) => { const { t } = useTranslation(); const [connectorName, setConnectorName] = useState(""); @@ -271,6 +273,16 @@ export const EditConnectorModal = ({
+ {connector.type === "csv" && ( + + )} diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 62c59cf80f..eeb3e8d840 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -2064,7 +2064,7 @@ "csv_files_only": "CSV files only", "csv_import": "CSV Import", "csv_import_complete": "CSV import complete: {successes} succeeded, {failures} failed, {skipped} skipped", - "csv_import_duplicate_warning": "Importing data that was already imported may create duplicate records.", + "csv_import_duplicate_warning": "Importing data twice will create duplicate records.", "csv_inconsistent_columns": "Row {row} has inconsistent columns. All rows must have the same headers.", "csv_max_records": "Maximum {max} records allowed.", "default_connector_name_csv": "CSV Import", @@ -2072,6 +2072,7 @@ "deselect_all": "Deselect all", "drop_a_field_here": "Drop a field here", "drop_field_or": "Drop field or", + "edit_csv_mapping": "Edit CSV mapping", "edit_source_connection": "Edit Source Connection", "enter_name_for_source": "Enter a name for this source", "enter_value": "Enter value...", @@ -2080,7 +2081,8 @@ "feedback_record_fields": "Feedback Record Fields", "formbricks_surveys": "Formbricks Surveys", "historical_import_complete": "Import complete: {successes} succeeded, {failures} failed, {skipped} skipped (no data)", - "import_csv_data": "Import CSV Data", + "import_csv_data": "Import feedback", + "import_feedback": "Import feedback", "import_rows": "Import {count} rows", "importing_data": "Importing data...", "importing_historical_data": "Importing historical data...",