mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-28 12:42:44 -05:00
feat: feedback record directories
This commit is contained in:
@@ -138,6 +138,12 @@ export const OrganizationBreadcrumb = ({
|
||||
label: t("common.members_and_teams"),
|
||||
href: `/environments/${currentEnvironmentId}/settings/teams`,
|
||||
},
|
||||
{
|
||||
id: "feedback-record-directories",
|
||||
label: t("environments.settings.feedback_record_directories.nav_label"),
|
||||
href: `/environments/${currentEnvironmentId}/settings/feedback-record-directories`,
|
||||
hidden: isMember,
|
||||
},
|
||||
{
|
||||
id: "api-keys",
|
||||
label: t("common.api_keys"),
|
||||
|
||||
+7
@@ -40,6 +40,13 @@ export const OrganizationSettingsNavbar = ({
|
||||
href: `/environments/${environmentId}/settings/teams`,
|
||||
current: pathname?.includes("/teams"),
|
||||
},
|
||||
{
|
||||
id: "feedback-record-directories",
|
||||
label: t("environments.settings.feedback_record_directories.nav_label"),
|
||||
href: `/environments/${environmentId}/settings/feedback-record-directories`,
|
||||
current: pathname?.includes("/feedback-record-directories"),
|
||||
hidden: isMember,
|
||||
},
|
||||
{
|
||||
id: "api-keys",
|
||||
label: t("common.api_keys"),
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { FeedbackRecordDirectoriesPage } from "@/modules/ee/feedback-record-directory/page";
|
||||
|
||||
export default FeedbackRecordDirectoriesPage;
|
||||
@@ -91,6 +91,7 @@ checksums:
|
||||
common/action: c92af0bdf1698b0d10cf5b28d2ad4945
|
||||
common/actions: c46571856723b03262fd33f511116298
|
||||
common/actions_description: 8e35b1538d1006fa8470183310ad21ef
|
||||
common/active: 3e1ec025c4a50830bbb9ad57a176630a
|
||||
common/active_surveys: 95bf0fd5d2bf62cdd010b7cf66795ed7
|
||||
common/activity: 1948763de8e531483a798b68195e297e
|
||||
common/add: 87c4a663507f2bcbbf79934af8164e13
|
||||
@@ -112,6 +113,7 @@ checksums:
|
||||
common/app: 77e32ac4e5a1e01bc9a6a15fdfef9bf8
|
||||
common/app_survey: f076d131d20bfdadb35fba29c8275232
|
||||
common/apply_filters: 6543c1e80038b3da0f4a42848d08d4d1
|
||||
common/archived: cf5127ecfd7e43a35466a1ba5fe16450
|
||||
common/are_you_sure: 6d5cd13628a7887711fd0c29f1123652
|
||||
common/attributes: 86d0ae6fea0fbb119722ed3841f8385a
|
||||
common/back: f541015a827e37cb3b1234e56bc2aa3c
|
||||
@@ -154,6 +156,7 @@ checksums:
|
||||
common/count_questions: a7a34376a01eda781381fe7544541293
|
||||
common/count_responses: 437e022825c7a08481d8f7e56926742d
|
||||
common/count_selections: a1ec41682b9a7d8601c3905dfba34e16
|
||||
common/create: 757ccd28dd533ff3a933355273c1e32a
|
||||
common/create_new_organization: 51dae7b33143686ee218abf5bea764a5
|
||||
common/create_segment: 9d8291cd4d778b53b73bbc84fd91c181
|
||||
common/create_survey: 1cfbba08d34876566d84b2960054a987
|
||||
@@ -809,8 +812,14 @@ checksums:
|
||||
environments/integrations/webhooks/created_by_third_party: b40197eabbbce500b80b44268b8b1ee9
|
||||
environments/integrations/webhooks/discord_webhook_not_supported: 23432534f908b2ba63a517fb1f9bbe0e
|
||||
environments/integrations/webhooks/empty_webhook_message: 4c4d8709576a38cb8eb59866331d2405
|
||||
environments/integrations/webhooks/endpoint_bad_gateway_error: 48ab17e9a77030b289ec22f497f50b63
|
||||
environments/integrations/webhooks/endpoint_gateway_timeout_error: 5da45e2f6933927d1f8b0aaa9566e6a6
|
||||
environments/integrations/webhooks/endpoint_internal_server_error: 6773fc34349febf95475cde88d8ee072
|
||||
environments/integrations/webhooks/endpoint_method_not_allowed_error: 9963b503311393f4d7bffae9df46d422
|
||||
environments/integrations/webhooks/endpoint_not_found_error: 607b75b7b7aa92ca81fe44e466f7c318
|
||||
environments/integrations/webhooks/endpoint_pinged: 3b1fce00e61d4b9d2bdca390649c58b6
|
||||
environments/integrations/webhooks/endpoint_pinged_error: 96c312fe8214757c4a934cdfbe177027
|
||||
environments/integrations/webhooks/endpoint_service_unavailable_error: f9d4874c322f2963f5afaede354c9416
|
||||
environments/integrations/webhooks/learn_to_verify: 25b2a035e2109170b28f4e16db76ad39
|
||||
environments/integrations/webhooks/no_triggers: 6b68cddfc45b3f7e20644a24a1bbea69
|
||||
environments/integrations/webhooks/please_check_console: 7b1787e82a0d762df02c011ebb1650ea
|
||||
@@ -1067,6 +1076,32 @@ checksums:
|
||||
environments/settings/enterprise/sso: 95e98e279bb89233d63549b202bd9112
|
||||
environments/settings/enterprise/teams: 21ab78abcba0f16c3029741563f789ea
|
||||
environments/settings/enterprise/unlock_the_full_power_of_formbricks_free_for_30_days: 104d07b63a42911c9673ceb08a4dbd43
|
||||
environments/settings/feedback_record_directories/all_workspaces_added: 0e726cacab775953965c3e39235d0cad
|
||||
environments/settings/feedback_record_directories/archive: fa813ab3074103e5daad07462af25789
|
||||
environments/settings/feedback_record_directories/archive_directory: 1114a8e1c5c5af3d30a174b28582f424
|
||||
environments/settings/feedback_record_directories/archive_not_allowed: 3ffe3336572a633406858887de60a470
|
||||
environments/settings/feedback_record_directories/are_you_sure_you_want_to_archive: d249e6e8bc0345835a13f70856eb1c30
|
||||
environments/settings/feedback_record_directories/assign_workspaces_description: 6c3f0bbf3bd7744bb313f4cd7886e184
|
||||
environments/settings/feedback_record_directories/create_directory: 9744e2395a5350bd555a7b3b5e1b763a
|
||||
environments/settings/feedback_record_directories/create_new_directory: 912bd70bb19e90512c9904bd6ff15932
|
||||
environments/settings/feedback_record_directories/description: 8f56b169cb38d8c7b2697bf3a3ed7a61
|
||||
environments/settings/feedback_record_directories/directory: 1568e9b2f954aae4edb9e3a2503943af
|
||||
environments/settings/feedback_record_directories/directory_archived_successfully: fba5b99ced59d0546c8f2241c092a5dd
|
||||
environments/settings/feedback_record_directories/directory_created_successfully: 5db20153b840d91842543f71cdd91043
|
||||
environments/settings/feedback_record_directories/directory_id: 933a1376d7d8a8dc41ded90ef1c0f619
|
||||
environments/settings/feedback_record_directories/directory_name: 353de006e16451bf64da469a81fbe451
|
||||
environments/settings/feedback_record_directories/directory_settings_description: 895d890b4292effa5ade45fe7164990f
|
||||
environments/settings/feedback_record_directories/directory_settings_title: c32af860e3254dea9dfaeefc7cd92d49
|
||||
environments/settings/feedback_record_directories/directory_unarchived_successfully: 08d56e260decc62fe664b50ab774b728
|
||||
environments/settings/feedback_record_directories/directory_updated_successfully: 638cb6c92f535328d809274cf2be4d7d
|
||||
environments/settings/feedback_record_directories/empty_state: 665593dcb7cfa081a3e719677d0f6b0d
|
||||
environments/settings/feedback_record_directories/enter_directory_name: a1c950988199bb4c4e014dcf430cce41
|
||||
environments/settings/feedback_record_directories/nav_label: cf9a57b3cbac0f04b98e06fb693e986e
|
||||
environments/settings/feedback_record_directories/no_access: cc3385cd01a11e3949003a2cc6fb5b31
|
||||
environments/settings/feedback_record_directories/please_fill_all_workspace_fields: f5a9f4e4b011c29f96eb10230165a64b
|
||||
environments/settings/feedback_record_directories/show_archived: c4c1c3bbddc1bb1540c079b589a2d3de
|
||||
environments/settings/feedback_record_directories/title: e3d425c27f80162f29ce094e31a3fd8f
|
||||
environments/settings/feedback_record_directories/unarchive: 671fc7e9d7c8cb4d182a25a46551c168
|
||||
environments/settings/general/bulk_invite_warning_description: e8737a2fbd5ff353db5580d17b4b5a37
|
||||
environments/settings/general/cannot_delete_only_organization: 833cc6848b28f2694a4552b4de91a6ba
|
||||
environments/settings/general/cannot_leave_only_organization: dd8463262e4299fef7ad73512225c55b
|
||||
@@ -1608,6 +1643,8 @@ checksums:
|
||||
environments/surveys/edit/response_limit_needs_to_exceed_number_of_received_responses: 9a9c223c0918ded716ddfaa84fbaa8d9
|
||||
environments/surveys/edit/response_limits_redirections_and_more: e4f1cf94e56ad0e1b08701158d688802
|
||||
environments/surveys/edit/response_options: 2988136d5248d7726583108992dcbaee
|
||||
environments/surveys/edit/reverse_order_occasionally: 170fd50de940f382fa2e605228e4e088
|
||||
environments/surveys/edit/reverse_order_occasionally_except_last: 1c833001b940f1419dd7534b199a0b4a
|
||||
environments/surveys/edit/roundness: 5a161c8f5f258defb57ed1d551737cc4
|
||||
environments/surveys/edit/roundness_description: 03940a6871ae43efa4810cba7cadb74b
|
||||
environments/surveys/edit/row_used_in_logic_error: f89453ff1b6db77ad84af840fedd9813
|
||||
|
||||
@@ -22,6 +22,7 @@ export type AuditLoggingCtx = {
|
||||
quotaId?: string;
|
||||
teamId?: string;
|
||||
integrationId?: string;
|
||||
feedbackRecordDirectoryId?: string;
|
||||
};
|
||||
|
||||
export type ActionClientCtx = {
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Aktion",
|
||||
"actions": "Aktionen",
|
||||
"actions_description": "Code- und No-Code-Aktionen werden verwendet, um Abfangumfragen innerhalb von Apps und auf Websites auszulösen.",
|
||||
"active": "Aktiv",
|
||||
"active_surveys": "Aktive Umfragen",
|
||||
"activity": "Aktivität",
|
||||
"add": "Hinzufügen",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "App",
|
||||
"app_survey": "App-Umfrage",
|
||||
"apply_filters": "Filter anwenden",
|
||||
"archived": "Archiviert",
|
||||
"are_you_sure": "Bist Du sicher?",
|
||||
"attributes": "Attribute",
|
||||
"back": "Zurück",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} Frage} other {{count} Fragen}}",
|
||||
"count_responses": "{count, plural, one {{count} Antwort} other {{count} Antworten}}",
|
||||
"count_selections": "{count, plural, one {{count} Auswahl} other {{count} Auswahlen}}",
|
||||
"create": "Erstellen",
|
||||
"create_new_organization": "Neue Organisation erstellen",
|
||||
"create_segment": "Segment erstellen",
|
||||
"create_survey": "Umfrage erstellen",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Teams & Zugriffskontrolle (Lesen, Lesen & Schreiben, Verwalten)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Alle Workspaces wurden zu diesem Verzeichnis hinzugefügt.",
|
||||
"archive": "Archivieren",
|
||||
"archive_directory": "Verzeichnis archivieren",
|
||||
"archive_not_allowed": "Du darfst dieses Verzeichnis nicht archivieren.",
|
||||
"are_you_sure_you_want_to_archive": "Möchtest du dieses Verzeichnis wirklich archivieren? Workspaces haben dann keinen Zugriff mehr darauf.",
|
||||
"assign_workspaces_description": "Lege fest, welche Workspaces auf dieses Feedback-Verzeichnis zugreifen können.",
|
||||
"create_directory": "Verzeichnis erstellen",
|
||||
"create_new_directory": "Neues Feedback-Verzeichnis erstellen",
|
||||
"description": "Verwalte Feedback-Verzeichnisse und ihre Workspace-Zuordnungen.",
|
||||
"directory": "Verzeichnis",
|
||||
"directory_archived_successfully": "Verzeichnis erfolgreich archiviert",
|
||||
"directory_created_successfully": "Verzeichnis erfolgreich erstellt",
|
||||
"directory_id": "Verzeichnis-ID",
|
||||
"directory_name": "Verzeichnisname",
|
||||
"directory_settings_description": "Verwalte Verzeichnisnamen, Workspace-Zuordnungen und mehr.",
|
||||
"directory_settings_title": "Einstellungen für {directoryName}",
|
||||
"directory_unarchived_successfully": "Archivierung des Verzeichnisses erfolgreich aufgehoben",
|
||||
"directory_updated_successfully": "Verzeichnis erfolgreich aktualisiert",
|
||||
"empty_state": "Keine Feedback-Verzeichnisse gefunden. Erstelle eins, um loszulegen.",
|
||||
"enter_directory_name": "Verzeichnisnamen eingeben",
|
||||
"nav_label": "Feedback-Verzeichnisse",
|
||||
"no_access": "Du hast keine Berechtigung, Feedback-Verzeichnisse zu verwalten.",
|
||||
"please_fill_all_workspace_fields": "Bitte fülle zuerst alle Workspace-Felder aus.",
|
||||
"show_archived": "Archivierte anzeigen",
|
||||
"title": "Feedback-Aufzeichnungsverzeichnisse",
|
||||
"unarchive": "Aus Archiv wiederherstellen"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "Bitte beachte, dass im Free-Plan alle Organisationsmitglieder automatisch die Rolle \"Owner\" zugewiesen bekommen, unabhängig von der im CSV-File angegebenen Rolle.",
|
||||
"cannot_delete_only_organization": "Das ist deine einzige Organisation, sie kann nicht gelöscht werden. Erstelle zuerst eine neue Organisation.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Action",
|
||||
"actions": "Actions",
|
||||
"actions_description": "Code and No-Code Actions are used to trigger intercept surveys within apps & on websites.",
|
||||
"active": "Active",
|
||||
"active_surveys": "Active surveys",
|
||||
"activity": "Activity",
|
||||
"add": "Add",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "App",
|
||||
"app_survey": "App Survey",
|
||||
"apply_filters": "Apply filters",
|
||||
"archived": "Archived",
|
||||
"are_you_sure": "Are you sure?",
|
||||
"attributes": "Attributes",
|
||||
"back": "Back",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} question} other {{count} questions}}",
|
||||
"count_responses": "{count, plural, one {{count} response} other {{count} responses}}",
|
||||
"count_selections": "{count, plural, one {{count} selection} other {{count} selections}}",
|
||||
"create": "Create",
|
||||
"create_new_organization": "Create new organization",
|
||||
"create_segment": "Create segment",
|
||||
"create_survey": "Create survey",
|
||||
@@ -261,11 +264,11 @@
|
||||
"invalid_file_type": "Invalid file type",
|
||||
"invite": "Invite",
|
||||
"invite_them": "Invite them",
|
||||
"javascript_required": "JavaScript Required",
|
||||
"javascript_required_description": "Formbricks requires JavaScript to function properly. Please enable JavaScript in your browser settings to continue.",
|
||||
"key": "Key",
|
||||
"label": "Label",
|
||||
"language": "Language",
|
||||
"javascript_required": "JavaScript Required",
|
||||
"javascript_required_description": "Formbricks requires JavaScript to function properly. Please enable JavaScript in your browser settings to continue.",
|
||||
"last_name": "Last Name",
|
||||
"learn_more": "Learn more",
|
||||
"license_expired": "License Expired",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Teams & Access Roles (Read, Read & Write, Manage)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Unlock the full power of Formbricks. Free for 30 days."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "All workspaces added to this directory.",
|
||||
"archive": "Archive",
|
||||
"archive_directory": "Archive Directory",
|
||||
"archive_not_allowed": "You are not allowed to archive this directory.",
|
||||
"are_you_sure_you_want_to_archive": "Are you sure you want to archive this directory? Workspaces will no longer have access to it.",
|
||||
"assign_workspaces_description": "Control which workspaces can access this feedback record directory.",
|
||||
"create_directory": "Create Directory",
|
||||
"create_new_directory": "Create new feedback record directory",
|
||||
"description": "Manage feedback record directories and their workspace assignments.",
|
||||
"directory": "directory",
|
||||
"directory_archived_successfully": "Directory archived successfully",
|
||||
"directory_created_successfully": "Directory created successfully",
|
||||
"directory_id": "Directory ID",
|
||||
"directory_name": "Directory Name",
|
||||
"directory_settings_description": "Manage directory name, workspace assignments, and more.",
|
||||
"directory_settings_title": "{directoryName} Settings",
|
||||
"directory_unarchived_successfully": "Directory unarchived successfully",
|
||||
"directory_updated_successfully": "Directory updated successfully",
|
||||
"empty_state": "No feedback record directories found. Create one to get started.",
|
||||
"enter_directory_name": "Enter directory name",
|
||||
"nav_label": "Feedback Directories",
|
||||
"no_access": "You do not have permission to manage feedback record directories.",
|
||||
"please_fill_all_workspace_fields": "Please fill all workspace fields first.",
|
||||
"show_archived": "Show archived",
|
||||
"title": "Feedback Record Directories",
|
||||
"unarchive": "Unarchive"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "On the free plan, all organization members are always assigned the “Owner” role.",
|
||||
"cannot_delete_only_organization": "This is your only organization, it cannot be deleted. Create a new organization first.",
|
||||
@@ -3354,4 +3385,4 @@
|
||||
"thank_you_description": "Your input helps us build the Workflows feature you actually need. We will keep you posted on our progress.",
|
||||
"thank_you_title": "Thank you for your feedback!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Acción",
|
||||
"actions": "Acciones",
|
||||
"actions_description": "Las acciones de código y sin código se utilizan para activar encuestas de intercepción en aplicaciones y sitios web.",
|
||||
"active": "Activo",
|
||||
"active_surveys": "Encuestas activas",
|
||||
"activity": "Actividad",
|
||||
"add": "Añadir",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Aplicación",
|
||||
"app_survey": "Encuesta de aplicación",
|
||||
"apply_filters": "Aplicar filtros",
|
||||
"archived": "Archivado",
|
||||
"are_you_sure": "¿Estás seguro?",
|
||||
"attributes": "Atributos",
|
||||
"back": "Atrás",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} pregunta} other {{count} preguntas}}",
|
||||
"count_responses": "{count, plural, one {{count} respuesta} other {{count} respuestas}}",
|
||||
"count_selections": "{count, plural, one {{count} selección} other {{count} selecciones}}",
|
||||
"create": "Crear",
|
||||
"create_new_organization": "Crear organización nueva",
|
||||
"create_segment": "Crear segmento",
|
||||
"create_survey": "Crear encuesta",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Equipos y roles de acceso (lectura, lectura y escritura, gestión)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Todos los espacios de trabajo añadidos a este directorio.",
|
||||
"archive": "Archivar",
|
||||
"archive_directory": "Archivar Directorio",
|
||||
"archive_not_allowed": "No tienes permiso para archivar este directorio.",
|
||||
"are_you_sure_you_want_to_archive": "¿Estás seguro de que quieres archivar este directorio? Los espacios de trabajo ya no tendrán acceso a él.",
|
||||
"assign_workspaces_description": "Controla qué espacios de trabajo pueden acceder a este directorio de registros de feedback.",
|
||||
"create_directory": "Crear Directorio",
|
||||
"create_new_directory": "Crear nuevo directorio de registros de feedback",
|
||||
"description": "Gestiona los directorios de registros de feedback y sus asignaciones de espacios de trabajo.",
|
||||
"directory": "directorio",
|
||||
"directory_archived_successfully": "Directorio archivado correctamente",
|
||||
"directory_created_successfully": "Directorio creado correctamente",
|
||||
"directory_id": "ID del Directorio",
|
||||
"directory_name": "Nombre del Directorio",
|
||||
"directory_settings_description": "Gestiona el nombre del directorio, las asignaciones de espacios de trabajo y más.",
|
||||
"directory_settings_title": "Configuración de {directoryName}",
|
||||
"directory_unarchived_successfully": "Directorio desarchivado correctamente",
|
||||
"directory_updated_successfully": "Directorio actualizado correctamente",
|
||||
"empty_state": "No se encontraron directorios de registros de feedback. Crea uno para empezar.",
|
||||
"enter_directory_name": "Introduce el nombre del directorio",
|
||||
"nav_label": "Directorios de Feedback",
|
||||
"no_access": "No tienes permiso para gestionar los directorios de registros de feedback.",
|
||||
"please_fill_all_workspace_fields": "Por favor, completa primero todos los campos del espacio de trabajo.",
|
||||
"show_archived": "Mostrar archivados",
|
||||
"title": "Directorios de Registros de Feedback",
|
||||
"unarchive": "Desarchivar"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "En el plan gratuito, a todos los miembros de la organización se les asigna siempre el rol de \"Propietario\".",
|
||||
"cannot_delete_only_organization": "Esta es tu única organización, no se puede eliminar. Crea una nueva organización primero.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Action",
|
||||
"actions": "Actions",
|
||||
"actions_description": "Les actions avec et sans code permettent de déclencher des enquêtes dans des applications et sur des sites Web.",
|
||||
"active": "Actif",
|
||||
"active_surveys": "Sondages actifs",
|
||||
"activity": "Activité",
|
||||
"add": "Ajouter",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Application",
|
||||
"app_survey": "Sondage d'application",
|
||||
"apply_filters": "Appliquer des filtres",
|
||||
"archived": "Archivé",
|
||||
"are_you_sure": "Es-tu sûr ?",
|
||||
"attributes": "Attributs",
|
||||
"back": "Retour",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} question} other {{count} questions}}",
|
||||
"count_responses": "{count, plural, one {{count} réponse} other {{count} réponses}}",
|
||||
"count_selections": "{count, plural, one {{count} sélection} other {{count} sélections}}",
|
||||
"create": "Créer",
|
||||
"create_new_organization": "Créer une nouvelle organisation",
|
||||
"create_segment": "Créer un segment",
|
||||
"create_survey": "Créer un sondage",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Équipes et Rôles d'Accès (Lire, Lire et Écrire, Gérer)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Tous les espaces de travail ont été ajoutés à ce répertoire.",
|
||||
"archive": "Archiver",
|
||||
"archive_directory": "Archiver le répertoire",
|
||||
"archive_not_allowed": "Vous n'êtes pas autorisé à archiver ce répertoire.",
|
||||
"are_you_sure_you_want_to_archive": "Es-tu sûr de vouloir archiver ce répertoire ? Les espaces de travail n'y auront plus accès.",
|
||||
"assign_workspaces_description": "Contrôle quels espaces de travail peuvent accéder à ce répertoire de feedback.",
|
||||
"create_directory": "Créer un répertoire",
|
||||
"create_new_directory": "Créer un nouveau répertoire de feedback",
|
||||
"description": "Gère les répertoires de feedback et leurs affectations aux espaces de travail.",
|
||||
"directory": "répertoire",
|
||||
"directory_archived_successfully": "Répertoire archivé avec succès",
|
||||
"directory_created_successfully": "Répertoire créé avec succès",
|
||||
"directory_id": "ID du répertoire",
|
||||
"directory_name": "Nom du répertoire",
|
||||
"directory_settings_description": "Gère le nom du répertoire, les affectations aux espaces de travail et plus encore.",
|
||||
"directory_settings_title": "Paramètres de {directoryName}",
|
||||
"directory_unarchived_successfully": "Répertoire désarchivé avec succès",
|
||||
"directory_updated_successfully": "Répertoire mis à jour avec succès",
|
||||
"empty_state": "Aucun répertoire de feedback trouvé. Crée-en un pour commencer.",
|
||||
"enter_directory_name": "Saisir le nom du répertoire",
|
||||
"nav_label": "Répertoires de feedback",
|
||||
"no_access": "Tu n'as pas la permission de gérer les répertoires de feedback.",
|
||||
"please_fill_all_workspace_fields": "Veuillez d'abord remplir tous les champs de l'espace de travail.",
|
||||
"show_archived": "Afficher les éléments archivés",
|
||||
"title": "Répertoires d'enregistrement des retours",
|
||||
"unarchive": "Désarchiver"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "Dans le plan gratuit, tous les membres de l'organisation se voient toujours attribuer le rôle \"Owner\".",
|
||||
"cannot_delete_only_organization": "C'est votre seule organisation, elle ne peut pas être supprimée. Créez d'abord une nouvelle organisation.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Művelet",
|
||||
"actions": "Műveletek",
|
||||
"actions_description": "A kód vagy kód nélküli műveleteket arra használják, hogy aktiválják a kérdőívek alkalmazásokon és webhelyeken belüli elfogását.",
|
||||
"active": "Aktív",
|
||||
"active_surveys": "Aktív kérdőívek",
|
||||
"activity": "Tevékenység",
|
||||
"add": "Hozzáadás",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Alkalmazás",
|
||||
"app_survey": "Alkalmazás-kérdőív",
|
||||
"apply_filters": "Szűrők alkalmazása",
|
||||
"archived": "Archivált",
|
||||
"are_you_sure": "Biztos benne?",
|
||||
"attributes": "Attribútumok",
|
||||
"back": "Vissza",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} kérdés} other {{count} kérdés}}",
|
||||
"count_responses": "{count, plural, one {{count} válasz} other {{count} válasz}}",
|
||||
"count_selections": "{count, plural, one {{count} kiválasztás} other {{count} kiválasztás}}",
|
||||
"create": "Létrehozás",
|
||||
"create_new_organization": "Új szervezet létrehozása",
|
||||
"create_segment": "Szakasz létrehozása",
|
||||
"create_survey": "Kérdőív létrehozása",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Csapatok és hozzáférési szerepek (olvasás, olvasás és írás, kezelés)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "A Formbricks teljes erejének feloldása. 30 napig ingyen."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Minden munkaterület hozzáadva ehhez a könyvtárhoz.",
|
||||
"archive": "Archiválás",
|
||||
"archive_directory": "Könyvtár archiválása",
|
||||
"archive_not_allowed": "Nem rendelkezik jogosultsággal ezen könyvtár archiválásához.",
|
||||
"are_you_sure_you_want_to_archive": "Biztosan archiválni kívánja ezt a könyvtárat? A munkaterületek többé nem férhetnek hozzá.",
|
||||
"assign_workspaces_description": "Szabályozza, mely munkaterületek férhetnek hozzá ehhez a visszajelzési nyilvántartási könyvtárhoz.",
|
||||
"create_directory": "Könyvtár létrehozása",
|
||||
"create_new_directory": "Új visszajelzési nyilvántartási könyvtár létrehozása",
|
||||
"description": "Visszajelzési nyilvántartási könyvtárak és munkaterület-hozzárendeléseik kezelése.",
|
||||
"directory": "könyvtár",
|
||||
"directory_archived_successfully": "A könyvtár sikeresen archiválva",
|
||||
"directory_created_successfully": "A könyvtár sikeresen létrehozva",
|
||||
"directory_id": "Könyvtár azonosító",
|
||||
"directory_name": "Könyvtár neve",
|
||||
"directory_settings_description": "Könyvtár nevének, munkaterület-hozzárendeléseinek és egyéb beállítások kezelése.",
|
||||
"directory_settings_title": "{directoryName} beállításai",
|
||||
"directory_unarchived_successfully": "A könyvtár archiválása sikeresen visszavonva",
|
||||
"directory_updated_successfully": "A könyvtár sikeresen frissítve",
|
||||
"empty_state": "Nem található visszajelzési nyilvántartási könyvtár. Hozzon létre egyet a kezdéshez.",
|
||||
"enter_directory_name": "Adja meg a könyvtár nevét",
|
||||
"nav_label": "Visszajelzési könyvtárak",
|
||||
"no_access": "Nem rendelkezik jogosultsággal a visszajelzési nyilvántartási könyvtárak kezeléséhez.",
|
||||
"please_fill_all_workspace_fields": "Kérem, először töltse ki az összes munkaterület mezőt.",
|
||||
"show_archived": "Archivált elemek megjelenítése",
|
||||
"title": "Visszajelzési Nyilvántartási Könyvtárak",
|
||||
"unarchive": "Archiválás visszavonása"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "Az ingyenes csomagban az összes szervezeti tag mindig a „Tulajdonos” szerephez van hozzárendelve.",
|
||||
"cannot_delete_only_organization": "Ez az egyetlen szervezete, nem lehet törölni. Először hozzon létre egy új szervezetet.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "アクション",
|
||||
"actions": "アクション",
|
||||
"actions_description": "コードとノーコードアクションは、アプリ内やウェブサイト上で調査を発動するために使用されます。",
|
||||
"active": "アクティブ",
|
||||
"active_surveys": "アクティブなフォーム",
|
||||
"activity": "アクティビティ",
|
||||
"add": "追加",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "アプリ",
|
||||
"app_survey": "アプリ内フォーム",
|
||||
"apply_filters": "フィルターを適用",
|
||||
"archived": "アーカイブ済み",
|
||||
"are_you_sure": "よろしいですか?",
|
||||
"attributes": "属性",
|
||||
"back": "戻る",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, other {# 件の質問}}",
|
||||
"count_responses": "{count, plural, other {{count} 件の回答}}",
|
||||
"count_selections": "{count, plural, other {{count} 件の選択}}",
|
||||
"create": "作成",
|
||||
"create_new_organization": "新しい組織を作成",
|
||||
"create_segment": "セグメントを作成",
|
||||
"create_survey": "フォームを作成",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "チーム&アクセスロール(読み取り、読み書き、管理)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。"
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "すべてのワークスペースがこのディレクトリに追加されました。",
|
||||
"archive": "アーカイブ",
|
||||
"archive_directory": "ディレクトリをアーカイブ",
|
||||
"archive_not_allowed": "このディレクトリをアーカイブする権限がありません。",
|
||||
"are_you_sure_you_want_to_archive": "このディレクトリをアーカイブしてもよろしいですか?ワークスペースはアクセスできなくなります。",
|
||||
"assign_workspaces_description": "このフィードバック記録ディレクトリにアクセスできるワークスペースを管理します。",
|
||||
"create_directory": "ディレクトリを作成",
|
||||
"create_new_directory": "新しいフィードバック記録ディレクトリを作成",
|
||||
"description": "フィードバック記録ディレクトリとワークスペースの割り当てを管理します。",
|
||||
"directory": "ディレクトリ",
|
||||
"directory_archived_successfully": "ディレクトリをアーカイブしました",
|
||||
"directory_created_successfully": "ディレクトリを作成しました",
|
||||
"directory_id": "ディレクトリID",
|
||||
"directory_name": "ディレクトリ名",
|
||||
"directory_settings_description": "ディレクトリ名、ワークスペースの割り当てなどを管理します。",
|
||||
"directory_settings_title": "{directoryName}の設定",
|
||||
"directory_unarchived_successfully": "ディレクトリのアーカイブを解除しました",
|
||||
"directory_updated_successfully": "ディレクトリを更新しました",
|
||||
"empty_state": "フィードバック記録ディレクトリが見つかりません。最初のディレクトリを作成してください。",
|
||||
"enter_directory_name": "ディレクトリ名を入力してください",
|
||||
"nav_label": "フィードバックディレクトリ",
|
||||
"no_access": "フィードバック記録ディレクトリを管理する権限がありません。",
|
||||
"please_fill_all_workspace_fields": "まず、すべてのワークスペースフィールドを入力してください。",
|
||||
"show_archived": "アーカイブ済みを表示",
|
||||
"title": "フィードバック記録ディレクトリ",
|
||||
"unarchive": "アーカイブ解除"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "無料プランでは、すべての組織メンバーに常に「オーナー」ロールが割り当てられます。",
|
||||
"cannot_delete_only_organization": "これはあなたの唯一の組織です。削除できません。まず新しい組織を作成してください。",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Actie",
|
||||
"actions": "Acties",
|
||||
"actions_description": "Code- en no-code-acties worden gebruikt om onderscheppingsenquêtes in apps en op websites te activeren.",
|
||||
"active": "Actief",
|
||||
"active_surveys": "Actieve enquêtes",
|
||||
"activity": "Activiteit",
|
||||
"add": "Toevoegen",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "App",
|
||||
"app_survey": "App-enquête",
|
||||
"apply_filters": "Pas filters toe",
|
||||
"archived": "Gearchiveerd",
|
||||
"are_you_sure": "Weet je het zeker?",
|
||||
"attributes": "Kenmerken",
|
||||
"back": "Rug",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} vraag} other {{count} vragen}}",
|
||||
"count_responses": "{count, plural, one {{count} reactie} other {{count} reacties}}",
|
||||
"count_selections": "{count, plural, one {{count} selectie} other {{count} selecties}}",
|
||||
"create": "Aanmaken",
|
||||
"create_new_organization": "Creëer een nieuwe organisatie",
|
||||
"create_segment": "Segment maken",
|
||||
"create_survey": "Enquête maken",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Teams en toegangsrollen (lezen, lezen en schrijven, beheren)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Alle workspaces zijn toegevoegd aan deze map.",
|
||||
"archive": "Archiveren",
|
||||
"archive_directory": "Map archiveren",
|
||||
"archive_not_allowed": "Je hebt geen toestemming om deze map te archiveren.",
|
||||
"are_you_sure_you_want_to_archive": "Weet je zeker dat je deze map wilt archiveren? Workspaces hebben er dan geen toegang meer toe.",
|
||||
"assign_workspaces_description": "Bepaal welke workspaces toegang hebben tot deze feedbackregistratiemap.",
|
||||
"create_directory": "Map aanmaken",
|
||||
"create_new_directory": "Nieuwe feedbackregistratiemap aanmaken",
|
||||
"description": "Beheer feedbackregistratiemappen en hun workspace-toewijzingen.",
|
||||
"directory": "map",
|
||||
"directory_archived_successfully": "Map succesvol gearchiveerd",
|
||||
"directory_created_successfully": "Map succesvol aangemaakt",
|
||||
"directory_id": "Map-ID",
|
||||
"directory_name": "Mapnaam",
|
||||
"directory_settings_description": "Beheer mapnaam, workspace-toewijzingen en meer.",
|
||||
"directory_settings_title": "Instellingen voor {directoryName}",
|
||||
"directory_unarchived_successfully": "Map succesvol gedearchiveerd",
|
||||
"directory_updated_successfully": "Map succesvol bijgewerkt",
|
||||
"empty_state": "Geen feedbackregistratiemappen gevonden. Maak er een aan om te beginnen.",
|
||||
"enter_directory_name": "Voer mapnaam in",
|
||||
"nav_label": "Feedbackmappen",
|
||||
"no_access": "Je hebt geen toestemming om feedbackregistratiemappen te beheren.",
|
||||
"please_fill_all_workspace_fields": "Vul eerst alle werkruimte-velden in.",
|
||||
"show_archived": "Gearchiveerde weergeven",
|
||||
"title": "Feedbackregistratiemappen",
|
||||
"unarchive": "Dearchiveren"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "Bij het gratis abonnement krijgen alle organisatieleden altijd de rol 'Eigenaar' toegewezen.",
|
||||
"cannot_delete_only_organization": "Dit is uw enige organisatie. Deze kan niet worden verwijderd. Maak eerst een nieuwe organisatie aan.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Ação",
|
||||
"actions": "Ações",
|
||||
"actions_description": "Ações de Código e Sem Código são usadas para acionar interceptar pesquisas dentro de apps & em sites.",
|
||||
"active": "Ativo",
|
||||
"active_surveys": "Pesquisas ativas",
|
||||
"activity": "Atividade",
|
||||
"add": "Adicionar",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "app",
|
||||
"app_survey": "Pesquisa de App",
|
||||
"apply_filters": "Aplicar filtros",
|
||||
"archived": "Arquivado",
|
||||
"are_you_sure": "Certeza?",
|
||||
"attributes": "atributos",
|
||||
"back": "Voltar",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} pergunta} other {{count} perguntas}}",
|
||||
"count_responses": "{count, plural, one {{count} resposta} other {{count} respostas}}",
|
||||
"count_selections": "{count, plural, one {{count} seleção} other {{count} seleções}}",
|
||||
"create": "Criar",
|
||||
"create_new_organization": "Criar nova organização",
|
||||
"create_segment": "Criar segmento",
|
||||
"create_survey": "Criar pesquisa",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Equipes e Funções de Acesso (Ler, Ler e Escrever, Gerenciar)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Todos os espaços de trabalho adicionados a este diretório.",
|
||||
"archive": "Arquivar",
|
||||
"archive_directory": "Arquivar Diretório",
|
||||
"archive_not_allowed": "Você não tem permissão para arquivar este diretório.",
|
||||
"are_you_sure_you_want_to_archive": "Tem certeza de que deseja arquivar este diretório? Os espaços de trabalho não terão mais acesso a ele.",
|
||||
"assign_workspaces_description": "Controle quais espaços de trabalho podem acessar este diretório de registros de feedback.",
|
||||
"create_directory": "Criar Diretório",
|
||||
"create_new_directory": "Criar novo diretório de registros de feedback",
|
||||
"description": "Gerencie diretórios de registros de feedback e suas atribuições de espaços de trabalho.",
|
||||
"directory": "diretório",
|
||||
"directory_archived_successfully": "Diretório arquivado com sucesso",
|
||||
"directory_created_successfully": "Diretório criado com sucesso",
|
||||
"directory_id": "ID do Diretório",
|
||||
"directory_name": "Nome do Diretório",
|
||||
"directory_settings_description": "Gerencie o nome do diretório, atribuições de espaços de trabalho e muito mais.",
|
||||
"directory_settings_title": "Configurações de {directoryName}",
|
||||
"directory_unarchived_successfully": "Diretório desarquivado com sucesso",
|
||||
"directory_updated_successfully": "Diretório atualizado com sucesso",
|
||||
"empty_state": "Nenhum diretório de registros de feedback encontrado. Crie um para começar.",
|
||||
"enter_directory_name": "Digite o nome do diretório",
|
||||
"nav_label": "Diretórios de Feedback",
|
||||
"no_access": "Você não tem permissão para gerenciar diretórios de registros de feedback.",
|
||||
"please_fill_all_workspace_fields": "Por favor, preencha todos os campos do workspace primeiro.",
|
||||
"show_archived": "Mostrar arquivados",
|
||||
"title": "Diretórios de Registros de Feedback",
|
||||
"unarchive": "Desarquivar"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "Por favor, note que no Plano Gratuito, todos os membros da organização são automaticamente atribuídos ao papel de 'Owner', independentemente do papel especificado no arquivo CSV.",
|
||||
"cannot_delete_only_organization": "Essa é sua única organização, não pode ser deletada. Crie uma nova organização primeiro.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Ação",
|
||||
"actions": "Ações",
|
||||
"actions_description": "As ações com código e sem código são usadas para acionar pesquisas de interceptação em apps e em sites.",
|
||||
"active": "Ativo",
|
||||
"active_surveys": "Inquéritos ativos",
|
||||
"activity": "Atividade",
|
||||
"add": "Adicionar",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Aplicação",
|
||||
"app_survey": "Inquérito (app)",
|
||||
"apply_filters": "Aplicar filtros",
|
||||
"archived": "Arquivado",
|
||||
"are_you_sure": "Tem a certeza?",
|
||||
"attributes": "Atributos",
|
||||
"back": "Voltar",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} pergunta} other {{count} perguntas}}",
|
||||
"count_responses": "{count, plural, one {{count} resposta} other {{count} respostas}}",
|
||||
"count_selections": "{count, plural, one {{count} seleção} other {{count} seleções}}",
|
||||
"create": "Criar",
|
||||
"create_new_organization": "Criar nova organização",
|
||||
"create_segment": "Criar segmento",
|
||||
"create_survey": "Criar inquérito",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Equipas e Funções de Acesso (Ler, Ler e Escrever, Gerir)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Todos os espaços de trabalho adicionados a este diretório.",
|
||||
"archive": "Arquivar",
|
||||
"archive_directory": "Arquivar Diretório",
|
||||
"archive_not_allowed": "Não tens permissão para arquivar este diretório.",
|
||||
"are_you_sure_you_want_to_archive": "Tens a certeza de que queres arquivar este diretório? Os espaços de trabalho deixarão de ter acesso ao mesmo.",
|
||||
"assign_workspaces_description": "Controla quais os espaços de trabalho que podem aceder a este diretório de registos de feedback.",
|
||||
"create_directory": "Criar Diretório",
|
||||
"create_new_directory": "Criar novo diretório de registos de feedback",
|
||||
"description": "Gere diretórios de registos de feedback e as suas atribuições de espaços de trabalho.",
|
||||
"directory": "diretório",
|
||||
"directory_archived_successfully": "Diretório arquivado com sucesso",
|
||||
"directory_created_successfully": "Diretório criado com sucesso",
|
||||
"directory_id": "ID do Diretório",
|
||||
"directory_name": "Nome do Diretório",
|
||||
"directory_settings_description": "Gere o nome do diretório, atribuições de espaços de trabalho e muito mais.",
|
||||
"directory_settings_title": "Definições de {directoryName}",
|
||||
"directory_unarchived_successfully": "Diretório desarquivado com sucesso",
|
||||
"directory_updated_successfully": "Diretório atualizado com sucesso",
|
||||
"empty_state": "Não foram encontrados diretórios de registos de feedback. Cria um para começar.",
|
||||
"enter_directory_name": "Insere o nome do diretório",
|
||||
"nav_label": "Diretórios de Feedback",
|
||||
"no_access": "Não tens permissão para gerir diretórios de registos de feedback.",
|
||||
"please_fill_all_workspace_fields": "Por favor, preenche primeiro todos os campos da área de trabalho.",
|
||||
"show_archived": "Mostrar arquivados",
|
||||
"title": "Diretórios de Registos de Feedback",
|
||||
"unarchive": "Desarquivar"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "No plano gratuito, todos os membros da organização são sempre atribuídos ao papel de \"Proprietário\".",
|
||||
"cannot_delete_only_organization": "Esta é a sua única organização, não pode ser eliminada. Crie uma nova organização primeiro.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Acțiune",
|
||||
"actions": "Acțiuni",
|
||||
"actions_description": "Acțiunile Cod și No-Code sunt utilizate pentru a declanșa chestionare de interceptare în aplicații și pe site-uri web.",
|
||||
"active": "Activ",
|
||||
"active_surveys": "Sondaje active",
|
||||
"activity": "Activitate",
|
||||
"add": "Adaugă",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Aplicație",
|
||||
"app_survey": "Sondaj aplicație",
|
||||
"apply_filters": "Aplică filtre",
|
||||
"archived": "Arhivat",
|
||||
"are_you_sure": "Ești sigur?",
|
||||
"attributes": "Atribute",
|
||||
"back": "Înapoi",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {# întrebare} few {# întrebări} other {# de întrebări}}",
|
||||
"count_responses": "{count, plural, one {{count} răspuns} few {{count} răspunsuri} other {{count} de răspunsuri}}",
|
||||
"count_selections": "{count, plural, one {{count} selecție} few {{count} selecții} other {{count} de selecții}}",
|
||||
"create": "Creează",
|
||||
"create_new_organization": "Creează organizație nouă",
|
||||
"create_segment": "Creați segment",
|
||||
"create_survey": "Creează sondaj",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Echipe & Roluri de Acces (Citiți, Citiți și Scrieți, Gestionați)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Toate spațiile de lucru au fost adăugate la acest director.",
|
||||
"archive": "Arhivează",
|
||||
"archive_directory": "Arhivează directorul",
|
||||
"archive_not_allowed": "Nu ai permisiunea să arhivezi acest director.",
|
||||
"are_you_sure_you_want_to_archive": "Ești sigur că vrei să arhivezi acest director? Spațiile de lucru nu vor mai avea acces la el.",
|
||||
"assign_workspaces_description": "Controlează care spații de lucru pot accesa acest director de înregistrări de feedback.",
|
||||
"create_directory": "Creează director",
|
||||
"create_new_directory": "Creează un nou director de înregistrări de feedback",
|
||||
"description": "Gestionează directoarele de înregistrări de feedback și atribuirile lor la spații de lucru.",
|
||||
"directory": "director",
|
||||
"directory_archived_successfully": "Directorul a fost arhivat cu succes",
|
||||
"directory_created_successfully": "Directorul a fost creat cu succes",
|
||||
"directory_id": "ID director",
|
||||
"directory_name": "Numele directorului",
|
||||
"directory_settings_description": "Gestionează numele directorului, atribuirile la spații de lucru și multe altele.",
|
||||
"directory_settings_title": "Setări {directoryName}",
|
||||
"directory_unarchived_successfully": "Directorul a fost dezarhivat cu succes",
|
||||
"directory_updated_successfully": "Directorul a fost actualizat cu succes",
|
||||
"empty_state": "Nu au fost găsite directoare de înregistrări de feedback. Creează unul pentru a începe.",
|
||||
"enter_directory_name": "Introdu numele directorului",
|
||||
"nav_label": "Directoare de feedback",
|
||||
"no_access": "Nu ai permisiunea de a gestiona directoarele de înregistrări de feedback.",
|
||||
"please_fill_all_workspace_fields": "Te rugăm să completezi mai întâi toate câmpurile workspace-ului.",
|
||||
"show_archived": "Afișează arhivate",
|
||||
"title": "Directoare de Înregistrări Feedback",
|
||||
"unarchive": "Dezarhivează"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "În planul gratuit, toți membrii organizației sunt întotdeauna alocați rolului „Proprietar”.",
|
||||
"cannot_delete_only_organization": "Aceasta este singura ta organizație, nu poate fi ștearsă. Creează mai întâi o nouă organizație.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Действие",
|
||||
"actions": "Действия",
|
||||
"actions_description": "Действия с кодом и без кода используются для запуска опросов-перехватчиков в приложениях и на сайтах.",
|
||||
"active": "Активный",
|
||||
"active_surveys": "Активные опросы",
|
||||
"activity": "Активность",
|
||||
"add": "Добавить",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "Приложение",
|
||||
"app_survey": "Опрос о приложении",
|
||||
"apply_filters": "Применить фильтры",
|
||||
"archived": "Архивный",
|
||||
"are_you_sure": "Вы уверены?",
|
||||
"attributes": "Атрибуты",
|
||||
"back": "Назад",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} вопрос} few {{count} вопроса} many {{count} вопросов} other {{count} вопросов}}",
|
||||
"count_responses": "{count, plural, one {{count} ответ} few {{count} ответа} many {{count} ответов} other {{count} ответа}}",
|
||||
"count_selections": "{count, plural, one {{count} выбор} few {{count} выбора} many {{count} выборов} other {{count} выбора}}",
|
||||
"create": "Создать",
|
||||
"create_new_organization": "Создать новую организацию",
|
||||
"create_segment": "Создать сегмент",
|
||||
"create_survey": "Создать опрос",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Команды и роли доступа (чтение, чтение и запись, управление)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Все рабочие пространства добавлены в этот каталог.",
|
||||
"archive": "Архивировать",
|
||||
"archive_directory": "Архивировать каталог",
|
||||
"archive_not_allowed": "У тебя нет прав для архивирования этого каталога.",
|
||||
"are_you_sure_you_want_to_archive": "Ты уверен, что хочешь архивировать этот каталог? Рабочие пространства больше не будут иметь к нему доступа.",
|
||||
"assign_workspaces_description": "Управляй тем, какие рабочие пространства могут получить доступ к этому каталогу записей отзывов.",
|
||||
"create_directory": "Создать каталог",
|
||||
"create_new_directory": "Создать новый каталог записей отзывов",
|
||||
"description": "Управляй каталогами записей отзывов и их назначением рабочим пространствам.",
|
||||
"directory": "каталог",
|
||||
"directory_archived_successfully": "Каталог успешно архивирован",
|
||||
"directory_created_successfully": "Каталог успешно создан",
|
||||
"directory_id": "ID каталога",
|
||||
"directory_name": "Название каталога",
|
||||
"directory_settings_description": "Управляй названием каталога, назначением рабочих пространств и другими параметрами.",
|
||||
"directory_settings_title": "Настройки {directoryName}",
|
||||
"directory_unarchived_successfully": "Каталог успешно разархивирован",
|
||||
"directory_updated_successfully": "Каталог успешно обновлён",
|
||||
"empty_state": "Каталоги записей отзывов не найдены. Создай один, чтобы начать.",
|
||||
"enter_directory_name": "Введи название каталога",
|
||||
"nav_label": "Каталоги отзывов",
|
||||
"no_access": "У тебя нет прав для управления каталогами записей отзывов.",
|
||||
"please_fill_all_workspace_fields": "Сначала заполни все поля рабочего пространства.",
|
||||
"show_archived": "Показать архивные",
|
||||
"title": "Директории записей обратной связи",
|
||||
"unarchive": "Разархивировать"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "В бесплатном тарифе всем участникам организации всегда назначается роль \"Владелец\".",
|
||||
"cannot_delete_only_organization": "Это ваша единственная организация, её нельзя удалить. Сначала создайте новую организацию.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "Åtgärd",
|
||||
"actions": "Åtgärder",
|
||||
"actions_description": "Kod- och No-Code-åtgärder används för att utlösa enkäter i appar och på webbplatser.",
|
||||
"active": "Aktiv",
|
||||
"active_surveys": "Aktiva enkäter",
|
||||
"activity": "Aktivitet",
|
||||
"add": "Lägg till",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "App",
|
||||
"app_survey": "App-enkät",
|
||||
"apply_filters": "Tillämpa filter",
|
||||
"archived": "Arkiverad",
|
||||
"are_you_sure": "Är du säker?",
|
||||
"attributes": "Attribut",
|
||||
"back": "Tillbaka",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, one {{count} fråga} other {{count} frågor}}",
|
||||
"count_responses": "{count, plural, one {{count} svar} other {{count} svar}}",
|
||||
"count_selections": "{count, plural, one {{count} val} other {{count} val}}",
|
||||
"create": "Skapa",
|
||||
"create_new_organization": "Skapa ny organisation",
|
||||
"create_segment": "Skapa segment",
|
||||
"create_survey": "Skapa enkät",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "Team och åtkomstroller (Läs, Läs och skriv, Hantera)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar."
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "Alla arbetsytor har lagts till i den här katalogen.",
|
||||
"archive": "Arkivera",
|
||||
"archive_directory": "Arkivera katalog",
|
||||
"archive_not_allowed": "Du har inte behörighet att arkivera den här katalogen.",
|
||||
"are_you_sure_you_want_to_archive": "Är du säker på att du vill arkivera den här katalogen? Arbetsytor kommer inte längre ha tillgång till den.",
|
||||
"assign_workspaces_description": "Styr vilka arbetsytor som kan komma åt den här katalogen för feedbackposter.",
|
||||
"create_directory": "Skapa katalog",
|
||||
"create_new_directory": "Skapa ny katalog för feedbackposter",
|
||||
"description": "Hantera kataloger för feedbackposter och deras arbetsytstilldelningar.",
|
||||
"directory": "katalog",
|
||||
"directory_archived_successfully": "Katalogen arkiverades",
|
||||
"directory_created_successfully": "Katalogen skapades",
|
||||
"directory_id": "Katalog-ID",
|
||||
"directory_name": "Katalognamn",
|
||||
"directory_settings_description": "Hantera katalognamn, arbetsytstilldelningar och mer.",
|
||||
"directory_settings_title": "Inställningar för {directoryName}",
|
||||
"directory_unarchived_successfully": "Katalogen återställdes från arkivet",
|
||||
"directory_updated_successfully": "Katalogen uppdaterades",
|
||||
"empty_state": "Inga kataloger för feedbackposter hittades. Skapa en för att komma igång.",
|
||||
"enter_directory_name": "Ange katalognamn",
|
||||
"nav_label": "Feedbackkataloger",
|
||||
"no_access": "Du har inte behörighet att hantera kataloger för feedbackposter.",
|
||||
"please_fill_all_workspace_fields": "Vänligen fyll i alla arbetsytefält först.",
|
||||
"show_archived": "Visa arkiverade",
|
||||
"title": "Feedbackkataloger",
|
||||
"unarchive": "Avarkivera"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "På gratisplanen tilldelas alla organisationsmedlemmar alltid rollen \"Ägare\".",
|
||||
"cannot_delete_only_organization": "Detta är din enda organisation, den kan inte tas bort. Skapa en ny organisation först.",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "操作",
|
||||
"actions": "操作",
|
||||
"actions_description": "代码 和 无代码 操作 用于 触发 拦截 调查 在 应用程序 和 网站 中。",
|
||||
"active": "活跃",
|
||||
"active_surveys": "活跃 调查",
|
||||
"activity": "活动",
|
||||
"add": "添加",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "应用",
|
||||
"app_survey": "应用 程序 调查",
|
||||
"apply_filters": "应用 筛选",
|
||||
"archived": "已归档",
|
||||
"are_you_sure": "你 确定 吗?",
|
||||
"attributes": "属性",
|
||||
"back": "返回",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "共{count}个问题",
|
||||
"count_responses": "{count, plural, other {{count} 回复} }",
|
||||
"count_selections": "{count, plural, other {已选择{count}项}}",
|
||||
"create": "创建",
|
||||
"create_new_organization": "创建 新的 组织",
|
||||
"create_segment": "创建 细分",
|
||||
"create_survey": "创建 调查",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "团队 & 访问 角色(读取, 读取 & 写入, 管理)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。"
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "所有工作区已添加到此目录。",
|
||||
"archive": "归档",
|
||||
"archive_directory": "归档目录",
|
||||
"archive_not_allowed": "你无权归档此目录。",
|
||||
"are_you_sure_you_want_to_archive": "确定要归档此目录吗?工作区将无法再访问它。",
|
||||
"assign_workspaces_description": "控制哪些工作区可以访问此反馈记录目录。",
|
||||
"create_directory": "创建目录",
|
||||
"create_new_directory": "创建新的反馈记录目录",
|
||||
"description": "管理反馈记录目录及其工作区分配。",
|
||||
"directory": "目录",
|
||||
"directory_archived_successfully": "目录已成功归档",
|
||||
"directory_created_successfully": "目录已成功创建",
|
||||
"directory_id": "目录 ID",
|
||||
"directory_name": "目录名称",
|
||||
"directory_settings_description": "管理目录名称、工作区分配等。",
|
||||
"directory_settings_title": "{directoryName} 设置",
|
||||
"directory_unarchived_successfully": "目录已成功取消归档",
|
||||
"directory_updated_successfully": "目录已成功更新",
|
||||
"empty_state": "未找到反馈记录目录。创建一个开始使用吧。",
|
||||
"enter_directory_name": "输入目录名称",
|
||||
"nav_label": "反馈目录",
|
||||
"no_access": "你没有管理反馈记录目录的权限。",
|
||||
"please_fill_all_workspace_fields": "请先填写所有工作区字段。",
|
||||
"show_archived": "显示已归档",
|
||||
"title": "反馈记录目录",
|
||||
"unarchive": "取消归档"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "在免费计划中,所有组织成员都会被分配为 \"Owner \"角色。",
|
||||
"cannot_delete_only_organization": "这是 您 唯一的 组织,不可 删除。请 先 创建一个新的 组织。",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"action": "操作",
|
||||
"actions": "操作",
|
||||
"actions_description": "代碼 和 無代碼 動作 用於 觸發 截取 調查 於 應用程式 和 網站上 。",
|
||||
"active": "啟用中",
|
||||
"active_surveys": "啟用中的問卷",
|
||||
"activity": "活動",
|
||||
"add": "新增",
|
||||
@@ -139,6 +140,7 @@
|
||||
"app": "應用程式",
|
||||
"app_survey": "應用程式問卷",
|
||||
"apply_filters": "套用篩選器",
|
||||
"archived": "已封存",
|
||||
"are_you_sure": "您確定嗎?",
|
||||
"attributes": "屬性",
|
||||
"back": "返回",
|
||||
@@ -181,6 +183,7 @@
|
||||
"count_questions": "{count, plural, other {{count} 個問題}}",
|
||||
"count_responses": "{count, plural, other {{count} 答覆}}",
|
||||
"count_selections": "{count, plural, other {{count} 個選擇}}",
|
||||
"create": "建立",
|
||||
"create_new_organization": "建立新組織",
|
||||
"create_segment": "建立區隔",
|
||||
"create_survey": "建立問卷",
|
||||
@@ -1133,6 +1136,34 @@
|
||||
"teams": "團隊和存取角色(讀取、讀取和寫入、管理)",
|
||||
"unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。"
|
||||
},
|
||||
"feedback_record_directories": {
|
||||
"all_workspaces_added": "所有工作區都已加入此目錄。",
|
||||
"archive": "封存",
|
||||
"archive_directory": "封存目錄",
|
||||
"archive_not_allowed": "您沒有權限封存此目錄。",
|
||||
"are_you_sure_you_want_to_archive": "確定要封存此目錄嗎?工作區將無法再存取它。",
|
||||
"assign_workspaces_description": "控制哪些工作區可以存取此意見回饋記錄目錄。",
|
||||
"create_directory": "建立目錄",
|
||||
"create_new_directory": "建立新的意見回饋記錄目錄",
|
||||
"description": "管理意見回饋記錄目錄及其工作區配置。",
|
||||
"directory": "目錄",
|
||||
"directory_archived_successfully": "目錄已成功封存",
|
||||
"directory_created_successfully": "目錄已成功建立",
|
||||
"directory_id": "目錄 ID",
|
||||
"directory_name": "目錄名稱",
|
||||
"directory_settings_description": "管理目錄名稱、工作區配置等設定。",
|
||||
"directory_settings_title": "{directoryName} 設定",
|
||||
"directory_unarchived_successfully": "目錄已成功取消封存",
|
||||
"directory_updated_successfully": "目錄已成功更新",
|
||||
"empty_state": "找不到任何意見回饋記錄目錄。建立一個開始使用吧。",
|
||||
"enter_directory_name": "輸入目錄名稱",
|
||||
"nav_label": "意見回饋目錄",
|
||||
"no_access": "您沒有權限管理意見回饋記錄目錄。",
|
||||
"please_fill_all_workspace_fields": "請先填寫所有工作區欄位。",
|
||||
"show_archived": "顯示已封存",
|
||||
"title": "意見回饋記錄目錄",
|
||||
"unarchive": "取消封存"
|
||||
},
|
||||
"general": {
|
||||
"bulk_invite_warning_description": "在免費方案中,所有組織成員始終會被指派「擁有者」角色。",
|
||||
"cannot_delete_only_organization": "這是您唯一的組織,無法刪除。請先建立新組織。",
|
||||
|
||||
@@ -290,6 +290,9 @@ export const withAuditLogging = <
|
||||
case "quota":
|
||||
targetId = auditLoggingCtx.quotaId;
|
||||
break;
|
||||
case "feedbackRecordDirectory":
|
||||
targetId = auditLoggingCtx.feedbackRecordDirectoryId;
|
||||
break;
|
||||
default:
|
||||
targetId = UNKNOWN_DATA;
|
||||
break;
|
||||
|
||||
@@ -25,6 +25,7 @@ export const ZAuditTarget = z.enum([
|
||||
"integration",
|
||||
"file",
|
||||
"quota",
|
||||
"feedbackRecordDirectory",
|
||||
]);
|
||||
export const ZAuditAction = z.enum([
|
||||
"created",
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
|
||||
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
|
||||
import {
|
||||
archiveFeedbackRecordDirectory,
|
||||
createFeedbackRecordDirectory,
|
||||
getFeedbackRecordDirectoryDetails,
|
||||
getOrganizationIdFromDirectoryId,
|
||||
unarchiveFeedbackRecordDirectory,
|
||||
updateFeedbackRecordDirectory,
|
||||
} from "@/modules/ee/feedback-record-directory/lib/feedback-record-directory";
|
||||
import { ZFeedbackRecordDirectoryFormSchema } from "@/modules/ee/feedback-record-directory/types/feedback-record-directory";
|
||||
|
||||
const ZCreateFeedbackRecordDirectoryAction = z.object({
|
||||
organizationId: z.cuid(),
|
||||
name: z.string().trim().min(1, "Directory name is required"),
|
||||
});
|
||||
|
||||
export const createFeedbackRecordDirectoryAction = authenticatedActionClient
|
||||
.inputSchema(ZCreateFeedbackRecordDirectoryAction)
|
||||
.action(
|
||||
withAuditLogging("created", "feedbackRecordDirectory", async ({ ctx, parsedInput }) => {
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId: parsedInput.organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = await createFeedbackRecordDirectory(parsedInput.organizationId, parsedInput.name);
|
||||
ctx.auditLoggingCtx.organizationId = parsedInput.organizationId;
|
||||
ctx.auditLoggingCtx.feedbackRecordDirectoryId = result;
|
||||
ctx.auditLoggingCtx.newObject = {
|
||||
...(await getFeedbackRecordDirectoryDetails(result)),
|
||||
};
|
||||
return result;
|
||||
})
|
||||
);
|
||||
|
||||
const ZGetFeedbackRecordDirectoryDetailsAction = z.object({
|
||||
directoryId: ZId,
|
||||
});
|
||||
|
||||
export const getFeedbackRecordDirectoryDetailsAction = authenticatedActionClient
|
||||
.inputSchema(ZGetFeedbackRecordDirectoryDetailsAction)
|
||||
.action(async ({ parsedInput, ctx }) => {
|
||||
const organizationId = await getOrganizationIdFromDirectoryId(parsedInput.directoryId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
});
|
||||
|
||||
const ZUpdateFeedbackRecordDirectoryAction = z.object({
|
||||
directoryId: ZId,
|
||||
data: ZFeedbackRecordDirectoryFormSchema,
|
||||
});
|
||||
|
||||
export const updateFeedbackRecordDirectoryAction = authenticatedActionClient
|
||||
.inputSchema(ZUpdateFeedbackRecordDirectoryAction)
|
||||
.action(
|
||||
withAuditLogging("updated", "feedbackRecordDirectory", async ({ ctx, parsedInput }) => {
|
||||
const organizationId = await getOrganizationIdFromDirectoryId(parsedInput.directoryId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.feedbackRecordDirectoryId = parsedInput.directoryId;
|
||||
const oldObject = await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
const result = await updateFeedbackRecordDirectory(parsedInput.directoryId, parsedInput.data);
|
||||
ctx.auditLoggingCtx.oldObject = oldObject;
|
||||
ctx.auditLoggingCtx.newObject = await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
return result;
|
||||
})
|
||||
);
|
||||
|
||||
const ZArchiveFeedbackRecordDirectoryAction = z.object({
|
||||
directoryId: ZId,
|
||||
});
|
||||
|
||||
export const archiveFeedbackRecordDirectoryAction = authenticatedActionClient
|
||||
.inputSchema(ZArchiveFeedbackRecordDirectoryAction)
|
||||
.action(
|
||||
withAuditLogging("deleted", "feedbackRecordDirectory", async ({ ctx, parsedInput }) => {
|
||||
const organizationId = await getOrganizationIdFromDirectoryId(parsedInput.directoryId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.feedbackRecordDirectoryId = parsedInput.directoryId;
|
||||
const oldObject = await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
ctx.auditLoggingCtx.oldObject = oldObject;
|
||||
return await archiveFeedbackRecordDirectory(parsedInput.directoryId);
|
||||
})
|
||||
);
|
||||
|
||||
const ZUnarchiveFeedbackRecordDirectoryAction = z.object({
|
||||
directoryId: ZId,
|
||||
});
|
||||
|
||||
export const unarchiveFeedbackRecordDirectoryAction = authenticatedActionClient
|
||||
.inputSchema(ZUnarchiveFeedbackRecordDirectoryAction)
|
||||
.action(
|
||||
withAuditLogging("updated", "feedbackRecordDirectory", async ({ ctx, parsedInput }) => {
|
||||
const organizationId = await getOrganizationIdFromDirectoryId(parsedInput.directoryId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
ctx.auditLoggingCtx.organizationId = organizationId;
|
||||
ctx.auditLoggingCtx.feedbackRecordDirectoryId = parsedInput.directoryId;
|
||||
const oldObject = await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
ctx.auditLoggingCtx.oldObject = oldObject;
|
||||
const result = await unarchiveFeedbackRecordDirectory(parsedInput.directoryId);
|
||||
ctx.auditLoggingCtx.newObject = await getFeedbackRecordDirectoryDetails(parsedInput.directoryId);
|
||||
return result;
|
||||
})
|
||||
);
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import { FolderIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { createFeedbackRecordDirectoryAction } from "@/modules/ee/feedback-record-directory/actions";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/modules/ui/components/dialog";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
|
||||
interface CreateFeedbackRecordDirectoryModalProps {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
export const CreateFeedbackRecordDirectoryModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
organizationId,
|
||||
}: CreateFeedbackRecordDirectoryModalProps) => {
|
||||
const [directoryName, setDirectoryName] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
const handleCreation = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
const name = directoryName.trim();
|
||||
const response = await createFeedbackRecordDirectoryAction({ name, organizationId });
|
||||
if (response?.data) {
|
||||
toast.success(t("environments.settings.feedback_record_directories.directory_created_successfully"));
|
||||
router.refresh();
|
||||
setOpen(false);
|
||||
setDirectoryName("");
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(response);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<FolderIcon />
|
||||
<DialogTitle>
|
||||
{t("environments.settings.feedback_record_directories.create_new_directory")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleCreation} className="gap-y-4 pt-4">
|
||||
<DialogBody>
|
||||
<div className="grid w-full gap-y-2 pb-4">
|
||||
<Label htmlFor="directory-name">
|
||||
{t("environments.settings.feedback_record_directories.directory_name")}
|
||||
</Label>
|
||||
<Input
|
||||
id="directory-name"
|
||||
name="directory-name"
|
||||
value={directoryName}
|
||||
onChange={(e) => {
|
||||
setDirectoryName(e.target.value);
|
||||
}}
|
||||
placeholder={t("environments.settings.feedback_record_directories.enter_directory_name")}
|
||||
/>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setDirectoryName("");
|
||||
}}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!directoryName || isLoading} loading={isLoading} type="submit">
|
||||
{t("common.create")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { archiveFeedbackRecordDirectoryAction } from "@/modules/ee/feedback-record-directory/actions";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||
|
||||
interface ArchiveFeedbackRecordDirectoryProps {
|
||||
directoryId: string;
|
||||
onArchive: () => void;
|
||||
isOwnerOrManager: boolean;
|
||||
}
|
||||
|
||||
export const ArchiveFeedbackRecordDirectory = ({
|
||||
directoryId,
|
||||
onArchive,
|
||||
isOwnerOrManager,
|
||||
}: ArchiveFeedbackRecordDirectoryProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [isArchiveDialogOpen, setIsArchiveDialogOpen] = useState(false);
|
||||
const [isArchiving, setIsArchiving] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleArchive = async () => {
|
||||
setIsArchiving(true);
|
||||
|
||||
const response = await archiveFeedbackRecordDirectoryAction({ directoryId });
|
||||
if (response?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(response));
|
||||
setIsArchiveDialogOpen(false);
|
||||
setIsArchiving(false);
|
||||
return;
|
||||
}
|
||||
if (response?.data) {
|
||||
toast.success(t("environments.settings.feedback_record_directories.directory_archived_successfully"));
|
||||
onArchive?.();
|
||||
router.refresh();
|
||||
} else {
|
||||
toast.error(t("common.something_went_wrong_please_try_again"));
|
||||
}
|
||||
|
||||
setIsArchiveDialogOpen(false);
|
||||
setIsArchiving(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-baseline space-x-2">
|
||||
<TooltipRenderer
|
||||
shouldRender={!isOwnerOrManager}
|
||||
tooltipContent={t("environments.settings.feedback_record_directories.archive_not_allowed")}
|
||||
className="w-auto">
|
||||
<Button
|
||||
variant="destructive"
|
||||
type="button"
|
||||
className="w-auto"
|
||||
disabled={!isOwnerOrManager}
|
||||
onClick={() => setIsArchiveDialogOpen(true)}>
|
||||
{t("environments.settings.feedback_record_directories.archive_directory")}
|
||||
</Button>
|
||||
</TooltipRenderer>
|
||||
</div>
|
||||
|
||||
{isArchiveDialogOpen && (
|
||||
<DeleteDialog
|
||||
open={isArchiveDialogOpen}
|
||||
setOpen={setIsArchiveDialogOpen}
|
||||
deleteWhat={t("environments.settings.feedback_record_directories.directory")}
|
||||
text={t("environments.settings.feedback_record_directories.are_you_sure_you_want_to_archive")}
|
||||
onDelete={handleArchive}
|
||||
isDeleting={isArchiving}
|
||||
title={t("environments.settings.feedback_record_directories.archive_directory")}
|
||||
buttonLabel={t("environments.settings.feedback_record_directories.archive")}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { FormProvider, SubmitHandler, useForm, useWatch } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { updateFeedbackRecordDirectoryAction } from "@/modules/ee/feedback-record-directory/actions";
|
||||
import { ArchiveFeedbackRecordDirectory } from "@/modules/ee/feedback-record-directory/components/feedback-record-directory-settings/archive-feedback-record-directory";
|
||||
import {
|
||||
TFeedbackRecordDirectoryDetails,
|
||||
TFeedbackRecordDirectoryFormSchema,
|
||||
ZFeedbackRecordDirectoryFormSchema,
|
||||
} from "@/modules/ee/feedback-record-directory/types/feedback-record-directory";
|
||||
import { TOrganizationProject } from "@/modules/ee/teams/team-list/types/project";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/modules/ui/components/dialog";
|
||||
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
|
||||
import { IdBadge } from "@/modules/ui/components/id-badge";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { InputCombobox } from "@/modules/ui/components/input-combo-box";
|
||||
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
|
||||
import { Muted } from "@/modules/ui/components/typography";
|
||||
|
||||
interface FeedbackRecordDirectorySettingsModalProps {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
directory: TFeedbackRecordDirectoryDetails;
|
||||
orgProjects: TOrganizationProject[];
|
||||
membershipRole?: TOrganizationRole;
|
||||
}
|
||||
|
||||
export const FeedbackRecordDirectorySettingsModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
directory,
|
||||
orgProjects,
|
||||
membershipRole,
|
||||
}: FeedbackRecordDirectorySettingsModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOwner, isManager } = getAccessFlags(membershipRole);
|
||||
const isOwnerOrManager = isOwner || isManager;
|
||||
const router = useRouter();
|
||||
|
||||
const initialProjectIds = useMemo(() => {
|
||||
return new Set(directory.projects.map((project) => project.projectId));
|
||||
}, [directory.projects]);
|
||||
|
||||
const initialProjects = useMemo(() => {
|
||||
const projects = directory.projects.map((project) => ({
|
||||
projectId: project.projectId,
|
||||
}));
|
||||
return projects.length ? projects : [{ projectId: "" }];
|
||||
}, [directory.projects]);
|
||||
|
||||
const form = useForm<TFeedbackRecordDirectoryFormSchema>({
|
||||
defaultValues: {
|
||||
name: directory.name,
|
||||
projects: initialProjects,
|
||||
},
|
||||
mode: "onChange",
|
||||
resolver: zodResolver(ZFeedbackRecordDirectoryFormSchema),
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
setValue,
|
||||
} = form;
|
||||
|
||||
const closeSettingsModal = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleUpdate: SubmitHandler<TFeedbackRecordDirectoryFormSchema> = async (data) => {
|
||||
const projects = data.projects.filter((p) => p.projectId);
|
||||
|
||||
const response = await updateFeedbackRecordDirectoryAction({
|
||||
directoryId: directory.id,
|
||||
data: {
|
||||
name: data.name,
|
||||
projects,
|
||||
},
|
||||
});
|
||||
|
||||
if (response?.data) {
|
||||
toast.success(t("environments.settings.feedback_record_directories.directory_updated_successfully"));
|
||||
closeSettingsModal();
|
||||
router.refresh();
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(response);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const watchProjects = useWatch({ control, name: "projects" }) || [];
|
||||
|
||||
const handleAddProject = () => {
|
||||
const newProjects = [...watchProjects, { projectId: "" }];
|
||||
setValue("projects", newProjects);
|
||||
};
|
||||
|
||||
const handleRemoveProject = (index: number) => {
|
||||
setValue(
|
||||
"projects",
|
||||
watchProjects.filter((_, i) => i !== index)
|
||||
);
|
||||
};
|
||||
|
||||
const selectedProjectIds = watchProjects.map((p) => p.projectId);
|
||||
|
||||
const getProjectOptionsForIndex = (index: number) => {
|
||||
const currentProjectId = watchProjects[index]?.projectId;
|
||||
return orgProjects
|
||||
.filter((op) => !selectedProjectIds.includes(op?.id) || op?.id === currentProjectId)
|
||||
.map((op) => ({ label: op?.name ?? "", value: op?.id }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||
};
|
||||
|
||||
const hasEmptyProject = watchProjects.some((p) => !p.projectId);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader className="pb-4">
|
||||
<DialogTitle>
|
||||
{t("environments.settings.feedback_record_directories.directory_settings_title", {
|
||||
directoryName: directory.name,
|
||||
})}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("environments.settings.feedback_record_directories.directory_settings_description")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FormProvider {...form}>
|
||||
<form className="contents space-y-4" onSubmit={handleSubmit(handleUpdate)}>
|
||||
<DialogBody className="flex-grow space-y-6 overflow-y-auto">
|
||||
<FormField
|
||||
control={control}
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("environments.settings.feedback_record_directories.directory_name")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("environments.settings.feedback_record_directories.directory_name")}
|
||||
{...field}
|
||||
disabled={!isOwnerOrManager}
|
||||
/>
|
||||
</FormControl>
|
||||
{error?.message && <FormError className="text-left">{error.message}</FormError>}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<IdBadge
|
||||
id={directory.id}
|
||||
label={t("environments.settings.feedback_record_directories.directory_id")}
|
||||
variant="column"
|
||||
/>
|
||||
|
||||
{/* Workspace Assignments Section */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<FormLabel>{t("common.workspaces")}</FormLabel>
|
||||
<Muted className="block text-slate-500">
|
||||
{t("environments.settings.feedback_record_directories.assign_workspaces_description")}
|
||||
</Muted>
|
||||
</div>
|
||||
<FormField
|
||||
control={control}
|
||||
name="projects"
|
||||
render={({ fieldState: { error } }) => (
|
||||
<FormItem className="flex-1">
|
||||
<div className="space-y-2">
|
||||
{watchProjects.map((project, index) => {
|
||||
const isExistingProject =
|
||||
project.projectId && initialProjectIds.has(project.projectId);
|
||||
const isSelectDisabled = isExistingProject || !isOwnerOrManager;
|
||||
|
||||
return (
|
||||
<div key={`project-${project.projectId}-${index}`} className="flex gap-2.5">
|
||||
<FormField
|
||||
control={control}
|
||||
name={`projects.${index}.projectId`}
|
||||
render={({ field, fieldState: { error: fieldError } }) => (
|
||||
<FormItem className="flex-1">
|
||||
<div
|
||||
className={
|
||||
isSelectDisabled ? "pointer-events-none opacity-50" : undefined
|
||||
}>
|
||||
<InputCombobox
|
||||
id={`project-select-${index}`}
|
||||
options={getProjectOptionsForIndex(index)}
|
||||
value={field.value || null}
|
||||
onChangeValue={(val) => {
|
||||
const value = typeof val === "string" ? val : "";
|
||||
field.onChange(value);
|
||||
}}
|
||||
showSearch
|
||||
searchPlaceholder={t("common.search")}
|
||||
comboboxClasses="flex-1 min-w-0 w-full"
|
||||
emptyDropdownText={t("environments.surveys.edit.no_option_found")}
|
||||
/>
|
||||
</div>
|
||||
{fieldError?.message && (
|
||||
<FormError className="text-left">{fieldError.message}</FormError>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{watchProjects.length > 1 && (
|
||||
<Button
|
||||
size="icon"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
className="shrink-0"
|
||||
disabled={!isOwnerOrManager}
|
||||
onClick={() => handleRemoveProject(index)}>
|
||||
<Trash2Icon className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{error?.root?.message && (
|
||||
<FormError className="text-left">{error.root.message}</FormError>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TooltipRenderer
|
||||
shouldRender={selectedProjectIds.length === orgProjects.length || hasEmptyProject}
|
||||
triggerClass="inline-block"
|
||||
tooltipContent={
|
||||
hasEmptyProject
|
||||
? t(
|
||||
"environments.settings.feedback_record_directories.please_fill_all_workspace_fields"
|
||||
)
|
||||
: t("environments.settings.feedback_record_directories.all_workspaces_added")
|
||||
}>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={handleAddProject}
|
||||
disabled={
|
||||
!isOwnerOrManager || selectedProjectIds.length === orgProjects.length || hasEmptyProject
|
||||
}>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
{t("common.add_workspace")}
|
||||
</Button>
|
||||
</TooltipRenderer>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<div className="w-full">
|
||||
<ArchiveFeedbackRecordDirectory
|
||||
directoryId={directory.id}
|
||||
onArchive={closeSettingsModal}
|
||||
isOwnerOrManager={isOwnerOrManager}
|
||||
/>
|
||||
</div>
|
||||
<Button size="default" type="button" variant="outline" onClick={closeSettingsModal}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" size="default" loading={isSubmitting} disabled={!isOwnerOrManager}>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import {
|
||||
getFeedbackRecordDirectoryDetailsAction,
|
||||
unarchiveFeedbackRecordDirectoryAction,
|
||||
} from "@/modules/ee/feedback-record-directory/actions";
|
||||
import { CreateFeedbackRecordDirectoryModal } from "@/modules/ee/feedback-record-directory/components/create-feedback-record-directory-modal";
|
||||
import { FeedbackRecordDirectorySettingsModal } from "@/modules/ee/feedback-record-directory/components/feedback-record-directory-settings/feedback-record-directory-settings-modal";
|
||||
import {
|
||||
TFeedbackRecordDirectory,
|
||||
TFeedbackRecordDirectoryDetails,
|
||||
} from "@/modules/ee/feedback-record-directory/types/feedback-record-directory";
|
||||
import { TOrganizationProject } from "@/modules/ee/teams/team-list/types/project";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table";
|
||||
|
||||
interface FeedbackRecordDirectoryTableProps {
|
||||
directories: TFeedbackRecordDirectory[];
|
||||
organizationId: string;
|
||||
orgProjects: TOrganizationProject[];
|
||||
membershipRole?: TOrganizationRole;
|
||||
}
|
||||
|
||||
export const FeedbackRecordDirectoryTable = ({
|
||||
directories,
|
||||
organizationId,
|
||||
orgProjects,
|
||||
membershipRole,
|
||||
}: FeedbackRecordDirectoryTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openSettingsModal, setOpenSettingsModal] = useState(false);
|
||||
const [selectedDirectory, setSelectedDirectory] = useState<TFeedbackRecordDirectoryDetails>();
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { isOwner, isManager } = getAccessFlags(membershipRole);
|
||||
const isOwnerOrManager = isOwner || isManager;
|
||||
|
||||
const handleManageDirectory = async (directoryId: string) => {
|
||||
const response = await getFeedbackRecordDirectoryDetailsAction({ directoryId });
|
||||
|
||||
if (response?.data) {
|
||||
setSelectedDirectory(response.data);
|
||||
setOpenSettingsModal(true);
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(response);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnarchiveDirectory = async (directoryId: string) => {
|
||||
const response = await unarchiveFeedbackRecordDirectoryAction({ directoryId });
|
||||
if (response?.data) {
|
||||
toast.success(t("environments.settings.feedback_record_directories.directory_unarchived_successfully"));
|
||||
router.refresh();
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(response);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredDirectories = showArchived ? directories : directories.filter((d) => !d.isArchived);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOwnerOrManager && (
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 text-sm text-slate-500">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showArchived}
|
||||
onChange={(e) => setShowArchived(e.target.checked)}
|
||||
className="rounded border-slate-300"
|
||||
/>
|
||||
{t("environments.settings.feedback_record_directories.show_archived")}
|
||||
</label>
|
||||
<Button size="sm" onClick={() => setOpenCreateModal(true)}>
|
||||
{t("environments.settings.feedback_record_directories.create_directory")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="overflow-hidden rounded-lg" aria-label="Feedback record directories list">
|
||||
<Table>
|
||||
<TableHeader role="rowgroup">
|
||||
<TableRow className="bg-slate-100" role="row">
|
||||
<TableHead className="font-medium text-slate-500">
|
||||
{t("environments.settings.feedback_record_directories.directory_name")}
|
||||
</TableHead>
|
||||
<TableHead className="font-medium text-slate-500">{t("common.workspaces")}</TableHead>
|
||||
<TableHead className="font-medium text-slate-500">{t("common.status")}</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="[&_tr:last-child]:border-b">
|
||||
{filteredDirectories.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center hover:bg-transparent">
|
||||
{t("environments.settings.feedback_record_directories.empty_state")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{filteredDirectories.map((directory) => (
|
||||
<TableRow key={directory.id} className="hover:bg-transparent">
|
||||
<TableCell>{directory.name}</TableCell>
|
||||
<TableCell>{directory.projectCount}</TableCell>
|
||||
<TableCell>
|
||||
{directory.isArchived ? (
|
||||
<Badge type="gray" size="tiny" text={t("common.archived")} />
|
||||
) : (
|
||||
<Badge type="success" size="tiny" text={t("common.active")} />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="flex justify-end gap-2">
|
||||
{isOwnerOrManager && !directory.isArchived && (
|
||||
<Button size="sm" variant="secondary" onClick={() => handleManageDirectory(directory.id)}>
|
||||
{t("common.manage")}
|
||||
</Button>
|
||||
)}
|
||||
{isOwnerOrManager && directory.isArchived && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => handleUnarchiveDirectory(directory.id)}>
|
||||
{t("environments.settings.feedback_record_directories.unarchive")}
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<CreateFeedbackRecordDirectoryModal
|
||||
open={openCreateModal}
|
||||
setOpen={setOpenCreateModal}
|
||||
organizationId={organizationId}
|
||||
/>
|
||||
|
||||
{openSettingsModal && selectedDirectory && (
|
||||
<FeedbackRecordDirectorySettingsModal
|
||||
open={openSettingsModal}
|
||||
setOpen={setOpenSettingsModal}
|
||||
directory={selectedDirectory}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole={membershipRole}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
import { FeedbackRecordDirectoryTable } from "@/modules/ee/feedback-record-directory/components/feedback-record-directory-table";
|
||||
import { getFeedbackRecordDirectories } from "@/modules/ee/feedback-record-directory/lib/feedback-record-directory";
|
||||
import { getProjectsByOrganizationId } from "@/modules/ee/teams/team-list/lib/project";
|
||||
|
||||
interface FeedbackRecordDirectoryViewProps {
|
||||
organizationId: string;
|
||||
membershipRole?: TOrganizationRole;
|
||||
}
|
||||
|
||||
export const FeedbackRecordDirectoryView = async ({
|
||||
organizationId,
|
||||
membershipRole,
|
||||
}: FeedbackRecordDirectoryViewProps) => {
|
||||
const t = await getTranslate();
|
||||
|
||||
const [directories, orgProjects] = await Promise.all([
|
||||
getFeedbackRecordDirectories(organizationId),
|
||||
getProjectsByOrganizationId(organizationId),
|
||||
]);
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
title={t("environments.settings.feedback_record_directories.title")}
|
||||
description={t("environments.settings.feedback_record_directories.description")}>
|
||||
<FeedbackRecordDirectoryTable
|
||||
directories={directories}
|
||||
organizationId={organizationId}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole={membershipRole}
|
||||
/>
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,266 @@
|
||||
import "server-only";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import {
|
||||
TFeedbackRecordDirectory,
|
||||
TFeedbackRecordDirectoryDetails,
|
||||
TFeedbackRecordDirectoryFormSchema,
|
||||
ZFeedbackRecordDirectoryFormSchema,
|
||||
} from "@/modules/ee/feedback-record-directory/types/feedback-record-directory";
|
||||
|
||||
export const getFeedbackRecordDirectories = reactCache(
|
||||
async (organizationId: string): Promise<TFeedbackRecordDirectory[]> => {
|
||||
validateInputs([organizationId, ZId]);
|
||||
try {
|
||||
const directories = await prisma.feedbackRecordDirectory.findMany({
|
||||
where: {
|
||||
organizationId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
isArchived: true,
|
||||
_count: {
|
||||
select: {
|
||||
projects: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return directories.map((dir) => ({
|
||||
id: dir.id,
|
||||
name: dir.name,
|
||||
isArchived: dir.isArchived,
|
||||
projectCount: dir._count.projects,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getFeedbackRecordDirectoryDetails = reactCache(
|
||||
async (directoryId: string): Promise<TFeedbackRecordDirectoryDetails | null> => {
|
||||
validateInputs([directoryId, ZId]);
|
||||
try {
|
||||
const directory = await prisma.feedbackRecordDirectory.findUnique({
|
||||
where: {
|
||||
id: directoryId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
isArchived: true,
|
||||
organizationId: true,
|
||||
projects: {
|
||||
select: {
|
||||
projectId: true,
|
||||
project: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!directory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: directory.id,
|
||||
name: directory.name,
|
||||
isArchived: directory.isArchived,
|
||||
organizationId: directory.organizationId,
|
||||
projects: directory.projects.map((dp) => ({
|
||||
projectId: dp.projectId,
|
||||
projectName: dp.project.name,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const createFeedbackRecordDirectory = async (
|
||||
organizationId: string,
|
||||
name: string
|
||||
): Promise<string> => {
|
||||
validateInputs([organizationId, ZId], [name, z.string()]);
|
||||
try {
|
||||
const existingDirectory = await prisma.feedbackRecordDirectory.findFirst({
|
||||
where: {
|
||||
name,
|
||||
organizationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingDirectory) {
|
||||
throw new InvalidInputError("A feedback record directory with this name already exists");
|
||||
}
|
||||
|
||||
if (name.trim().length < 1) {
|
||||
throw new InvalidInputError("Directory name must be at least 1 character long");
|
||||
}
|
||||
|
||||
const directory = await prisma.feedbackRecordDirectory.create({
|
||||
data: {
|
||||
name,
|
||||
organizationId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
return directory.id;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFeedbackRecordDirectory = async (
|
||||
directoryId: string,
|
||||
data: TFeedbackRecordDirectoryFormSchema
|
||||
): Promise<boolean> => {
|
||||
validateInputs([directoryId, ZId], [data, ZFeedbackRecordDirectoryFormSchema]);
|
||||
|
||||
try {
|
||||
const { name, projects } = data;
|
||||
|
||||
const directory = await prisma.feedbackRecordDirectory.findUnique({
|
||||
where: { id: directoryId },
|
||||
});
|
||||
|
||||
if (!directory) {
|
||||
throw new ResourceNotFoundError("FeedbackRecordDirectory", directoryId);
|
||||
}
|
||||
|
||||
const currentDetails = await getFeedbackRecordDirectoryDetails(directoryId);
|
||||
if (!currentDetails) {
|
||||
throw new ResourceNotFoundError("FeedbackRecordDirectory", directoryId);
|
||||
}
|
||||
|
||||
// Validate that all specified projects belong to the same organization
|
||||
const projectIds = projects.map((p) => p.projectId);
|
||||
if (projectIds.length > 0) {
|
||||
const orgProjectsCount = await prisma.project.count({
|
||||
where: {
|
||||
id: { in: projectIds },
|
||||
organizationId: directory.organizationId,
|
||||
},
|
||||
});
|
||||
if (orgProjectsCount !== projectIds.length) {
|
||||
throw new InvalidInputError("Some specified projects do not belong to the organization.");
|
||||
}
|
||||
}
|
||||
|
||||
// Determine deleted projects (in current but not in new)
|
||||
const deletedProjects: string[] = [];
|
||||
for (const cp of currentDetails.projects) {
|
||||
if (!projects.some((p) => p.projectId === cp.projectId)) {
|
||||
deletedProjects.push(cp.projectId);
|
||||
}
|
||||
}
|
||||
|
||||
const payload: Prisma.FeedbackRecordDirectoryUpdateInput = {
|
||||
name: currentDetails.name !== name ? name : undefined,
|
||||
projects: {
|
||||
deleteMany: {
|
||||
projectId: { in: deletedProjects },
|
||||
},
|
||||
upsert: projects.map((p) => ({
|
||||
where: {
|
||||
feedbackRecordDirectoryId_projectId: {
|
||||
feedbackRecordDirectoryId: directoryId,
|
||||
projectId: p.projectId,
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: { projectId: p.projectId },
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
await prisma.feedbackRecordDirectory.update({
|
||||
where: { id: directoryId },
|
||||
data: payload,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const archiveFeedbackRecordDirectory = async (directoryId: string): Promise<boolean> => {
|
||||
validateInputs([directoryId, ZId]);
|
||||
try {
|
||||
await prisma.feedbackRecordDirectory.update({
|
||||
where: { id: directoryId },
|
||||
data: { isArchived: true },
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const unarchiveFeedbackRecordDirectory = async (directoryId: string): Promise<boolean> => {
|
||||
validateInputs([directoryId, ZId]);
|
||||
try {
|
||||
await prisma.feedbackRecordDirectory.update({
|
||||
where: { id: directoryId },
|
||||
data: { isArchived: false },
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganizationIdFromDirectoryId = async (directoryId: string): Promise<string> => {
|
||||
validateInputs([directoryId, ZId]);
|
||||
const directory = await prisma.feedbackRecordDirectory.findUnique({
|
||||
where: { id: directoryId },
|
||||
select: { organizationId: true },
|
||||
});
|
||||
|
||||
if (!directory) {
|
||||
throw new ResourceNotFoundError("FeedbackRecordDirectory", directoryId);
|
||||
}
|
||||
|
||||
return directory.organizationId;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getTranslate } from "@/lingodotdev/server";
|
||||
import { FeedbackRecordDirectoryView } from "@/modules/ee/feedback-record-directory/components/feedback-record-directory-view";
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
|
||||
export const FeedbackRecordDirectoriesPage = async (props: {
|
||||
params: Promise<{ environmentId: string }>;
|
||||
}) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslate();
|
||||
|
||||
const { currentUserMembership, organization } = await getEnvironmentAuth(params.environmentId);
|
||||
|
||||
const { isOwner, isManager } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
if (!isOwner && !isManager) {
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("environments.settings.general.organization_settings")}>
|
||||
<OrganizationSettingsNavbar
|
||||
environmentId={params.environmentId}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
activeId="feedback-record-directories"
|
||||
/>
|
||||
</PageHeader>
|
||||
<p className="text-sm text-slate-500">
|
||||
{t("environments.settings.feedback_record_directories.no_access")}
|
||||
</p>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("environments.settings.general.organization_settings")}>
|
||||
<OrganizationSettingsNavbar
|
||||
environmentId={params.environmentId}
|
||||
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
activeId="feedback-record-directories"
|
||||
/>
|
||||
</PageHeader>
|
||||
<FeedbackRecordDirectoryView
|
||||
organizationId={organization.id}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
/>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { z } from "zod";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
|
||||
export const ZFeedbackRecordDirectory = z.object({
|
||||
id: ZId,
|
||||
name: z.string(),
|
||||
isArchived: z.boolean(),
|
||||
projectCount: z.number(),
|
||||
});
|
||||
|
||||
export type TFeedbackRecordDirectory = z.infer<typeof ZFeedbackRecordDirectory>;
|
||||
|
||||
export const ZFeedbackRecordDirectoryDetails = z.object({
|
||||
id: ZId,
|
||||
name: z.string(),
|
||||
isArchived: z.boolean(),
|
||||
organizationId: ZId,
|
||||
projects: z.array(
|
||||
z.object({
|
||||
projectId: ZId,
|
||||
projectName: z.string(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type TFeedbackRecordDirectoryDetails = z.infer<typeof ZFeedbackRecordDirectoryDetails>;
|
||||
|
||||
export const ZFeedbackRecordDirectoryFormSchema = z.object({
|
||||
name: z.string().trim().min(1, "Directory name is required"),
|
||||
projects: z.array(
|
||||
z.object({
|
||||
projectId: z.string().trim().min(1, "Please select a workspace"),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type TFeedbackRecordDirectoryFormSchema = z.infer<typeof ZFeedbackRecordDirectoryFormSchema>;
|
||||
@@ -25,6 +25,8 @@ interface DeleteDialogProps {
|
||||
onSave?: () => void;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
buttonLabel?: string;
|
||||
}
|
||||
|
||||
export const DeleteDialog = ({
|
||||
@@ -39,6 +41,8 @@ export const DeleteDialog = ({
|
||||
onSave,
|
||||
children,
|
||||
disabled,
|
||||
title,
|
||||
buttonLabel,
|
||||
}: DeleteDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -48,7 +52,7 @@ export const DeleteDialog = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<CircleAlert className="h-4 w-4" />
|
||||
<div>
|
||||
<DialogTitle>{t("common.delete_what", { deleteWhat })}</DialogTitle>
|
||||
<DialogTitle>{title || t("common.delete_what", { deleteWhat })}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("environments.workspace.general.this_action_cannot_be_undone")}
|
||||
</DialogDescription>
|
||||
@@ -80,7 +84,7 @@ export const DeleteDialog = ({
|
||||
loading={isDeleting}
|
||||
disabled={disabled || isDeleting || isSaving}>
|
||||
<TrashIcon />
|
||||
{t("common.delete")}
|
||||
{buttonLabel || t("common.delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "FeedbackRecordDirectory" (
|
||||
"id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"isArchived" BOOLEAN NOT NULL DEFAULT false,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "FeedbackRecordDirectory_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FeedbackRecordDirectoryProject" (
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"feedbackRecordDirectoryId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "FeedbackRecordDirectoryProject_pkey" PRIMARY KEY ("feedbackRecordDirectoryId","projectId")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FeedbackRecordDirectory_organizationId_name_key" ON "FeedbackRecordDirectory"("organizationId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FeedbackRecordDirectoryProject_projectId_idx" ON "FeedbackRecordDirectoryProject"("projectId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FeedbackRecordDirectory" ADD CONSTRAINT "FeedbackRecordDirectory_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FeedbackRecordDirectoryProject" ADD CONSTRAINT "FeedbackRecordDirectoryProject_feedbackRecordDirectoryId_fkey" FOREIGN KEY ("feedbackRecordDirectoryId") REFERENCES "FeedbackRecordDirectory"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FeedbackRecordDirectoryProject" ADD CONSTRAINT "FeedbackRecordDirectoryProject_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -625,28 +625,29 @@ enum SurveyOverlay {
|
||||
/// @property recontactDays - Default recontact delay for surveys
|
||||
/// @property placement - Default widget placement for in-app surveys
|
||||
model Project {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
organizationId String
|
||||
environments Environment[]
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
organizationId String
|
||||
environments Environment[]
|
||||
/// [Styling]
|
||||
styling Json @default("{\"allowStyleOverwrite\":true}")
|
||||
styling Json @default("{\"allowStyleOverwrite\":true}")
|
||||
/// [ProjectConfig]
|
||||
config Json @default("{}")
|
||||
recontactDays Int @default(7)
|
||||
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
|
||||
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
|
||||
placement WidgetPlacement @default(bottomRight)
|
||||
clickOutsideClose Boolean @default(true)
|
||||
overlay SurveyOverlay @default(none)
|
||||
languages Language[]
|
||||
config Json @default("{}")
|
||||
recontactDays Int @default(7)
|
||||
linkSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in link surveys
|
||||
inAppSurveyBranding Boolean @default(true) // Determines if the survey branding should be displayed in in-app surveys
|
||||
placement WidgetPlacement @default(bottomRight)
|
||||
clickOutsideClose Boolean @default(true)
|
||||
overlay SurveyOverlay @default(none)
|
||||
languages Language[]
|
||||
/// [Logo]
|
||||
logo Json?
|
||||
projectTeams ProjectTeam[]
|
||||
customHeadScripts String? // Custom HTML scripts for link surveys (self-hosted only)
|
||||
logo Json?
|
||||
projectTeams ProjectTeam[]
|
||||
customHeadScripts String? // Custom HTML scripts for link surveys (self-hosted only)
|
||||
feedbackRecordDirectoryProjects FeedbackRecordDirectoryProject[]
|
||||
|
||||
@@unique([organizationId, name])
|
||||
}
|
||||
@@ -663,19 +664,20 @@ model Project {
|
||||
/// @property whitelabel - Whitelabel configuration for the organization
|
||||
/// @property isAIEnabled - Controls access to AI-powered features
|
||||
model Organization {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
memberships Membership[]
|
||||
projects Project[]
|
||||
billing OrganizationBilling?
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
memberships Membership[]
|
||||
projects Project[]
|
||||
billing OrganizationBilling?
|
||||
/// [OrganizationWhitelabel]
|
||||
whitelabel Json @default("{}")
|
||||
invites Invite[]
|
||||
isAIEnabled Boolean @default(false)
|
||||
teams Team[]
|
||||
apiKeys ApiKey[]
|
||||
whitelabel Json @default("{}")
|
||||
invites Invite[]
|
||||
isAIEnabled Boolean @default(false)
|
||||
teams Team[]
|
||||
apiKeys ApiKey[]
|
||||
feedbackRecordDirectories FeedbackRecordDirectory[]
|
||||
}
|
||||
|
||||
/// Stores billing and Stripe synchronization data for an organization.
|
||||
@@ -1023,3 +1025,41 @@ model ProjectTeam {
|
||||
@@id([projectId, teamId])
|
||||
@@index([teamId])
|
||||
}
|
||||
|
||||
/// Represents a feedback record directory (Hub tenant) owned by an organization.
|
||||
/// Directories group feedback data and are assigned to workspaces for access control.
|
||||
///
|
||||
/// @property id - Unique identifier for the directory
|
||||
/// @property name - Display name of the directory
|
||||
/// @property isArchived - Soft delete flag
|
||||
/// @property organization - The parent organization
|
||||
/// @property projects - Workspaces assigned to this directory
|
||||
model FeedbackRecordDirectory {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
isArchived Boolean @default(false)
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
projects FeedbackRecordDirectoryProject[]
|
||||
|
||||
@@unique([organizationId, name])
|
||||
}
|
||||
|
||||
/// Links feedback record directories to projects (workspaces).
|
||||
/// Manages which workspaces can access a given directory.
|
||||
///
|
||||
/// @property feedbackRecordDirectory - The directory being accessed
|
||||
/// @property project - The workspace receiving access
|
||||
model FeedbackRecordDirectoryProject {
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
feedbackRecordDirectoryId String
|
||||
feedbackRecordDirectory FeedbackRecordDirectory @relation(fields: [feedbackRecordDirectoryId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([feedbackRecordDirectoryId, projectId])
|
||||
@@index([projectId])
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ checksums:
|
||||
common/finish: ffa7a10f71182b48fefed7135bee24fa
|
||||
common/language_switch: fd72a9ada13f672f4fd5da863b22cc46
|
||||
common/next: 89ddbcf710eba274963494f312bdc8a9
|
||||
common/no_results_found: 5518f2865757dc73900aa03ef8be6934
|
||||
common/open_in_new_tab: 6844e4922a7a40a7ee25c10ea109cdeb
|
||||
common/people_responded: b685fb877090d8658db724ad07a0dbd8
|
||||
common/please_retry_now_or_try_again_later: 949a3841e2eb01fa249790a42bf23aa5
|
||||
@@ -22,6 +23,7 @@ checksums:
|
||||
common/respondents_will_not_see_this_card: 18c3dd44d6ff6ca2310ad196b84f30d3
|
||||
common/retry: 6e44d18639560596569a1278f9c83676
|
||||
common/retrying: 40989361ea5f6b95897b95ac928b5bd9
|
||||
common/search: fe877a75eac472fc5b188c135c78a558
|
||||
common/select_option: d68a0fb9afd0817dc31b3e9cb11855cb
|
||||
common/select_options: d5a80087e889848e0fed3f1be359366f
|
||||
common/sending_responses: 244f1aebc3f6a101ae2f8b630d7967ec
|
||||
|
||||
Reference in New Issue
Block a user