diff --git a/apps/app/src/actions/change-organization.ts b/apps/app/src/actions/change-organization.ts new file mode 100644 index 0000000000..c8ad0fda76 --- /dev/null +++ b/apps/app/src/actions/change-organization.ts @@ -0,0 +1,59 @@ +"use server"; + +import { authActionClient } from "./safe-action"; +import { z } from "zod"; +import { db } from "@bubba/db"; + +export const changeOrganizationAction = authActionClient + .schema( + z.object({ + organizationId: z.string(), + }) + ) + .metadata({ + name: "change-organization", + track: { + event: "create-employee", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { organizationId } = parsedInput; + const { user } = ctx; + + try { + const organization = await db.organization.findUnique({ + where: { + id: organizationId, + }, + }); + + if (!organization) { + return { + success: false, + error: "Organization not found", + }; + } + + await db.user.update({ + where: { + id: user.id, + }, + data: { + organizationId: organization.id, + }, + }); + + return { + success: true, + data: organization, + }; + } catch (error) { + console.error("Error changing organization:", error); + + return { + success: false, + error: "Failed to change organization", + }; + } + }); diff --git a/apps/app/src/actions/organization/create-api-key-action.ts b/apps/app/src/actions/organization/create-api-key-action.ts index 106cb1642b..344b4adb4a 100644 --- a/apps/app/src/actions/organization/create-api-key-action.ts +++ b/apps/app/src/actions/organization/create-api-key-action.ts @@ -7,113 +7,113 @@ import { db } from "@bubba/db"; import { revalidatePath } from "next/cache"; export const createApiKeyAction = authActionClient - .schema(apiKeySchema) - .metadata({ - name: "createApiKey", - track: { - event: "createApiKey", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - try { - const { name, expiresAt } = parsedInput; - console.log(`Creating API key "${name}" with expiration: ${expiresAt}`); + .schema(apiKeySchema) + .metadata({ + name: "createApiKey", + track: { + event: "createApiKey", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + try { + const { name, expiresAt } = parsedInput; + console.log(`Creating API key "${name}" with expiration: ${expiresAt}`); - // Generate a new API key and salt - const apiKey = generateApiKey(); - const salt = generateSalt(); - const hashedKey = hashApiKey(apiKey, salt); - console.log( - `Generated new API key for organization: ${ctx.user.organizationId}`, - ); + // Generate a new API key and salt + const apiKey = generateApiKey(); + const salt = generateSalt(); + const hashedKey = hashApiKey(apiKey, salt); + console.log( + `Generated new API key for organization: ${ctx.user.organizationId}` + ); - // Parse the expiration date - let expirationDate: Date | null = null; - if (expiresAt && expiresAt !== "never") { - const now = new Date(); - switch (expiresAt) { - case "30days": - expirationDate = new Date(now.setDate(now.getDate() + 30)); - break; - case "90days": - expirationDate = new Date(now.setDate(now.getDate() + 90)); - break; - case "1year": - expirationDate = new Date(now.setFullYear(now.getFullYear() + 1)); - break; - } - console.log(`Set expiration date to: ${expirationDate?.toISOString()}`); - } else { - console.log("No expiration date set for API key"); - } + // Parse the expiration date + let expirationDate: Date | null = null; + if (expiresAt && expiresAt !== "never") { + const now = new Date(); + switch (expiresAt) { + case "30days": + expirationDate = new Date(now.setDate(now.getDate() + 30)); + break; + case "90days": + expirationDate = new Date(now.setDate(now.getDate() + 90)); + break; + case "1year": + expirationDate = new Date(now.setFullYear(now.getFullYear() + 1)); + break; + } + console.log(`Set expiration date to: ${expirationDate?.toISOString()}`); + } else { + console.log("No expiration date set for API key"); + } - // Create the API key in the database - const apiKeyRecord = await db.organizationApiKey.create({ - data: { - name, - key: hashedKey, - salt, // Store the salt with the hashed key - expiresAt: expirationDate, - organizationId: ctx.user.organizationId!, - }, - select: { - id: true, - name: true, - createdAt: true, - expiresAt: true, - }, - }); - console.log(`Successfully created API key with ID: ${apiKeyRecord.id}`); + // Create the API key in the database + const apiKeyRecord = await db.organizationApiKey.create({ + data: { + name, + key: hashedKey, + salt, // Store the salt with the hashed key + expiresAt: expirationDate, + organizationId: ctx.user.organizationId!, + }, + select: { + id: true, + name: true, + createdAt: true, + expiresAt: true, + }, + }); + console.log(`Successfully created API key with ID: ${apiKeyRecord.id}`); - revalidatePath("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/settings/api-keys"); + revalidatePath(`/${ctx.user.organizationId}/settings/api-keys`); - return { - success: true, - data: { - ...apiKeyRecord, - key: apiKey, - createdAt: apiKeyRecord.createdAt.toISOString(), - expiresAt: apiKeyRecord.expiresAt - ? apiKeyRecord.expiresAt.toISOString() - : null, - }, - }; - } catch (error) { - console.error("Error creating API key:", error); + return { + success: true, + data: { + ...apiKeyRecord, + key: apiKey, + createdAt: apiKeyRecord.createdAt.toISOString(), + expiresAt: apiKeyRecord.expiresAt + ? apiKeyRecord.expiresAt.toISOString() + : null, + }, + }; + } catch (error) { + console.error("Error creating API key:", error); - // Provide more specific error messages based on error type - if (error instanceof Error) { - console.error(`Error details: ${error.message}`); + // Provide more specific error messages based on error type + if (error instanceof Error) { + console.error(`Error details: ${error.message}`); - if (error.message.includes("Unique constraint")) { - return { - success: false, - error: { - code: "DUPLICATE_NAME", - message: "An API key with this name already exists", - }, - }; - } + if (error.message.includes("Unique constraint")) { + return { + success: false, + error: { + code: "DUPLICATE_NAME", + message: "An API key with this name already exists", + }, + }; + } - if (error.message.includes("Foreign key constraint")) { - return { - success: false, - error: { - code: "INVALID_ORGANIZATION", - message: - "The organization does not exist or you don't have access", - }, - }; - } - } + if (error.message.includes("Foreign key constraint")) { + return { + success: false, + error: { + code: "INVALID_ORGANIZATION", + message: + "The organization does not exist or you don't have access", + }, + }; + } + } - return { - success: false, - error: { - code: "INTERNAL_ERROR", - message: "An unexpected error occurred while creating the API key", - }, - }; - } - }); + return { + success: false, + error: { + code: "INTERNAL_ERROR", + message: "An unexpected error occurred while creating the API key", + }, + }; + } + }); diff --git a/apps/app/src/actions/organization/create-organization-action.ts b/apps/app/src/actions/organization/create-organization-action.ts index 21432c933f..728e4f54c4 100644 --- a/apps/app/src/actions/organization/create-organization-action.ts +++ b/apps/app/src/actions/organization/create-organization-action.ts @@ -1,73 +1,56 @@ -// update-organization-action.ts - "use server"; import { createOrganizationAndConnectUser } from "@/auth/org"; -import { db } from "@bubba/db"; import { authActionClient } from "../safe-action"; import { organizationSchema } from "../schema"; import { tasks } from "@trigger.dev/sdk/v3"; import type { createOrganizationTask } from "@/jobs/tasks/organization/create-organization"; export const createOrganizationAction = authActionClient - .schema(organizationSchema) - .metadata({ - name: "create-organization", - track: { - event: "create-organization", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - const { name, website, frameworks } = parsedInput; - const { id: userId, organizationId } = ctx.user; - - if (!name || !website) { - console.log("Invalid input detected:", { name, website }); - throw new Error("Invalid user input"); - } - - if (!organizationId) { - await createOrganizationAndConnectUser({ - userId, - normalizedEmail: ctx.user.email!, - }); - } - - const organization = await db.organization.findFirst({ - where: { - users: { - some: { - id: userId, - }, - }, - }, - }); - - if (!organization) { - throw new Error("Organization not found"); - } - - try { - const handle = await tasks.trigger( - "create-organization", - { - userId, - fullName: name, - website, - frameworkIds: frameworks, - organizationId: organization.id, - }, - ); - - return { - success: true, - runId: handle.id, - publicAccessToken: handle.publicAccessToken, - }; - } catch (error) { - console.error("Error during organization update:", error); - - throw new Error("Failed to update organization"); - } - }); + .schema(organizationSchema) + .metadata({ + name: "create-organization", + track: { + event: "create-organization", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { name, website, frameworks } = parsedInput; + const { id: userId } = ctx.user; + + if (!name || !website) { + console.log("Invalid input detected:", { name, website }); + throw new Error("Invalid user input"); + } + + const newOrganization = await createOrganizationAndConnectUser({ + userId, + normalizedEmail: ctx.user.email!, + orgName: name, + }); + + try { + const handle = await tasks.trigger( + "create-organization", + { + userId, + fullName: name, + website, + frameworkIds: frameworks, + organizationId: newOrganization.id, + } + ); + + return { + success: true, + runId: handle.id, + publicAccessToken: handle.publicAccessToken, + newOrganization, + }; + } catch (error) { + console.error("Error during organization update:", error); + + throw new Error("Failed to update organization"); + } + }); diff --git a/apps/app/src/actions/organization/revoke-api-key-action.ts b/apps/app/src/actions/organization/revoke-api-key-action.ts index 43534697e7..b5ba2f4da3 100644 --- a/apps/app/src/actions/organization/revoke-api-key-action.ts +++ b/apps/app/src/actions/organization/revoke-api-key-action.ts @@ -6,50 +6,50 @@ import { db } from "@bubba/db"; import { revalidatePath } from "next/cache"; const revokeApiKeySchema = z.object({ - id: z.string().min(1), + id: z.string().min(1), }); export const revokeApiKeyAction = authActionClient - .schema(revokeApiKeySchema) - .metadata({ - name: "revokeApiKey", - track: { - event: "revokeApiKey", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - try { - const { id } = parsedInput; + .schema(revokeApiKeySchema) + .metadata({ + name: "revokeApiKey", + track: { + event: "revokeApiKey", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + try { + const { id } = parsedInput; - const result = await db.organizationApiKey.updateMany({ - where: { - id, - organizationId: ctx.user.organizationId!, - }, - data: { - isActive: false, - }, - }); + const result = await db.organizationApiKey.updateMany({ + where: { + id, + organizationId: ctx.user.organizationId!, + }, + data: { + isActive: false, + }, + }); - if (result.count === 0) { - return { - success: false, - error: "API key not found or not authorized to revoke", - }; - } + if (result.count === 0) { + return { + success: false, + error: "API key not found or not authorized to revoke", + }; + } - revalidatePath("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/settings/api-keys"); + revalidatePath(`/${ctx.user.organizationId}/settings/api-keys`); - return { - success: true, - message: "API key revoked successfully", - }; - } catch (error) { - console.error("Error revoking API key:", error); - return { - success: false, - error: "An error occurred while revoking the API key", - }; - } - }); + return { + success: true, + message: "API key revoked successfully", + }; + } catch (error) { + console.error("Error revoking API key:", error); + return { + success: false, + error: "An error occurred while revoking the API key", + }; + } + }); diff --git a/apps/app/src/actions/policies/create-new-policy.ts b/apps/app/src/actions/policies/create-new-policy.ts index ec1594e558..63c176b6fb 100644 --- a/apps/app/src/actions/policies/create-new-policy.ts +++ b/apps/app/src/actions/policies/create-new-policy.ts @@ -1,76 +1,73 @@ "use server"; import { db } from "@bubba/db"; -import { - Departments, - Frequency, -} from "@bubba/db/types"; +import { Departments, Frequency } from "@bubba/db/types"; import { revalidatePath, revalidateTag } from "next/cache"; import { authActionClient } from "../safe-action"; import { createPolicySchema } from "../schema"; export const createPolicyAction = authActionClient - .schema(createPolicySchema) - .metadata({ - name: "create-policy", - track: { - event: "create-policy", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - const { title, description, frameworkIds, controlIds } = parsedInput; - const { user } = ctx; + .schema(createPolicySchema) + .metadata({ + name: "create-policy", + track: { + event: "create-policy", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { title, description, frameworkIds, controlIds } = parsedInput; + const { user } = ctx; - if (!user || !user.organizationId) { - return { - success: false, - error: "Not authorized", - }; - } + if (!user || !user.organizationId) { + return { + success: false, + error: "Not authorized", + }; + } - try { - const policy = await db.policy.create({ - data: { - slug: title, - name: title, - description, - content: [ - { type: "paragraph", content: [{ type: "text", text: "" }] }, - ], - usedBy: JSON.stringify([]), - policyFrameworks: { - create: frameworkIds.map((id) => ({ frameworkId: id })), - }, - PolicyControl: { - create: controlIds.map((id) => ({ controlId: id })), - }, - }, - }); + try { + const policy = await db.policy.create({ + data: { + slug: title, + name: title, + description, + content: [ + { type: "paragraph", content: [{ type: "text", text: "" }] }, + ], + usedBy: JSON.stringify([]), + policyFrameworks: { + create: frameworkIds.map((id) => ({ frameworkId: id })), + }, + PolicyControl: { + create: controlIds.map((id) => ({ controlId: id })), + }, + }, + }); - await db.organizationPolicy.create({ - data: { - organizationId: user.organizationId, - policyId: policy.id, - ownerId: user.id, - department: Departments.none, - frequency: Frequency.monthly, - }, - }); + await db.organizationPolicy.create({ + data: { + organizationId: user.organizationId, + policyId: policy.id, + ownerId: user.id, + department: Departments.none, + frequency: Frequency.monthly, + }, + }); - revalidatePath("/policies"); - revalidateTag("policies"); + revalidatePath(`/${user.organizationId}/policies`); + revalidateTag("policies"); - return { - success: true, - policyId: policy.id, - }; - } catch (error) { - console.error(error); + return { + success: true, + policyId: policy.id, + }; + } catch (error) { + console.error(error); - return { - success: false, - error: "Failed to create policy", - }; - } - }); + return { + success: false, + error: "Failed to create policy", + }; + } + }); diff --git a/apps/app/src/actions/policies/update-policy-action.ts b/apps/app/src/actions/policies/update-policy-action.ts index 014b944ae5..f75dc79c40 100644 --- a/apps/app/src/actions/policies/update-policy-action.ts +++ b/apps/app/src/actions/policies/update-policy-action.ts @@ -17,7 +17,7 @@ interface ContentNode { // Simplified content processor that creates a new plain object function processContent( - content: ContentNode | ContentNode[], + content: ContentNode | ContentNode[] ): ContentNode | ContentNode[] { if (!content) return content; @@ -87,7 +87,7 @@ export const updatePolicyAction = authActionClient // Create a new plain object from the content const processedContent = JSON.parse( - JSON.stringify(processContent(content as ContentNode)), + JSON.stringify(processContent(content as ContentNode)) ); await db.organizationPolicy.update({ @@ -95,8 +95,8 @@ export const updatePolicyAction = authActionClient data: { content: processedContent.content }, }); - revalidatePath(`/policies/${id}`); - revalidatePath("/policies"); + revalidatePath(`/${user.organizationId}/policies/${id}`); + revalidatePath(`/${user.organizationId}/policies`); revalidateTag(`user_${user.id}`); return { 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 74dfef2371..3743c528e2 100644 --- a/apps/app/src/actions/policies/update-policy-form-action.ts +++ b/apps/app/src/actions/policies/update-policy-form-action.ts @@ -11,7 +11,7 @@ import { PolicyStatus } from "@bubba/db/types"; // Helper function to calculate next review date based on frequency function calculateNextReviewDate( frequency: string, - baseDate: Date = new Date(), + baseDate: Date = new Date() ): Date { const nextDate = new Date(baseDate); @@ -43,8 +43,15 @@ export const updatePolicyFormAction = authActionClient }, }) .action(async ({ parsedInput, ctx }) => { - const { id, status, ownerId, department, review_frequency, review_date, isRequiredToSign } = - parsedInput; + const { + id, + status, + ownerId, + department, + review_frequency, + review_date, + isRequiredToSign, + } = parsedInput; const { user } = ctx; if (!user.id || !user.organizationId) { @@ -95,26 +102,26 @@ export const updatePolicyFormAction = authActionClient if (isRequiredToSign !== undefined) { const orgPolicy = await db.organizationPolicy.findUnique({ where: { id }, - select: { + select: { policyId: true, }, }); - + if (orgPolicy?.policyId) { // Update the policy using the Prisma client with type assertion await db.policy.update({ where: { id: orgPolicy.policyId }, - data: { + data: { // Use type assertion to handle the new field // that might not be in the generated types yet - isRequiredToSign: isRequiredToSign === "required" + isRequiredToSign: isRequiredToSign === "required", } as any, }); } } - revalidatePath("/policies"); - revalidatePath(`/policies/all/${id}`); + revalidatePath(`/${user.organizationId}/policies`); + revalidatePath(`/${user.organizationId}/policies/all/${id}`); revalidateTag("policies"); return { 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 f3e9830ac6..4a821c12f6 100644 --- a/apps/app/src/actions/policies/update-policy-overview-action.ts +++ b/apps/app/src/actions/policies/update-policy-overview-action.ts @@ -51,15 +51,17 @@ export const updatePolicyOverviewAction = authActionClient description, // Use type assertion to handle the new field // that might not be in the generated types yet - ...(isRequiredToSign !== undefined ? { isRequiredToSign: isRequiredToSign === "required" } as any : {}), + ...(isRequiredToSign !== undefined + ? ({ isRequiredToSign: isRequiredToSign === "required" } as any) + : {}), }, }, }, }); - revalidatePath(`/policies/all/${id}`); - revalidatePath("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/policies/all"); - revalidatePath("/policies"); + revalidatePath(`/${user.organizationId}/policies/all/${id}`); + revalidatePath(`/${user.organizationId}/policies/all`); + revalidatePath(`/${user.organizationId}/policies`); return { success: true, diff --git a/apps/app/src/actions/policy/create-policy-comment.ts b/apps/app/src/actions/policy/create-policy-comment.ts index d896b50112..7e286b22a4 100644 --- a/apps/app/src/actions/policy/create-policy-comment.ts +++ b/apps/app/src/actions/policy/create-policy-comment.ts @@ -31,7 +31,7 @@ export const createPolicyCommentAction = authActionClient }, }); - revalidatePath(`/policies/all/${policyId}`); + revalidatePath(`/${user.organizationId}/policies/all/${policyId}`); revalidateTag(`policy_${user.organizationId}`); return { success: true }; diff --git a/apps/app/src/actions/risk/create-risk-action.ts b/apps/app/src/actions/risk/create-risk-action.ts index 39a4dbf4de..f30f6f3862 100644 --- a/apps/app/src/actions/risk/create-risk-action.ts +++ b/apps/app/src/actions/risk/create-risk-action.ts @@ -38,8 +38,8 @@ export const createRiskAction = authActionClient }, }); - revalidatePath("/risk"); - revalidatePath("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/risk/register"); + revalidatePath(`/${user.organizationId}/risk`); + revalidatePath(`/${user.organizationId}/risk/register`); revalidateTag(`risk_${user.organizationId}`); return { diff --git a/apps/app/src/actions/risk/create-risk-comment.ts b/apps/app/src/actions/risk/create-risk-comment.ts index d06ab8d3f4..030cf6a63a 100644 --- a/apps/app/src/actions/risk/create-risk-comment.ts +++ b/apps/app/src/actions/risk/create-risk-comment.ts @@ -33,8 +33,8 @@ export const createRiskCommentAction = authActionClient }, }); - revalidatePath(`/risk/${riskId}`); - revalidatePath(`/risk/${riskId}/comments`); + revalidatePath(`/${user.organizationId}/risk/${riskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}/comments`); revalidateTag(`risk_${user.organizationId}`); return { success: true }; diff --git a/apps/app/src/actions/risk/task/create-task-action.ts b/apps/app/src/actions/risk/task/create-task-action.ts index 25a61a047e..cf4a1da3b1 100644 --- a/apps/app/src/actions/risk/task/create-task-action.ts +++ b/apps/app/src/actions/risk/task/create-task-action.ts @@ -36,7 +36,7 @@ export const createTaskAction = authActionClient }, }); - revalidatePath(`/risk/${riskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}`); revalidateTag(`risk_${user.organizationId}`); return { diff --git a/apps/app/src/actions/risk/task/create-task-comment.ts b/apps/app/src/actions/risk/task/create-task-comment.ts index ed94094d82..488d32ceb1 100644 --- a/apps/app/src/actions/risk/task/create-task-comment.ts +++ b/apps/app/src/actions/risk/task/create-task-comment.ts @@ -34,8 +34,8 @@ export const createTaskCommentAction = authActionClient }, }); - revalidatePath(`/risk/${riskId}`); - revalidatePath(`/risk/${riskId}/tasks/${taskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}/tasks/${taskId}`); revalidateTag(`risk_${user.organizationId}`); return { success: true }; diff --git a/apps/app/src/actions/risk/task/revalidate-upload.ts b/apps/app/src/actions/risk/task/revalidate-upload.ts index b2cbf78339..52bc32d218 100644 --- a/apps/app/src/actions/risk/task/revalidate-upload.ts +++ b/apps/app/src/actions/risk/task/revalidate-upload.ts @@ -22,8 +22,8 @@ export const revalidateUpload = authActionClient throw new Error("Invalid user input"); } - revalidatePath(`/risk/${riskId}`); - revalidatePath(`/risk/${riskId}/tasks/${taskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}`); + revalidatePath(`/${user.organizationId}/risk/${riskId}/tasks/${taskId}`); revalidateTag("risk-cache"); return { diff --git a/apps/app/src/actions/risk/task/update-task-action.ts b/apps/app/src/actions/risk/task/update-task-action.ts index b14b4dc827..709a1bc3fb 100644 --- a/apps/app/src/actions/risk/task/update-task-action.ts +++ b/apps/app/src/actions/risk/task/update-task-action.ts @@ -53,9 +53,11 @@ export const updateTaskAction = authActionClient }, }); - revalidatePath("/risk"); - revalidatePath(`/risk/${riskId.riskId}`); - revalidatePath(`/risk/${riskId.riskId}/tasks/${id}`); + revalidatePath(`/${user.organizationId}/risk`); + revalidatePath(`/${user.organizationId}/risk/${riskId.riskId}`); + revalidatePath( + `/${user.organizationId}/risk/${riskId.riskId}/tasks/${id}` + ); revalidateTag("risks"); return { success: true }; diff --git a/apps/app/src/actions/risk/update-inherent-risk-action.ts b/apps/app/src/actions/risk/update-inherent-risk-action.ts index e2ef247821..a612dfe699 100644 --- a/apps/app/src/actions/risk/update-inherent-risk-action.ts +++ b/apps/app/src/actions/risk/update-inherent-risk-action.ts @@ -34,9 +34,9 @@ export const updateInherentRiskAction = authActionClient }, }); - revalidatePath("/risk"); - revalidatePath("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/risk/register"); - revalidatePath(`/risk/${id}`); + revalidatePath(`/${user.organizationId}/risk`); + revalidatePath(`/${user.organizationId}/risk/register`); + revalidatePath(`/${user.organizationId}/risk/${id}`); revalidateTag("risks"); return { diff --git a/apps/app/src/actions/risk/update-residual-risk-action.ts b/apps/app/src/actions/risk/update-residual-risk-action.ts index 325d3152ca..23325bf7e1 100644 --- a/apps/app/src/actions/risk/update-residual-risk-action.ts +++ b/apps/app/src/actions/risk/update-residual-risk-action.ts @@ -34,9 +34,9 @@ export const updateResidualRiskAction = authActionClient }, }); - revalidatePath("/risk"); - revalidatePath("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/risk/register"); - revalidatePath(`/risk/${id}`); + revalidatePath(`/${user.organizationId}/risk`); + revalidatePath(`/${user.organizationId}/risk/register`); + revalidatePath(`/${user.organizationId}/risk/${id}`); revalidateTag("risks"); return { diff --git a/apps/app/src/actions/risk/update-risk-action.ts b/apps/app/src/actions/risk/update-risk-action.ts index 57573056df..a32b31f841 100644 --- a/apps/app/src/actions/risk/update-risk-action.ts +++ b/apps/app/src/actions/risk/update-risk-action.ts @@ -41,9 +41,9 @@ export const updateRiskAction = authActionClient }, }); - revalidatePath("/risk"); - revalidatePath("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/risk/register"); - revalidatePath(`/risk/${id}`); + revalidatePath(`/${user.organizationId}/risk`); + revalidatePath(`/${user.organizationId}/risk/register`); + revalidatePath(`/${user.organizationId}/risk/${id}`); revalidateTag("risks"); return { diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx deleted file mode 100644 index 6ea731744b..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx +++ /dev/null @@ -1,181 +0,0 @@ -"use client"; - -import { useComplianceScores } from "@/hooks/use-compliance-scores"; -import { useI18n } from "@/locales/client"; -import type { - Framework, - OrganizationControl, - OrganizationFramework, -} from "@bubba/db/types"; -import { Button } from "@bubba/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; -import { FileStack } from "lucide-react"; -import Link from "next/link"; -import { useMediaQuery } from "@bubba/ui/hooks"; -import type { ReactNode } from "react"; - -interface Props { - frameworks: (OrganizationFramework & { - organizationControl: OrganizationControl[]; - framework: Framework; - })[]; -} - -export function FrameworkProgress({ frameworks }: Props) { - const t = useI18n(); - const { - policiesCompliance, - evidenceTasksCompliance, - cloudTestsCompliance, - overallCompliance, - isLoading, - } = useComplianceScores({ frameworks }); - - const isMobile = useMediaQuery("(max-width: 640px)"); - - const CircleProgress = ({ - percentage, - label, - href, - }: { - percentage: number; - label: ReactNode; - href: string; - }) => ( - -
- - {/* Background circle */} - - {/* Progress circle */} - - -
-
{percentage}%
-
-
-
- {label} -
- - ); - - return ( - - - {t("frameworks.overview.progress.title")} - - - {isLoading ? ( -
-
-

- Loading compliance data... -

-
- ) : frameworks.length === 0 ? ( -
- -

- {t("frameworks.overview.progress.empty.title")} -

-

- {t("frameworks.overview.progress.empty.description")} -

- -
- ) : ( -
- {/* Main compliance circle */} -
-
- - {/* Background circle */} - - {/* Progress circle */} - - -
-
- {overallCompliance}% -
-
Compliant
-
-
-
- - {/* Three smaller circles */} -
- - - Evidence - Evidence Tasks - - } - href="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/evidence/list" - /> - - Tests - Cloud Tests - - } - href="/tests" - /> -
-
- )} - - - ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/getFrameworkCategoriesAction.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/getFrameworkCategoriesAction.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/getFrameworkCategoriesAction.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/getFrameworkCategoriesAction.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/getFrameworksAction.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/getFrameworksAction.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/getFrameworksAction.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/getFrameworksAction.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/selectFrameworksAction.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/selectFrameworksAction.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/actions/selectFrameworksAction.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/actions/selectFrameworksAction.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworkProgress.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworkProgress.tsx new file mode 100644 index 0000000000..87cf230595 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworkProgress.tsx @@ -0,0 +1,184 @@ +"use client"; + +import { useComplianceScores } from "@/hooks/use-compliance-scores"; +import { useI18n } from "@/locales/client"; +import type { + Framework, + OrganizationControl, + OrganizationFramework, +} from "@bubba/db/types"; +import { Button } from "@bubba/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; +import { FileStack } from "lucide-react"; +import Link from "next/link"; +import { useMediaQuery } from "@bubba/ui/hooks"; +import type { ReactNode } from "react"; +import { useParams } from "next/navigation"; +import { auth } from "@/auth"; +interface Props { + frameworks: (OrganizationFramework & { + organizationControl: OrganizationControl[]; + framework: Framework; + })[]; +} + +export function FrameworkProgress({ frameworks }: Props) { + const t = useI18n(); + const { + policiesCompliance, + evidenceTasksCompliance, + cloudTestsCompliance, + overallCompliance, + isLoading, + } = useComplianceScores({ frameworks }); + + const { orgId } = useParams<{ orgId: string }>(); + + const isMobile = useMediaQuery("(max-width: 640px)"); + + const CircleProgress = ({ + percentage, + label, + href, + }: { + percentage: number; + label: ReactNode; + href: string; + }) => ( + +
+ + {/* Background circle */} + + {/* Progress circle */} + + +
+
{percentage}%
+
+
+
+ {label} +
+ + ); + + return ( + + + {t("frameworks.overview.progress.title")} + + + {isLoading ? ( +
+
+

+ Loading compliance data... +

+
+ ) : frameworks.length === 0 ? ( +
+ +

+ {t("frameworks.overview.progress.empty.title")} +

+

+ {t("frameworks.overview.progress.empty.description")} +

+ +
+ ) : ( +
+ {/* Main compliance circle */} +
+
+ + {/* Background circle */} + + {/* Progress circle */} + + +
+
+ {overallCompliance}% +
+
Compliant
+
+
+
+ + {/* Three smaller circles */} +
+ + + Evidence + Evidence Tasks + + } + href={`/${orgId}/evidence/list`} + /> + + Tests + Cloud Tests + + } + href={`/${orgId}/tests`} + /> +
+
+ )} + + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworksGrid.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworksGrid.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworksGrid.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworksGrid.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworksOverview.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworksOverview.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworksOverview.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/FrameworksOverview.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/RequirementStatusChart.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/components/RequirementStatusChart.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/hooks/useFrameworks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/hooks/useFrameworks.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/hooks/useFrameworks.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/hooks/useFrameworks.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/layout.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/layout.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/layout.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationCategories.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationCategories.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationCategories.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationCategories.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationControlsProgress.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationControlsProgress.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationControlsProgress.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationControlsProgress.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationFramework.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationFramework.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationFramework.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/actions/getOrganizationFramework.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/FrameworkControls.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkControls.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/FrameworkControls.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkControls.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/FrameworkOverview.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkOverview.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/FrameworkOverview.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/FrameworkOverview.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTable.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTable.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableColumns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableColumns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableColumns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableHeader.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableHeader.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/components/table/FrameworkControlsTableHeader.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationFramework.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationFramework.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationFramework.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/hooks/useOrganizationFramework.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/[frameworkId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/[frameworkId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControl.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControl.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControl.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControl.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlProgress.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlProgress.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlProgress.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlProgress.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlRequirements.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlRequirements.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlRequirements.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/actions/getOrganizationControlRequirements.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/SingleControl.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/SingleControl.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/SingleControl.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/SingleControl.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/SingleControlSkeleton.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/SingleControlSkeleton.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/SingleControlSkeleton.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/SingleControlSkeleton.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx similarity index 88% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx index 660ffaa0ef..8b6972d6b9 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTable.tsx @@ -9,7 +9,7 @@ import { import { Table, TableBody, TableCell, TableRow } from "@bubba/ui/table"; import { ControlRequirementsTableColumns } from "./ControlRequirementsTableColumns"; import { ControlRequirementsTableHeader } from "./ControlRequirementsTableHeader"; -import { useRouter } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import type { OrganizationControlRequirement, OrganizationEvidence, @@ -28,6 +28,7 @@ interface DataTableProps { export function ControlRequirementsTable({ data }: DataTableProps) { const router = useRouter(); + const { orgId } = useParams<{ orgId: string }>(); const table = useReactTable({ data, @@ -42,12 +43,16 @@ export function ControlRequirementsTable({ data }: DataTableProps) { switch (requirement.type) { case "policy": if (requirement.organizationPolicyId) { - router.push(`/policies/all/${requirement.organizationPolicyId}`); + router.push( + `/${orgId}/policies/all/${requirement.organizationPolicyId}`, + ); } break; case "evidence": if (requirement.organizationEvidenceId) { - router.push(`/evidence/${requirement.organizationEvidenceId}`); + router.push( + `/${orgId}/evidence/${requirement.organizationEvidenceId}`, + ); } break; default: diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableColumns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableColumns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableColumns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableHeader.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableHeader.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/ControlRequirementsTableHeader.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/components/table/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/components/table/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControl.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControl.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControl.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControl.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlProgress.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlProgress.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlProgress.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlProgress.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlRequirements.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlRequirements.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlRequirements.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/hooks/useOrganizationControlRequirements.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/overview/frameworks/controls/[id]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/overview/frameworks/controls/[id]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/(home)/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/(home)/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/error.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/error.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/error.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/error.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/PathProvider.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/PathProvider.tsx new file mode 100644 index 0000000000..aebd8d008e --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/PathProvider.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; +import { useEffect, useState } from "react"; + +interface PathProviderProps { + organizationId: string; + translationBackButton: string; + translationOverview: string; + translationEdit: string; +} + +export function PathProvider({ + organizationId, + translationBackButton, + translationOverview, + translationEdit, +}: PathProviderProps) { + const pathname = usePathname(); + const [evidenceId, setEvidenceId] = useState(""); + + useEffect(() => { + const pathParts = pathname.split("/"); + const evidenceIndex = pathParts.findIndex((part) => part === "evidence"); + const id = + evidenceIndex >= 0 && pathParts.length > evidenceIndex + 1 + ? pathParts[evidenceIndex + 1] + : ""; + + // Only update if we've got a valid ID + if (id && id !== "list") { + setEvidenceId(id); + } + }, [pathname]); + + // The back button should always be visible when we're in any evidence detail page + // This is when we have an evidence ID in the URL that's not "list" + const showBackButton = evidenceId !== ""; + + // Format paths exactly as they would appear in the URL to ensure matching works correctly + const overviewPath = `/${organizationId}/evidence/${evidenceId}`; + const editPath = `/${organizationId}/evidence/${evidenceId}/edit`; + + return ( + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/assignEvidence.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/assignEvidence.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/assignEvidence.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/assignEvidence.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/deleteEvidenceFile.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/deleteEvidenceFile.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/deleteEvidenceFile.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/deleteEvidenceFile.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/getEvidenceFileUrl.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/getEvidenceFileUrl.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/getEvidenceFileUrl.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/getEvidenceFileUrl.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/publishEvidence.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/publishEvidence.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/publishEvidence.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/publishEvidence.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/toggleRelevance.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/toggleRelevance.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/toggleRelevance.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/toggleRelevance.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceDepartment.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceDepartment.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceDepartment.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceDepartment.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceFrequency.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceFrequency.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceFrequency.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceFrequency.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceUrls.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceUrls.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/updateEvidenceUrls.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/updateEvidenceUrls.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/uploadEvidenceFile.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/uploadEvidenceFile.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/actions/uploadEvidenceFile.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/actions/uploadEvidenceFile.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/AssigneeSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/AssigneeSection.tsx new file mode 100644 index 0000000000..b2ac857217 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/AssigneeSection.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@bubba/ui/select"; +import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; +import { useAction } from "next-safe-action/hooks"; +import { toast } from "sonner"; +import { assignEvidence } from "../actions/assignEvidence"; +import { useOrganizationAdmins } from "../../../hooks/useOrganizationAdmins"; +import type { Admin } from "../../../hooks/useOrganizationAdmins"; + +interface AssigneeSectionProps { + evidenceId: string; + currentAssigneeId: string | null | undefined; + onSuccess: () => Promise; +} + +export function AssigneeSection({ + evidenceId, + currentAssigneeId, + onSuccess, +}: AssigneeSectionProps) { + const [assigneeId, setAssigneeId] = useState( + currentAssigneeId || null, + ); + const { data: admins, isLoading, error } = useOrganizationAdmins(); + const [selectedAdmin, setSelectedAdmin] = useState(null); + + const { execute: updateAssignee, isExecuting } = useAction(assignEvidence, { + onSuccess: async () => { + toast.success("Assignee updated successfully"); + await onSuccess(); + }, + onError: () => { + toast.error("Failed to update assignee"); + }, + }); + + useEffect(() => { + setAssigneeId(currentAssigneeId || null); + }, [currentAssigneeId]); + + useEffect(() => { + if (admins && assigneeId) { + const admin = admins.find((a) => a.id === assigneeId); + setSelectedAdmin(admin || null); + } else { + setSelectedAdmin(null); + } + }, [admins, assigneeId]); + + const handleAssigneeChange = (value: string) => { + const newAssigneeId = value === "none" ? null : value; + setAssigneeId(newAssigneeId); + + if (newAssigneeId && admins) { + const admin = admins.find((a) => a.id === newAssigneeId); + setSelectedAdmin(admin || null); + } else { + setSelectedAdmin(null); + } + + updateAssignee({ id: evidenceId, assigneeId: newAssigneeId }); + }; + + if (isLoading) { + return
; + } + + if (error || !admins) { + return

Failed to load

; + } + + return ( +
+ +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/DepartmentSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/DepartmentSection.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/DepartmentSection.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/DepartmentSection.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/EvidenceDetails.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/EvidenceDetails.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileCard.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileCard.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileCard.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileCard.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileIcon.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileIcon.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileIcon.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileIcon.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileSection.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileSection.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileSection.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileUpload.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileUpload.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FileUpload.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FileUpload.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FrequencySection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FrequencySection.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/FrequencySection.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/FrequencySection.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewDateCard.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/ReviewDateCard.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewDateCard.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/ReviewDateCard.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/ReviewSection.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/ReviewSection.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/UrlSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/UrlSection.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/UrlSection.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/components/UrlSection.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/edit/components/EditEvidenceForm.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/edit/components/EditEvidenceForm.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/edit/components/EditEvidenceForm.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/edit/components/EditEvidenceForm.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/edit/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/edit/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/edit/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/edit/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFileDelete.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFileDelete.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFileDelete.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFileDelete.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFilePreview.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFilePreview.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFilePreview.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFilePreview.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFileUpload.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFileUpload.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useFileUpload.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useFileUpload.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationEvidence.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useOrganizationEvidence.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationEvidence.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useOrganizationEvidence.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useUrlManagement.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useUrlManagement.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useUrlManagement.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/hooks/useUrlManagement.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/layout.tsx new file mode 100644 index 0000000000..5b3c097905 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/layout.tsx @@ -0,0 +1,26 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { PathProvider } from "./PathProvider"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[id]/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getAllOrganizationControlRequirements.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getAllOrganizationControlRequirements.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getAllOrganizationControlRequirements.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getAllOrganizationControlRequirements.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getEvidence.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getEvidence.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getEvidence.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getEvidence.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getEvidenceTasksStats.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getEvidenceTasksStats.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getEvidenceTasksStats.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getEvidenceTasksStats.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationAdmins.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationAdmins.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationAdmins.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationAdmins.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEmployees.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEmployees.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEmployees.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEmployees.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEvidence.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEvidence.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEvidence.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEvidence.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEvidenceTasks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEvidenceTasks.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/actions/getOrganizationEvidenceTasks.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/actions/getOrganizationEvidenceTasks.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/constants.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/constants.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/constants.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/constants.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTasks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/hooks/useEvidenceTasks.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTasks.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/hooks/useEvidenceTasks.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTasksStats.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/hooks/useEvidenceTasksStats.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTasksStats.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/hooks/useEvidenceTasksStats.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceList.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceList.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceListUIStates.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceListUIStates.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceSummaryCards.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceSummaryCards.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceSummaryCards.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/EvidenceSummaryCards.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceFilters/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceFilters/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListColumns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListColumns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListColumns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListHeader.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListHeader.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListHeader.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListTable.tsx similarity index 92% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListTable.tsx index fdbe556334..685905dd6d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/EvidenceListTable.tsx @@ -3,12 +3,14 @@ import { DataTable } from "@/components/ui/data-table"; import { EvidenceListColumns } from "./EvidenceListColumns"; import type { EvidenceTaskRow } from "../../types"; -import { useRouter } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { useEvidenceTable } from "../../hooks/useEvidenceTableContext"; import { getFilterCategories } from "./components/filterCategories"; export function EvidenceListTable({ data }: { data: EvidenceTaskRow[] }) { const router = useRouter(); + const { orgId } = useParams<{ orgId: string }>(); + const { page, setPage, @@ -37,7 +39,7 @@ export function EvidenceListTable({ data }: { data: EvidenceTaskRow[] }) { } = useEvidenceTable(); const handleRowClick = (evidenceId: string) => { - router.push(`/evidence/${evidenceId}`); + router.push(`/${orgId}/evidence/${evidenceId}`); }; const activeFilterCount = [ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/AssigneeAvatar.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/AssigneeAvatar.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/AssigneeAvatar.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/AssigneeAvatar.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/filterCategories.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/filterCategories.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/filterCategories.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/filterCategories.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/filterConfigs.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/filterConfigs.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/components/filterConfigs.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/filterConfigs.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/hooks/useEvidenceTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/hooks/useEvidenceTableContext.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/hooks/useEvidenceTableContext.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/hooks/useEvidenceTableContext.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/layout.tsx new file mode 100644 index 0000000000..12a3e9d9cb --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/layout.tsx @@ -0,0 +1,33 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/loading.tsx new file mode 100644 index 0000000000..a683eafd55 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/loading.tsx @@ -0,0 +1,16 @@ +import { DataTableSkeleton } from "@/components/ui/data-table"; +import { Card } from "@bubba/ui/card"; + +export default function EvidenceListLoading() { + return ( + + + + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AnimatedBar.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AnimatedBar.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AnimatedBar.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AnimatedBar.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AssigneeChart/AssigneeBarChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AssigneeChart/AssigneeBarChart.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AssigneeChart/AssigneeBarChart.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AssigneeChart/AssigneeBarChart.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AssigneeChart/AssigneeChartRow.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AssigneeChart/AssigneeChartRow.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/AssigneeChart/AssigneeChartRow.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/AssigneeChart/AssigneeChartRow.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/DepartmentChart/DepartmentBarChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/DepartmentChart/DepartmentBarChart.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/DepartmentChart/DepartmentBarChart.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/DepartmentChart/DepartmentBarChart.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/DepartmentChart/DepartmentChartRow.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/DepartmentChart/DepartmentChartRow.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/DepartmentChart/DepartmentChartRow.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/DepartmentChart/DepartmentChartRow.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/EvidenceOverview.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/EvidenceOverview.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/EvidenceOverview.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/EvidenceOverview.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/EvidenceUIStates.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/EvidenceUIStates.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/EvidenceUIStates.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/EvidenceUIStates.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/FrameworkChart/FrameworkBarChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/FrameworkChart/FrameworkBarChart.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/FrameworkChart/FrameworkBarChart.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/FrameworkChart/FrameworkBarChart.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/FrameworkChart/FrameworkChartRow.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/FrameworkChart/FrameworkChartRow.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/FrameworkChart/FrameworkChartRow.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/FrameworkChart/FrameworkChartRow.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/HorizontalBarChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/HorizontalBarChart.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/components/HorizontalBarChart.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/components/HorizontalBarChart.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/constants/departments.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/constants/departments.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/constants/departments.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/constants/departments.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/constants/evidence-status.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/constants/evidence-status.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/constants/evidence-status.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/constants/evidence-status.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/data/getEvidenceDashboard.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/data/getEvidenceDashboard.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/data/getEvidenceDashboard.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/data/getEvidenceDashboard.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useAssigneeData.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useAssigneeData.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useAssigneeData.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useAssigneeData.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useEvidenceData.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useEvidenceData.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useEvidenceData.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useEvidenceData.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useFrameworkData.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useFrameworkData.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useFrameworkData.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useFrameworkData.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useScrollPosition.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useScrollPosition.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useScrollPosition.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useScrollPosition.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useStatusTranslation.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useStatusTranslation.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/hooks/useStatusTranslation.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/hooks/useStatusTranslation.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/layout.tsx new file mode 100644 index 0000000000..12a3e9d9cb --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/layout.tsx @@ -0,0 +1,33 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/loading.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/loading.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/loading.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/overview/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/hooks/useOrganizationAdmins.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins.ts similarity index 90% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/hooks/useOrganizationAdmins.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins.ts index 66d94fbc7a..b1712d344b 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/hooks/useOrganizationAdmins.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins.ts @@ -1,7 +1,7 @@ "use client"; import useSWR from "swr"; -import { getOrganizationAdmins } from "../../actions/getOrganizationAdmins"; +import { getOrganizationAdmins } from "../evidence/actions/getOrganizationAdmins"; export interface Admin { id: string; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/integrations/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/integrations/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/integrations/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/integrations/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx new file mode 100644 index 0000000000..b14581486d --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx @@ -0,0 +1,46 @@ +import { auth } from "@/auth"; +import { Header } from "@/components/header"; +import { Sidebar } from "@/components/sidebar"; +import { db } from "@bubba/db"; +import dynamic from "next/dynamic"; +import { redirect } from "next/navigation"; +import { cache } from "react"; + +const HotKeys = dynamic( + () => import("@/components/hot-keys").then((mod) => mod.HotKeys), + { + ssr: true, + }, +); + +export default async function Layout({ + children, + params, +}: { + children: React.ReactNode; + params: Promise<{ orgId: string }>; +}) { + const session = await auth(); + const orgId = (await params).orgId; + + if (!session) { + redirect("/auth"); + } + + if (!orgId) { + redirect("/"); + } + + return ( +
+ + +
+
+
{children}
+
+ + +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/actions/get-employee-details.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/get-employee-details.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/actions/get-employee-details.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/get-employee-details.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/actions/update-department.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-department.ts similarity index 94% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/actions/update-department.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-department.ts index b9d9a7dee0..0621492e89 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/actions/update-department.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-department.ts @@ -63,8 +63,8 @@ export const updateEmployeeDepartment = authActionClient }); // Revalidate related paths - revalidatePath(`/people/${employeeId}`); - revalidatePath("/people"); + revalidatePath(`/${organizationId}/people/${employeeId}`); + revalidatePath(`/${organizationId}/people`); return { success: true, diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/components/EditableDepartment.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/components/EditableDepartment.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EmployeeDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/components/EmployeeDetails.tsx similarity index 95% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EmployeeDetails.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/components/EmployeeDetails.tsx index d68e9fa6e3..010309b3b3 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EmployeeDetails.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/components/EmployeeDetails.tsx @@ -1,6 +1,6 @@ "use client"; -import { redirect } from "next/navigation"; +import { redirect, useParams } from "next/navigation"; import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; import { useI18n } from "@/locales/client"; import { useEmployeeDetails } from "../../hooks/useEmployee"; @@ -18,12 +18,12 @@ interface EmployeeDetailsProps { } export function EmployeeDetails({ employeeId }: EmployeeDetailsProps) { - const t = useI18n(); const { employee, isLoading, error, mutate } = useEmployeeDetails(employeeId); + const { orgId } = useParams<{ orgId: string }>(); if (error) { if (error.code === "NOT_FOUND") { - redirect("/people"); + redirect(`/${orgId}/people`); } return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/types/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/actions/get-employees.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/actions/get-employees.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/actions/get-employees.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/actions/get-employees.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/EmployeesList.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesList.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/EmployeesList.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/EmployeesListSkeleton.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/EmployeesListSkeleton.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/EmployeesTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/EmployeesTable.tsx similarity index 91% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/EmployeesTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/EmployeesTable.tsx index 0cb71c46c8..f6c0c21e4c 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/EmployeesTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/EmployeesTable.tsx @@ -3,7 +3,7 @@ import { EmployeeInviteSheet } from "@/components/sheets/invite-user-sheet"; import { DataTable } from "@/components/ui/data-table"; import { Plus } from "lucide-react"; -import { useRouter } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { useQueryState } from "nuqs"; import { getColumns } from "./columns"; import { useEmployeesTable } from "./hooks/useEmployeesTableContext"; @@ -11,6 +11,8 @@ import { useEmployeesTable } from "./hooks/useEmployeesTableContext"; export function EmployeesTable() { const router = useRouter(); const [, setInviteSheetOpen] = useQueryState("invite-user-sheet"); + const { orgId } = useParams<{ orgId: string }>(); + const { page, setPage, @@ -25,7 +27,7 @@ export function EmployeesTable() { } = useEmployeesTable(); const handleRowClick = (employeeId: string) => { - router.push(`/people/${employeeId}`); + router.push(`/${orgId}/people/${employeeId}`); }; // Calculate pagination values only when total is defined diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/columns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/columns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/columns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/components/filterCategories.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/components/filterCategories.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/components/filterCategories.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/components/filterCategories.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/hooks/useEmployeesTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/hooks/useEmployeesTableContext.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/hooks/useEmployeesTableContext.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/hooks/useEmployeesTableContext.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/components/table/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/components/table/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/hooks/useEmployee.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/hooks/useEmployee.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/hooks/useEmployee.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/hooks/useEmployee.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/hooks/useEmployees.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/hooks/useEmployees.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/hooks/useEmployees.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/hooks/useEmployees.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/layout.tsx similarity index 56% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/layout.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/layout.tsx index 5b647fae1f..b101e64580 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/layout.tsx @@ -1,3 +1,4 @@ +import { auth } from "@/auth"; import { getI18n } from "@/locales/server"; import { SecondaryMenu } from "@bubba/ui/secondary-menu"; @@ -7,15 +8,16 @@ export default async function Layout({ children: React.ReactNode; }) { const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const orgId = user?.organizationId; return ( -
+
+
{children}
); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/types/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types/search-params.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/people/types/search-params.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/types/search-params.ts 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 new file mode 100644 index 0000000000..ed7c769148 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/layout.tsx @@ -0,0 +1,33 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx new file mode 100644 index 0000000000..662593c842 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx @@ -0,0 +1,111 @@ +import { auth } from "@/auth"; +import { PoliciesStatus } from "@/components/policies/charts/policies-status"; +import { PoliciesByAssignee } from "@/components/policies/charts/policies-by-assignee"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import { unstable_cache } from "next/cache"; +import { redirect } from "next/navigation"; + +export default async function PoliciesOverview({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setStaticParamsLocale(locale); + + const session = await auth(); + + if (!session?.user?.organizationId) { + redirect("/onboarding"); + } + + const overview = await getPoliciesOverview(session.user.organizationId); + + if (overview?.totalPolicies === 0) { + redirect(`/${session.user.organizationId}/policies/all`); + } + + return ( +
+
+ + +
+
+ ); +} +const getPoliciesOverview = unstable_cache( + async (organizationId: string) => { + return await db.$transaction(async (tx) => { + const [ + totalPolicies, + publishedPolicies, + draftPolicies, + archivedPolicies, + needsReviewPolicies, + ] = await Promise.all([ + tx.organizationPolicy.count({ + where: { + organizationId, + }, + }), + tx.organizationPolicy.count({ + where: { + organizationId, + status: "published", + }, + }), + tx.organizationPolicy.count({ + where: { + organizationId, + status: "draft", + }, + }), + tx.organizationPolicy.count({ + where: { + organizationId, + status: "archived", + }, + }), + tx.organizationPolicy.count({ + where: { + organizationId, + status: "needs_review", + }, + }), + ]); + + return { + totalPolicies, + publishedPolicies, + draftPolicies, + archivedPolicies, + needsReviewPolicies, + }; + }); + }, + ["policies-overview-cache"], +); + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sidebar.policies"), + }; +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/actions/get-policies.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/actions/get-policies.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/actions/get-policies.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/actions/get-policies.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/PoliciesList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/PoliciesList.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/PoliciesList.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/PoliciesList.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/PoliciesListSkeleton.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/PoliciesListSkeleton.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/PoliciesListSkeleton.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/PoliciesListSkeleton.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/PoliciesTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/PoliciesTable.tsx similarity index 85% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/PoliciesTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/PoliciesTable.tsx index 6c87823857..42a1e911eb 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/PoliciesTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/PoliciesTable.tsx @@ -1,7 +1,7 @@ "use client"; import { DataTable } from "@/components/ui/data-table"; -import { useRouter } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { Plus } from "lucide-react"; import { getFilterCategories } from "./components/filterCategories"; import { getColumns } from "./columns"; @@ -10,6 +10,7 @@ import { usePoliciesTable } from "./hooks/usePoliciesTableContext"; export function PoliciesTable({ users }: PoliciesTableProps) { const router = useRouter(); + const { orgId } = useParams<{ orgId: string }>(); const { page, setPage, @@ -30,7 +31,7 @@ export function PoliciesTable({ users }: PoliciesTableProps) { } = usePoliciesTable(); const handleRowClick = (policyId: string) => { - router.push(`/policies/all/${policyId}`); + router.replace(`/${orgId}/policies/all/${policyId}`); }; const activeFilterCount = [status, ownerId].filter(Boolean).length; @@ -78,11 +79,11 @@ export function PoliciesTable({ users }: PoliciesTableProps) { onClearFilters: clearFilters, activeFilterCount, }} - ctaButton={{ - label: "Create Policy", - onClick: () => router.push("/policies/all/new"), - icon: , - }} + // ctaButton={{ + // label: "Create Policy", + // onClick: () => router.push(`/${orgId}/policies/all/new`), + // icon: , + // }} /> ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/columns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/columns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/columns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/components/filterCategories.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/components/filterCategories.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/components/filterCategories.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/components/filterCategories.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/components/filterConfigs.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/components/filterConfigs.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/components/filterConfigs.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/components/filterConfigs.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/hooks/usePoliciesTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/hooks/usePoliciesTableContext.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/hooks/usePoliciesTableContext.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/hooks/usePoliciesTableContext.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/utils.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/utils.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/components/table/utils.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/components/table/utils.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/hooks/usePolicies.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/hooks/usePolicies.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/hooks/usePolicies.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/hooks/usePolicies.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/hooks/usePolicy.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/hooks/usePolicy.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/hooks/usePolicy.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/hooks/usePolicy.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/(overview)/layout.tsx new file mode 100644 index 0000000000..ed7c769148 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/layout.tsx @@ -0,0 +1,33 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/types/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/types/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/types/search-params.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/types/search-params.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/(overview)/types/search-params.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/actions/get-policy-details.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/get-policy-details.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/actions/get-policy-details.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/get-policy-details.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/actions/update-policy.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/actions/update-policy.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/actions/update-policy.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[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/all/[policyId]/editor/components/PolicyDetails.tsx new file mode 100644 index 0000000000..a80bd88f09 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyDetails.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { Card, CardContent, CardHeader } from "@bubba/ui/card"; +import { Skeleton } from "@bubba/ui/skeleton"; +import { redirect, useParams } from "next/navigation"; +import { PolicyEditor } from "@/components/editor/policy-editor"; +import { usePolicyDetails } from "../../../(overview)/hooks/usePolicy"; +import type { JSONContent } from "@tiptap/react"; +import "@bubba/ui/editor.css"; + +interface PolicyDetailsProps { + policyId: string; +} + +export function PolicyDetails({ policyId }: PolicyDetailsProps) { + const { policy, isLoading, updatePolicy } = usePolicyDetails(policyId); + const { orgId } = useParams(); + + if (isLoading) { + return ( +
+
+ + +
+ + + + + +
+ + + +
+
+
+
+ ); + } + + if (!policy) return redirect(`/${orgId}/policies`); + + const formattedContent = Array.isArray(policy.content) + ? policy.content + : typeof policy.content === "object" && policy.content !== null + ? [policy.content as JSONContent] + : []; + + const handleSavePolicy = async (content: JSONContent[]): Promise => { + if (!policy) return; + + try { + await updatePolicy({ + ...policy, + content, + }); + } catch (error) { + console.error("Error saving policy:", error); + throw error; + } + }; + + return ( +
+ +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/components/PolicyHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/components/PolicyHeader.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/components/PolicyHeader.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[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/all/[policyId]/editor/page.tsx new file mode 100644 index 0000000000..dd96ab394d --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/page.tsx @@ -0,0 +1,30 @@ +import { getI18n } from "@/locales/server"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import { PolicyDetails } from "./components/PolicyDetails"; + +export default async function PolicyEditor({ + params, +}: { + params: Promise<{ locale: string; policyId: string }>; +}) { + const { locale, policyId } = await params; + setStaticParamsLocale(locale); + + return ; +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string; policyId: string }>; +}): Promise { + const { locale } = await params; + + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sub_pages.policies.policy_details"), + }; +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/editor/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[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/all/[policyId]/layout.tsx new file mode 100644 index 0000000000..90e98cfa72 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/layout.tsx @@ -0,0 +1,64 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; +import { redirect } from "next/navigation"; +import { cache } from "react"; + +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; + + const session = await auth(); + const organizationId = session?.user.organizationId; + + if (!organizationId) { + redirect("/"); + } + + const policy = await getPolicy(policyId, organizationId); + + if (!policy) { + redirect("/"); + } + + return ( +
+ + +
{children}
+
+ ); +} + +const getPolicy = cache(async (policyId: string, organizationId: string) => { + const policy = await db.organizationPolicy.findUnique({ + where: { + id: policyId, + organizationId: organizationId, + }, + include: { + policy: true, + }, + }); + + return 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/all/[policyId]/page.tsx new file mode 100644 index 0000000000..f07a4063d4 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/all/[policyId]/page.tsx @@ -0,0 +1,70 @@ +import { PolicyComment } from "@/components/policies/policy-comments"; +import { PolicyOverview } from "@/components/policies/policy-overview"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import { redirect } from "next/navigation"; +import { cache } from "react"; + +export default async function PolicyDetails({ + params, +}: { + params: Promise<{ locale: string; policyId: string; orgId: string }>; +}) { + const { locale, policyId, orgId } = await params; + setStaticParamsLocale(locale); + + const policy = await getPolicy(policyId, orgId); + const users = await getUsers(orgId); + + if (!policy) { + redirect(`/${orgId}/policies/all`); + } + + return ( +
+ + +
+ ); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string; policyId: string }>; +}): Promise { + const { locale } = await params; + + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sub_pages.policies.policy_details"), + }; +} + +const getPolicy = cache(async (policyId: string, organizationId: string) => { + const policy = await db.organizationPolicy.findUnique({ + where: { id: policyId, organizationId }, + include: { + policy: true, + PolicyComments: true, + }, + }); + return policy; +}); + +const getUsers = async (organizationId: string) => { + const orgMembers = await db.organizationMember.findMany({ + where: { organizationId: organizationId }, + include: { + user: true, + }, + }); + + const users = orgMembers.map((member) => member.user); + + return users; +}; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/layout.tsx new file mode 100644 index 0000000000..a4050bd3a9 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/layout.tsx @@ -0,0 +1,27 @@ +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; +import { auth } from "@/auth"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const orgId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx new file mode 100644 index 0000000000..16c79a8a47 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx @@ -0,0 +1,74 @@ +import { auth } from "@/auth"; +import { RiskOverview } from "@/components/risks/charts/risk-overview"; +import { RisksAssignee } from "@/components/risks/charts/risks-assignee"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import { unstable_cache } from "next/cache"; +import { redirect } from "next/navigation"; + +export default async function RiskManagement({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setStaticParamsLocale(locale); + + const session = await auth(); + + if (!session?.user?.organizationId) { + redirect("/onboarding"); + } + + const overview = await getRiskOverview(session.user.organizationId); + const orgId = session.user.organizationId; + + if (overview?.risks === 0) { + redirect(`/${orgId}/risk/register`); + } + + return ( +
+
+ +
+ +
+ +
+
+ ); +} + +const getRiskOverview = unstable_cache( + async (organizationId: string) => { + return await db.$transaction(async (tx) => { + const [risks] = await Promise.all([ + tx.risk.count({ + where: { organizationId }, + }), + ]); + + return { + risks, + }; + }); + }, + ["risk-overview-cache"], +); + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sidebar.risk"), + }; +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/comments/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/comments/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/comments/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/comments/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/layout.tsx new file mode 100644 index 0000000000..bf9900e16a --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/layout.tsx @@ -0,0 +1,65 @@ +import { auth } from "@/auth"; +import { Title } from "@/components/title"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; +import { unstable_cache } from "next/cache"; +import { redirect } from "next/navigation"; + +interface LayoutProps { + children: React.ReactNode; + params: Promise<{ riskId: string }>; +} + +export default async function Layout({ children, params }: LayoutProps) { + const t = await getI18n(); + const session = await auth(); + + if (!session || !session.user.organizationId) { + redirect("/"); + } + + const riskId = await params; + const risk = await getRisk(riskId.riskId, session.user.organizationId); + + if (!risk) { + redirect("/risk"); + } + + const orgId = session.user.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} + +const getRisk = unstable_cache( + async (riskId: string, organizationId: string) => { + const risk = await db.risk.findUnique({ + where: { + id: riskId, + organizationId: organizationId, + }, + }); + + return risk; + }, + ["risk-cache"], +); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/actions/getTaskAttachments.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/hooks/useTaskAttachments.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/[taskId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/[taskId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/search-params.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/tasks/search-params.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/tasks/search-params.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/RiskRegisterTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/RiskRegisterTable.tsx similarity index 94% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/RiskRegisterTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/RiskRegisterTable.tsx index 1b044a91fc..d409458dd1 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/RiskRegisterTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/RiskRegisterTable.tsx @@ -5,9 +5,10 @@ import { DataTable } from "@/components/ui/data-table"; import { useI18n } from "@/locales/client"; import type { Departments, Risk, RiskStatus, User } from "@bubba/db/types"; import { Plus } from "lucide-react"; +import { useParams } from "next/navigation"; import { useQueryState } from "nuqs"; import { useState } from "react"; -import { useOrganizationAdmins } from "../../evidence/[id]/hooks/useOrganizationAdmins"; +import { useOrganizationAdmins } from "../../hooks/useOrganizationAdmins"; import { columns } from "./components/table/RiskRegisterColumns"; import { RiskRegisterFilters } from "./components/table/RiskRegisterFilters"; import { useRisks } from "./hooks/useRisks"; @@ -16,6 +17,8 @@ type RiskRegisterTableRow = Risk & { owner: User | null }; export const RiskRegisterTable = () => { const t = useI18n(); + const { orgId } = useParams<{ orgId: string }>(); + // State const [search, setSearch] = useState(""); const [open, setOpen] = useQueryState("create-risk-sheet"); @@ -92,7 +95,7 @@ export const RiskRegisterTable = () => { return ( <> - columns={columns} + columns={columns(orgId)} data={data} isLoading={isLoading} search={{ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/actions/getRisks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/actions/getRisks.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/actions/getRisks.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/actions/getRisks.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterColumns.tsx similarity index 87% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterColumns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterColumns.tsx index 52fd8129b8..73de780daa 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterColumns.tsx @@ -5,13 +5,17 @@ import Link from "next/link"; import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; import { Status } from "@/components/status"; -export const columns: ColumnDef[] = [ +export const columns = ( + orgId: string, +): ColumnDef[] => [ { header: "Risk", accessorKey: "title", cell: ({ row }) => { return ( - {row.original.title} + + {row.original.title} + ); }, }, diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterFilters.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterFilters.tsx similarity index 95% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterFilters.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterFilters.tsx index 33a9ad3c22..7d7f7b99d2 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/components/table/RiskRegisterFilters.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/components/table/RiskRegisterFilters.tsx @@ -1,6 +1,6 @@ import { Status } from "@/components/status"; import { type Departments, RiskStatus } from "@bubba/db/types"; -import type { Admin } from "../../../../evidence/[id]/hooks/useOrganizationAdmins"; +import type { Admin } from "../../../../hooks/useOrganizationAdmins"; import { AssigneeAvatar } from "../../../../evidence/list/components/table/components/AssigneeAvatar"; export const RiskRegisterFilters = ({ diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/hooks/useRisks.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/hooks/useRisks.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/hooks/useRisks.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/hooks/useRisks.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/layout.tsx new file mode 100644 index 0000000000..cec65b4622 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/layout.tsx @@ -0,0 +1,27 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const orgId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/search-params.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/search-params.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/search-params.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/api-keys/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/loading.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/settings/api-keys/loading.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/loading.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/api-keys/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/settings/api-keys/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/api-keys/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/layout.tsx similarity index 55% rename from apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/layout.tsx index 8e70f1159c..56b4226399 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/layout.tsx @@ -1,3 +1,4 @@ +import { auth } from "@/auth"; import { getI18n } from "@/locales/server"; import { SecondaryMenu } from "@bubba/ui/secondary-menu"; import { Suspense } from "react"; @@ -8,17 +9,26 @@ export default async function Layout({ children: React.ReactNode; }) { const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const orgId = user?.organizationId; return (
Loading...
}> + + + + {t("settings.team.tabs.members")} + + + {t("settings.team.tabs.invite")} + + + + + + + {t("settings.team.members.title")} + + + + + + + + + +
+ + + {t("settings.team.invite.title")} + + {t("settings.team.invite.description")} + + + + + +
+
+
+ +
+ + +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/members/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/members/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx new file mode 100644 index 0000000000..dc0ffbb307 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx @@ -0,0 +1,32 @@ +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; +import { auth } from "@/auth"; +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const organizationId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx new file mode 100644 index 0000000000..53fce7bab4 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx @@ -0,0 +1,113 @@ +import { auth } from "@/auth"; +import { TestsSeverity } from "@/components/tests/charts/tests-severity"; +import { TestsByAssignee } from "@/components/tests/charts/tests-by-assignee"; +import { getI18n } from "@/locales/server"; +import { db } from "@bubba/db"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import { unstable_cache } from "next/cache"; +import { redirect } from "next/navigation"; + +export default async function TestsOverview({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + setStaticParamsLocale(locale); + + const session = await auth(); + + if (!session?.user?.organizationId) { + redirect("/onboarding"); + } + + const overview = await getTestsOverview(session.user.organizationId); + const organizationId = session.user.organizationId; + + if (overview?.totalTests === 0) { + redirect(`/${organizationId}/tests/all`); + } + + return ( +
+
+ + +
+
+ ); +} + +const getTestsOverview = unstable_cache( + async (organizationId: string) => { + return await db.$transaction(async (tx) => { + const [ + totalTests, + lowSeverityTests, + mediumSeverityTests, + highSeverityTests, + criticalSeverityTests, + ] = await Promise.all([ + tx.organizationIntegrationResults.count({ + where: { + organizationId, + }, + }), + tx.organizationIntegrationResults.count({ + where: { + organizationId, + label: "LOW", + }, + }), + tx.organizationIntegrationResults.count({ + where: { + organizationId, + label: "MEDIUM", + }, + }), + tx.organizationIntegrationResults.count({ + where: { + organizationId, + label: "HIGH", + }, + }), + tx.organizationIntegrationResults.count({ + where: { + organizationId, + label: "CRITICAL", + }, + }), + ]); + + return { + totalTests, + lowSeverityTests, + mediumSeverityTests, + highSeverityTests, + criticalSeverityTests, + }; + }); + }, + ["tests-overview-cache"], +); + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sidebar.tests"), + }; +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/assignTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/assignTest.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/assignTest.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/assignTest.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/createTestComment.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/createTestComment.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/createTestComment.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/createTestComment.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/getTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/getTest.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx new file mode 100644 index 0000000000..7a03b5680d --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@bubba/ui/select"; +import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; +import { useAction } from "next-safe-action/hooks"; +import { toast } from "sonner"; +import { assignTest } from "../actions/assignTest"; +import { + type Admin, + useOrganizationAdmins, +} from "@/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins"; + +interface AssigneeSectionProps { + testId: string; + currentAssigneeId: string | null | undefined; + onSuccess: () => Promise; +} + +export function AssigneeSection({ + testId, + currentAssigneeId, + onSuccess, +}: AssigneeSectionProps) { + const [assigneeId, setAssigneeId] = useState( + currentAssigneeId || null, + ); + const { data: admins, isLoading, error } = useOrganizationAdmins(); + const [selectedAdmin, setSelectedAdmin] = useState(null); + + const { execute: updateAssignee, isExecuting } = useAction(assignTest, { + onSuccess: async () => { + toast.success("Assignee updated successfully"); + await onSuccess(); + }, + onError: (error) => { + console.log("error", error); + toast.error("Failed to update Assignee"); + }, + }); + + useEffect(() => { + setAssigneeId(currentAssigneeId || null); + }, [currentAssigneeId]); + + useEffect(() => { + if (admins && assigneeId) { + const admin = admins.find((a) => a.id === assigneeId); + setSelectedAdmin(admin || null); + } else { + setSelectedAdmin(null); + } + }, [admins, assigneeId]); + + const handleAssigneeChange = (value: string) => { + const newAssigneeId = value === "none" ? null : value; + setAssigneeId(newAssigneeId); + + if (newAssigneeId && admins) { + const admin = admins.find((a) => a.id === newAssigneeId); + setSelectedAdmin(admin || null); + } else { + setSelectedAdmin(null); + } + + updateAssignee({ id: testId, assigneeId: newAssigneeId }); + }; + + if (isLoading) { + return
; + } + + if (error || !admins) { + return

Failed to load

; + } + + return ( +
+ +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/TestComments.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestComments.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/TestComments.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestComments.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/TestDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestDetails.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/TestDetails.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestDetails.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/getTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/getTests.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/getTests.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/getTests.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/refreshTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/refreshTests.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/components/TestsList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsList.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/components/TestsList.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsList.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/components/TestsListSkeleton.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsListSkeleton.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/components/TestsListSkeleton.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsListSkeleton.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/hooks/useTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts similarity index 69% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/hooks/useTest.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts index 6d94481a38..bb0b6a0ff6 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/hooks/useTest.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts @@ -1,19 +1,22 @@ "use client"; import useSWR from "swr"; -import { getTest } from "@/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/getTest"; -import type { AppError, Test } from "@/app/[locale]/(app)/(dashboard)/tests/all/types"; +import { getTest } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest"; +import type { + AppError, + Test, +} from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types"; async function fetchTest(testId: string): Promise { try { const response = await getTest({ testId }); - + if (response.success) { return response.data; - } else { - throw response.error; } + + throw response.error; } catch (error) { - if (error && typeof error === 'object' && 'message' in error) { + if (error && typeof error === "object" && "message" in error) { throw error as AppError; } throw { message: "An unexpected error occurred" }; @@ -36,4 +39,4 @@ export function useTest(testId: string) { error, mutate, }; -} \ No newline at end of file +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/hooks/useTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTests.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/hooks/useTests.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTests.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx new file mode 100644 index 0000000000..f07a4308bf --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx @@ -0,0 +1,31 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const organizationId = session?.user?.organizationId; + + return ( +
+ +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/types/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/types/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/search-params.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/types/search-params.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/search-params.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/layout.tsx new file mode 100644 index 0000000000..2babdff01c --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/layout.tsx @@ -0,0 +1,30 @@ +import { auth } from "@/auth"; +import { getI18n } from "@/locales/server"; +import { SecondaryMenu } from "@bubba/ui/secondary-menu"; + +export default async function Layout({ + children, +}: { + children: React.ReactNode; +}) { + const t = await getI18n(); + const session = await auth(); + const user = session?.user; + const orgId = user?.organizationId; + + return ( +
+ + +
{children}
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/page.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/AssigneeSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/AssigneeSection.tsx deleted file mode 100644 index b83f66ddba..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/AssigneeSection.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@bubba/ui/select"; -import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; -import { useAction } from "next-safe-action/hooks"; -import { toast } from "sonner"; -import { assignEvidence } from "../actions/assignEvidence"; -import { useOrganizationAdmins } from "../hooks/useOrganizationAdmins"; -import type { Admin } from "../hooks/useOrganizationAdmins"; - -interface AssigneeSectionProps { - evidenceId: string; - currentAssigneeId: string | null | undefined; - onSuccess: () => Promise; -} - -export function AssigneeSection({ - evidenceId, - currentAssigneeId, - onSuccess, -}: AssigneeSectionProps) { - const [assigneeId, setAssigneeId] = useState( - currentAssigneeId || null - ); - const { data: admins, isLoading, error } = useOrganizationAdmins(); - const [selectedAdmin, setSelectedAdmin] = useState(null); - - const { execute: updateAssignee, isExecuting } = useAction(assignEvidence, { - onSuccess: async () => { - toast.success("Assignee updated successfully"); - await onSuccess(); - }, - onError: () => { - toast.error("Failed to update assignee"); - }, - }); - - useEffect(() => { - setAssigneeId(currentAssigneeId || null); - }, [currentAssigneeId]); - - useEffect(() => { - if (admins && assigneeId) { - const admin = admins.find((a) => a.id === assigneeId); - setSelectedAdmin(admin || null); - } else { - setSelectedAdmin(null); - } - }, [admins, assigneeId]); - - const handleAssigneeChange = (value: string) => { - const newAssigneeId = value === "none" ? null : value; - setAssigneeId(newAssigneeId); - - if (newAssigneeId && admins) { - const admin = admins.find((a) => a.id === newAssigneeId); - setSelectedAdmin(admin || null); - } else { - setSelectedAdmin(null); - } - - updateAssignee({ id: evidenceId, assigneeId: newAssigneeId }); - }; - - if (isLoading) { - return
; - } - - if (error || !admins) { - return

Failed to load

; - } - - return ( -
- -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationAdmins.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationAdmins.ts deleted file mode 100644 index 66d94fbc7a..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationAdmins.ts +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import useSWR from "swr"; -import { getOrganizationAdmins } from "../../actions/getOrganizationAdmins"; - -export interface Admin { - id: string; - name: string; - email: string; - image: string | null; - role: string; -} - -async function fetchOrganizationAdmins(): Promise { - try { - const result = await getOrganizationAdmins(); - - if (!result) { - throw new Error("Failed to fetch organization admins"); - } - - if ("error" in result && typeof result.error === "string") { - throw new Error(result.error); - } - - return result.data?.data || []; - } catch (error) { - console.error("Error fetching admins:", error); - throw error; - } -} - -export function useOrganizationAdmins() { - return useSWR("organization-admins", fetchOrganizationAdmins, { - revalidateOnFocus: false, - revalidateOnReconnect: false, - }); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/layout.tsx deleted file mode 100644 index 1e0bf26c89..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; -import { headers } from "next/headers"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - const headersList = await headers(); - const pathname = headersList.get("x-pathname") || ""; - const isEvidenceDetails = /\/evidence\/[a-z0-9]+/.test(pathname); - const pathParts = pathname.split("/"); - const evidenceIndex = pathParts.findIndex((part) => part === "evidence"); - const id = - evidenceIndex >= 0 && pathParts.length > evidenceIndex + 1 - ? pathParts[evidenceIndex + 1] - : ""; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/layout.tsx deleted file mode 100644 index 063655d889..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/layout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/layout.tsx deleted file mode 100644 index 063655d889..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/overview/layout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx deleted file mode 100644 index c5be7799bc..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { auth } from "@/auth"; -import { Header } from "@/components/header"; -import { Sidebar } from "@/components/sidebar"; -import { db } from "@bubba/db"; -import dynamic from "next/dynamic"; -import { redirect } from "next/navigation"; -import { cache } from "react"; - -const HotKeys = dynamic( - () => import("@/components/hot-keys").then((mod) => mod.HotKeys), - { - ssr: true, - }, -); - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const session = await auth(); - - if (!session?.user || !session.user.organizationId) { - redirect("/auth"); - } - - const isSetup = await isOrganizationSetup(session.user.organizationId); - - if (!isSetup) { - redirect("/setup"); - } - - return ( -
- - -
-
-
{children}
-
- - -
- ); -} - -const isOrganizationSetup = cache(async (organizationId: string) => { - const organization = await db.organization.findUnique({ - where: { - id: organizationId, - }, - select: { - setup: true, - }, - }); - - return organization?.setup; -}); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/people/layout.tsx deleted file mode 100644 index f686ef7fd1..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/layout.tsx deleted file mode 100644 index 17ae5cf486..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/page.tsx deleted file mode 100644 index 8aef55f8f7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/page.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { auth } from "@/auth"; -import { PoliciesStatus } from "@/components/policies/charts/policies-status"; -import { PoliciesByAssignee } from "@/components/policies/charts/policies-by-assignee"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -export default async function PoliciesOverview({ - params, -}: { - params: Promise<{ locale: string }>; -}) { - const { locale } = await params; - setStaticParamsLocale(locale); - - const session = await auth(); - - if (!session?.user?.organizationId) { - redirect("/onboarding"); - } - - const overview = await getPoliciesOverview(session.user.organizationId); - - if (overview?.totalPolicies === 0) { - redirect("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/policies/all"); - } - - return ( -
-
- - -
-
- ); -} -const getPoliciesOverview = unstable_cache( - async (organizationId: string) => { - return await db.$transaction(async (tx) => { - const [ - totalPolicies, - publishedPolicies, - draftPolicies, - archivedPolicies, - needsReviewPolicies, - ] = await Promise.all([ - tx.organizationPolicy.count({ - where: { - organizationId, - }, - }), - tx.organizationPolicy.count({ - where: { - organizationId, - status: "published", - }, - }), - tx.organizationPolicy.count({ - where: { - organizationId, - status: "draft", - }, - }), - tx.organizationPolicy.count({ - where: { - organizationId, - status: "archived", - }, - }), - tx.organizationPolicy.count({ - where: { - organizationId, - status: "needs_review", - }, - }), - ]); - - return { - totalPolicies, - publishedPolicies, - draftPolicies, - archivedPolicies, - needsReviewPolicies, - }; - }); - }, - ["policies-overview-cache"], -); - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sidebar.policies"), - }; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/layout.tsx deleted file mode 100644 index 17ae5cf486..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/(overview)/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/components/PolicyDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/components/PolicyDetails.tsx deleted file mode 100644 index 666003e60d..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/components/PolicyDetails.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import { Card, CardContent, CardHeader } from "@bubba/ui/card"; -import { Skeleton } from "@bubba/ui/skeleton"; -import { redirect } from "next/navigation"; -import { PolicyEditor } from "@/components/editor/policy-editor"; -import { usePolicyDetails } from "../../../(overview)/hooks/usePolicy"; -import type { JSONContent } from "@tiptap/react"; -import "@bubba/ui/editor.css"; - -interface PolicyDetailsProps { - policyId: string; -} - -export function PolicyDetails({ policyId }: PolicyDetailsProps) { - const { policy, isLoading, updatePolicy } = usePolicyDetails(policyId); - - if (isLoading) { - return ( -
-
- - -
- - - - - -
- - - -
-
-
-
- ); - } - - if (!policy) return redirect("/policies"); - - const formattedContent = Array.isArray(policy.content) - ? policy.content - : typeof policy.content === "object" && policy.content !== null - ? [policy.content as JSONContent] - : []; - - const handleSavePolicy = async (content: JSONContent[]): Promise => { - if (!policy) return; - - try { - await updatePolicy({ - ...policy, - content, - }); - } catch (error) { - console.error("Error saving policy:", error); - throw error; - } - }; - - return ( -
- -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/page.tsx deleted file mode 100644 index ee9818a971..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/editor/page.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { auth } from "@/auth"; -import { getI18n } from "@/locales/server"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { redirect } from "next/navigation"; -import { PolicyDetails } from "./components/PolicyDetails"; - -export default async function PolicyEditor({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}) { - const { locale, policyId } = await params; - setStaticParamsLocale(locale); - - const session = await auth(); - const organizationId = session?.user.organizationId; - - if (!organizationId) { - redirect("/"); - } - - return ; -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}): Promise { - const { locale } = await params; - - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sub_pages.policies.policy_details"), - }; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/layout.tsx deleted file mode 100644 index 0f710e56a0..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/layout.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { auth } from "@/auth"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -interface LayoutProps { - children: React.ReactNode; - params: Promise<{ policyId: string }>; -} - -export default async function Layout({ children, params }: LayoutProps) { - const t = await getI18n(); - const session = await auth(); - - if (!session || !session.user.organizationId) { - redirect("/"); - } - - const policyId = await params; - const policy = await getPolicy( - policyId.policyId, - session.user.organizationId, - ); - - if (!policy) { - redirect("/policies"); - } - - return ( -
- - -
{children}
-
- ); -} - -const getPolicy = unstable_cache( - async (policyId: string, organizationId: string) => { - const policy = await db.organizationPolicy.findUnique({ - where: { - id: policyId, - organizationId: organizationId, - }, - include: { - policy: true, - }, - }); - - return policy; - }, - ["policy-cache"], -); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx deleted file mode 100644 index 89227fc5b3..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { auth } from "@/auth"; -import { PolicyComment } from "@/components/policies/policy-comments"; -import { PolicyOverview } from "@/components/policies/policy-overview"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -export default async function PolicyDetails({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}) { - const { locale, policyId } = await params; - - setStaticParamsLocale(locale); - - const session = await auth(); - const organizationId = session?.user.organizationId; - - if (!organizationId) { - redirect("/"); - } - - const policy = await getPolicy(policyId, organizationId); - const users = await getUsers(organizationId); - - if (!policy) { - redirect("/policies"); - } - - return ( -
- - -
- ); -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}): Promise { - const { locale } = await params; - - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sub_pages.policies.policy_details"), - }; -} - -const getPolicy = unstable_cache( - async (policyId: string, organizationId: string) => { - const policy = await db.organizationPolicy.findUnique({ - where: { id: policyId, organizationId }, - include: { - policy: true, - PolicyComments: true, - }, - }); - return policy; - }, - ["policy-details"], - { tags: ["policies", "policy-details"] }, -); - -const getUsers = unstable_cache( - async (organizationId: string) => { - const users = await db.user.findMany({ - where: { organizationId: organizationId }, - }); - - return users; - }, - ["users-cache"], -); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/layout.tsx deleted file mode 100644 index d6a83f50a7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/page.tsx deleted file mode 100644 index bd4d24bd77..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/(overview)/page.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { auth } from "@/auth"; -import { RiskOverview } from "@/components/risks/charts/risk-overview"; -import { RisksAssignee } from "@/components/risks/charts/risks-assignee"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -export default async function RiskManagement({ - params, -}: { - params: Promise<{ locale: string }>; -}) { - const { locale } = await params; - setStaticParamsLocale(locale); - - const session = await auth(); - - if (!session?.user?.organizationId) { - redirect("/onboarding"); - } - - const overview = await getRiskOverview(session.user.organizationId); - - if (overview?.risks === 0) { - redirect("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/risk/register"); - } - - return ( -
-
- -
- -
- -
-
- ); -} - -const getRiskOverview = unstable_cache( - async (organizationId: string) => { - return await db.$transaction(async (tx) => { - const [risks] = await Promise.all([ - tx.risk.count({ - where: { organizationId }, - }), - ]); - - return { - risks, - }; - }); - }, - ["risk-overview-cache"], -); - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sidebar.risk"), - }; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/layout.tsx deleted file mode 100644 index f69ab0e564..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/[riskId]/layout.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { auth } from "@/auth"; -import { Title } from "@/components/title"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -interface LayoutProps { - children: React.ReactNode; - params: Promise<{ riskId: string }>; -} - -export default async function Layout({ children, params }: LayoutProps) { - const t = await getI18n(); - const session = await auth(); - - if (!session || !session.user.organizationId) { - redirect("/"); - } - - const riskId = await params; - const risk = await getRisk(riskId.riskId, session.user.organizationId); - - if (!risk) { - redirect("/risk"); - } - - return ( -
- - -
{children}
-
- ); -} - -const getRisk = unstable_cache( - async (riskId: string, organizationId: string) => { - const risk = await db.risk.findUnique({ - where: { - id: riskId, - organizationId: organizationId, - }, - }); - - return risk; - }, - ["risk-cache"], -); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/layout.tsx deleted file mode 100644 index d6a83f50a7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/risk/register/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/loading.tsx deleted file mode 100644 index b9353c994c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/members/loading.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@bubba/ui/tabs"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@bubba/ui/card"; -import { Avatar, AvatarFallback } from "@bubba/ui/avatar"; -import { Badge } from "@bubba/ui/badge"; -import { getI18n } from "@/locales/server"; -import { LogoSpinner } from "@/components/logo-spinner"; - -export default async function Loading() { - const t = await getI18n(); - - return ( -
- - - - {t("settings.team.tabs.members")} - - - {t("settings.team.tabs.invite")} - - - - - - - {t("settings.team.members.title")} - - - - - - - - - -
- - - {t("settings.team.invite.title")} - - {t("settings.team.invite.description")} - - - - - -
-
-
- -
- - -
- ); -} \ No newline at end of file diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/layout.tsx deleted file mode 100644 index f1fa1f2b71..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/page.tsx deleted file mode 100644 index c48a62a4a3..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/(overview)/page.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { auth } from "@/auth"; -import { TestsSeverity } from "@/components/tests/charts/tests-severity"; -import { TestsByAssignee } from "@/components/tests/charts/tests-by-assignee"; -import { getI18n } from "@/locales/server"; -import { db } from "@bubba/db"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { unstable_cache } from "next/cache"; -import { redirect } from "next/navigation"; - -export default async function TestsOverview({ - params, -}: { - params: Promise<{ locale: string }>; -}) { - const { locale } = await params; - setStaticParamsLocale(locale); - - const session = await auth(); - - if (!session?.user?.organizationId) { - redirect("/onboarding"); - } - - const overview = await getTestsOverview(session.user.organizationId); - - if (overview?.totalTests === 0) { - redirect("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/tests/all"); - } - - return ( -
-
- - -
-
- ); -} - -const getTestsOverview = unstable_cache( - async (organizationId: string) => { - return await db.$transaction(async (tx) => { - const [ - totalTests, - lowSeverityTests, - mediumSeverityTests, - highSeverityTests, - criticalSeverityTests, - ] = await Promise.all([ - tx.organizationIntegrationResults.count({ - where: { - organizationId, - }, - }), - tx.organizationIntegrationResults.count({ - where: { - organizationId, - label: "LOW", - }, - }), - tx.organizationIntegrationResults.count({ - where: { - organizationId, - label: "MEDIUM", - }, - }), - tx.organizationIntegrationResults.count({ - where: { - organizationId, - label: "HIGH", - }, - }), - tx.organizationIntegrationResults.count({ - where: { - organizationId, - label: "CRITICAL", - }, - }), - ]); - - return { - totalTests, - lowSeverityTests, - mediumSeverityTests, - highSeverityTests, - criticalSeverityTests, - }; - }); - }, - ["tests-overview-cache"], -); - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sidebar.tests"), - }; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/AssigneeSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/AssigneeSection.tsx deleted file mode 100644 index f16ab4df81..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/[testId]/components/AssigneeSection.tsx +++ /dev/null @@ -1,137 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@bubba/ui/select"; -import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; -import { useAction } from "next-safe-action/hooks"; -import { toast } from "sonner"; -import { assignTest } from "../actions/assignTest"; -import { useOrganizationAdmins } from "../hooks/useOrganizationAdmins"; -import type { Admin } from "../hooks/useOrganizationAdmins"; - -interface AssigneeSectionProps { - testId: string; - currentAssigneeId: string | null | undefined; - onSuccess: () => Promise; -} - -export function AssigneeSection({ - testId, - currentAssigneeId, - onSuccess, -}: AssigneeSectionProps) { - const [assigneeId, setAssigneeId] = useState( - currentAssigneeId || null - ); - const { data: admins, isLoading, error } = useOrganizationAdmins(); - const [selectedAdmin, setSelectedAdmin] = useState(null); - - const { execute: updateAssignee, isExecuting } = useAction(assignTest, { - onSuccess: async () => { - toast.success("Assignee updated successfully"); - await onSuccess(); - }, - onError: (error) => { - console.log("error", error); - toast.error("Failed to update Assignee"); - }, - }); - - useEffect(() => { - setAssigneeId(currentAssigneeId || null); - }, [currentAssigneeId]); - - useEffect(() => { - if (admins && assigneeId) { - const admin = admins.find((a) => a.id === assigneeId); - setSelectedAdmin(admin || null); - } else { - setSelectedAdmin(null); - } - }, [admins, assigneeId]); - - const handleAssigneeChange = (value: string) => { - const newAssigneeId = value === "none" ? null : value; - setAssigneeId(newAssigneeId); - - if (newAssigneeId && admins) { - const admin = admins.find((a) => a.id === newAssigneeId); - setSelectedAdmin(admin || null); - } else { - setSelectedAdmin(null); - } - - updateAssignee({ id: testId, assigneeId: newAssigneeId }); - }; - - if (isLoading) { - return
; - } - - if (error || !admins) { - return

Failed to load

; - } - - return ( -
- -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/getOrganizationAdmins.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/getOrganizationAdmins.ts deleted file mode 100644 index a3a7a41452..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/tests/all/actions/getOrganizationAdmins.ts +++ /dev/null @@ -1,71 +0,0 @@ -"use server"; - -import { authActionClient } from "@/actions/safe-action"; -import { db } from "@bubba/db"; -import { MembershipRole } from "@prisma/client"; - -export const getOrganizationAdmins = authActionClient - .metadata({ - name: "getOrganizationAdmins", - track: { - event: "get-organization-admins", - channel: "server", - }, - }) - .action(async ({ ctx }) => { - const { user } = ctx; - - if (!user.organizationId) { - return { - success: false, - error: "Not authorized - no organization found", - }; - } - - try { - // Find organization members with admin or owner roles - const adminMembers = await db.organizationMember.findMany({ - where: { - organizationId: user.organizationId, - role: { - in: [MembershipRole.admin, MembershipRole.owner], - }, - }, - include: { - user: { - select: { - id: true, - name: true, - email: true, - image: true, - }, - }, - }, - orderBy: { - user: { - name: "asc", - }, - }, - }); - - // Transform the data to a simpler format - const admins = adminMembers.map((member) => ({ - id: member.userId, - name: member.user.name || "Unknown", - email: member.user.email || "", - image: member.user.image || null, - role: member.role, - })); - - return { - success: true, - data: admins, - }; - } catch (error) { - console.error("Error fetching organization admins:", error); - return { - success: false, - error: "Failed to fetch organization admins", - }; - } - }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx deleted file mode 100644 index 71699d2dc1..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/(overview)/layout.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@bubba/ui/secondary-menu"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx index acd015e90b..d79fce263d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-risk-form.tsx @@ -2,34 +2,34 @@ import { createRiskAction } from "@/actions/risk/create-risk-action"; import { createRiskSchema } from "@/actions/schema"; -import { useOrganizationAdmins } from "@/app/[locale]/(app)/(dashboard)/evidence/[id]/hooks/useOrganizationAdmins"; -import { useRisks } from "@/app/[locale]/(app)/(dashboard)/risk/register/hooks/useRisks"; +import { useOrganizationAdmins } from "@/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins"; +import { useRisks } from "@/app/[locale]/(app)/(dashboard)/[orgId]/risk/register/hooks/useRisks"; import { SelectUser } from "@/components/select-user"; import { useI18n } from "@/locales/client"; import type { RiskStatus } from "@bubba/db/types"; import { Departments, RiskCategory } from "@bubba/db/types"; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, } from "@bubba/ui/accordion"; import { Button } from "@bubba/ui/button"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@bubba/ui/form"; import { Input } from "@bubba/ui/input"; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@bubba/ui/select"; import { Textarea } from "@bubba/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx index 45baecbc25..749faa7eed 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/create-vendor-sheet.tsx @@ -11,45 +11,45 @@ import { useQueryState } from "nuqs"; import { CreateRisk } from "./create-risk-form"; export function CreateVendorSheet() { - const t = useI18n(); - const isDesktop = useMediaQuery("(min-width: 768px)"); - const [open, setOpen] = useQueryState("createVendorSheet"); - const isOpen = Boolean(open); + const t = useI18n(); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = useQueryState("createVendorSheet"); + const isOpen = Boolean(open); - const handleOpenChange = (open: boolean) => { - setOpen(open ? "true" : null); - }; + const handleOpenChange = (open: boolean) => { + setOpen(open ? "true" : null); + }; - if (isDesktop) { - return ( - - - - {t("risk.create")} - - + if (isDesktop) { + return ( + + + + {t("risk.create")} + + - - - - - - ); - } + + + + + + ); + } - return ( - - - - - - - ); + return ( + + + + + + + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx index 3b5d9d8518..47e9fc0476 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/components/task/update-task-form.tsx @@ -5,24 +5,28 @@ import { updateTaskSchema } from "@/actions/schema"; import { SelectUser } from "@/components/select-user"; import { STATUS_TYPES, Status, type StatusType } from "@/components/status"; import { useI18n } from "@/locales/client"; -import { type RiskMitigationTask, RiskTaskStatus, type User } from "@bubba/db/types"; +import { + type RiskMitigationTask, + RiskTaskStatus, + type User, +} from "@bubba/db/types"; import { Button } from "@bubba/ui/button"; import { Calendar } from "@bubba/ui/calendar"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@bubba/ui/form"; import { Popover, PopoverContent, PopoverTrigger } from "@bubba/ui/popover"; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@bubba/ui/select"; import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; @@ -33,165 +37,165 @@ import React from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import type { z } from "zod"; -import { cn } from "../../../../../../../packages/ui/src/utils"; +import { cn } from "@bubba/ui/cn"; export function UpdateTaskForm({ - task, - users, + task, + users, }: { - task: RiskMitigationTask; - users: User[]; + task: RiskMitigationTask; + users: User[]; }) { - const t = useI18n(); + const t = useI18n(); - const updateTask = useAction(updateTaskAction, { - onSuccess: () => { - toast.success("Task updated successfully"); - }, - onError: () => { - toast.error("Something went wrong, please try again."); - }, - }); + const updateTask = useAction(updateTaskAction, { + onSuccess: () => { + toast.success("Task updated successfully"); + }, + onError: () => { + toast.error("Something went wrong, please try again."); + }, + }); - const form = useForm>({ - resolver: zodResolver(updateTaskSchema), - defaultValues: { - id: task.id, - dueDate: task.dueDate ? new Date(task.dueDate) : undefined, - ownerId: task.ownerId ?? undefined, - status: task.status ?? RiskTaskStatus.open, - }, - }); + const form = useForm>({ + resolver: zodResolver(updateTaskSchema), + defaultValues: { + id: task.id, + dueDate: task.dueDate ? new Date(task.dueDate) : undefined, + ownerId: task.ownerId ?? undefined, + status: task.status ?? RiskTaskStatus.open, + }, + }); - const onSubmit = (data: z.infer) => { - updateTask.execute({ - id: data.id, - dueDate: data.dueDate ? new Date(data.dueDate) : undefined, - ownerId: data.ownerId, - status: data.status as RiskTaskStatus, - }); - }; + const onSubmit = (data: z.infer) => { + updateTask.execute({ + id: data.id, + dueDate: data.dueDate ? new Date(data.dueDate) : undefined, + ownerId: data.ownerId, + status: data.status as RiskTaskStatus, + }); + }; - return ( -
- -
- ( - - {t("common.assignee.label")} - - - - - - )} - /> - ( - - {t("risk.tasks.form.status")} - - - - - - )} - /> + return ( + + +
+ ( + + {t("common.assignee.label")} + + + + + + )} + /> + ( + + {t("risk.tasks.form.status")} + + + + + + )} + /> - ( - - {t("risk.tasks.form.due_date")} - - - - - - - - date <= new Date()} - initialFocus - /> - - - - - )} - /> -
-
- -
- - - ); + ( + + {t("risk.tasks.form.due_date")} + + + + + + + + date <= new Date()} + initialFocus + /> + + + + + )} + /> +
+
+ +
+ + + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx index 544bc98864..36136f4a30 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/VendorRegisterTable.tsx @@ -1,19 +1,20 @@ "use client"; - import { DataTable } from "@/components/ui/data-table"; import { useI18n } from "@/locales/client"; import type { Departments, RiskStatus, User, Vendor } from "@bubba/db/types"; import { Plus } from "lucide-react"; import { useQueryState } from "nuqs"; import { useState } from "react"; -import { useOrganizationAdmins } from "../../evidence/[id]/hooks/useOrganizationAdmins"; +import { useOrganizationAdmins } from "@/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins"; import { CreateVendorSheet } from "../components/create-vendor-sheet"; import { columns } from "./components/table/RiskRegisterColumns"; type VendorRegisterTableRow = Vendor & { owner: User | null }; -export const VendorRegisterTable = ({ data }: { data: VendorRegisterTableRow[] }) => { +export const VendorRegisterTable = ({ + data, +}: { data: VendorRegisterTableRow[] }) => { const t = useI18n(); // State const [search, setSearch] = useState(""); @@ -46,7 +47,6 @@ export const VendorRegisterTable = ({ data }: { data: VendorRegisterTableRow[] } }, ); - const hasActiveFilters = Boolean(status || department || assigneeId); const handleClearFilters = () => { diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx index 33a9ad3c22..ead50a73f8 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/components/table/RiskRegisterFilters.tsx @@ -1,7 +1,7 @@ import { Status } from "@/components/status"; import { type Departments, RiskStatus } from "@bubba/db/types"; -import type { Admin } from "../../../../evidence/[id]/hooks/useOrganizationAdmins"; -import { AssigneeAvatar } from "../../../../evidence/list/components/table/components/AssigneeAvatar"; +import type { Admin } from "@/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins"; +import { AssigneeAvatar } from "@/app/[locale]/(app)/(dashboard)/[orgId]/evidence/list/components/table/components/AssigneeAvatar"; export const RiskRegisterFilters = ({ setPage, diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx index 59ec00175c..100a2fa973 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/vendors/register/page.tsx @@ -9,18 +9,16 @@ import { Departments, VendorStatus } from "@bubba/db/types"; import { z } from "zod"; export default async function Page({ - params, - searchParams, + searchParams, }: { - params: { locale: string }, - searchParams: Promise<{ - createVendorSheet?: string; - page?: string; - pageSize?: string; - status?: string; - department?: string; - assigneeId?: string; - }> + searchParams: Promise<{ + createVendorSheet?: string; + page?: string; + pageSize?: string; + status?: string; + department?: string; + assigneeId?: string; + }>; }) { const searchParamsSchema = z.object({ createVendorSheet: z.string().optional(), @@ -28,17 +26,18 @@ export default async function Page({ pageSize: z.string().regex(/^\d+$/).transform(Number).optional(), status: z.nativeEnum(VendorStatus).optional(), department: z.nativeEnum(Departments).optional(), - assigneeId: z.string().uuid().optional() + assigneeId: z.string().uuid().optional(), }); const result = searchParamsSchema.safeParse(await searchParams); if (!result.success) { - console.error('Invalid search params:', result.error); - redirect('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/vendors/register'); + console.error("Invalid search params:", result.error); + redirect("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/vendors/register"); } - const { createVendorSheet, page, pageSize, status, department, assigneeId } = result.data; + const { createVendorSheet, page, pageSize, status, department, assigneeId } = + result.data; const session = await auth(); @@ -51,13 +50,13 @@ export default async function Page({ organizationId: session.user.organizationId, ...(status && { status: status }), ...(department && { department: department }), - ...(assigneeId && { ownerId: assigneeId }) + ...(assigneeId && { ownerId: assigneeId }), }, include: { - owner: true + owner: true, }, skip: page ? (Number(page) - 1) * Number(pageSize || 10) : 0, - take: Number(pageSize || 10) + take: Number(pageSize || 10), }); return ; diff --git a/apps/app/src/app/[locale]/(app)/setup/page.tsx b/apps/app/src/app/[locale]/(app)/setup/page.tsx index 9397d81300..66a03c985d 100644 --- a/apps/app/src/app/[locale]/(app)/setup/page.tsx +++ b/apps/app/src/app/[locale]/(app)/setup/page.tsx @@ -1,50 +1,22 @@ -import { auth } from "@/auth"; import { Onboarding } from "@/components/forms/create-organization-form"; import { db } from "@bubba/db"; import type { Metadata } from "next"; -import { redirect } from "next/navigation"; import { cache } from "react"; export const metadata: Metadata = { - title: "Organization Setup | Comp AI", + title: "Organization Setup | Comp AI", }; export default async function Page() { - const session = await auth(); + const frameworks = await getFrameworks(); - if (!session?.user?.id) { - return redirect("/"); - } - - if (!session.user.organizationId) { - return redirect("/"); - } - - const isSetup = await isOrganizationSetup(session.user.organizationId); - const frameworks = await getFrameworks(); - - if (isSetup) { - return redirect("/"); - } - - return ; + return ; } const getFrameworks = cache(async () => { - return await db.framework.findMany({ - orderBy: { - name: "asc", - }, - }); -}); - -const isOrganizationSetup = cache(async (organizationId: string) => { - const organization = await db.organization.findUnique({ - where: { id: organizationId }, - select: { - setup: true, - }, - }); - - return organization?.setup; + return await db.framework.findMany({ + orderBy: { + name: "asc", + }, + }); }); diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx new file mode 100644 index 0000000000..677510c260 --- /dev/null +++ b/apps/app/src/app/layout.tsx @@ -0,0 +1,3 @@ +export default function Layout({ children }: { children: React.ReactNode }) { + return <>{children}; +} diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx new file mode 100644 index 0000000000..e0ead87c27 --- /dev/null +++ b/apps/app/src/app/page.tsx @@ -0,0 +1,16 @@ +import { auth } from "@/auth"; +import { redirect } from "next/navigation"; + +export default async function RootPage() { + const session = await auth(); + + if (!session) { + redirect("/auth"); + } + + if (session.user?.organizationId) { + redirect(`/${session.user.organizationId}`); + } + + redirect("/setup"); +} diff --git a/apps/app/src/auth/config.ts b/apps/app/src/auth/config.ts index 39972711c8..fc03a3d016 100644 --- a/apps/app/src/auth/config.ts +++ b/apps/app/src/auth/config.ts @@ -7,76 +7,77 @@ import Resend from "next-auth/providers/resend"; import { createOrganizationAndConnectUser } from "./org"; declare module "next-auth" { - interface User { - organizationId?: string; - onboarded?: boolean; - full_name?: string; - avatar_url?: string; - } + interface User { + organizationId?: string; + onboarded?: boolean; + full_name?: string; + avatar_url?: string; + } - interface Session extends DefaultSession { - user: { - id: string; - organizationId?: string; - onboarded?: boolean; - full_name?: string; - avatar_url?: string; - } & DefaultSession["user"]; - } + interface Session extends DefaultSession { + user: { + id: string; + organizationId?: string; + onboarded?: boolean; + full_name?: string; + avatar_url?: string; + } & DefaultSession["user"]; + } } export const authConfig: NextAuthConfig = { - providers: [ - GoogleProvider({ - clientId: process.env.AUTH_GOOGLE_ID!, - clientSecret: process.env.AUTH_GOOGLE_SECRET!, - allowDangerousEmailAccountLinking: true, - }), - Resend({ - apiKey: process.env.RESEND_API_KEY!, - from: "noreply@mail.trycomp.ai", - async sendVerificationRequest(params) { - await sendMagicLinkEmail({ - email: params.identifier, - url: params.url, - }); - }, - }), - ], - adapter: PrismaAdapter(db), - callbacks: { - session: ({ session, user }) => ({ - ...session, - user: { - ...session.user, - id: user.id, - organizationId: user.organizationId, - onboarded: user.onboarded, - }, - }), - }, - events: { - signIn: async ({ user, isNewUser }) => { - if (user?.id) { - await db.user.update({ - where: { - id: user.id, - }, - data: { - lastLogin: new Date(), - organizationId: user.organizationId, - }, - }); - } + providers: [ + GoogleProvider({ + clientId: process.env.AUTH_GOOGLE_ID!, + clientSecret: process.env.AUTH_GOOGLE_SECRET!, + allowDangerousEmailAccountLinking: true, + }), + Resend({ + apiKey: process.env.RESEND_API_KEY!, + from: "noreply@mail.trycomp.ai", + async sendVerificationRequest(params) { + await sendMagicLinkEmail({ + email: params.identifier, + url: params.url, + }); + }, + }), + ], + adapter: PrismaAdapter(db), + callbacks: { + session: ({ session, user }) => ({ + ...session, + user: { + ...session.user, + id: user.id, + organizationId: user.organizationId, + onboarded: user.onboarded, + }, + }), + }, + events: { + signIn: async ({ user, isNewUser }) => { + if (user?.id) { + await db.user.update({ + where: { + id: user.id, + }, + data: { + lastLogin: new Date(), + organizationId: user.organizationId, + }, + }); + } - if (isNewUser && user.email && user.id) { - if (!user.organizationId) { - await createOrganizationAndConnectUser({ - userId: user.id, - normalizedEmail: user.email, - }); - } - } - }, - }, + if (isNewUser && user.email && user.id) { + if (!user.organizationId) { + await createOrganizationAndConnectUser({ + userId: user.id, + normalizedEmail: user.email, + orgName: "My Organization", + }); + } + } + }, + }, } satisfies NextAuthConfig; diff --git a/apps/app/src/auth/org.ts b/apps/app/src/auth/org.ts index 8471dc2ffa..ef9092be33 100644 --- a/apps/app/src/auth/org.ts +++ b/apps/app/src/auth/org.ts @@ -2,77 +2,77 @@ import { db } from "@bubba/db"; import { stripe } from "./stripe"; async function createStripeCustomer(input: { - name: string; - email: string; - organizationId: string; + name: string; + email: string; + organizationId: string; }): Promise { - try { - const customer = await stripe.customers.create({ - name: input.name, - email: input.email, - metadata: { - organizationId: input.organizationId, - }, - }); + try { + const customer = await stripe.customers.create({ + name: input.name, + email: input.email, + metadata: { + organizationId: input.organizationId, + }, + }); - return customer.id; - } catch (error) { - console.error("Error creating Stripe customer", error); - throw error; - } + return customer.id; + } catch (error) { + console.error("Error creating Stripe customer", error); + throw error; + } } export async function createOrganizationAndConnectUser(input: { - userId: string; - normalizedEmail: string; -}): Promise { - const initialName = "New Organization"; - - const [organization] = await db.$transaction([ - db.organization.create({ - data: { - name: initialName, - tier: "free", - website: "", - members: { - create: { - userId: input.userId, - role: "owner", - }, - }, - }, - select: { - id: true, - }, - }), - db.user.update({ - where: { id: input.userId }, - data: { role: "admin" }, - select: { - id: true, - }, - }), - ]); + userId: string; + normalizedEmail: string; + orgName: string; +}) { + const [organization] = await db.$transaction([ + db.organization.create({ + data: { + name: input.orgName, + tier: "free", + website: "", + members: { + create: { + userId: input.userId, + role: "owner", + }, + }, + }, + select: { + id: true, + name: true, + }, + }), + db.user.update({ + where: { id: input.userId }, + data: { role: "admin" }, + select: { + id: true, + }, + }), + ]); - const stripeCustomerId = await createStripeCustomer({ - name: initialName, - email: input.normalizedEmail, - organizationId: organization.id, - }); + const stripeCustomerId = await createStripeCustomer({ + name: organization.name, + email: input.normalizedEmail, + organizationId: organization.id, + }); - if (!stripeCustomerId) { - console.warn("Stripe customer ID is missing"); - } + if (!stripeCustomerId) { + console.warn("Stripe customer ID is missing"); + } - await db.organization.update({ - where: { id: organization.id }, - data: { stripeCustomerId }, - }); + await db.organization.update({ + where: { id: organization.id }, + data: { stripeCustomerId }, + }); - await db.user.update({ - where: { id: input.userId }, - data: { organizationId: organization.id }, - }); + await db.user.update({ + where: { id: input.userId }, + data: { organizationId: organization.id }, + }); - return organization.id; + return organization; } diff --git a/apps/app/src/components/forms/create-organization-form.tsx b/apps/app/src/components/forms/create-organization-form.tsx index fead4419e9..a49c1cde4d 100644 --- a/apps/app/src/components/forms/create-organization-form.tsx +++ b/apps/app/src/components/forms/create-organization-form.tsx @@ -5,23 +5,23 @@ import { organizationSchema } from "@/actions/schema"; import { useI18n } from "@/locales/client"; import { Button } from "@bubba/ui/button"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@bubba/ui/form"; import { Input } from "@bubba/ui/input"; import { zodResolver } from "@hookform/resolvers/zod"; import { ArrowRight, Loader2 } from "lucide-react"; import { useSession } from "next-auth/react"; import { useAction } from "next-safe-action/hooks"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import type { z } from "zod"; -import type { Framework } from "@bubba/db/types"; +import type { Framework, Organization } from "@bubba/db/types"; import { Icons } from "@bubba/ui/icons"; import Link from "next/link"; import { Checkbox } from "@bubba/ui/checkbox"; @@ -30,243 +30,266 @@ import { useRealtimeRun } from "@trigger.dev/react-hooks"; import { useRouter } from "next/navigation"; import { LogoSpinner } from "../logo-spinner"; -function RealtimeStatus({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) { - const t = useI18n(); - const router = useRouter() +function RealtimeStatus({ + runId, + publicAccessToken, + newOrganization, +}: { + runId: string; + publicAccessToken: string; + newOrganization: Pick | null; +}) { + const t = useI18n(); + const router = useRouter(); - const { run, error } = useRealtimeRun(runId, { - accessToken: publicAccessToken, - onComplete: (run, error) => { - if (error) { - toast.error(t("common.actions.error"), { duration: 5000 }); - return; - } - }, - }); + const { run, error } = useRealtimeRun(runId, { + accessToken: publicAccessToken, + onComplete: (run, error) => { + if (error) { + toast.error(t("common.actions.error"), { duration: 5000 }); + return; + } + }, + }); - useEffect(() => { - if (run?.status === "COMPLETED") { - router.push("/"); - } - }, [run, router]); + useEffect(() => { + if (run?.status === "COMPLETED" && newOrganization) { + router.push(`/${newOrganization.id}`); + } + }, [run, router, newOrganization]); - return ( -
- {run?.status !== "FAILED" && run?.status !== "COMPLETED" && ( -
- -

- {t("onboarding.trigger.title")} -

-

- {t("onboarding.trigger.creating")} -

-
- )} -
- ); + return ( +
+ {run?.status !== "FAILED" && run?.status !== "COMPLETED" && ( +
+ +

+ {t("onboarding.trigger.title")} +

+

+ {t("onboarding.trigger.creating")} +

+
+ )} +
+ ); } function OnboardingClient({ frameworks }: { frameworks: Framework[] }) { - const t = useI18n(); - const { data: session } = useSession(); - const [runId, setRunId] = useState(null); - const [publicAccessToken, setPublicAccessToken] = useState(null); + const t = useI18n(); + const { data: session } = useSession(); + const [runId, setRunId] = useState(null); + const [publicAccessToken, setPublicAccessToken] = useState( + null, + ); + const newOrganizationRef = useRef | null>( + null, + ); - const createOrganization = useAction(createOrganizationAction, { - onSuccess: async (data) => { - setRunId(data.data?.runId ?? null); - setPublicAccessToken(data.data?.publicAccessToken ?? null); - }, - onError: () => { - toast.error(t("common.actions.error"), { duration: 5000 }); - }, - }); + const createOrganization = useAction(createOrganizationAction, { + onSuccess: async (data) => { + setRunId(data.data?.runId ?? null); + setPublicAccessToken(data.data?.publicAccessToken ?? null); + newOrganizationRef.current = data.data?.newOrganization ?? null; + }, + onError: () => { + toast.error(t("common.actions.error"), { duration: 5000 }); + }, + }); - const form = useForm>({ - resolver: zodResolver(organizationSchema), - defaultValues: { - fullName: session?.user?.name ?? "", - name: "", - website: "", - frameworks: [], - }, - mode: "onChange", - }); + const form = useForm>({ + resolver: zodResolver(organizationSchema), + defaultValues: { + fullName: session?.user?.name ?? "", + name: "", + website: "", + frameworks: [], + }, + mode: "onChange", + }); - const onSubmit = async (data: z.infer) => { - createOrganization.execute(data); - }; + const onSubmit = async (data: z.infer) => { + createOrganization.execute(data); + }; - if (runId && publicAccessToken) { - return ( -
-
- -
-
- ); - } + if (runId && publicAccessToken) { + return ( +
+
+ +
+
+ ); + } - return ( -
-
-
- - - -
+ return ( +
+
+
+ + + +
-
-

- {t("onboarding.setup")} -

-

- {t("onboarding.description")} -

-
+
+

+ {t("onboarding.setup")} +

+

+ {t("onboarding.description")} +

+
-
- - ( - - - {t("onboarding.fields.fullName.label")} - - - - - - - )} - /> + + + ( + + + {t("onboarding.fields.fullName.label")} + + + + + + + )} + /> - ( - - - {t("onboarding.fields.name.label")} - - - - - - - )} - /> + ( + + + {t("onboarding.fields.name.label")} + + + + + + + )} + /> - ( - - - {t("onboarding.fields.website.label")} - - - - - - - )} - /> - ( - - - {t("frameworks.overview.grid.title")} - - -
- {frameworks.map((framework) => ( -
{ - const newValue = !field.value.includes(framework.id) - ? [...field.value, framework.id] - : field.value.filter((id) => id !== framework.id); - field.onChange(newValue); - }} - > -
-
-

{framework.name}

-

- {framework.description} -

-

- {`${t("frameworks.overview.grid.version")}: ${framework.version}`} -

-
-
e.stopPropagation()}> - { - const newValue = checked - ? [...field.value, framework.id] - : field.value.filter((id) => id !== framework.id); - field.onChange(newValue); - }} - /> -
-
-
- ))} -
-
- -
- )} - /> + ( + + + {t("onboarding.fields.website.label")} + + + + + + + )} + /> + ( + + + {t("frameworks.overview.grid.title")} + + +
+ + {t("frameworks.overview.grid.title")} + + {frameworks.map((framework) => ( + + ))} +
+
+ +
+ )} + /> - - - -
-
- ); + + + +
+
+ ); } export function Onboarding({ frameworks }: { frameworks: Framework[] }) { - return ; + return ; } diff --git a/apps/app/src/components/forms/create-test-comment-form.tsx b/apps/app/src/components/forms/create-test-comment-form.tsx index 7420a8ca09..ca1e65a98d 100644 --- a/apps/app/src/components/forms/create-test-comment-form.tsx +++ b/apps/app/src/components/forms/create-test-comment-form.tsx @@ -1,21 +1,21 @@ "use client"; -import { createTestCommentAction } from "@/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/createTestComment"; -import { createTestCommentSchema } from "@/app/[locale]/(app)/(dashboard)/tests/all/[testId]/actions/types"; +import { createTestCommentAction } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/createTestComment"; +import { createTestCommentSchema } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types"; import { useI18n } from "@/locales/client"; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, } from "@bubba/ui/accordion"; import { Button } from "@bubba/ui/button"; import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormMessage, } from "@bubba/ui/form"; import { Textarea } from "@bubba/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -29,80 +29,80 @@ import { toast } from "sonner"; import type { z } from "zod"; export function CreateTestCommentForm() { - const t = useI18n(); - const [_, setTestCommentSheet] = useQueryState("test-comment-sheet"); - const params = useParams<{ testId: string }>(); + const t = useI18n(); + const [_, setTestCommentSheet] = useQueryState("test-comment-sheet"); + const params = useParams<{ testId: string }>(); - const createPolicyComment = useAction(createTestCommentAction, { - onSuccess: () => { - toast.success(t("common.comments.success")); - setTestCommentSheet(null); - }, - onError: () => { - toast.error(t("common.comments.error")); - }, - }); + const createPolicyComment = useAction(createTestCommentAction, { + onSuccess: () => { + toast.success(t("common.comments.success")); + setTestCommentSheet(null); + }, + onError: () => { + toast.error(t("common.comments.error")); + }, + }); - const form = useForm>({ - resolver: zodResolver(createTestCommentSchema), - defaultValues: { - content: "", - testId: params.testId, - }, - }); + const form = useForm>({ + resolver: zodResolver(createTestCommentSchema), + defaultValues: { + content: "", + testId: params.testId, + }, + }); - const onSubmit = (data: z.infer) => { - createPolicyComment.execute(data); - }; + const onSubmit = (data: z.infer) => { + createPolicyComment.execute(data); + }; - return ( -
- -
-
- - - {t("common.comments.new")} - -
- ( - - -