+ );
}
diff --git a/apps/app/src/components/tables/people/employee-status.tsx b/apps/app/src/components/tables/people/employee-status.tsx
new file mode 100644
index 0000000000..21095ee726
--- /dev/null
+++ b/apps/app/src/components/tables/people/employee-status.tsx
@@ -0,0 +1,39 @@
+import { useI18n } from "@/locales/client";
+import { cn } from "@bubba/ui/cn";
+
+// Define employee status types
+export const EMPLOYEE_STATUS_TYPES = ["active", "inactive"] as const;
+export type EmployeeStatusType = (typeof EMPLOYEE_STATUS_TYPES)[number];
+
+// Define colors for employee statuses
+const EMPLOYEE_STATUS_COLORS: Record = {
+ active: "#00DC73", // Green - Same as "closed" in Status component
+ inactive: "#ffc107", // Yellow/amber - Same as "open" in Status component
+};
+
+/**
+ * EmployeeStatus component that matches the styling of the Status component
+ * but uses active/inactive states specific to employees
+ */
+export function EmployeeStatus({ status }: { status: EmployeeStatusType }) {
+ const t = useI18n();
+
+ return (
+
+
+ {t(`common.status.${status}`)}
+
+ );
+}
+
+/**
+ * Converts boolean isActive to EmployeeStatusType
+ */
+export function getEmployeeStatusFromBoolean(
+ isActive: boolean,
+): EmployeeStatusType {
+ return isActive ? "active" : "inactive";
+}
diff --git a/apps/app/src/components/tables/people/server-columns.tsx b/apps/app/src/components/tables/people/server-columns.tsx
index e0b0ff7c18..794e0e972a 100644
--- a/apps/app/src/components/tables/people/server-columns.tsx
+++ b/apps/app/src/components/tables/people/server-columns.tsx
@@ -1,11 +1,12 @@
import { getI18n } from "@/locales/server";
export async function getServerColumnHeaders() {
- const t = await getI18n();
+ const t = await getI18n();
- return {
- name: t("people.table.name"),
- email: t("people.table.email"),
- department: t("people.table.department"),
- };
+ return {
+ name: t("people.table.name"),
+ email: t("people.table.email"),
+ department: t("people.table.department"),
+ status: t("people.table.status"),
+ };
}
diff --git a/apps/app/src/lib/db/employee.ts b/apps/app/src/lib/db/employee.ts
new file mode 100644
index 0000000000..afade935d5
--- /dev/null
+++ b/apps/app/src/lib/db/employee.ts
@@ -0,0 +1,183 @@
+import { type Employee, db } from "@bubba/db";
+import type { Departments } from "@prisma/client";
+
+const DEFAULT_TASKS = [
+ {
+ code: "POLICY-ACCEPT",
+ name: "Policy Acceptance",
+ description: "Review and accept company policies",
+ },
+ {
+ code: "DEVICE-SECURITY",
+ name: "Device Security",
+ description: "Complete device security checklist and configuration",
+ },
+] as const;
+
+/**
+ * Find an existing employee by email and organization ID
+ */
+export async function findEmployeeByEmail(
+ email: string,
+ organizationId: string
+): Promise {
+ return db.employee.findUnique({
+ where: {
+ email_organizationId: {
+ email,
+ organizationId,
+ },
+ },
+ });
+}
+
+/**
+ * Create a new employee or reactivate an existing one
+ */
+export async function createOrReactivateEmployee(params: {
+ name: string;
+ email: string;
+ department: Departments;
+ organizationId: string;
+ externalEmployeeId?: string;
+}): Promise {
+ const { name, email, department, organizationId, externalEmployeeId } =
+ params;
+
+ // First check if an employee exists
+ const existingEmployee = await findEmployeeByEmail(email, organizationId);
+
+ if (existingEmployee) {
+ if (existingEmployee.isActive) {
+ throw new Error(
+ "An employee with this email already exists in your organization"
+ );
+ }
+
+ // Reactivate the existing employee
+ return db.employee.update({
+ where: { id: existingEmployee.id },
+ data: {
+ name,
+ department,
+ externalEmployeeId,
+ organizationId,
+ updatedAt: new Date(),
+ },
+ });
+ }
+
+ // Create a new employee
+ return db.employee.create({
+ data: {
+ name,
+ email,
+ department,
+ organizationId,
+ externalEmployeeId,
+ },
+ });
+}
+
+/**
+ * Create or update a portal user for an employee
+ */
+export async function createOrUpdatePortalUser(params: {
+ employeeId: string;
+ name: string;
+ email: string;
+ organizationId: string;
+}): Promise {
+ const { employeeId, name, email, organizationId } = params;
+
+ await db.portalUser.upsert({
+ where: { email },
+ create: {
+ id: employeeId,
+ name,
+ email,
+ organizationId,
+ emailVerified: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ employees: {
+ connect: {
+ id: employeeId,
+ },
+ },
+ },
+ update: {
+ updatedAt: new Date(),
+ name,
+ email,
+ organizationId,
+ employees: {
+ connect: {
+ id: employeeId,
+ },
+ },
+ },
+ });
+}
+
+/**
+ * Create default tasks for an employee
+ */
+export async function createDefaultTasksForEmployee(
+ employeeId: string
+): Promise {
+ // Create or get the required task definitions first and store their IDs
+ const requiredTasks = await Promise.all(
+ DEFAULT_TASKS.map(async (task) => {
+ return db.employeeRequiredTask.upsert({
+ where: { code: task.code },
+ create: {
+ code: task.code,
+ name: task.name,
+ description: task.description,
+ },
+ update: {},
+ });
+ })
+ );
+
+ // Now create the employee tasks using the actual task IDs
+ await Promise.all(
+ requiredTasks.map(async (task) => {
+ return db.employeeTask.create({
+ data: {
+ employeeId: employeeId,
+ requiredTaskId: task.id,
+ status: "assigned",
+ },
+ });
+ })
+ );
+}
+
+/**
+ * Complete employee creation by handling all steps:
+ * 1. Create/reactivate the employee
+ * 2. Create/update portal user
+ * 3. Create default tasks
+ */
+export async function completeEmployeeCreation(params: {
+ name: string;
+ email: string;
+ department: Departments;
+ organizationId: string;
+ externalEmployeeId?: string;
+}): Promise {
+ const employee = await createOrReactivateEmployee(params);
+
+ await createOrUpdatePortalUser({
+ employeeId: employee.id,
+ name: params.name,
+ email: params.email,
+ organizationId: params.organizationId,
+ });
+
+ await createDefaultTasksForEmployee(employee.id);
+
+ return employee;
+}
diff --git a/apps/app/src/locales/en.ts b/apps/app/src/locales/en.ts
index f4011ec632..92e0756d80 100644
--- a/apps/app/src/locales/en.ts
+++ b/apps/app/src/locales/en.ts
@@ -1,931 +1,932 @@
export default {
- language: {
- title: "Languages",
- description: "Change the language used in the user interface.",
- placeholder: "Select language",
- },
- languages: {
- en: "English",
- es: "Spanish",
- fr: "French",
- no: "Norwegian",
- pt: "Portuguese",
- },
- common: {
- frequency: {
- daily: "Daily",
- weekly: "Weekly",
- monthly: "Monthly",
- quarterly: "Quarterly",
- yearly: "Yearly",
- },
- notifications: {
- inbox: "Inbox",
- archive: "Archive",
- archive_all: "Archive all",
- no_notifications: "No new notifications",
- },
- actions: {
- save: "Save",
- edit: "Edit",
- delete: "Delete",
- cancel: "Cancel",
- clear: "Clear",
- create: "Create",
- addNew: "Add New",
- send: "Send",
- return: "Return",
- success: "Success",
- error: "Error",
- next: "Next",
- complete: "Complete",
- },
- assignee: {
- label: "Assignee",
- placeholder: "Select assignee",
- },
- date: {
- pick: "Pick a date",
- due_date: "Due Date",
- },
- status: {
- open: "Open",
- pending: "Pending",
- closed: "Closed",
- archived: "Archived",
- compliant: "Compliant",
- non_compliant: "Non Compliant",
- not_started: "Not Started",
- in_progress: "In Progress",
- published: "Published",
- needs_review: "Needs Review",
- draft: "Draft",
- not_assessed: "Not Assessed",
- assessed: "Assessed",
- active: "Active",
- inactive: "Inactive",
- title: "Status",
- },
- filters: {
- clear: "Clear filters",
- search: "Search...",
- status: "Status",
- department: "Department",
- owner: {
- label: "Assignee",
- placeholder: "Filter by assignee",
- },
- },
- table: {
- title: "Title",
- status: "Status",
- assigned_to: "Assigned To",
- due_date: "Due Date",
- last_updated: "Last Updated",
- no_results: "No results found",
- },
- empty_states: {
- no_results: {
- title: "No results found",
- title_tasks: "No tasks found",
- title_risks: "No risks found",
- description: "Try another search, or adjusting the filters",
- description_filters: "Try another search, or adjusting the filters",
- description_no_tasks: "Create a task to get started",
- description_no_risks: "Create a risk to get started",
- },
- no_items: {
- title: "No items found",
- description: "Try adjusting your search or filters",
- },
- },
- pagination: {
- of: "of",
- items_per_page: "Items per page",
- rows_per_page: "Rows per page",
- page_x_of_y: "Page {{current}} of {{total}}",
- go_to_first_page: "Go to first page",
- go_to_previous_page: "Go to previous page",
- go_to_next_page: "Go to next page",
- go_to_last_page: "Go to last page",
- },
- comments: {
- title: "Comments",
- description: "Add a comment using the form below.",
- add: "New Comment",
- new: "New Comment",
- save: "Save Comment",
- success: "Comment added successfully",
- error: "Failed to add comment",
- placeholder: "Write your comment here...",
- empty: {
- title: "No comments yet",
- description: "Be the first to add a comment",
- },
- },
- upload: {
- fileUpload: {
- uploadingText: "Uploading...",
- uploadingFile: "Uploading file...",
- dropFileHere: "Drop file here",
- dropFileHereAlt: "Drop file here",
- releaseToUpload: "Release to upload",
- addFiles: "Add Files",
- uploadAdditionalEvidence: "Upload a file or document",
- dragDropOrClick: "Drag and drop or click to browse",
- dragDropOrClickToSelect: "Drag and drop or click to select file",
- maxFileSize: "Max file size: {size}MB",
- },
- fileUrl: {
- additionalLinks: "Additional Links",
- add: "Add",
- linksAdded: "{count} link{s} added",
- enterUrl: "Enter URL",
- addAnotherLink: "Add Another Link",
- saveLinks: "Save Links",
- urlBadge: "URL",
- copyLink: "Copy Link",
- openLink: "Open Link",
- deleteLink: "Delete Link",
- },
- fileCard: {
- preview: "Preview",
- filePreview: "File Preview: {fileName}",
- previewNotAvailable: "Preview not available for this file type",
- openFile: "Open File",
- deleteFile: "Delete File",
- deleteFileConfirmTitle: "Delete File",
- deleteFileConfirmDescription:
- "This action cannot be undone. The file will be permanently deleted.",
- },
- fileSection: {
- filesUploaded: "{count} files uploaded",
- },
- },
- attachments: {
- title: "Attachments",
- description: "Add a file by clicking 'Add Attachment'.",
- upload: "Upload attachment",
- upload_description:
- "Upload an attachment or add a link to an external resource.",
- drop: "Drop the files here",
- drop_description:
- "Drop files here or click to choose files from your device.",
- drop_files_description: "Files can be up to ",
- empty: {
- title: "No attachments",
- description: "Add a file by clicking 'Add Attachment'.",
- },
- toasts: {
- error: "Something went wrong, please try again.",
- error_uploading_files: "Cannot upload more than 1 file at a time",
- error_uploading_files_multiple: "Cannot upload more than 10 files",
- error_no_files_selected: "No files selected",
- error_file_rejected: "File {file} was rejected",
- error_failed_to_upload_files: "Failed to upload files",
- error_failed_to_upload_files_multiple: "Failed to upload files",
- error_failed_to_upload_files_single: "Failed to upload file",
- success_uploading_files: "Files uploaded successfully",
- success_uploading_files_multiple: "Files uploaded successfully",
- success_uploading_files_single: "File uploaded successfully",
- success_uploading_files_target: "Files uploaded",
- uploading_files: "Uploading {target}...",
- remove_file: "Remove file",
- },
- },
- edit: "Edit",
- errors: {
- unexpected_error: "An unexpected error occurred",
- },
- description: "Description",
- last_updated: "Last Updated",
- },
- header: {
- discord: {
- button: "Join us on Discord",
- },
- feedback: {
- button: "Feedback",
- title: "Thank you for your feedback!",
- description: "We will be back with you as soon as possible",
- placeholder: "Ideas to improve this page or issues you are experiencing.",
- success: "Thank you for your feedback!",
- error: "Error sending feedback - try again?",
- send: "Send Feedback",
- },
- },
- not_found: {
- title: "404 - Page not found",
- description: "The page you are looking for does not exist.",
- return: "Return to dashboard",
- },
- theme: {
- options: {
- light: "Light",
- dark: "Dark",
- system: "System",
- },
- },
- sidebar: {
- overview: "Overview",
- policies: "Policies",
- risk: "Risk Management",
- vendors: "Vendors",
- integrations: "Integrations",
- settings: "Settings",
- evidence: "Evidence Tasks",
- people: "People",
- tests: "Cloud Tests",
- },
- sub_pages: {
- evidence: {
- title: "Evidence",
- list: "Evidence List",
- overview: "Evidence Overview",
- },
- risk: {
- overview: "Risk Management",
- register: "Risk Register",
- risk_overview: "Risk Overview",
- risk_comments: "Risk Comments",
- tasks: {
- task_overview: "Task Overview",
- },
- },
- policies: {
- all: "All Policies",
- editor: "Policy Editor",
- policy_details: "Policy Details",
- },
- people: {
- all: "People",
- employee_details: "Employee Details",
- },
- settings: {
- members: "Team Members",
- },
- frameworks: {
- overview: "Frameworks",
- },
- },
- auth: {
- title: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- description:
- "Create a free account or log in with an existing account to continue.",
- options: "More options",
- google: "Continue with Google",
- email: {
- description: "Enter your email address to continue.",
- placeholder: "Enter email address",
- button: "Continue with email",
- magic_link_sent: "Magic link sent",
- magic_link_description: "Check your inbox for a magic link.",
- magic_link_try_again: "Try again.",
- success: "Email sent - check your inbox!",
- error: "Error sending email - try again?",
- },
- terms:
- "By clicking continue, you acknowledge that you have read and agree to the Terms of Service and Privacy Policy.",
- },
- onboarding: {
- title: "Create an organization",
- setup: "Setup",
- description: "Tell us a bit about your organization.",
- fields: {
- fullName: {
- label: "Your Name",
- placeholder: "Your full name",
- },
- name: {
- label: "Organization Name",
- placeholder: "Your organization name",
- },
- subdomain: {
- label: "Subdomain",
- placeholder: "example",
- },
- website: {
- label: "Website",
- placeholder: "Your organization website",
- },
- },
- success: "Thanks, you're all set!",
- error: "Something went wrong, please try again.",
- check_availability: "Checking availability",
- available: "Available",
- unavailable: "Unavailable",
- },
- overview: {
- title: "Overview",
- framework_chart: {
- title: "Framework Progress",
- },
- requirement_chart: {
- title: "Compliance Status",
- },
- },
- policies: {
- dashboard: {
- title: "Dashboard",
- all: "All Policies",
- policy_status: "Policy by Status",
- policies_by_assignee: "Policies by Assignee",
- policies_by_framework: "Policies by Framework",
- sub_pages: {
- overview: "Overview",
- edit_policy: "Edit Policy",
- },
- },
- overview: {
- title: "Policy Overview",
- form: {
- update_policy: "Update Policy",
- update_policy_description: "Update the policy title or description.",
- update_policy_success: "Policy updated successfully",
- update_policy_error: "Failed to update policy",
- update_policy_title: "Policy Name",
- review_frequency: "Review Frequency",
- review_frequency_placeholder: "Select a review frequency",
- review_date: "Review Date",
- review_date_placeholder: "Select a review date",
- required_to_sign: "Required to be signed by employees",
- signature_required: "Require employees signature",
- signature_not_required: "Do not ask employees to sign",
- signature_requirement: "Signature Requirement",
- signature_requirement_placeholder: "Select signature requirement",
- },
- },
- table: {
- name: "Policy Name",
- statuses: {
- draft: "Draft",
- published: "Published",
- archived: "Archived",
- },
- filters: {
- owner: {
- label: "Assignee",
- placeholder: "Filter by assignee",
- },
- },
- },
- filters: {
- search: "Search policies...",
- all: "All Policies",
- },
- status: {
- draft: "Draft",
- published: "Published",
- needs_review: "Needs Review",
- archived: "Archived",
- },
- policies: "policies",
- title: "Policies",
- create_new: "Create New Policy",
- search_placeholder: "Search policies...",
- status_filter: "Filter by status",
- all_statuses: "All statuses",
- no_policies_title: "No policies yet",
- no_policies_description: "Get started by creating your first policy",
- create_first: "Create first policy",
- no_description: "No description provided",
- last_updated: "Last updated: {{date}}",
- save: "Save",
- saving: "Saving...",
- saved_success: "Policy saved successfully",
- saved_error: "Failed to save policy",
- },
- evidence_tasks: {
- evidence_tasks: "Evidence Tasks",
- overview: "Overview",
- },
- risk: {
- risks: "risks",
- overview: "Overview",
- create: "Create New Risk",
- vendor: {
- title: "Vendor Management",
- dashboard: {
- title: "Vendor Dashboard",
- overview: "Vendor Overview",
- vendor_status: "Vendor Status",
- vendor_category: "Vendor Categories",
- vendors_by_assignee: "Vendors by Assignee",
- inherent_risk_description:
- "Initial risk level before any controls are applied",
- residual_risk_description:
- "Remaining risk level after controls are applied",
- },
- register: {
- title: "Vendor Register",
- table: {
- name: "Name",
- category: "Category",
- status: "Status",
- owner: "Owner",
- },
- },
- assessment: {
- title: "Vendor Assessment",
- update_success: "Vendor risk assessment updated successfully",
- update_error: "Failed to update vendor risk assessment",
- inherent_risk: "Inherent Risk",
- residual_risk: "Residual Risk",
- },
- form: {
- vendor_details: "Vendor Details",
- vendor_name: "Name",
- vendor_name_placeholder: "Enter vendor name",
- vendor_website: "Website",
- vendor_website_placeholder: "Enter vendor website",
- vendor_description: "Description",
- vendor_description_placeholder: "Enter vendor description",
- vendor_category: "Category",
- vendor_category_placeholder: "Select category",
- vendor_status: "Status",
- vendor_status_placeholder: "Select status",
- create_vendor_success: "Vendor created successfully",
- create_vendor_error: "Failed to create vendor",
- update_vendor: "Update Vendor",
- update_vendor_success: "Vendor updated successfully",
- update_vendor_error: "Failed to update vendor",
- add_comment: "Add Comment",
- },
- table: {
- name: "Name",
- category: "Category",
- status: "Status",
- owner: "Owner",
- },
- filters: {
- search_placeholder: "Search vendors...",
- status_placeholder: "Filter by status",
- category_placeholder: "Filter by category",
- owner_placeholder: "Filter by owner",
- },
- empty_states: {
- no_vendors: {
- title: "No vendors yet",
- description: "Get started by creating your first vendor",
- },
- no_results: {
- title: "No results found",
- description: "No vendors match your search",
- description_with_filters: "Try adjusting your filters",
- },
- },
- actions: {
- create: "Create Vendor",
- },
- status: {
- not_assessed: "Not Assessed",
- in_progress: "In Progress",
- assessed: "Assessed",
- },
- category: {
- cloud: "Cloud",
- infrastructure: "Infrastructure",
- software_as_a_service: "Software as a Service",
- finance: "Finance",
- marketing: "Marketing",
- sales: "Sales",
- hr: "HR",
- other: "Other",
- },
- risk_level: {
- low: "Low Risk",
- medium: "Medium Risk",
- high: "High Risk",
- unknown: "Unknown Risk",
- },
- },
- dashboard: {
- title: "Dashboard",
- overview: "Risk Overview",
- risk_status: "Risk Status",
- risks_by_department: "Risks by Department",
- risks_by_assignee: "Risks by Assignee",
- inherent_risk_description:
- "Inherent risk is calculated as likelihood * impact. Calculated before any controls are applied.",
- residual_risk_description:
- "Residual risk is calculated as likelihood * impact. This is the risk level after controls are applied.",
- risk_assessment_description: "Compare inherent and residual risk levels",
- },
- register: {
- title: "Risk Register",
- table: {
- risk: "Risk",
- },
- empty: {
- no_risks: {
- title: "Create a risk to get started",
- description:
- "Track and score risks, create and assign mitigation tasks for your team, and manage your risk register all in one simple interface.",
- },
- create_risk: "Create a risk",
- },
- },
- metrics: {
- probability: "Probability",
- impact: "Impact",
- inherentRisk: "Inherent Risk",
- residualRisk: "Residual Risk",
- },
- form: {
- update_inherent_risk: "Save Inherent Risk",
- update_inherent_risk_description:
- "Update the inherent risk of the risk. This is the risk level before any controls are applied.",
- update_inherent_risk_success: "Inherent risk updated successfully",
- update_inherent_risk_error: "Failed to update inherent risk",
- update_residual_risk: "Save Residual Risk",
- update_residual_risk_description:
- "Update the residual risk of the risk. This is the risk level after controls are applied.",
- update_residual_risk_success: "Residual risk updated successfully",
- update_residual_risk_error: "Failed to update residual risk",
- update_risk: "Update Risk",
- update_risk_description: "Update the risk title or description.",
- update_risk_success: "Risk updated successfully",
- update_risk_error: "Failed to update risk",
- create_risk_success: "Risk created successfully",
- create_risk_error: "Failed to create risk",
- risk_details: "Risk Details",
- risk_title: "Risk Title",
- risk_title_description: "Enter a name for the risk",
- risk_description: "Description",
- risk_description_description: "Enter a description for the risk",
- risk_category: "Category",
- risk_category_placeholder: "Select a category",
- risk_department: "Department",
- risk_department_placeholder: "Select a department",
- risk_status: "Risk Status",
- risk_status_placeholder: "Select a risk status",
- },
- tasks: {
- title: "Tasks",
- attachments: "Attachments",
- overview: "Task Overview",
- form: {
- title: "Task Details",
- task_title: "Task Title",
- status: "Task Status",
- status_placeholder: "Select a task status",
- task_title_description: "Enter a name for the task",
- description: "Description",
- description_description: "Enter a description for the task",
- due_date: "Due Date",
- due_date_description: "Select the due date for the task",
- success: "Task created successfully",
- error: "Failed to create task",
- },
- sheet: {
- title: "Create Task",
- update: "Update Task",
- update_description: "Update the task title or description.",
- },
- empty: {
- description_create:
- "Create a mitigation task for this risk, add a treatment plan, and assign it to a team member.",
- },
- },
- },
- people: {
- title: "People",
- details: {
- taskProgress: "Task Progress",
- tasks: "Tasks",
- noTasks: "No tasks assigned yet",
- },
- description: "Manage your team members and their roles.",
- filters: {
- search: "Search people...",
- role: "Filter by role",
- },
- actions: {
- invite: "Add Employee",
- clear: "Clear filters",
- },
- table: {
- name: "Name",
- email: "Email",
- department: "Department",
- externalId: "External ID",
- },
- empty: {
- no_employees: {
- title: "No employees yet",
- description: "Get started by inviting your first team member.",
- },
- no_results: {
- title: "No results found",
- description: "No employees match your search",
- description_with_filters: "Try adjusting your filters",
- },
- },
- invite: {
- title: "Add Employee",
- description: "Add an employee to your organization.",
- email: {
- label: "Email address",
- placeholder: "Enter email address",
- },
- role: {
- label: "Role",
- placeholder: "Select a role",
- },
- name: {
- label: "Name",
- placeholder: "Enter name",
- },
- department: {
- label: "Department",
- placeholder: "Select a department",
- },
- submit: "Add Employee",
- success: "Employee added successfully",
- error: "Failed to add employee",
- },
- },
- settings: {
- general: {
- title: "General",
- org_name: "Organization name",
- org_name_description:
- "This is your organizations visible name. You should use the legal name of your organization.",
- org_name_tip: "Please use 32 characters at maximum.",
- org_website: "Organization Website",
- org_website_description:
- "This is your organization's official website URL. Make sure to include the full URL with https://.",
- org_website_tip: "Please enter a valid URL including https://",
- org_website_error: "Error updating organization website",
- org_website_updated: "Organization website updated",
- org_delete: "Delete organization",
- org_delete_description:
- "Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.",
- org_delete_alert_title: "Are you absolutely sure?",
- org_delete_alert_description:
- "This action cannot be undone. This will permanently delete your organization and remove your data from our servers.",
- org_delete_error: "Error deleting organization",
- org_delete_success: "Organization deleted",
- org_name_updated: "Organization name updated",
- org_name_error: "Error updating organization name",
- save_button: "Save",
- delete_button: "Delete",
- delete_confirm: "DELETE",
- delete_confirm_tip: "Type DELETE to confirm.",
- cancel_button: "Cancel",
- },
- members: {
- title: "Members",
- },
- api_keys: {
- title: "API Keys",
- description:
- "Manage API keys for programmatic access to your organization's data.",
- list_title: "API Keys",
- list_description:
- "API keys allow secure access to your organization's data via our API.",
- create: "New API Key",
- create_title: "New API Key",
- create_description:
- "Create a new API key for programmatic access to your organization's data.",
- created_title: "API Key Created",
- created_description:
- "Your API key has been created. Make sure to copy it now as you won't be able to see it again.",
- name: "Name",
- name_label: "Name",
- name_placeholder: "Enter a name for this API key",
- expiration: "Expiration",
- expiration_placeholder: "Select expiration",
- expires_label: "Expires",
- expires_placeholder: "Select expiration",
- expires_30days: "30 days",
- expires_90days: "90 days",
- expires_1year: "1 year",
- expires_never: "Never",
- thirty_days: "30 days",
- ninety_days: "90 days",
- one_year: "1 year",
- your_key: "Your API Key",
- api_key: "API Key",
- save_warning:
- "This key will only be shown once. Make sure to copy it now.",
- copied: "API key copied to clipboard",
- close_confirm:
- "Are you sure you want to close? You won't be able to see this API key again.",
- revoke_confirm:
- "Are you sure you want to revoke this API key? This action cannot be undone.",
- revoke_title: "Revoke API Key",
- revoke: "Revoke",
- created: "Created",
- expires: "Expires",
- last_used: "Last Used",
- actions: "Actions",
- never: "Never",
- never_used: "Never used",
- no_keys: "No API keys found. Create one to get started.",
- security_note:
- "API keys provide full access to your organization's data. Keep them secure and rotate them regularly.",
- fetch_error: "Failed to fetch API keys",
- create_error: "Failed to create API key",
- revoked_success: "API key revoked successfully",
- revoked_error: "Failed to revoke API key",
- done: "Done",
- },
- billing: {
- title: "Billing",
- },
- },
- tests: {
- name: "Cloud Tests",
- title: "Cloud Tests",
- actions: {
- create: "Add Cloud Test",
- clear: "Clear filters",
- refresh: "Refresh",
- },
- empty: {
- no_tests: {
- title: "No cloud tests yet",
- description: "Get started by creating your first cloud test.",
- },
- no_results: {
- title: "No results found",
- description: "No tests match your search",
- description_with_filters: "Try adjusting your filters",
- },
- },
- filters: {
- search: "Search tests...",
- role: "Filter by vendor",
- },
- register: {
- title: "Add Cloud Test",
- description: "Configure a new cloud compliance test.",
- submit: "Create Test",
- success: "Test created successfully",
- invalid_json: "Invalid JSON configuration provided",
+ language: {
+ title: "Languages",
+ description: "Change the language used in the user interface.",
+ placeholder: "Select language",
+ },
+ languages: {
+ en: "English",
+ es: "Spanish",
+ fr: "French",
+ no: "Norwegian",
+ pt: "Portuguese",
+ },
+ common: {
+ frequency: {
+ daily: "Daily",
+ weekly: "Weekly",
+ monthly: "Monthly",
+ quarterly: "Quarterly",
+ yearly: "Yearly",
+ },
+ notifications: {
+ inbox: "Inbox",
+ archive: "Archive",
+ archive_all: "Archive all",
+ no_notifications: "No new notifications",
+ },
+ actions: {
+ save: "Save",
+ edit: "Edit",
+ delete: "Delete",
+ cancel: "Cancel",
+ clear: "Clear",
+ create: "Create",
+ addNew: "Add New",
+ send: "Send",
+ return: "Return",
+ success: "Success",
+ error: "Error",
+ next: "Next",
+ complete: "Complete",
+ },
+ assignee: {
+ label: "Assignee",
+ placeholder: "Select assignee",
+ },
+ date: {
+ pick: "Pick a date",
+ due_date: "Due Date",
+ },
+ status: {
+ open: "Open",
+ pending: "Pending",
+ closed: "Closed",
+ archived: "Archived",
+ compliant: "Compliant",
+ non_compliant: "Non Compliant",
+ not_started: "Not Started",
+ in_progress: "In Progress",
+ published: "Published",
+ needs_review: "Needs Review",
+ draft: "Draft",
+ not_assessed: "Not Assessed",
+ assessed: "Assessed",
+ active: "Active",
+ inactive: "Inactive",
+ title: "Status",
+ },
+ filters: {
+ clear: "Clear filters",
+ search: "Search...",
+ status: "Status",
+ department: "Department",
+ owner: {
+ label: "Assignee",
+ placeholder: "Filter by assignee",
+ },
+ },
+ table: {
+ title: "Title",
+ status: "Status",
+ assigned_to: "Assigned To",
+ due_date: "Due Date",
+ last_updated: "Last Updated",
+ no_results: "No results found",
+ },
+ empty_states: {
+ no_results: {
+ title: "No results found",
+ title_tasks: "No tasks found",
+ title_risks: "No risks found",
+ description: "Try another search, or adjusting the filters",
+ description_filters: "Try another search, or adjusting the filters",
+ description_no_tasks: "Create a task to get started",
+ description_no_risks: "Create a risk to get started",
+ },
+ no_items: {
+ title: "No items found",
+ description: "Try adjusting your search or filters",
+ },
+ },
+ pagination: {
+ of: "of",
+ items_per_page: "Items per page",
+ rows_per_page: "Rows per page",
+ page_x_of_y: "Page {{current}} of {{total}}",
+ go_to_first_page: "Go to first page",
+ go_to_previous_page: "Go to previous page",
+ go_to_next_page: "Go to next page",
+ go_to_last_page: "Go to last page",
+ },
+ comments: {
+ title: "Comments",
+ description: "Add a comment using the form below.",
+ add: "New Comment",
+ new: "New Comment",
+ save: "Save Comment",
+ success: "Comment added successfully",
+ error: "Failed to add comment",
+ placeholder: "Write your comment here...",
+ empty: {
+ title: "No comments yet",
+ description: "Be the first to add a comment",
+ },
+ },
+ upload: {
+ fileUpload: {
+ uploadingText: "Uploading...",
+ uploadingFile: "Uploading file...",
+ dropFileHere: "Drop file here",
+ dropFileHereAlt: "Drop file here",
+ releaseToUpload: "Release to upload",
+ addFiles: "Add Files",
+ uploadAdditionalEvidence: "Upload a file or document",
+ dragDropOrClick: "Drag and drop or click to browse",
+ dragDropOrClickToSelect: "Drag and drop or click to select file",
+ maxFileSize: "Max file size: {size}MB",
+ },
+ fileUrl: {
+ additionalLinks: "Additional Links",
+ add: "Add",
+ linksAdded: "{count} link{s} added",
+ enterUrl: "Enter URL",
+ addAnotherLink: "Add Another Link",
+ saveLinks: "Save Links",
+ urlBadge: "URL",
+ copyLink: "Copy Link",
+ openLink: "Open Link",
+ deleteLink: "Delete Link",
+ },
+ fileCard: {
+ preview: "Preview",
+ filePreview: "File Preview: {fileName}",
+ previewNotAvailable: "Preview not available for this file type",
+ openFile: "Open File",
+ deleteFile: "Delete File",
+ deleteFileConfirmTitle: "Delete File",
+ deleteFileConfirmDescription:
+ "This action cannot be undone. The file will be permanently deleted.",
+ },
+ fileSection: {
+ filesUploaded: "{count} files uploaded",
+ },
+ },
+ attachments: {
+ title: "Attachments",
+ description: "Add a file by clicking 'Add Attachment'.",
+ upload: "Upload attachment",
+ upload_description:
+ "Upload an attachment or add a link to an external resource.",
+ drop: "Drop the files here",
+ drop_description:
+ "Drop files here or click to choose files from your device.",
+ drop_files_description: "Files can be up to ",
+ empty: {
+ title: "No attachments",
+ description: "Add a file by clicking 'Add Attachment'.",
+ },
+ toasts: {
+ error: "Something went wrong, please try again.",
+ error_uploading_files: "Cannot upload more than 1 file at a time",
+ error_uploading_files_multiple: "Cannot upload more than 10 files",
+ error_no_files_selected: "No files selected",
+ error_file_rejected: "File {file} was rejected",
+ error_failed_to_upload_files: "Failed to upload files",
+ error_failed_to_upload_files_multiple: "Failed to upload files",
+ error_failed_to_upload_files_single: "Failed to upload file",
+ success_uploading_files: "Files uploaded successfully",
+ success_uploading_files_multiple: "Files uploaded successfully",
+ success_uploading_files_single: "File uploaded successfully",
+ success_uploading_files_target: "Files uploaded",
+ uploading_files: "Uploading {target}...",
+ remove_file: "Remove file",
+ },
+ },
+ edit: "Edit",
+ errors: {
+ unexpected_error: "An unexpected error occurred",
+ },
+ description: "Description",
+ last_updated: "Last Updated",
+ },
+ header: {
+ discord: {
+ button: "Join us on Discord",
+ },
+ feedback: {
+ button: "Feedback",
+ title: "Thank you for your feedback!",
+ description: "We will be back with you as soon as possible",
+ placeholder: "Ideas to improve this page or issues you are experiencing.",
+ success: "Thank you for your feedback!",
+ error: "Error sending feedback - try again?",
+ send: "Send Feedback",
+ },
+ },
+ not_found: {
+ title: "404 - Page not found",
+ description: "The page you are looking for does not exist.",
+ return: "Return to dashboard",
+ },
+ theme: {
+ options: {
+ light: "Light",
+ dark: "Dark",
+ system: "System",
+ },
+ },
+ sidebar: {
+ overview: "Overview",
+ policies: "Policies",
+ risk: "Risk Management",
+ vendors: "Vendors",
+ integrations: "Integrations",
+ settings: "Settings",
+ evidence: "Evidence Tasks",
+ people: "People",
+ tests: "Cloud Tests",
+ },
+ sub_pages: {
+ evidence: {
+ title: "Evidence",
+ list: "Evidence List",
+ overview: "Evidence Overview",
+ },
+ risk: {
+ overview: "Risk Management",
+ register: "Risk Register",
+ risk_overview: "Risk Overview",
+ risk_comments: "Risk Comments",
+ tasks: {
+ task_overview: "Task Overview",
+ },
+ },
+ policies: {
+ all: "All Policies",
+ editor: "Policy Editor",
+ policy_details: "Policy Details",
+ },
+ people: {
+ all: "People",
+ employee_details: "Employee Details",
+ },
+ settings: {
+ members: "Team Members",
+ },
+ frameworks: {
+ overview: "Frameworks",
+ },
+ },
+ auth: {
+ title: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ description:
+ "Create a free account or log in with an existing account to continue.",
+ options: "More options",
+ google: "Continue with Google",
+ email: {
+ description: "Enter your email address to continue.",
+ placeholder: "Enter email address",
+ button: "Continue with email",
+ magic_link_sent: "Magic link sent",
+ magic_link_description: "Check your inbox for a magic link.",
+ magic_link_try_again: "Try again.",
+ success: "Email sent - check your inbox!",
+ error: "Error sending email - try again?",
+ },
+ terms:
+ "By clicking continue, you acknowledge that you have read and agree to the Terms of Service and Privacy Policy.",
+ },
+ onboarding: {
+ title: "Create an organization",
+ setup: "Setup",
+ description: "Tell us a bit about your organization.",
+ fields: {
+ fullName: {
+ label: "Your Name",
+ placeholder: "Your full name",
+ },
+ name: {
+ label: "Organization Name",
+ placeholder: "Your organization name",
+ },
+ subdomain: {
+ label: "Subdomain",
+ placeholder: "example",
+ },
+ website: {
+ label: "Website",
+ placeholder: "Your organization website",
+ },
+ },
+ success: "Thanks, you're all set!",
+ error: "Something went wrong, please try again.",
+ check_availability: "Checking availability",
+ available: "Available",
+ unavailable: "Unavailable",
+ },
+ overview: {
+ title: "Overview",
+ framework_chart: {
+ title: "Framework Progress",
+ },
+ requirement_chart: {
+ title: "Compliance Status",
+ },
+ },
+ policies: {
+ dashboard: {
+ title: "Dashboard",
+ all: "All Policies",
+ policy_status: "Policy by Status",
+ policies_by_assignee: "Policies by Assignee",
+ policies_by_framework: "Policies by Framework",
+ sub_pages: {
+ overview: "Overview",
+ edit_policy: "Edit Policy",
+ },
+ },
+ overview: {
+ title: "Policy Overview",
+ form: {
+ update_policy: "Update Policy",
+ update_policy_description: "Update the policy title or description.",
+ update_policy_success: "Policy updated successfully",
+ update_policy_error: "Failed to update policy",
+ update_policy_title: "Policy Name",
+ review_frequency: "Review Frequency",
+ review_frequency_placeholder: "Select a review frequency",
+ review_date: "Review Date",
+ review_date_placeholder: "Select a review date",
+ required_to_sign: "Required to be signed by employees",
+ signature_required: "Require employees signature",
+ signature_not_required: "Do not ask employees to sign",
+ signature_requirement: "Signature Requirement",
+ signature_requirement_placeholder: "Select signature requirement",
+ },
+ },
+ table: {
+ name: "Policy Name",
+ statuses: {
+ draft: "Draft",
+ published: "Published",
+ archived: "Archived",
+ },
+ filters: {
+ owner: {
+ label: "Assignee",
+ placeholder: "Filter by assignee",
+ },
+ },
+ },
+ filters: {
+ search: "Search policies...",
+ all: "All Policies",
+ },
+ status: {
+ draft: "Draft",
+ published: "Published",
+ needs_review: "Needs Review",
+ archived: "Archived",
+ },
+ policies: "policies",
+ title: "Policies",
+ create_new: "Create New Policy",
+ search_placeholder: "Search policies...",
+ status_filter: "Filter by status",
+ all_statuses: "All statuses",
+ no_policies_title: "No policies yet",
+ no_policies_description: "Get started by creating your first policy",
+ create_first: "Create first policy",
+ no_description: "No description provided",
+ last_updated: "Last updated: {{date}}",
+ save: "Save",
+ saving: "Saving...",
+ saved_success: "Policy saved successfully",
+ saved_error: "Failed to save policy",
+ },
+ evidence_tasks: {
+ evidence_tasks: "Evidence Tasks",
+ overview: "Overview",
+ },
+ risk: {
+ risks: "risks",
+ overview: "Overview",
+ create: "Create New Risk",
+ vendor: {
+ title: "Vendor Management",
+ dashboard: {
+ title: "Vendor Dashboard",
+ overview: "Vendor Overview",
+ vendor_status: "Vendor Status",
+ vendor_category: "Vendor Categories",
+ vendors_by_assignee: "Vendors by Assignee",
+ inherent_risk_description:
+ "Initial risk level before any controls are applied",
+ residual_risk_description:
+ "Remaining risk level after controls are applied",
+ },
+ register: {
+ title: "Vendor Register",
+ table: {
+ name: "Name",
+ category: "Category",
+ status: "Status",
+ owner: "Owner",
+ },
+ },
+ assessment: {
+ title: "Vendor Assessment",
+ update_success: "Vendor risk assessment updated successfully",
+ update_error: "Failed to update vendor risk assessment",
+ inherent_risk: "Inherent Risk",
+ residual_risk: "Residual Risk",
+ },
+ form: {
+ vendor_details: "Vendor Details",
+ vendor_name: "Name",
+ vendor_name_placeholder: "Enter vendor name",
+ vendor_website: "Website",
+ vendor_website_placeholder: "Enter vendor website",
+ vendor_description: "Description",
+ vendor_description_placeholder: "Enter vendor description",
+ vendor_category: "Category",
+ vendor_category_placeholder: "Select category",
+ vendor_status: "Status",
+ vendor_status_placeholder: "Select status",
+ create_vendor_success: "Vendor created successfully",
+ create_vendor_error: "Failed to create vendor",
+ update_vendor: "Update Vendor",
+ update_vendor_success: "Vendor updated successfully",
+ update_vendor_error: "Failed to update vendor",
+ add_comment: "Add Comment",
+ },
+ table: {
+ name: "Name",
+ category: "Category",
+ status: "Status",
+ owner: "Owner",
+ },
+ filters: {
+ search_placeholder: "Search vendors...",
+ status_placeholder: "Filter by status",
+ category_placeholder: "Filter by category",
+ owner_placeholder: "Filter by owner",
+ },
+ empty_states: {
+ no_vendors: {
+ title: "No vendors yet",
+ description: "Get started by creating your first vendor",
+ },
+ no_results: {
+ title: "No results found",
+ description: "No vendors match your search",
+ description_with_filters: "Try adjusting your filters",
+ },
+ },
+ actions: {
+ create: "Create Vendor",
+ },
+ status: {
+ not_assessed: "Not Assessed",
+ in_progress: "In Progress",
+ assessed: "Assessed",
+ },
+ category: {
+ cloud: "Cloud",
+ infrastructure: "Infrastructure",
+ software_as_a_service: "Software as a Service",
+ finance: "Finance",
+ marketing: "Marketing",
+ sales: "Sales",
+ hr: "HR",
+ other: "Other",
+ },
+ risk_level: {
+ low: "Low Risk",
+ medium: "Medium Risk",
+ high: "High Risk",
+ unknown: "Unknown Risk",
+ },
+ },
+ dashboard: {
+ title: "Dashboard",
+ overview: "Risk Overview",
+ risk_status: "Risk Status",
+ risks_by_department: "Risks by Department",
+ risks_by_assignee: "Risks by Assignee",
+ inherent_risk_description:
+ "Inherent risk is calculated as likelihood * impact. Calculated before any controls are applied.",
+ residual_risk_description:
+ "Residual risk is calculated as likelihood * impact. This is the risk level after controls are applied.",
+ risk_assessment_description: "Compare inherent and residual risk levels",
+ },
+ register: {
+ title: "Risk Register",
+ table: {
+ risk: "Risk",
+ },
+ empty: {
+ no_risks: {
+ title: "Create a risk to get started",
+ description:
+ "Track and score risks, create and assign mitigation tasks for your team, and manage your risk register all in one simple interface.",
+ },
+ create_risk: "Create a risk",
+ },
+ },
+ metrics: {
+ probability: "Probability",
+ impact: "Impact",
+ inherentRisk: "Inherent Risk",
+ residualRisk: "Residual Risk",
+ },
+ form: {
+ update_inherent_risk: "Save Inherent Risk",
+ update_inherent_risk_description:
+ "Update the inherent risk of the risk. This is the risk level before any controls are applied.",
+ update_inherent_risk_success: "Inherent risk updated successfully",
+ update_inherent_risk_error: "Failed to update inherent risk",
+ update_residual_risk: "Save Residual Risk",
+ update_residual_risk_description:
+ "Update the residual risk of the risk. This is the risk level after controls are applied.",
+ update_residual_risk_success: "Residual risk updated successfully",
+ update_residual_risk_error: "Failed to update residual risk",
+ update_risk: "Update Risk",
+ update_risk_description: "Update the risk title or description.",
+ update_risk_success: "Risk updated successfully",
+ update_risk_error: "Failed to update risk",
+ create_risk_success: "Risk created successfully",
+ create_risk_error: "Failed to create risk",
+ risk_details: "Risk Details",
+ risk_title: "Risk Title",
+ risk_title_description: "Enter a name for the risk",
+ risk_description: "Description",
+ risk_description_description: "Enter a description for the risk",
+ risk_category: "Category",
+ risk_category_placeholder: "Select a category",
+ risk_department: "Department",
+ risk_department_placeholder: "Select a department",
+ risk_status: "Risk Status",
+ risk_status_placeholder: "Select a risk status",
+ },
+ tasks: {
+ title: "Tasks",
+ attachments: "Attachments",
+ overview: "Task Overview",
+ form: {
+ title: "Task Details",
+ task_title: "Task Title",
+ status: "Task Status",
+ status_placeholder: "Select a task status",
+ task_title_description: "Enter a name for the task",
+ description: "Description",
+ description_description: "Enter a description for the task",
+ due_date: "Due Date",
+ due_date_description: "Select the due date for the task",
+ success: "Task created successfully",
+ error: "Failed to create task",
+ },
+ sheet: {
+ title: "Create Task",
+ update: "Update Task",
+ update_description: "Update the task title or description.",
+ },
+ empty: {
+ description_create:
+ "Create a mitigation task for this risk, add a treatment plan, and assign it to a team member.",
+ },
+ },
+ },
+ people: {
+ title: "People",
+ details: {
+ taskProgress: "Task Progress",
+ tasks: "Tasks",
+ noTasks: "No tasks assigned yet",
+ },
+ description: "Manage your team members and their roles.",
+ filters: {
+ search: "Search people...",
+ role: "Filter by role",
+ },
+ actions: {
+ invite: "Add Employee",
+ clear: "Clear filters",
+ },
+ table: {
+ name: "Employee Name",
+ email: "Email",
+ department: "Department",
+ status: "Status",
+ externalId: "External ID",
+ },
+ empty: {
+ no_employees: {
+ title: "No employees yet",
+ description: "Get started by inviting your first team member.",
+ },
+ no_results: {
+ title: "No results found",
+ description: "No employees match your search",
+ description_with_filters: "Try adjusting your filters",
+ },
+ },
+ invite: {
+ title: "Add Employee",
+ description: "Add an employee to your organization.",
+ email: {
+ label: "Email address",
+ placeholder: "Enter email address",
+ },
+ role: {
+ label: "Role",
+ placeholder: "Select a role",
+ },
+ name: {
+ label: "Name",
+ placeholder: "Enter name",
+ },
+ department: {
+ label: "Department",
+ placeholder: "Select a department",
+ },
+ submit: "Add Employee",
+ success: "Employee added successfully",
+ error: "Failed to add employee",
+ },
+ },
+ settings: {
+ general: {
+ title: "General",
+ org_name: "Organization name",
+ org_name_description:
+ "This is your organizations visible name. You should use the legal name of your organization.",
+ org_name_tip: "Please use 32 characters at maximum.",
+ org_website: "Organization Website",
+ org_website_description:
+ "This is your organization's official website URL. Make sure to include the full URL with https://.",
+ org_website_tip: "Please enter a valid URL including https://",
+ org_website_error: "Error updating organization website",
+ org_website_updated: "Organization website updated",
+ org_delete: "Delete organization",
+ org_delete_description:
+ "Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.",
+ org_delete_alert_title: "Are you absolutely sure?",
+ org_delete_alert_description:
+ "This action cannot be undone. This will permanently delete your organization and remove your data from our servers.",
+ org_delete_error: "Error deleting organization",
+ org_delete_success: "Organization deleted",
+ org_name_updated: "Organization name updated",
+ org_name_error: "Error updating organization name",
+ save_button: "Save",
+ delete_button: "Delete",
+ delete_confirm: "DELETE",
+ delete_confirm_tip: "Type DELETE to confirm.",
+ cancel_button: "Cancel",
+ },
+ members: {
+ title: "Members",
+ },
+ api_keys: {
+ title: "API Keys",
+ description:
+ "Manage API keys for programmatic access to your organization's data.",
+ list_title: "API Keys",
+ list_description:
+ "API keys allow secure access to your organization's data via our API.",
+ create: "New API Key",
+ create_title: "New API Key",
+ create_description:
+ "Create a new API key for programmatic access to your organization's data.",
+ created_title: "API Key Created",
+ created_description:
+ "Your API key has been created. Make sure to copy it now as you won't be able to see it again.",
+ name: "Name",
+ name_label: "Name",
+ name_placeholder: "Enter a name for this API key",
+ expiration: "Expiration",
+ expiration_placeholder: "Select expiration",
+ expires_label: "Expires",
+ expires_placeholder: "Select expiration",
+ expires_30days: "30 days",
+ expires_90days: "90 days",
+ expires_1year: "1 year",
+ expires_never: "Never",
+ thirty_days: "30 days",
+ ninety_days: "90 days",
+ one_year: "1 year",
+ your_key: "Your API Key",
+ api_key: "API Key",
+ save_warning:
+ "This key will only be shown once. Make sure to copy it now.",
+ copied: "API key copied to clipboard",
+ close_confirm:
+ "Are you sure you want to close? You won't be able to see this API key again.",
+ revoke_confirm:
+ "Are you sure you want to revoke this API key? This action cannot be undone.",
+ revoke_title: "Revoke API Key",
+ revoke: "Revoke",
+ created: "Created",
+ expires: "Expires",
+ last_used: "Last Used",
+ actions: "Actions",
+ never: "Never",
+ never_used: "Never used",
+ no_keys: "No API keys found. Create one to get started.",
+ security_note:
+ "API keys provide full access to your organization's data. Keep them secure and rotate them regularly.",
+ fetch_error: "Failed to fetch API keys",
+ create_error: "Failed to create API key",
+ revoked_success: "API key revoked successfully",
+ revoked_error: "Failed to revoke API key",
+ done: "Done",
+ },
+ billing: {
+ title: "Billing",
+ },
+ },
+ tests: {
+ name: "Cloud Tests",
+ title: "Cloud Tests",
+ actions: {
+ create: "Add Cloud Test",
+ clear: "Clear filters",
+ refresh: "Refresh",
+ },
+ empty: {
+ no_tests: {
+ title: "No cloud tests yet",
+ description: "Get started by creating your first cloud test.",
+ },
+ no_results: {
+ title: "No results found",
+ description: "No tests match your search",
+ description_with_filters: "Try adjusting your filters",
+ },
+ },
+ filters: {
+ search: "Search tests...",
+ role: "Filter by vendor",
+ },
+ register: {
+ title: "Add Cloud Test",
+ description: "Configure a new cloud compliance test.",
+ submit: "Create Test",
+ success: "Test created successfully",
+ invalid_json: "Invalid JSON configuration provided",
- title_field: {
- label: "Test Title",
- placeholder: "Enter test title",
- },
- description_field: {
- label: "Description",
- placeholder: "Enter test description",
- },
- provider: {
- label: "Cloud Provider",
- placeholder: "Select cloud provider",
- },
- config: {
- label: "Test Configuration",
- placeholder: "Enter JSON configuration for the test",
- },
- auth_config: {
- label: "Authentication Configuration",
- placeholder: "Enter JSON authentication configuration",
- },
- },
- table: {
- title: "Title",
- provider: "Provider",
- status: "Status",
- severity: "Severity",
- result: "Result",
- createdAt: "Created At",
- assignedUser: "Assigned User",
- assignedUserEmpty: "Not Assigned",
- no_results: "No results found",
- },
- },
- user_menu: {
- theme: "Theme",
- language: "Language",
- sign_out: "Sign out",
- account: "Account",
- support: "Support",
- settings: "Settings",
- teams: "Teams",
- },
- frameworks: {
- title: "Frameworks",
- overview: {
- error: "Failed to load frameworks",
- loading: "Loading frameworks...",
- empty: {
- title: "No frameworks selected",
- description:
- "Select frameworks to get started with your compliance journey",
- },
- progress: {
- title: "Framework Progress",
- empty: {
- title: "No frameworks yet",
- description:
- "Get started by adding a compliance framework to track your progress",
- action: "Add Framework",
- },
- },
- grid: {
- welcome: {
- title: "Welcome to Comp AI",
- description:
- "Get started by selecting the compliance frameworks you would like to implement. We'll help you manage and track your compliance journey across multiple standards.",
- action: "Get Started",
- },
- title: "Select Frameworks",
- version: "Version",
- actions: {
- clear: "Clear",
- confirm: "Confirm Selection",
- },
- },
- },
- controls: {
- title: "Controls",
- description: "Review and manage compliance controls",
- table: {
- status: "Status",
- control: "Control",
- artifacts: "Artifacts",
- actions: "Actions",
- },
- statuses: {
- completed: "Completed",
- in_progress: "In Progress",
- not_started: "Not Started",
- },
- },
- },
- errors: {
- unexpected: "Something went wrong, please try again",
- },
- editor: {
- ai: {
- thinking: "AI is thinking",
- thinking_spinner: "AI is thinking",
- edit_or_generate: "Edit or generate...",
- tell_ai_what_to_do_next: "Tell AI what to do next",
- request_limit_reached: "You have reached your request limit for the day.",
- },
- ai_selector: {
- improve: "Improve writing",
- fix: "Fix grammar",
- shorter: "Make shorter",
- longer: "Make longer",
- continue: "Continue writing",
- replace: "Replace selection",
- insert: "Insert below",
- discard: "Discard",
- },
- },
- evidence: {
- title: "Evidence",
- list: "All Evidence",
- overview: "Evidence Overview",
- edit: "Uploaded Evidence",
- dashboard: {
- layout: "Dashboard",
- layout_back_button: "Back",
- title: "Evidence Dashboard",
- by_department: "By Department",
- by_assignee: "By Assignee",
- by_framework: "By Framework",
- },
- items: "items",
- status: {
- up_to_date: "Up to Date",
- needs_review: "Needs Review",
- draft: "Draft",
- empty: "Empty",
- },
- departments: {
- none: "Uncategorized",
- admin: "Administration",
- gov: "Governance",
- hr: "Human Resources",
- it: "Information Technology",
- itsm: "IT Service Management",
- qms: "Quality Management",
- },
- details: {
- review_section: "Review Information",
- content: "Evidence Content",
- },
- },
- vendors: {
- title: "Vendors",
- register: {
- title: "Vendor Register",
- },
- dashboard: {
- title: "Vendor Overview",
- },
- },
- dashboard: {
- risk_status: "Risk Status",
- risks_by_department: "Risks by Department",
- vendor_status: "Vendor Status",
- vendors_by_category: "Vendors by Category",
- },
+ title_field: {
+ label: "Test Title",
+ placeholder: "Enter test title",
+ },
+ description_field: {
+ label: "Description",
+ placeholder: "Enter test description",
+ },
+ provider: {
+ label: "Cloud Provider",
+ placeholder: "Select cloud provider",
+ },
+ config: {
+ label: "Test Configuration",
+ placeholder: "Enter JSON configuration for the test",
+ },
+ auth_config: {
+ label: "Authentication Configuration",
+ placeholder: "Enter JSON authentication configuration",
+ },
+ },
+ table: {
+ title: "Title",
+ provider: "Provider",
+ status: "Status",
+ severity: "Severity",
+ result: "Result",
+ createdAt: "Created At",
+ assignedUser: "Assigned User",
+ assignedUserEmpty: "Not Assigned",
+ no_results: "No results found",
+ },
+ },
+ user_menu: {
+ theme: "Theme",
+ language: "Language",
+ sign_out: "Sign out",
+ account: "Account",
+ support: "Support",
+ settings: "Settings",
+ teams: "Teams",
+ },
+ frameworks: {
+ title: "Frameworks",
+ overview: {
+ error: "Failed to load frameworks",
+ loading: "Loading frameworks...",
+ empty: {
+ title: "No frameworks selected",
+ description:
+ "Select frameworks to get started with your compliance journey",
+ },
+ progress: {
+ title: "Framework Progress",
+ empty: {
+ title: "No frameworks yet",
+ description:
+ "Get started by adding a compliance framework to track your progress",
+ action: "Add Framework",
+ },
+ },
+ grid: {
+ welcome: {
+ title: "Welcome to Comp AI",
+ description:
+ "Get started by selecting the compliance frameworks you would like to implement. We'll help you manage and track your compliance journey across multiple standards.",
+ action: "Get Started",
+ },
+ title: "Select Frameworks",
+ version: "Version",
+ actions: {
+ clear: "Clear",
+ confirm: "Confirm Selection",
+ },
+ },
+ },
+ controls: {
+ title: "Controls",
+ description: "Review and manage compliance controls",
+ table: {
+ status: "Status",
+ control: "Control",
+ artifacts: "Artifacts",
+ actions: "Actions",
+ },
+ statuses: {
+ completed: "Completed",
+ in_progress: "In Progress",
+ not_started: "Not Started",
+ },
+ },
+ },
+ errors: {
+ unexpected: "Something went wrong, please try again",
+ },
+ editor: {
+ ai: {
+ thinking: "AI is thinking",
+ thinking_spinner: "AI is thinking",
+ edit_or_generate: "Edit or generate...",
+ tell_ai_what_to_do_next: "Tell AI what to do next",
+ request_limit_reached: "You have reached your request limit for the day.",
+ },
+ ai_selector: {
+ improve: "Improve writing",
+ fix: "Fix grammar",
+ shorter: "Make shorter",
+ longer: "Make longer",
+ continue: "Continue writing",
+ replace: "Replace selection",
+ insert: "Insert below",
+ discard: "Discard",
+ },
+ },
+ evidence: {
+ title: "Evidence",
+ list: "All Evidence",
+ overview: "Evidence Overview",
+ edit: "Uploaded Evidence",
+ dashboard: {
+ layout: "Dashboard",
+ layout_back_button: "Back",
+ title: "Evidence Dashboard",
+ by_department: "By Department",
+ by_assignee: "By Assignee",
+ by_framework: "By Framework",
+ },
+ items: "items",
+ status: {
+ up_to_date: "Up to Date",
+ needs_review: "Needs Review",
+ draft: "Draft",
+ empty: "Empty",
+ },
+ departments: {
+ none: "Uncategorized",
+ admin: "Administration",
+ gov: "Governance",
+ hr: "Human Resources",
+ it: "Information Technology",
+ itsm: "IT Service Management",
+ qms: "Quality Management",
+ },
+ details: {
+ review_section: "Review Information",
+ content: "Evidence Content",
+ },
+ },
+ vendors: {
+ title: "Vendors",
+ register: {
+ title: "Vendor Register",
+ },
+ dashboard: {
+ title: "Vendor Overview",
+ },
+ },
+ dashboard: {
+ risk_status: "Risk Status",
+ risks_by_department: "Risks by Department",
+ vendor_status: "Vendor Status",
+ vendors_by_category: "Vendors by Category",
+ },
} as const;
diff --git a/apps/app/src/locales/es.ts b/apps/app/src/locales/es.ts
index d78de610eb..287929af28 100644
--- a/apps/app/src/locales/es.ts
+++ b/apps/app/src/locales/es.ts
@@ -763,10 +763,11 @@ export default {
clear: "Limpiar filtros"
},
table: {
- name: "Nombre",
+ name: "Nombre del empleado",
email: "Correo Electrónico",
department: "Departamento",
- externalId: "ID Externo"
+ externalId: "ID Externo",
+ status: "Estado"
},
empty: {
no_employees: {
diff --git a/apps/app/src/locales/fr.ts b/apps/app/src/locales/fr.ts
index afdd85f673..2df71afe91 100644
--- a/apps/app/src/locales/fr.ts
+++ b/apps/app/src/locales/fr.ts
@@ -763,10 +763,11 @@ export default {
clear: "Effacer les filtres"
},
table: {
- name: "Nom",
+ name: "Nom de l'employé",
email: "Email",
department: "Département",
- externalId: "ID externe"
+ externalId: "ID externe",
+ status: "Statut"
},
empty: {
no_employees: {
diff --git a/apps/app/src/locales/no.ts b/apps/app/src/locales/no.ts
index 5a4f1bf4a0..1adc667e3d 100644
--- a/apps/app/src/locales/no.ts
+++ b/apps/app/src/locales/no.ts
@@ -763,10 +763,11 @@ export default {
clear: "Fjern filtre"
},
table: {
- name: "Navn",
+ name: "Ansattnavn",
email: "E-post",
department: "Avdeling",
- externalId: "Ekstern ID"
+ externalId: "Ekstern ID",
+ status: "Status"
},
empty: {
no_employees: {
diff --git a/apps/app/src/locales/pt.ts b/apps/app/src/locales/pt.ts
index 2786d9bf64..c973ba858f 100644
--- a/apps/app/src/locales/pt.ts
+++ b/apps/app/src/locales/pt.ts
@@ -763,10 +763,11 @@ export default {
clear: "Limpar filtros"
},
table: {
- name: "Nome",
+ name: "Nome do Funcionário",
email: "Email",
department: "Departamento",
- externalId: "ID Externo"
+ externalId: "ID Externo",
+ status: "Status"
},
empty: {
no_employees: {
diff --git a/apps/app/src/trigger/deel/index.ts b/apps/app/src/trigger/deel/index.ts
index 521c9b43f9..d58c6d0122 100644
--- a/apps/app/src/trigger/deel/index.ts
+++ b/apps/app/src/trigger/deel/index.ts
@@ -4,6 +4,10 @@ import axios from "axios";
import { z } from "zod";
import { Departments } from "@bubba/db";
import { decrypt } from "@/lib/encryption";
+import {
+ findEmployeeByEmail,
+ completeEmployeeCreation,
+} from "@/lib/db/employee";
const deelTaskSchema = z.object({
integration: z.object({
@@ -187,26 +191,26 @@ export const syncDeelEmployees = schemaTask({
}
// Check if we've run this integration within the last 24 hours
- // const lastRun = await db.integrationLastRun.findUnique({
- // where: {
- // integrationId_organizationId: {
- // integrationId: integration.id,
- // organizationId: integration.organization.id,
- // },
- // },
- // });
+ const lastRun = await db.integrationLastRun.findUnique({
+ where: {
+ integrationId_organizationId: {
+ integrationId: integration.id,
+ organizationId: integration.organization.id,
+ },
+ },
+ });
const now = new Date();
- // if (
- // lastRun &&
- // lastRun.lastRunAt > new Date(now.getTime() - 24 * 60 * 60 * 1000)
- // ) {
- // logger.info("Skipping Deel sync as it was run less than 24 hours ago");
- // return {
- // success: true,
- // message: "Skipped - run less than 24 hours ago",
- // };
- // }
+ if (
+ lastRun &&
+ lastRun.lastRunAt > new Date(now.getTime() - 24 * 60 * 60 * 1000)
+ ) {
+ logger.info("Skipping Deel sync as it was run less than 24 hours ago");
+ return {
+ success: true,
+ message: "Skipped - run less than 24 hours ago",
+ };
+ }
// Extract access token from user settings
let accessToken: string | undefined;
@@ -267,8 +271,12 @@ export const syncDeelEmployees = schemaTask({
(emp) =>
emp.hiring_status === "active" || emp.new_hiring_status === "active"
);
+ const inactiveEmployees = deelEmployees.filter(
+ (emp) =>
+ emp.hiring_status !== "active" && emp.new_hiring_status !== "active"
+ );
logger.info(
- `Found ${activeEmployees.length} active employees out of ${deelEmployees.length} total`
+ `Found ${activeEmployees.length} active employees and ${inactiveEmployees.length} inactive employees out of ${deelEmployees.length} total`
);
// Process each employee
@@ -276,19 +284,13 @@ export const syncDeelEmployees = schemaTask({
logger.info("Starting to process employees...");
for (const deelEmployee of deelEmployees) {
- // Skip inactive employees - check both hiring_status and new_hiring_status
- if (
- deelEmployee.hiring_status !== "active" &&
- deelEmployee.new_hiring_status !== "active"
- ) {
- logger.info(
- `Skipping inactive employee: ${deelEmployee.full_name} (ID: ${deelEmployee.id})`
- );
- continue;
- }
+ // Process all employees, both active and inactive
+ const isActive =
+ deelEmployee.hiring_status === "active" ||
+ deelEmployee.new_hiring_status === "active";
logger.info(
- `Processing employee: ${deelEmployee.full_name} (ID: ${deelEmployee.id})`
+ `Processing employee: ${deelEmployee.full_name} (ID: ${deelEmployee.id}, Status: ${isActive ? "active" : "inactive"})`
);
const name =
@@ -333,15 +335,11 @@ export const syncDeelEmployees = schemaTask({
logger.info(`Looking for existing employee with email: ${email}`);
- // Check if employee already exists
- const existingEmployee = await db.employee.findUnique({
- where: {
- email_organizationId: {
- email,
- organizationId: integration.organization.id,
- },
- },
- });
+ // Check if employee already exists using the reusable function
+ const existingEmployee = await findEmployeeByEmail(
+ email,
+ integration.organization.id
+ );
if (existingEmployee) {
logger.info(
@@ -356,33 +354,58 @@ export const syncDeelEmployees = schemaTask({
name,
department,
externalEmployeeId: deelEmployee.id,
- // Keep isActive true as we're filtering inactive employees already
+ isActive, // Set isActive based on Deel status
},
});
- logger.info(`Successfully updated employee: ${name}`);
+ logger.info(
+ `Successfully updated employee: ${name} with isActive: ${isActive}`
+ );
processedEmployees.push({
id: existingEmployee.id,
action: "updated",
+ isActive,
});
} else {
logger.info(
`No existing employee found for ${email}, creating new employee...`
);
- // Create new employee
- const newEmployee = await db.employee.create({
- data: {
+ try {
+ // Create new employee using the reusable function
+ const newEmployee = await completeEmployeeCreation({
name,
email,
department,
- isActive: true,
- externalEmployeeId: deelEmployee.id,
organizationId: integration.organization.id,
- },
- });
- logger.info(
- `Successfully created new employee: ${name} with ID: ${newEmployee.id}`
- );
- processedEmployees.push({ id: newEmployee.id, action: "created" });
+ externalEmployeeId: deelEmployee.id,
+ });
+
+ // If employee is inactive, update the isActive status after creation
+ if (!isActive) {
+ await db.employee.update({
+ where: {
+ id: newEmployee.id,
+ },
+ data: {
+ isActive: false,
+ },
+ });
+ logger.info("Updated new employee to inactive status");
+ }
+
+ logger.info(
+ `Successfully created new employee: ${name} with ID: ${newEmployee.id}, isActive: ${isActive}`
+ );
+ processedEmployees.push({
+ id: newEmployee.id,
+ action: "created",
+ isActive,
+ });
+ } catch (error) {
+ logger.error(
+ `Error creating employee: ${error instanceof Error ? error.message : String(error)}`
+ );
+ // Skip to the next employee if there's an error
+ }
}
}