From 9490aa4650f993f34ec8f225f0e5ae1daf312c96 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Thu, 3 Apr 2025 13:12:23 -0400 Subject: [PATCH 1/5] refactor: update package references in yarn.lock to use workspace scope for consistency across the codebase --- yarn.lock | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4779ee735e..7f987c6cc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1448,14 +1448,14 @@ picocolors "^1.0.0" sisteransi "^1.0.5" -"@comp/analytics@packages/analytics": +"@comp/analytics@^workspace:packages/analytics": version "workspace:packages/analytics" resolved "workspace:packages/analytics" dependencies: posthog-js "^1.215.3" posthog-node "^4.4.1" -"@comp/app@apps/app", "@comp/app@workspace:*": +"@comp/app@^workspace:apps/app", "@comp/app@workspace:*": version "workspace:apps/app" resolved "workspace:apps/app" devDependencies: @@ -1554,7 +1554,7 @@ yarn "^1.22.22" zustand "^5.0.3" -"@comp/auth@packages/auth", "@comp/auth@workspace:*": +"@comp/auth@^workspace:packages/auth", "@comp/auth@workspace:*": version "workspace:packages/auth" resolved "workspace:packages/auth" devDependencies: @@ -1567,7 +1567,7 @@ better-auth "^1.2.5" server-only "0.0.1" -"@comp/data@packages/data", "@comp/data@workspace:*": +"@comp/data@^workspace:packages/data", "@comp/data@workspace:*": version "workspace:packages/data" resolved "workspace:packages/data" devDependencies: @@ -1576,11 +1576,12 @@ tsup "^8.4.0" typescript "^5.8.2" -"@comp/db@packages/db", "@comp/db@workspace:*": +"@comp/db@^workspace:packages/db", "@comp/db@workspace:*": version "workspace:packages/db" resolved "workspace:packages/db" devDependencies: "@comp/tsconfig" "workspace:*" + devDependencies: typescript "^5.8.2" dependencies: "@auth/prisma-adapter" "^2.8.0" @@ -1590,7 +1591,7 @@ "@prisma/extension-accelerate" "^1.2.2" prisma "^6.5.0" -"@comp/email@packages/email", "@comp/email@workspace:*": +"@comp/email@^workspace:packages/email", "@comp/email@workspace:*": version "workspace:packages/email" resolved "workspace:packages/email" devDependencies: @@ -1601,6 +1602,7 @@ dependencies: "@comp/ui" "workspace:*" "@comp/utils" "workspace:*" + dependencies: "@react-email/components" "0.0.31" "@react-email/render" "0.0.10" "@react-email/tailwind" "1.0.4" @@ -1610,7 +1612,7 @@ react-email "3.0.4" responsive-react-email "^0.0.5" -"@comp/integrations@packages/integrations": +"@comp/integrations@^workspace:packages/integrations": version "workspace:packages/integrations" resolved "workspace:packages/integrations" devDependencies: @@ -1633,14 +1635,14 @@ sharp "^0.33.5" zod "^3.24.2" -"@comp/kv@packages/kv": +"@comp/kv@^workspace:packages/kv": version "workspace:packages/kv" resolved "workspace:packages/kv" dependencies: "@upstash/redis" "^1.34.2" server-only "0.0.1" -"@comp/notifications@packages/notifications", "@comp/notifications@workspace:*": +"@comp/notifications@^workspace:packages/notifications", "@comp/notifications@workspace:*": version "workspace:packages/notifications" resolved "workspace:packages/notifications" devDependencies: @@ -1649,12 +1651,13 @@ "@novu/node" "^2.0.1" nanoid "5.1.0" -"@comp/portal@apps/portal": +"@comp/portal@^workspace:apps/portal": version "workspace:apps/portal" resolved "workspace:apps/portal" devDependencies: "@comp/db" "workspace:*" "@comp/ui" "workspace:*" + devDependencies: "@types/node" "^22.13.2" "@types/react" "^19.0.8" "@types/react-dom" "^19.0.3" @@ -1669,11 +1672,11 @@ react-dom "^19.0.0" react-otp-input "^3.1.1" -"@comp/tsconfig@packages/tsconfig", "@comp/tsconfig@workspace:*": +"@comp/tsconfig@^workspace:packages/tsconfig", "@comp/tsconfig@workspace:*": version "workspace:packages/tsconfig" resolved "workspace:packages/tsconfig" -"@comp/ui@packages/ui", "@comp/ui@workspace:*": +"@comp/ui@^workspace:packages/ui", "@comp/ui@workspace:*": version "workspace:packages/ui" resolved "workspace:packages/ui" devDependencies: @@ -1737,7 +1740,7 @@ use-callback-ref "^1.3.3" vaul "^1.0.0" -"@comp/utils@packages/utils", "@comp/utils@workspace:*": +"@comp/utils@^workspace:packages/utils", "@comp/utils@workspace:*": version "workspace:packages/utils" resolved "workspace:packages/utils" devDependencies: From 6408f93786e5c10bd2764f38b0f19c75f7d9793f Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Thu, 3 Apr 2025 15:19:43 -0400 Subject: [PATCH 2/5] refactor: update schemas for organization and risk management, streamline validation messages, and remove unused fields in create organization form --- apps/app/src/actions/schema.ts | 410 +++++++++--------- .../forms/create-organization-form.tsx | 25 +- 2 files changed, 205 insertions(+), 230 deletions(-) diff --git a/apps/app/src/actions/schema.ts b/apps/app/src/actions/schema.ts index ed254a0fbf..d7df9ff709 100644 --- a/apps/app/src/actions/schema.ts +++ b/apps/app/src/actions/schema.ts @@ -1,300 +1,296 @@ import { - Departments, - Frequency, - PolicyStatus, - RiskCategory, - RiskStatus, - VendorCategory, - VendorStatus, - FrameworkId, - TaskStatus, - Impact, - Likelihood, + Departments, + FrameworkId, + Frequency, + Impact, + Likelihood, + PolicyStatus, + RiskCategory, + RiskStatus, + TaskStatus, } from "@comp/db/types"; import { z } from "zod"; export const organizationSchema = z.object({ - name: z.string().min(1, "Name is required"), - fullName: z.string().min(1, "Full name is required"), - website: z.string().url("Must be a valid URL").optional().or(z.literal("")), - frameworks: z - .array(z.nativeEnum(FrameworkId)) - .min(1, "Please select at least one framework to get started with"), + name: z.string().min(1, "Organization name is required"), + frameworks: z + .array(z.nativeEnum(FrameworkId)) + .min(1, "Please select at least one framework to get started with"), }); export type OrganizationSchema = z.infer; export const organizationNameSchema = z.object({ - name: z - .string() - .min(1, "Organization name is required") - .max(255, "Organization name cannot exceed 255 characters"), + name: z + .string() + .min(1, "Organization name is required") + .max(255, "Organization name cannot exceed 255 characters"), }); export const subdomainAvailabilitySchema = z.object({ - subdomain: z - .string() - .min(1, "Subdomain is required") - .max(255, "Subdomain cannot exceed 255 characters") - .regex(/^[a-z0-9-]+$/, { - message: - "Subdomain can only contain lowercase letters, numbers, and hyphens", - }), + subdomain: z + .string() + .min(1, "Subdomain is required") + .max(255, "Subdomain cannot exceed 255 characters") + .regex(/^[a-z0-9-]+$/, { + message: + "Subdomain can only contain lowercase letters, numbers, and hyphens", + }), }); export const uploadSchema = z.object({ - file: z.instanceof(File), - organizationId: z.string(), + file: z.instanceof(File), + organizationId: z.string(), }); export const deleteOrganizationSchema = z.object({ - id: z.string(), - organizationId: z.string(), + id: z.string(), + organizationId: z.string(), }); export const sendFeedbackSchema = z.object({ - feedback: z.string(), + feedback: z.string(), }); export const updaterMenuSchema = z.array( - z.object({ - path: z.string(), - name: z.string(), - }) + z.object({ + path: z.string(), + name: z.string(), + }), ); export const organizationWebsiteSchema = z.object({ - website: z.string().url().max(255), + website: z.string().url().max(255), }); // Risks export const createRiskSchema = z.object({ - title: z - .string({ - required_error: "Risk name is required", - }) - .min(1, { - message: "Risk name should be at least 1 character", - }) - .max(100, { - message: "Risk name should be at most 100 characters", - }), - description: z - .string({ - required_error: "Risk description is required", - }) - .min(1, { - message: "Risk description should be at least 1 character", - }) - .max(255, { - message: "Risk description should be at most 255 characters", - }), - category: z.nativeEnum(RiskCategory, { - required_error: "Risk category is required", - }), - department: z.nativeEnum(Departments, { - required_error: "Risk department is required", - }), - ownerId: z.string({ - required_error: "You must assign an owner to the risk", - }), + title: z + .string({ + required_error: "Risk name is required", + }) + .min(1, { + message: "Risk name should be at least 1 character", + }) + .max(100, { + message: "Risk name should be at most 100 characters", + }), + description: z + .string({ + required_error: "Risk description is required", + }) + .min(1, { + message: "Risk description should be at least 1 character", + }) + .max(255, { + message: "Risk description should be at most 255 characters", + }), + category: z.nativeEnum(RiskCategory, { + required_error: "Risk category is required", + }), + department: z.nativeEnum(Departments, { + required_error: "Risk department is required", + }), + ownerId: z.string({ + required_error: "You must assign an owner to the risk", + }), }); export const updateRiskSchema = z.object({ - id: z.string().min(1, { - message: "Risk ID is required", - }), - title: z.string().min(1, { - message: "Risk title is required", - }), - description: z.string().min(1, { - message: "Risk description is required", - }), - category: z.nativeEnum(RiskCategory, { - required_error: "Risk category is required", - }), - department: z.nativeEnum(Departments, { - required_error: "Risk department is required", - }), - ownerId: z.string({ - required_error: "You must assign an owner to the risk", - }), - status: z.nativeEnum(RiskStatus, { - required_error: "Risk status is required", - }), + id: z.string().min(1, { + message: "Risk ID is required", + }), + title: z.string().min(1, { + message: "Risk title is required", + }), + description: z.string().min(1, { + message: "Risk description is required", + }), + category: z.nativeEnum(RiskCategory, { + required_error: "Risk category is required", + }), + department: z.nativeEnum(Departments, { + required_error: "Risk department is required", + }), + ownerId: z.string({ + required_error: "You must assign an owner to the risk", + }), + status: z.nativeEnum(RiskStatus, { + required_error: "Risk status is required", + }), }); export const createRiskCommentSchema = z.object({ - riskId: z.string().min(1, { - message: "Risk ID is required", - }), - content: z - .string() - .min(1, { - message: "Comment content is required", - }) - .max(1000, { - message: "Comment content should be at most 1000 characters", - }), + riskId: z.string().min(1, { + message: "Risk ID is required", + }), + content: z + .string() + .min(1, { + message: "Comment content is required", + }) + .max(1000, { + message: "Comment content should be at most 1000 characters", + }), }); export const createTaskSchema = z.object({ - riskId: z.string().min(1, { - message: "Risk ID is required", - }), - title: z.string().min(1, { - message: "Task title is required", - }), - description: z.string().min(1, { - message: "Task description is required", - }), - dueDate: z.date().optional(), - ownerId: z - .string({ - required_error: "You must assign an owner to the task", - }) - .min(1, { - message: "You must assign an owner to the task", - }), + riskId: z.string().min(1, { + message: "Risk ID is required", + }), + title: z.string().min(1, { + message: "Task title is required", + }), + description: z.string().min(1, { + message: "Task description is required", + }), + dueDate: z.date().optional(), + ownerId: z + .string({ + required_error: "You must assign an owner to the task", + }) + .min(1, { + message: "You must assign an owner to the task", + }), }); export const updateTaskSchema = z.object({ - id: z.string().min(1, { - message: "Task ID is required", - }), - title: z.string().optional(), - description: z.string().optional(), - dueDate: z.date().optional(), - status: z.nativeEnum(TaskStatus, { - required_error: "Task status is required", - }), - ownerId: z.string({ - required_error: "You must assign an owner to the task", - }), + id: z.string().min(1, { + message: "Task ID is required", + }), + title: z.string().optional(), + description: z.string().optional(), + dueDate: z.date().optional(), + status: z.nativeEnum(TaskStatus, { + required_error: "Task status is required", + }), + ownerId: z.string({ + required_error: "You must assign an owner to the task", + }), }); export const createTaskCommentSchema = z.object({ - riskId: z.string().min(1, { - message: "Risk ID is required", - }), - taskId: z.string().min(1, { - message: "Task ID is required", - }), - content: z - .string() - .min(1, { - message: "Comment content is required", - }) - .max(1000, { - message: "Comment content should be at most 1000 characters", - }), + riskId: z.string().min(1, { + message: "Risk ID is required", + }), + taskId: z.string().min(1, { + message: "Task ID is required", + }), + content: z + .string() + .min(1, { + message: "Comment content is required", + }) + .max(1000, { + message: "Comment content should be at most 1000 characters", + }), }); export const uploadTaskFileSchema = z.object({ - riskId: z.string().min(1, { - message: "Risk ID is required", - }), - taskId: z.string().min(1, { - message: "Task ID is required", - }), + riskId: z.string().min(1, { + message: "Risk ID is required", + }), + taskId: z.string().min(1, { + message: "Task ID is required", + }), }); // Integrations export const deleteIntegrationConnectionSchema = z.object({ - integrationId: z.string().min(1, { - message: "Integration ID is required", - }), + integrationId: z.string().min(1, { + message: "Integration ID is required", + }), }); export const createIntegrationSchema = z.object({ - integrationId: z.string().min(1, { - message: "Integration ID is required", - }), + integrationId: z.string().min(1, { + message: "Integration ID is required", + }), }); // Seed Data export const seedDataSchema = z.object({ - organizationId: z.string(), + organizationId: z.string(), }); export const updateInherentRiskSchema = z.object({ - id: z.string().min(1, { - message: "Risk ID is required", - }), - probability: z.nativeEnum(Likelihood), - impact: z.nativeEnum(Impact), + id: z.string().min(1, { + message: "Risk ID is required", + }), + probability: z.nativeEnum(Likelihood), + impact: z.nativeEnum(Impact), }); export const updateResidualRiskSchema = z.object({ - id: z.string().min(1, { - message: "Risk ID is required", - }), - probability: z.number().min(1).max(10), - impact: z.number().min(1).max(10), + id: z.string().min(1, { + message: "Risk ID is required", + }), + probability: z.number().min(1).max(10), + impact: z.number().min(1).max(10), }); // Policies export const createPolicySchema = z.object({ - title: z.string(), - description: z.string(), - frameworkIds: z.array(z.string()), - controlIds: z.array(z.string()), + title: z.string(), + description: z.string(), + frameworkIds: z.array(z.string()), + controlIds: z.array(z.string()), }); export const updatePolicySchema = z.object({ - id: z.string(), - content: z.any(), + id: z.string(), + content: z.any(), }); export const assistantSettingsSchema = z.object({ - enabled: z.boolean().optional(), + enabled: z.boolean().optional(), }); export const createEmployeeSchema = z.object({ - name: z.string().min(1, "Name is required"), - email: z.string().email("Invalid email address"), - department: z.nativeEnum(Departments, { - required_error: "Department is required", - }), - externalEmployeeId: z.string().optional(), - isActive: z.boolean().default(true), + name: z.string().min(1, "Name is required"), + email: z.string().email("Invalid email address"), + department: z.nativeEnum(Departments, { + required_error: "Department is required", + }), + externalEmployeeId: z.string().optional(), + isActive: z.boolean().default(true), }); export const updatePolicyOverviewSchema = z.object({ - id: z.string(), - title: z.string(), - description: z.string(), - isRequiredToSign: z.enum(["required", "not_required"]).optional(), + id: z.string(), + title: z.string(), + description: z.string(), + isRequiredToSign: z.enum(["required", "not_required"]).optional(), }); export const updatePolicyFormSchema = z.object({ - id: z.string(), - status: z.nativeEnum(PolicyStatus), - assigneeId: z.string().nullable(), - department: z.nativeEnum(Departments), - review_frequency: z.nativeEnum(Frequency), - review_date: z.date(), - isRequiredToSign: z.enum(["required", "not_required"]), + id: z.string(), + status: z.nativeEnum(PolicyStatus), + assigneeId: z.string().nullable(), + department: z.nativeEnum(Departments), + review_frequency: z.nativeEnum(Frequency), + review_date: z.date(), + isRequiredToSign: z.enum(["required", "not_required"]), }); export const apiKeySchema = z.object({ - name: z - .string() - .min(1, { message: "Name is required" }) - .max(64, { message: "Name must be less than 64 characters" }), - expiresAt: z.enum(["30days", "90days", "1year", "never"]), + name: z + .string() + .min(1, { message: "Name is required" }) + .max(64, { message: "Name must be less than 64 characters" }), + expiresAt: z.enum(["30days", "90days", "1year", "never"]), }); export const createPolicyCommentSchema = z.object({ - policyId: z.string().min(1, { - message: "Policy ID is required", - }), - content: z - .string() - .min(1, { - message: "Comment content is required", - }) - .max(1000, { - message: "Comment content should be at most 1000 characters", - }), + policyId: z.string().min(1, { + message: "Policy ID is required", + }), + content: z + .string() + .min(1, { + message: "Comment content is required", + }) + .max(1000, { + message: "Comment content should be at most 1000 characters", + }), }); diff --git a/apps/app/src/components/forms/create-organization-form.tsx b/apps/app/src/components/forms/create-organization-form.tsx index f91b1fe481..74a191d990 100644 --- a/apps/app/src/components/forms/create-organization-form.tsx +++ b/apps/app/src/components/forms/create-organization-form.tsx @@ -3,6 +3,7 @@ import { createOrganizationAction } from "@/actions/organization/create-organization-action"; import { organizationSchema } from "@/actions/schema"; import { useI18n } from "@/locales/client"; +import { authClient } from "@comp/auth"; import { frameworks, type FrameworkId } from "@comp/data"; import { Button } from "@comp/ui/button"; import { Checkbox } from "@comp/ui/checkbox"; @@ -19,15 +20,14 @@ import { Icons } from "@comp/ui/icons"; import { Input } from "@comp/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2 } from "lucide-react"; -import { authClient } from "@comp/auth"; import { useAction } from "next-safe-action/hooks"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import type { z } from "zod"; import { LogoSpinner } from "../logo-spinner"; -import { useRouter } from "next/navigation"; function OnboardingClient() { const [isCreatingOrganization, setIsCreatingOrganization] = useState(false); @@ -115,27 +115,6 @@ function OnboardingClient() { className="space-y-6" suppressHydrationWarning > - ( - - - {t("onboarding.fields.fullName.label")} - - - - - - - )} - /> - Date: Thu, 3 Apr 2025 15:29:04 -0400 Subject: [PATCH 3/5] refactor: enhance policy actions and components with improved validation, error handling, and layout structure --- .../src/actions/policies/archive-policy.ts | 114 +++++------ .../policies/update-policy-form-action.ts | 180 +++++++++--------- .../policies/update-policy-overview-action.ts | 2 +- .../editor/actions/get-policy-details.ts | 0 .../editor/actions/update-policy.ts | 0 .../editor/components/PolicyDetails.tsx | 0 .../editor/components/PolicyHeader.tsx | 0 .../{all => }/[policyId]/editor/page.tsx | 0 .../[policyId]/editor/types/index.ts | 0 .../policies/{all => }/[policyId]/layout.tsx | 4 +- .../policies/{all => }/[policyId]/page.tsx | 0 .../(overview)/components/policies-table.tsx | 49 ----- .../components/policies-table-action-bar.tsx | 0 .../components/policies-table-columns.tsx | 0 .../all/components/policies-table.tsx | 55 ++++++ .../all/{(overview) => }/data/queries.ts | 0 .../all/{(overview) => }/data/validations.ts | 0 .../policies/all/{(overview) => }/layout.tsx | 0 .../policies/all/{(overview) => }/page.tsx | 0 .../src/components/data-table/data-table.tsx | 13 +- .../policies/charts/policies-by-framework.tsx | 2 +- .../components/tables/policies/columns.tsx | 2 +- 22 files changed, 214 insertions(+), 207 deletions(-) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/actions/get-policy-details.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/actions/update-policy.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/components/PolicyDetails.tsx (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/components/PolicyHeader.tsx (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/page.tsx (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/editor/types/index.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/layout.tsx (87%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/{all => }/[policyId]/page.tsx (100%) delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table.tsx rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/components/policies-table-action-bar.tsx (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/components/policies-table-columns.tsx (100%) create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table.tsx rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/data/queries.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/data/validations.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/layout.tsx (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/{(overview) => }/page.tsx (100%) diff --git a/apps/app/src/actions/policies/archive-policy.ts b/apps/app/src/actions/policies/archive-policy.ts index d02f5c1813..8b10299622 100644 --- a/apps/app/src/actions/policies/archive-policy.ts +++ b/apps/app/src/actions/policies/archive-policy.ts @@ -6,70 +6,70 @@ import { authActionClient } from "../safe-action"; import { z } from "zod"; const archivePolicySchema = z.object({ - id: z.string(), - action: z.enum(["archive", "restore"]).optional(), + id: z.string(), + action: z.enum(["archive", "restore"]).optional(), }); export const archivePolicyAction = authActionClient - .schema(archivePolicySchema) - .metadata({ - name: "archive-policy", - track: { - event: "archive-policy", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - const { id, action } = parsedInput; - const { activeOrganizationId } = ctx.session; + .schema(archivePolicySchema) + .metadata({ + name: "archive-policy", + track: { + event: "archive-policy", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { id, action } = parsedInput; + const { activeOrganizationId } = ctx.session; - if (!activeOrganizationId) { - return { - success: false, - error: "Not authorized", - }; - } + if (!activeOrganizationId) { + return { + success: false, + error: "Not authorized", + }; + } - try { - const policy = await db.policy.findUnique({ - where: { - id, - organizationId: activeOrganizationId, - }, - }); + try { + const policy = await db.policy.findUnique({ + where: { + id, + organizationId: activeOrganizationId, + }, + }); - if (!policy) { - return { - success: false, - error: "Policy not found", - }; - } + if (!policy) { + return { + success: false, + error: "Policy not found", + }; + } - // Determine if we should archive or restore based on action or current state - const shouldArchive = - action === "archive" || (action === undefined && !policy.isArchived); + // Determine if we should archive or restore based on action or current state + const shouldArchive = + action === "archive" || (action === undefined && !policy.isArchived); - await db.policy.update({ - where: { id }, - data: { - isArchived: shouldArchive, - }, - }); + await db.policy.update({ + where: { id }, + data: { + isArchived: shouldArchive, + }, + }); - revalidatePath(`/${activeOrganizationId}/policies/all/${id}`); - revalidatePath(`/${activeOrganizationId}/policies/all`); - revalidatePath(`/${activeOrganizationId}/policies`); - revalidateTag("policies"); + revalidatePath(`/${activeOrganizationId}/policies/${id}`); + revalidatePath(`/${activeOrganizationId}/policies/all`); + revalidatePath(`/${activeOrganizationId}/policies`); + revalidateTag("policies"); - return { - success: true, - isArchived: shouldArchive, - }; - } catch (error) { - console.error(error); - return { - success: false, - error: "Failed to update policy archive status", - }; - } - }); + return { + success: true, + isArchived: shouldArchive, + }; + } catch (error) { + console.error(error); + return { + success: false, + error: "Failed to update policy archive status", + }; + } + }); diff --git a/apps/app/src/actions/policies/update-policy-form-action.ts b/apps/app/src/actions/policies/update-policy-form-action.ts index 6f2bdad24f..856cc479b1 100644 --- a/apps/app/src/actions/policies/update-policy-form-action.ts +++ b/apps/app/src/actions/policies/update-policy-form-action.ts @@ -10,107 +10,107 @@ import { PolicyStatus } from "@comp/db/types"; // Helper function to calculate next review date based on frequency function calculateNextReviewDate( - frequency: string, - baseDate: Date = new Date() + frequency: string, + baseDate: Date = new Date(), ): Date { - const nextDate = new Date(baseDate); + const nextDate = new Date(baseDate); - switch (frequency) { - case "monthly": - nextDate.setMonth(nextDate.getMonth() + 1); - break; - case "quarterly": - nextDate.setMonth(nextDate.getMonth() + 3); - break; - case "yearly": - nextDate.setFullYear(nextDate.getFullYear() + 1); - break; - default: - // If frequency is not recognized, default to yearly - nextDate.setFullYear(nextDate.getFullYear() + 1); - } + switch (frequency) { + case "monthly": + nextDate.setMonth(nextDate.getMonth() + 1); + break; + case "quarterly": + nextDate.setMonth(nextDate.getMonth() + 3); + break; + case "yearly": + nextDate.setFullYear(nextDate.getFullYear() + 1); + break; + default: + // If frequency is not recognized, default to yearly + nextDate.setFullYear(nextDate.getFullYear() + 1); + } - return nextDate; + return nextDate; } export const updatePolicyFormAction = authActionClient - .schema(updatePolicyFormSchema) - .metadata({ - name: "update-policy-form", - track: { - event: "update-policy-form", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - const { - id, - status, - assigneeId, - department, - review_frequency, - review_date, - isRequiredToSign, - } = parsedInput; - const { user, session } = ctx; + .schema(updatePolicyFormSchema) + .metadata({ + name: "update-policy-form", + track: { + event: "update-policy-form", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { + id, + status, + assigneeId, + department, + review_frequency, + review_date, + isRequiredToSign, + } = parsedInput; + const { user, session } = ctx; - if (!user.id || !session.activeOrganizationId) { - throw new Error("Unauthorized"); - } + if (!user.id || !session.activeOrganizationId) { + throw new Error("Unauthorized"); + } - try { - // Get the current policy to check if status is changing to published - const currentPolicy = await db.policy.findUnique({ - where: { - id, - organizationId: session.activeOrganizationId, - }, - select: { - status: true, - }, - }); + try { + // Get the current policy to check if status is changing to published + const currentPolicy = await db.policy.findUnique({ + where: { + id, + organizationId: session.activeOrganizationId, + }, + select: { + status: true, + }, + }); - // Determine if we need to update the review date - let reviewDate = review_date; - let lastPublishedAt = undefined; + // Determine if we need to update the review date + let reviewDate = review_date; + let lastPublishedAt = undefined; - // If status is changing to 'published', calculate next review date based on frequency - if ( - status === PolicyStatus.published && - currentPolicy?.status !== PolicyStatus.published - ) { - reviewDate = calculateNextReviewDate(review_frequency); - lastPublishedAt = new Date(); // Set lastPublishedAt to now when publishing - } + // If status is changing to 'published', calculate next review date based on frequency + if ( + status === PolicyStatus.published && + currentPolicy?.status !== PolicyStatus.published + ) { + reviewDate = calculateNextReviewDate(review_frequency); + lastPublishedAt = new Date(); // Set lastPublishedAt to now when publishing + } - await db.policy.update({ - where: { - id, - organizationId: session.activeOrganizationId, - }, - data: { - status, - assigneeId, - department, - frequency: review_frequency, - reviewDate, - isRequiredToSign: isRequiredToSign === "required", - ...(lastPublishedAt && { lastPublishedAt }), - }, - }); + await db.policy.update({ + where: { + id, + organizationId: session.activeOrganizationId, + }, + data: { + status, + assigneeId, + department, + frequency: review_frequency, + reviewDate, + isRequiredToSign: isRequiredToSign === "required", + ...(lastPublishedAt && { lastPublishedAt }), + }, + }); - revalidatePath(`/${session.activeOrganizationId}/policies`); - revalidatePath(`/${session.activeOrganizationId}/policies/all/${id}`); - revalidateTag("policies"); + revalidatePath(`/${session.activeOrganizationId}/policies`); + revalidatePath(`/${session.activeOrganizationId}/policies/${id}`); + revalidateTag("policies"); - return { - success: true, - }; - } catch (error) { - console.error("Error updating policy:", error); + return { + success: true, + }; + } catch (error) { + console.error("Error updating policy:", error); - return { - success: false, - }; - } - }); + return { + success: false, + }; + } + }); diff --git a/apps/app/src/actions/policies/update-policy-overview-action.ts b/apps/app/src/actions/policies/update-policy-overview-action.ts index 9c3d316ee4..1753e336b7 100644 --- a/apps/app/src/actions/policies/update-policy-overview-action.ts +++ b/apps/app/src/actions/policies/update-policy-overview-action.ts @@ -59,7 +59,7 @@ export const updatePolicyOverviewAction = authActionClient }, }); - revalidatePath(`/${session.activeOrganizationId}/policies/all/${id}`); + revalidatePath(`/${session.activeOrganizationId}/policies/${id}`); revalidatePath(`/${session.activeOrganizationId}/policies/all`); revalidatePath(`/${session.activeOrganizationId}/policies`); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/get-policy-details.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/actions/get-policy-details.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/get-policy-details.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/actions/get-policy-details.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/update-policy.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/actions/update-policy.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/update-policy.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/actions/update-policy.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/components/PolicyDetails.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyDetails.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/components/PolicyDetails.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/components/PolicyHeader.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyHeader.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/components/PolicyHeader.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/editor/types/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx similarity index 87% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/layout.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx index 315f4096a3..c030d40869 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx @@ -17,11 +17,11 @@ export default async function Layout({ children, params }: LayoutProps) { backButtonHref={`/${orgId}/policies/all`} items={[ { - path: `/${orgId}/policies/all/${policyId}`, + path: `/${orgId}/policies/${policyId}`, label: t("policies.dashboard.sub_pages.overview"), }, { - path: `/${orgId}/policies/all/${policyId}/editor`, + path: `/${orgId}/policies/${policyId}/editor`, label: t("policies.dashboard.sub_pages.edit_policy"), }, ]} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table.tsx deleted file mode 100644 index 611c13df8f..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import * as React from "react"; - -import { DataTable } from "@/components/data-table/data-table"; -import { useDataTable } from "@/hooks/use-data-table"; -import { getPolicies } from "../data/queries"; -import { DataTableSortList } from "@/components/data-table/data-table-sort-list"; -import { getPolicyColumns } from "./policies-table-columns"; -import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; -import { CreatePolicySheet } from "@/components/sheets/create-policy-sheet"; - -interface PoliciesTableProps { - promises: Promise<[ - Awaited> - ]>; -} - -export function PoliciesTable({ promises }: PoliciesTableProps) { - const [{ data, pageCount }] = React.use(promises); - - const columns = React.useMemo(() => getPolicyColumns(), []); - - const { table } = useDataTable({ - data, - columns, - pageCount, - initialState: { - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow) => originalRow.id, - shallow: false, - clearOnDefault: true, - }); - - return ( - <> - row.id} - > - - - - - - - ); -} \ No newline at end of file diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table-action-bar.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table-action-bar.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table-action-bar.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table-action-bar.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table-columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table-columns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/policies-table-columns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table-columns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table.tsx new file mode 100644 index 0000000000..fbbb297726 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/components/policies-table.tsx @@ -0,0 +1,55 @@ +"use client"; + +import * as React from "react"; + +import { DataTable } from "@/components/data-table/data-table"; +import { useDataTable } from "@/hooks/use-data-table"; +import { getPolicies } from "../data/queries"; +import { DataTableSortList } from "@/components/data-table/data-table-sort-list"; +import { getPolicyColumns } from "./policies-table-columns"; +import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; +import { CreatePolicySheet } from "@/components/sheets/create-policy-sheet"; +import { useSession } from "next-auth/react"; +import { useParams } from "next/navigation"; + +interface PoliciesTableProps { + promises: Promise<[Awaited>]>; +} + +export function PoliciesTable({ promises }: PoliciesTableProps) { + const [{ data, pageCount }] = React.use(promises); + const { orgId } = useParams(); + + const columns = React.useMemo(() => getPolicyColumns(), []); + + const { table } = useDataTable({ + data, + columns, + pageCount, + initialState: { + columnPinning: { right: ["actions"] }, + }, + getRowId: (originalRow) => originalRow.id, + shallow: false, + clearOnDefault: true, + }); + + return ( + <> + row.id} + rowClickBasePath={`/${orgId}/policies`} + > + + + + + + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/data/queries.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/queries.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/data/queries.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/queries.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/data/validations.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/data/validations.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/layout.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx diff --git a/apps/app/src/components/data-table/data-table.tsx b/apps/app/src/components/data-table/data-table.tsx index 2f9279a4e1..672f4642ce 100644 --- a/apps/app/src/components/data-table/data-table.tsx +++ b/apps/app/src/components/data-table/data-table.tsx @@ -1,8 +1,9 @@ import { type Table as TanstackTable, flexRender } from "@tanstack/react-table"; +import { useRouter } from "next/navigation"; import type * as React from "react"; -import { usePathname, useRouter } from "next/navigation"; -import { DataTablePagination } from "./data-table-pagination"; +import { getCommonPinningStyles } from "@/lib/data-table"; +import { cn } from "@comp/ui/cn"; import { Table, TableBody, @@ -11,13 +12,13 @@ import { TableHeader, TableRow, } from "@comp/ui/table"; -import { getCommonPinningStyles } from "@/lib/data-table"; -import { cn } from "@comp/ui/cn"; +import { DataTablePagination } from "./data-table-pagination"; interface DataTableProps extends React.ComponentProps<"div"> { table: TanstackTable; actionBar?: React.ReactNode; getRowId?: (row: TData) => string; + rowClickBasePath: string; } export function DataTable({ @@ -26,15 +27,15 @@ export function DataTable({ children, className, getRowId, + rowClickBasePath, ...props }: DataTableProps) { const router = useRouter(); - const currentPath = usePathname(); const handleRowClick = (row: TData) => { if (getRowId) { const id = getRowId(row); - router.push(`${currentPath}/${id}`); + router.push(`${rowClickBasePath}/${id}`); } }; diff --git a/apps/app/src/components/policies/charts/policies-by-framework.tsx b/apps/app/src/components/policies/charts/policies-by-framework.tsx index 42d027587a..cd1c0a607f 100644 --- a/apps/app/src/components/policies/charts/policies-by-framework.tsx +++ b/apps/app/src/components/policies/charts/policies-by-framework.tsx @@ -1,6 +1,6 @@ "use client"; -import { usePolicies } from "@/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/hooks/usePolicies"; +import { usePolicies } from "@/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/hooks/usePolicies"; import { useI18n } from "@/locales/client"; import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card"; import { diff --git a/apps/app/src/components/tables/policies/columns.tsx b/apps/app/src/components/tables/policies/columns.tsx index c06fb53a58..bd3f93e1d9 100644 --- a/apps/app/src/components/tables/policies/columns.tsx +++ b/apps/app/src/components/tables/policies/columns.tsx @@ -35,7 +35,7 @@ export function columns(): ColumnDef[] { return (
From a0e4a84affaf74ac09853fb3087223fd401dcd50 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Thu, 3 Apr 2025 15:38:11 -0400 Subject: [PATCH 4/5] refactor: update controls table to utilize useParams for dynamic routing and enhance layout structure with PageWithBreadcrumb --- .../controls/components/controls-table.tsx | 68 +++++++------- .../[orgId]/policies/(overview)/layout.tsx | 33 ------- .../[orgId]/policies/[policyId]/layout.tsx | 33 ------- .../[orgId]/policies/[policyId]/page.tsx | 7 +- .../[orgId]/policies/all/layout.tsx | 36 -------- .../[orgId]/policies/all/loading.tsx | 12 +++ .../(dashboard)/[orgId]/policies/all/page.tsx | 90 ++++++++----------- .../(dashboard)/[orgId]/policies/layout.tsx | 32 +++++++ 8 files changed, 121 insertions(+), 190 deletions(-) delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/layout.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/loading.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx index 228fdf2a94..6137e3cdee 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/components/controls-table.tsx @@ -9,42 +9,46 @@ import { DataTableSortList } from "@/components/data-table/data-table-sort-list" import { getControlColumns } from "./controls-table-columns"; import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; import { CreatePolicySheet } from "@/components/sheets/create-policy-sheet"; +import { useParams } from "next/navigation"; interface ControlsTableProps { - promises: Promise<[ - Awaited> - ]>; + promises: Promise<[Awaited>]>; } export function ControlsTable({ promises }: ControlsTableProps) { - const [{ data, pageCount }] = React.use(promises); + const [{ data, pageCount }] = React.use(promises); + const { orgId } = useParams(); + const columns = React.useMemo(() => getControlColumns(), []); - const columns = React.useMemo(() => getControlColumns(), []); + const { table } = useDataTable({ + data, + columns, + pageCount, + initialState: { + sorting: [{ id: "name", desc: true }], + columnPinning: { right: ["actions"] }, + }, + getRowId: (originalRow) => originalRow.id, + shallow: false, + clearOnDefault: true, + }); - const { table } = useDataTable({ - data, - columns, - pageCount, - initialState: { - sorting: [{ id: "name", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow) => originalRow.id, - shallow: false, - clearOnDefault: true, - }); - - return ( - <> - row.id} - > - - - - - - - ); -} \ No newline at end of file + return ( + <> + row.id} + rowClickBasePath={`/${orgId}/controls`} + > + + + + + + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/layout.tsx deleted file mode 100644 index fa86e00382..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { auth } from "@comp/auth"; -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@comp/ui/secondary-menu"; -import { headers } from "next/headers"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - const session = await auth.api.getSession({ - headers: await headers(), - }); - const user = session?.user; - const orgId = session?.session.activeOrganizationId; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx deleted file mode 100644 index c030d40869..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@comp/ui/secondary-menu"; - -interface LayoutProps { - children: React.ReactNode; - params: Promise<{ policyId: string; orgId: string }>; -} - -export default async function Layout({ children, params }: LayoutProps) { - const t = await getI18n(); - const { policyId, orgId } = await params; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx index bd51da859c..2da3ae0f32 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx @@ -6,6 +6,7 @@ import { auth } from "@comp/auth"; import type { Metadata } from "next"; import { setStaticParamsLocale } from "next-international/server"; import { headers } from "next/headers"; +import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb"; export default async function PolicyDetails({ params, @@ -18,9 +19,11 @@ export default async function PolicyDetails({ const assignees = await getAssignees(); return ( -
+ -
+ ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx deleted file mode 100644 index 3df8b9a0cb..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { auth } from "@comp/auth"; -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@comp/ui/secondary-menu"; -import { headers } from "next/headers"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - const session = await auth.api.getSession({ - headers: await headers(), - }); - const user = session?.user; - const organizationId = session?.session.activeOrganizationId; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/loading.tsx new file mode 100644 index 0000000000..d21bfe214c --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/loading.tsx @@ -0,0 +1,12 @@ +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; + +export default function Loading() { + return ( + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx index 5e7ebf9c75..37f3097bfd 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/page.tsx @@ -1,72 +1,54 @@ +import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb"; +import { getValidFilters } from "@/lib/data-table"; import { getI18n } from "@/locales/server"; +import type { SearchParams } from "@/types"; import type { Metadata } from "next"; import { setStaticParamsLocale } from "next-international/server"; -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; -import { Suspense } from "react"; -import type { SearchParams } from "@/types"; -import { searchParamsCache } from "./data/validations"; -import { getValidFilters } from "@/lib/data-table"; import { PoliciesTable } from "./components/policies-table"; import { getPolicies } from "./data/queries"; +import { searchParamsCache } from "./data/validations"; interface PolicyTableProps { - params: Promise<{ locale: string }>; - searchParams: Promise; + params: Promise<{ locale: string }>; + searchParams: Promise; } export default async function PoliciesPage({ - params, - ...props + params, + ...props }: PolicyTableProps) { - const { locale } = await params; - const searchParams = await props.searchParams; - const search = searchParamsCache.parse(searchParams); - const validFilters = getValidFilters(search.filters); - - setStaticParamsLocale(locale); - - const promises = Promise.all([ - getPolicies({ - ...search, - filters: validFilters, - }), - ]); - - return ( - - } - > - - - ); + const { locale } = await params; + const searchParams = await props.searchParams; + const search = searchParamsCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + setStaticParamsLocale(locale); + + const promises = Promise.all([ + getPolicies({ + ...search, + filters: validFilters, + }), + ]); + + return ( + + + + ); } export async function generateMetadata({ - params, + params, }: { - params: Promise<{ locale: string }>; + params: Promise<{ locale: string }>; }): Promise { - const { locale } = await params; + const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); + setStaticParamsLocale(locale); + const t = await getI18n(); - return { - title: t("sidebar.policies"), - }; -} \ No newline at end of file + return { + title: t("sidebar.policies"), + }; +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx new file mode 100644 index 0000000000..56d8dc9691 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx @@ -0,0 +1,32 @@ +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@comp/ui/secondary-menu"; +import { Separator } from "@comp/ui/separator"; + +interface LayoutProps { + children: React.ReactNode; + params: Promise<{ policyId: string; orgId: string }>; +} + +export default async function Layout({ children, params }: LayoutProps) { + const t = await getI18n(); + const { orgId } = await params; + + return ( +
+ + +
{children}
+
+ ); +} From 9b9c76e50f4d468ee88f18da0f77c7a989b0c635 Mon Sep 17 00:00:00 2001 From: Claudio Fuentes Date: Thu, 3 Apr 2025 15:45:30 -0400 Subject: [PATCH 5/5] refactor: simplify layout structure by removing unnecessary separator and enhance breadcrumb navigation with dynamic organization ID --- .../[orgId]/policies/[policyId]/page.tsx | 17 +- .../(dashboard)/[orgId]/policies/layout.tsx | 1 - apps/app/src/locales/features/policies.ts | 206 +++++++++--------- 3 files changed, 113 insertions(+), 111 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx index 2da3ae0f32..4b58dde86d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/[policyId]/page.tsx @@ -1,26 +1,29 @@ -import { cache } from "react"; +import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb"; import { PolicyOverview } from "@/components/policies/policy-overview"; import { getI18n } from "@/locales/server"; -import { db } from "@comp/db"; import { auth } from "@comp/auth"; +import { db } from "@comp/db"; import type { Metadata } from "next"; import { setStaticParamsLocale } from "next-international/server"; import { headers } from "next/headers"; -import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb"; - +import { cache } from "react"; export default async function PolicyDetails({ params, }: { - params: Promise<{ locale: string; policyId: string }>; + params: Promise<{ locale: string; policyId: string; orgId: string }>; }) { - const { locale, policyId } = await params; + const { locale, policyId, orgId } = await params; + setStaticParamsLocale(locale); const policy = await getPolicy(policyId); const assignees = await getAssignees(); return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx index 56d8dc9691..05555fc0fb 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/layout.tsx @@ -25,7 +25,6 @@ export default async function Layout({ children, params }: LayoutProps) { }, ]} /> -
{children}
); diff --git a/apps/app/src/locales/features/policies.ts b/apps/app/src/locales/features/policies.ts index 03ac23c4c8..0df4ac72ca 100644 --- a/apps/app/src/locales/features/policies.ts +++ b/apps/app/src/locales/features/policies.ts @@ -1,105 +1,105 @@ export const 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", - status: "Status", - status_placeholder: "Select status", - description: "Description", - description_placeholder: "Enter policy description", - review_frequency: "Review Frequency", - review_frequency_placeholder: "Select a review frequency", - review_date: "Review Date", - review_date_placeholder: "Select a review date", - signature_required: "Require employees signature", - signature_not_required: "Do not ask employees to sign", - signature_requirement: "Signature Requirement", - signature_requirement_placeholder: "Select signature requirement", - policy_department: "Department", - policy_department_placeholder: "Select a department", - policy_status: "Policy Status", - policy_status_placeholder: "Select a policy status", - policy_assignee: "Assignee", - policy_assignee_placeholder: "Select an assignee", - }, - }, - new: { - success: "Policy successfully created", - error: "Failed to create policy", - details: "Policy Details", - title: "Enter a title for the policy", - description: "Enter a description for the policy", - }, - 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", - relevant: "Relevant", - "not-relevant": "Not Relevant", - }, - 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", - last_updated: "Last updated: {{date}}", - save: "Save", - policy_details: "Policy Details", - archive: { - tooltip: "Archive policy", - restore_tooltip: "Restore policy", - title: "Archive Policy", - restore_title: "Restore Policy", - description: "Are you sure you want to archive this policy?", - restore_description: "Are you sure you want to restore this policy?", - confirm: "Archive", - restore_confirm: "Restore", - cancel: "Cancel", - success: "Policy archived successfully", - restore_success: "Policy restored successfully", - error: "Failed to update policy archive status", - status: "This policy is archived", - archived_on: "Archived on", - }, - edit: { - tooltip: "Edit policy", - }, + dashboard: { + title: "Dashboard", + 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", + status: "Status", + status_placeholder: "Select status", + description: "Description", + description_placeholder: "Enter policy description", + review_frequency: "Review Frequency", + review_frequency_placeholder: "Select a review frequency", + review_date: "Review Date", + review_date_placeholder: "Select a review date", + signature_required: "Require employees signature", + signature_not_required: "Do not ask employees to sign", + signature_requirement: "Signature Requirement", + signature_requirement_placeholder: "Select signature requirement", + policy_department: "Department", + policy_department_placeholder: "Select a department", + policy_status: "Policy Status", + policy_status_placeholder: "Select a policy status", + policy_assignee: "Assignee", + policy_assignee_placeholder: "Select an assignee", + }, + }, + new: { + success: "Policy successfully created", + error: "Failed to create policy", + details: "Policy Details", + title: "Enter a title for the policy", + description: "Enter a description for the policy", + }, + 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", + relevant: "Relevant", + "not-relevant": "Not Relevant", + }, + 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", + last_updated: "Last updated: {{date}}", + save: "Save", + policy_details: "Policy Details", + archive: { + tooltip: "Archive policy", + restore_tooltip: "Restore policy", + title: "Archive Policy", + restore_title: "Restore Policy", + description: "Are you sure you want to archive this policy?", + restore_description: "Are you sure you want to restore this policy?", + confirm: "Archive", + restore_confirm: "Restore", + cancel: "Cancel", + success: "Policy archived successfully", + restore_success: "Policy restored successfully", + error: "Failed to update policy archive status", + status: "This policy is archived", + archived_on: "Archived on", + }, + edit: { + tooltip: "Edit policy", + }, } as const;