Skip to content

refactor: Restructure policies module and enhance editor functionality - #78

Merged
carhartlewis merged 6 commits into
mainfrom
lewis/policies
Feb 24, 2025
Merged

carhartlewis merged 6 commits into
mainfrom
lewis/policies

Conversation

@carhartlewis

@carhartlewis carhartlewis commented Feb 24, 2025 •

Copy link
Copy Markdown
Contributor
  • Refactored policies pages and components with improved structure
  • Added new PolicyEditor with enhanced Tiptap extensions and features
  • Implemented new types and actions for policy management
  • Updated localization and UI components for policies
  • Added table and filter improvements for policies list
  • Integrated new status and empty state components

Summary by CodeRabbit

  • New Features

    • Introduced new components for managing and displaying policies, including PoliciesList and PolicyDetails.
    • Added support for policy frequency in the data model and editor.
    • Enhanced editor with new styling and functionality for better user interaction.
  • Style and Localization

    • Updated loading states and improved visual indicators for various components.
    • Expanded multi-language support with additional labels and messages for better clarity.
  • Refactor

    • Removed obsolete components and streamlined policy management logic for improved performance.
    • Updated data handling and error management across the application for consistency.

- Refactored policies pages and components with improved structure
- Added new PolicyEditor with enhanced Tiptap extensions and features
- Implemented new types and actions for policy management
- Updated localization and UI components for policies
- Added table and filter improvements for policies list
- Integrated new status and empty state components
@vercel

vercel Bot commented Feb 24, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 24, 2025 11:49pm
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
comp-portal ⬜️ Skipped (Inspect) Feb 24, 2025 11:49pm
web ⬜️ Skipped (Inspect) Feb 24, 2025 11:49pm

@coderabbitai

coderabbitai Bot commented Feb 24, 2025 •

Copy link
Copy Markdown

Walkthrough

This pull request implements extensive modifications across the application. It adds new Tiptap table dependencies to support enhanced rich text editing and refines type imports and formatting consistency. The policy management domain received significant updates: several legacy components, pages, and hooks were removed, while new functions for retrieving, updating, and displaying policy details were introduced. The changes include updates to server actions, pagination and filtering in policy queries, and a revamped policy editor. In addition, UI elements such as tables, skeleton loaders, and localization files have been updated, and analytics initialization has been removed from provider files.

Changes

File(s) Change Summary
apps/app/package.json Added four new Tiptap table dependencies (@tiptap/extension-table*) for table functionalities.
apps/app/src/app/.../hooks/useOrganizationControl.ts Updated type import for OrganizationControlResponse and trailing commas for formatting consistency.
apps/app/src/app/[locale]/(app)/(dashboard)/policies/** Removed legacy components and pages (PoliciesOverview, PoliciesOverviewLoading, publish-policy.ts, old layout and page files) and introduced new ones: getPolicyDetails, updatePolicy, PolicyDetails, and a dynamic PolicyDetailsPage with metadata generation.
apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policies.ts Updated getPolicies to include input validation, pagination, filtering, and improved error handling.
apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicies.ts & usePolicy.ts Modified hooks to integrate search params, renamed usePolicy to usePolicyDetails, updated data fetching logic, and added content processing and update functionality.
apps/app/src/components/editor/** Removed legacy AI and Tiptap editor files; introduced a new PolicyEditor component with debounced updates and rich text editing enhancements.
apps/app/src/components/tables/policies/** Updated table column definitions, header and cell renderings, added new empty state components and filter toolbar refinements.
apps/app/src/locales/**/*.{ts} Added and updated several localization keys across English, Spanish, French, Norwegian, and Portuguese files.
apps/app/src/types/actions.ts & apps/app/src/utils/format.ts Removed the ActionResponse interface; updated date formatting in formatDate.
apps/portal/src/app/[locale]/providers.tsx & apps/web/src/app/providers.tsx Removed analytics initialization by deleting the Analytics import and related code.
packages/db/prisma/seed.js Enhanced database seeding logic by adding deletions and introducing a new frequency property for upsert operations.
packages/ui/package.json and packages/ui/src/editor.css Added new export for editor.css and introduced a comprehensive CSS file for editor styling.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant P as PolicyDetailsPage
    participant A as Auth Service
    participant G as getPolicyDetails
    participant E as PolicyDetails Component
    participant UPE as usePolicyDetails Hook
    participant UP as updatePolicy Function

    U->>P: Request policy details page
    P->>A: Authenticate user session
    A-->>P: Return session info (organizationId)
    P->>G: Call getPolicyDetails(policyId)
    G-->>P: Return policy details or error
    P->>E: Render PolicyDetails component with data
    E->>UPE: Initialize editor via usePolicyDetails
    U->>E: Update policy content using PolicyEditor
    E->>UP: Call updatePolicy with new content
    UP-->>E: Return update response
Loading

Poem

I'm a rabbit with a joyful beat,
Hopping through code with nimble feet.
New tables, hooks, and pages bloom,
Our policies shine amid this room.
Changes sprout like springtime cheer,
With every commit, we leap clear.
Carrots and code—let's celebrate the gear!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

- Wrap PostHog provider with Suspense for improved rendering
- Remove unnecessary comment in index file
- Import Suspense from React
@vercel
vercel Bot temporarily deployed to Preview – app February 24, 2025 23:43 Inactive
@vercel
vercel Bot temporarily deployed to Preview – web February 24, 2025 23:43 Inactive
@vercel
vercel Bot temporarily deployed to Preview – comp-portal February 24, 2025 23:43 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (27)
apps/app/src/utils/format.ts (1)

83-89: Consider maintaining consistent date formats.

The function now uses different formats for dates in the current year ("MMM dd, yyyy") versus other years (defaulting to "P"). This inconsistency might confuse users and could affect UI layouts due to varying string lengths.

Consider using the same format for all dates:

 export function formatDate(date: string, dateFormat?: string) {
-  if (isSameYear(new Date(), new Date(date))) {
-    return format(new Date(date), "MMM dd, yyyy");
-  }
-
-  return format(new Date(date), dateFormat ?? "P");
+  return format(new Date(date), dateFormat ?? "MMM dd, yyyy");
 }

Alternatively, if different formats are intentional, consider making both formats explicit:

 export function formatDate(date: string, dateFormat?: string) {
   if (isSameYear(new Date(), new Date(date))) {
     return format(new Date(date), "MMM dd, yyyy");
   }
 
-  return format(new Date(date), dateFormat ?? "P");
+  return format(new Date(date), dateFormat ?? "MMM dd, yyyy");
 }
apps/app/src/components/tables/policies/empty-states.tsx (3)

7-26: Enhance accessibility and UI/UX.

Consider the following improvements:

  1. Add aria-label to the icon for screen readers
  2. Add aria-live region for dynamic content updates
  3. Add an icon to the "Create" button for better visual hierarchy

Apply this diff to implement the suggestions:

 export function NoPolicies() {
   const t = useI18n();
 
   return (
-    <Card className="w-full">
+    <Card className="w-full" role="region" aria-live="polite">
       <CardContent className="flex flex-col items-center justify-center p-6 text-center">
-        <FileText className="h-12 w-12 text-muted-foreground mb-4" />
+        <FileText 
+          className="h-12 w-12 text-muted-foreground mb-4"
+          aria-label={t("policies.no_policies_icon_label")} 
+        />
         <h3 className="font-semibold text-lg mb-2">
           {t("policies.no_policies_title")}
         </h3>
         <p className="text-muted-foreground mb-6">
           {t("policies.no_policies_description")}
         </p>
         <Link href="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/policies/new">
-          <Button>{t("policies.create_first")}</Button>
+          <Button>
+            <FileText className="w-4 h-4 mr-2" />
+            {t("policies.create_first")}
+          </Button>
         </Link>
       </CardContent>
     </Card>
   );
 }

28-46: Enhance accessibility and add aria-live region.

Similar to the NoPolicies component, consider adding accessibility attributes for better screen reader support.

Apply this diff to implement the suggestions:

 export function NoResults({ hasFilters }: { hasFilters: boolean }) {
   const t = useI18n();
 
   return (
-    <Card className="w-full">
+    <Card className="w-full" role="region" aria-live="polite">
       <CardContent className="flex flex-col items-center justify-center p-6 text-center">
-        <FileText className="h-12 w-12 text-muted-foreground mb-4" />
+        <FileText 
+          className="h-12 w-12 text-muted-foreground mb-4"
+          aria-label={t("common.empty_states.no_results.icon_label")}
+        />
         <h3 className="font-semibold text-lg mb-2">
           {t("common.empty_states.no_results.title")}
         </h3>
         <p className="text-muted-foreground">
           {hasFilters
             ? t("common.empty_states.no_results.description_filters")
             : t("common.empty_states.no_results.description")}
         </p>
       </CardContent>
     </Card>
   );
 }

7-46: Consider extracting common card styles into a shared component.

Both NoPolicies and NoResults components share similar card structure and styles. Consider creating a reusable EmptyState component to reduce code duplication.

Here's how you could refactor this:

interface EmptyStateProps {
  icon?: React.ReactNode;
  title: string;
  description: string;
  action?: React.ReactNode;
}

function EmptyState({ icon, title, description, action }: EmptyStateProps) {
  return (
    <Card className="w-full" role="region" aria-live="polite">
      <CardContent className="flex flex-col items-center justify-center p-6 text-center">
        {icon}
        <h3 className="font-semibold text-lg mb-2">{title}</h3>
        <p className="text-muted-foreground mb-6">{description}</p>
        {action}
      </CardContent>
    </Card>
  );
}

export function NoPolicies() {
  const t = useI18n();
  return (
    <EmptyState
      icon={<FileText className="h-12 w-12 text-muted-foreground mb-4" aria-label={t("policies.no_policies_icon_label")} />}
      title={t("policies.no_policies_title")}
      description={t("policies.no_policies_description")}
      action={
        <Link href="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/policies/new">
          <Button>
            <FileText className="w-4 h-4 mr-2" />
            {t("policies.create_first")}
          </Button>
        </Link>
      }
    />
  );
}

export function NoResults({ hasFilters }: { hasFilters: boolean }) {
  const t = useI18n();
  return (
    <EmptyState
      icon={<FileText className="h-12 w-12 text-muted-foreground mb-4" aria-label={t("common.empty_states.no_results.icon_label")} />}
      title={t("common.empty_states.no_results.title")}
      description={
        hasFilters
          ? t("common.empty_states.no_results.description_filters")
          : t("common.empty_states.no_results.description")
      }
    />
  );
}
apps/app/src/app/[locale]/(app)/(dashboard)/policies/types/search-params.ts (1)

7-11: Consider starting page index at 1.

The default page value of 0 might be confusing for users, as pagination typically starts at 1. This could affect the user experience and API consistency.

export const searchParamsCache = createSearchParamsCache({
  q: parseAsString,
-  page: parseAsInteger.withDefault(0),
+  page: parseAsInteger.withDefault(1),
  status: parseAsString,
});
apps/app/src/components/tables/policies/loading.tsx (1)

28-42: LGTM! Well-implemented responsive loading states.

The responsive design using hidden md:table-cell and varied skeleton widths creates a polished loading experience.

Consider extracting repeated skeleton patterns.

The skeleton cell implementation could be made more maintainable by extracting it into a reusable component.

+interface SkeletonCellProps {
+  width: string;
+  skeletonWidth: string;
+  className?: string;
+}
+
+function SkeletonCell({ width, skeletonWidth, className }: SkeletonCellProps) {
+  return (
+    <TableCell className={cn(`w-[${width}] hidden md:table-cell`, className)}>
+      <Skeleton
+        className={cn(`h-3.5 w-[${skeletonWidth}]`, isEmpty && "animate-none")}
+      />
+    </TableCell>
+  );
+}

 <TableRow key={row.id} className="h-[45px]">
   <TableCell className="w-[300px]">
     <Skeleton
       className={cn("h-3.5 w-[80%]", isEmpty && "animate-none")}
     />
   </TableCell>
-  <TableCell className="w-[120px] hidden md:table-cell">
-    <Skeleton
-      className={cn("h-3.5 w-[70%]", isEmpty && "animate-none")}
-    />
-  </TableCell>
-  <TableCell className="w-[400px] hidden md:table-cell">
-    <Skeleton
-      className={cn("h-3.5 w-[90%]", isEmpty && "animate-none")}
-    />
-  </TableCell>
-  <TableCell className="w-[150px] hidden md:table-cell">
-    <Skeleton
-      className={cn("h-3.5 w-[60%]", isEmpty && "animate-none")}
-    />
-  </TableCell>
+  <SkeletonCell width="120px" skeletonWidth="70%" />
+  <SkeletonCell width="400px" skeletonWidth="90%" />
+  <SkeletonCell width="150px" skeletonWidth="60%" />
 </TableRow>
apps/web/src/app/components/pitch/pitch-carousel.tsx (1)

46-46: Consider enhancing the loading fallback UI.

The basic loading div could be improved to match the application's design system and provide a better user experience.

Consider replacing with a more sophisticated loading state:

-    <React.Suspense fallback={<div>Loading...</div>}>
+    <React.Suspense
+      fallback={
+        <div className="flex items-center justify-center w-full h-full">
+          <div className="animate-pulse space-y-4">
+            <div className="h-48 w-96 bg-muted rounded" />
+            <div className="h-4 w-48 bg-muted rounded mx-auto" />
+          </div>
+        </div>
+      }
+    >
apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesListSkeleton.tsx (1)

43-43: Consider making the skeleton row count configurable.

The hardcoded value of 5 rows might not match the actual data size. Consider making it a prop with a default value.

-            {Array.from({ length: 5 }).map((_, index) => (
+            {Array.from({ length: rowCount ?? 5 }).map((_, index) => (
apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/actions/get-policy-details.ts (1)

34-54: Consider optimizing the database query.

The query fetches all fields from the policy relation. Consider selecting only the required fields to optimize performance.

 const policy = await db.organizationPolicy.findUnique({
   where: {
     id: policyId,
     organizationId,
   },
   select: {
     id: true,
     status: true,
     content: true,
     createdAt: true,
     updatedAt: true,
     policy: {
       select: {
-        id: true,
-        name: true,
-        description: true,
-        slug: true,
+        // Select only the fields you need
+        name: true,
+        description: true,
       },
     },
   },
 });
apps/app/src/components/status-policies.tsx (1)

13-18: Consider using theme-based color tokens instead of hardcoded hex values.

The hardcoded hex values make it difficult to maintain consistent theming across the application and could cause issues with dark mode support.

Consider using semantic color tokens from your UI system:

const STATUS_COLORS: Record<StatusType, string> = {
-  draft: "#ffc107",
-  published: "#00DC73",
-  archived: "#0ea5e9",
-  needs_review: "#ff0000",
+  draft: "var(--color-warning)",
+  published: "var(--color-success)",
+  archived: "var(--color-info)",
+  needs_review: "var(--color-error)",
} as const;
apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/actions/update-policy.ts (2)

47-48: Improve type safety for updateData object.

Using Record<string, any> reduces type safety. Consider creating a specific type for the update data structure.

-      const updateData: Record<string, any> = {};
+      const updateData: Partial<{
+        content: typeof content;
+        status: typeof status;
+      }> = {};

91-94: Simplify path revalidation logic.

Multiple revalidation paths with similar patterns could be simplified using a helper function.

+      const paths = [
+        `/policies/${policyId}`,
+        "/policies",
+        `/[locale]/policies/${policyId}`,
+        "/[locale]/policies"
+      ];
+      paths.forEach(path => revalidatePath(path));
-      revalidatePath(`/policies/${policyId}`);
-      revalidatePath("/policies");
-      revalidatePath(`/[locale]/policies/${policyId}`);
-      revalidatePath("/[locale]/policies");
apps/app/src/components/tables/policies/data-table.tsx (2)

67-70: Improve column visibility logic maintainability.

The column visibility conditions are hardcoded and could become difficult to maintain as more columns are added.

Consider using a configuration object:

+const HIDDEN_COLUMNS = {
+  description: true,
+  updatedAt: true,
+  status: true,
+};
+
       className={cn(
-        (cell.column.id === "description" ||
-          cell.column.id === "updatedAt" ||
-          cell.column.id === "status") &&
-          "hidden md:table-cell",
+        HIDDEN_COLUMNS[cell.column.id as keyof typeof HIDDEN_COLUMNS] &&
+          "hidden md:table-cell",
       )}

30-35: Improve generic type usage.

The generic type parameter TValue is unused, and TData is overridden with PolicyType.

-export function DataTable<TData, TValue>({
+export function DataTable({
   columnHeaders,
   data,
   pageCount,
   currentPage,
-}: DataTableProps<TData, TValue>) {
+}: DataTableProps<PolicyType>) {

Update the interface accordingly:

-interface DataTableProps<TData, TValue> {
+interface DataTableProps<TData> {
packages/ui/src/editor.css (1)

24-29: Eliminate duplicate .hljs-name references.

.hljs-name is declared twice (on lines 24 and 27), which can complicate maintenance. Consider merging them into a single declaration block to keep the code clean.

packages/db/prisma/seed.js (1)

297-298: Use logs judiciously when seeding evidence.

Logging each evidence requirement might be verbose for production. Confirm that these logs are essential or limit them to dev-only.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/types/index.ts (2)

16-21: Consider restricting negative page values.

policiesInputSchema defaults page to 1 and per_page to 10, but does not prevent negative inputs. Enforce a lower bound to avoid unexpected pagination issues.

-  page: z.number().default(1),
-  per_page: z.number().default(10),
+  page: z.number().min(1).default(1),
+  per_page: z.number().min(1).default(10),

31-34: Potential expansions for AppError.

The narrow set of codes is sufficient for now. Consider adding more error codes as your application grows.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx (2)

41-46: Consider using more specific types for state variables.

The state variables could benefit from more specific typing:

  • saveStatus already uses a union type
  • currentContent is typed as any
-  const [currentContent, setCurrentContent] = useState<any>(null);
+  const [currentContent, setCurrentContent] = useState<JSONContent | null>(null);

118-138: Consider adding error boundary for editor content initialization.

The try-catch block handles errors silently. Consider wrapping the editor in an error boundary component.

apps/app/src/components/editor/policy-editor.tsx (1)

27-31: Consider adding loading state management.

The component manages saving and dirty states but lacks a loading state for initial content loading.

   const [editorContent, setEditorContent] = useState<JSONContent[]>(content);
   const [isSaving, setIsSaving] = useState<boolean>(false);
   const [isDirty, setIsDirty] = useState<boolean>(false);
+  const [isLoading, setIsLoading] = useState<boolean>(true);
apps/app/src/components/tables/policies/filter-toolbar.tsx (1)

143-153: Consider adding loading state to clear filters button.

The clear filters button shows isPending state but could benefit from a loading indicator.

   <Button
     variant="ghost"
     size="sm"
     onClick={handleReset}
     disabled={isPending}
   >
-    <X className="h-4 w-4 mr-2" />
+    {isPending ? (
+      <Loader2 className="h-4 w-4 mr-2 animate-spin" />
+    ) : (
+      <X className="h-4 w-4 mr-2" />
+    )}
     {t("common.actions.clear")}
   </Button>
apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicies.ts (1)

11-32: Consider unifying error checks.
Currently, the code checks !result and then separately checks result.serverError. You could unify them or share logic in one block if desired. This is more of a stylistic preference and has no impact on correctness.

Possible unified structure:

 async function fetchPolicies(input: PoliciesInput): Promise<PoliciesResponse> {
   const result = await getPolicies(input);
+  if (!result || result.serverError) {
+    const error: AppError = {
+      code: "UNEXPECTED_ERROR",
+      message: result?.serverError || "An unexpected error occurred",
+    };
+    throw error;
+  }
   return result.data?.data as PoliciesResponse;
 }
apps/app/src/components/tables/policies/columns.tsx (1)

9-20: New PolicyType definition is clear.
The nested policy object within PolicyType is easy to read, though it duplicates an id at both the top level and within policy. Consider consolidating if that duplication is unnecessary.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicy.ts (3)

17-41: fetchPolicyDetails error-handling is consistent with the rest of the app.
Similar to the logic in usePolicies—consider consolidating or unifying error checks if desired.


56-80: Use consistent user-facing error handling.
The console.error approach is fine for debugging, but consider a user notification or logging system for production to ensure visibility of errors.


91-126: Recursive content processing is well-structured.
The function handles arrays and nested attributes thoroughly. Be mindful that, if content is extremely large or deeply nested, performance might degrade.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14806fd and dc3adb8.

⛔ Files ignored due to path filters (2)
  • apps/app/languine.lock is excluded by !**/*.lock
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (51)
  • apps/app/package.json (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/hooks/useOrganizationControl.ts (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/components/PoliciesOverview.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/loading.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/actions/publish-policy.ts (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/actions/get-policy-details.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/actions/update-policy.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/types/index.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policies.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policy.ts (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/actions/get-policies.ts (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/components/PoliciesTable.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/hooks/usePolicies.ts (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/layout.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/page.tsx (0 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesList.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesListSkeleton.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicies.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicy.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/layout.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/page.tsx (3 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/types/index.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/types/search-params.ts (1 hunks)
  • apps/app/src/components/editor/actions/ai.ts (0 hunks)
  • apps/app/src/components/editor/editor.tsx (0 hunks)
  • apps/app/src/components/editor/policy-editor.tsx (1 hunks)
  • apps/app/src/components/status-policies.tsx (1 hunks)
  • apps/app/src/components/tables/policies/columns.tsx (1 hunks)
  • apps/app/src/components/tables/policies/data-table-header.tsx (1 hunks)
  • apps/app/src/components/tables/policies/data-table.tsx (4 hunks)
  • apps/app/src/components/tables/policies/empty-states.tsx (1 hunks)
  • apps/app/src/components/tables/policies/filter-toolbar.tsx (4 hunks)
  • apps/app/src/components/tables/policies/loading.tsx (1 hunks)
  • apps/app/src/components/tables/policies/server-columns.tsx (1 hunks)
  • apps/app/src/locales/en.ts (6 hunks)
  • apps/app/src/locales/es.ts (6 hunks)
  • apps/app/src/locales/fr.ts (6 hunks)
  • apps/app/src/locales/no.ts (6 hunks)
  • apps/app/src/locales/pt.ts (6 hunks)
  • apps/app/src/types/actions.ts (0 hunks)
  • apps/app/src/utils/format.ts (1 hunks)
  • apps/portal/src/app/[locale]/providers.tsx (1 hunks)
  • apps/web/src/app/components/pitch/pitch-carousel.tsx (1 hunks)
  • apps/web/src/app/providers.tsx (1 hunks)
  • packages/db/prisma/seed.js (5 hunks)
  • packages/ui/package.json (1 hunks)
  • packages/ui/src/editor.css (1 hunks)
💤 Files with no reviewable changes (14)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/loading.tsx
  • apps/app/src/components/editor/editor.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/components/PoliciesTable.tsx
  • apps/app/src/components/editor/actions/ai.ts
  • apps/app/src/types/actions.ts
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/(overview)/components/PoliciesOverview.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policy.ts
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/layout.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/page.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/actions/get-policies.ts
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/layout.tsx
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/hooks/usePolicies.ts
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/actions/publish-policy.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/editor.css

[error] 191-191: Expected a declaration, or an at rule but instead found ';'.

Expected a declaration, or an at rule here.

(parse)

🔇 Additional comments (74)
apps/app/src/components/tables/policies/empty-states.tsx (1)

1-6: LGTM! Well-organized imports.

The imports are properly structured, using appropriate aliasing and specific component imports.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/layout.tsx (2)

11-19: LGTM! Clean and well-structured layout implementation.

The layout implementation is clean and follows best practices:

  • Proper TypeScript typing
  • Efficient server-side i18n integration
  • Clear component structure with SecondaryMenu and main content

14-14:

Details

❓ Verification inconclusive

Verify the navigation changes and translation key.

The menu structure has been simplified to a single item. Please ensure:

  1. The removal of the "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/policies/all" path aligns with the new navigation structure
  2. The new translation key "policies.title" exists and contains the correct text

🏁 Script executed:

#!/bin/bash
# Check if the new translation key exists in localization files
rg -g '*.{json,ts,js}' '"policies.title"'

# Check for any remaining references to the old path
rg -g '*.{ts,tsx,js,jsx}' '"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/policies/all"'

Length of output: 89


Action Required: Manual Verification of Navigation & Translation Keys

The updated menu structure now features a single navigational item using the translation key "policies.title". Please manually verify that:

  • The new translation key "policies.title" exists in your localization files and contains the correct text.
  • The removal of the "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/policies/all" path fully aligns with the intended navigation behavior and doesn’t negatively impact user flows.
apps/portal/src/app/[locale]/providers.tsx (1)

5-5:

Details

❓ Verification inconclusive

Verify the impact of removing Analytics initialization.

The removal of the Analytics import and its initialization logic might affect analytics tracking functionality. Please confirm if this is intentional and if analytics tracking will still work as expected with just the AnalyticsProvider.

Run the following script to check for any remaining Analytics initialization in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Check for Analytics initialization and usage in the codebase.

# Search for Analytics initialization
rg -A 5 'Analytics\.' apps/

# Search for Analytics imports
rg -A 5 'import.*Analytics.*from.*@bubba/analytics' apps/

Length of output: 2691


Verify Analytics Initialization Behavior

The file still imports the AnalyticsProvider but the explicit initialization logic has been removed. Note that tracking calls in other parts of the codebase (e.g., in waitlist actions) are still using ServerAnalytics.track. Please confirm if the removal of the explicit Analytics initialization in this client component is intentional and ensure that the analytics tracking will function correctly under the new implementation.

packages/ui/package.json (1)

92-93: LGTM! Clean addition of editor-related CSS exports.

The new exports for prosemirror and editor CSS are well-structured and follow the established pattern in the package.

apps/app/src/components/tables/policies/server-columns.tsx (1)

6-10: LGTM! Improved translation key organization.

The changes improve semantic clarity by using more appropriate translation keys (common.status.title and common.last_updated).

apps/app/package.json (1)

26-29: LGTM! Tiptap table dependencies added consistently.

The new table-related dependencies are added with consistent versioning (^2.11.5), which is good for maintaining compatibility.

apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/hooks/useOrganizationControl.ts (1)

5-5: LGTM! Code style improvements.

The changes improve code consistency by:

  • Using explicit type import syntax
  • Adding trailing commas

Also applies to: 10-11, 34-34

apps/app/src/components/tables/policies/data-table-header.tsx (2)

75-89: LGTM! Column visibility and sorting for status.

The changes improve semantic clarity by using "status" instead of "published" and updating the translation key.


91-107: LGTM! Column visibility and sorting for updatedAt.

The changes improve semantic clarity by using "updatedAt" instead of "ownerId" and updating the translation key.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/page.tsx (2)

18-22: Verify the redirect path change.

The redirect path has been changed from "/onboarding" to "/". Please ensure this change aligns with the expected user flow when no organization ID is present.


24-26: LGTM! Server-side column headers.

Good separation of concerns by fetching column headers server-side and passing them as props.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx (2)

8-9: LGTM! Dynamic rendering and caching configuration.

Appropriate configuration for dynamic content that needs to be always up-to-date.


43-47: LGTM! Cache control headers.

Comprehensive cache control headers to prevent caching of sensitive policy details.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/actions/get-policy-details.ts (2)

11-20: LGTM! Action configuration with schema validation and metadata.

Well-structured server action with proper schema validation and tracking metadata.


67-73: LGTM! Error handling and logging.

Good practice to log errors and return a generic error message to the client.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesList.tsx (1)

68-68:

Details

❓ Verification inconclusive

Verify the empty users array usage.

An empty users array is being passed to FilterToolbar. This might indicate missing functionality or an incomplete implementation.

Also applies to: 79-79


🏁 Script executed:

#!/bin/bash
# Search for FilterToolbar usage and users prop implementation
ast-grep --pattern 'FilterToolbar $$$users={$_}$$$'

Length of output: 53


Action Required: Verify Empty Users Prop in FilterToolbar

It appears that PoliciesList.tsx currently passes an empty array (users={[]}) to the FilterToolbar component (at both line 68 and line 79). This might be intentional if the component is designed to handle an empty list, but it could also indicate missing functionality or incomplete data handling.

  • Please double-check the FilterToolbar component:
    • Verify its prop type definitions and intended behavior regarding the users array.
    • Ensure that an empty array is acceptable or that proper fallback logic is in place.
  • Confirm whether this is a temporary placeholder or if the component should be wired to actual user data.

Since our initial search for the users prop usage did not yield additional insights, a manual review of the FilterToolbar implementation is advised.

packages/ui/src/editor.css (1)

1-221: Overall, good use of Tailwind @apply and syntax highlighting styles.

The file demonstrates well-structured, comprehensive editor styling for code blocks, headings, and layout. No major issues beyond the minor duplicates and parsing error.

🧰 Tools
🪛 Biome (1.9.4)

[error] 191-191: Expected a declaration, or an at rule but instead found ';'.

Expected a declaration, or an at rule here.

(parse)

packages/db/prisma/seed.js (5)

19-20: Confirm data deletion of organization requirements and evidence.

Deleting all organizationControlRequirement and organizationEvidence records is drastic and may not be intended for all environments. Ensure you only run this in the correct context (e.g., development).


28-28: Redundant data deletion of evidence.

You’re already deleting organizationEvidence above. Confirm that this additional await prisma.evidence.deleteMany(); line is intended, as it removes data from a different table.


87-88: Ensure frequency alignment with design requirements.

Explicitly storing frequency for policies is beneficial. Verify that the data model (and default values) match your domain’s needs (e.g., number, string).

Also applies to: 96-97


222-223: Check consistency when upserting frequency in control requirements.

The frequency field is upserted in both create and update blocks. Make sure the domain logic or data type is consistent with other references.

Also applies to: 230-231


301-306: Upserting frequency for evidence.

The upsert logic consistently sets frequency to evidenceReq.frequency ?? null. This aligns well with other uses of frequency.

Also applies to: 309-313

apps/app/src/app/[locale]/(app)/(dashboard)/policies/types/index.ts (4)

3-14: Validate date fields in policySchema.

createdAt and updatedAt use z.date(). Ensure your data loading pipeline properly converts date strings. Otherwise, parse errors might occur.


23-25: Types Policy and PoliciesInput look correct.

The z.infer usage ensures strong type checks for consumers of these schemas.


26-29: PoliciesResponse interface is straightforward.

The interface handles multiple policies plus a total count. This is aligned with pagination best practices.


36-45: Centralized appErrors object.

This is a clean pattern for standardizing error handling. Great approach for clarity and reusability.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policies.ts (7)

4-5: Imports for database and appErrors introduced.

Code now centralizes error messages and references the shared DB instance. This improves consistency.


8-8: Added input schema validation.

Using .schema(policiesInputSchema) ensures your action enforces consistent request params.


16-17: Improved destructuring for pagination and filtering.

This clarifies the parameters (search, status, page, and per_page) for the policy query.


23-23: Use standardized unauthorized error message.

Returning appErrors.UNAUTHORIZED.message keeps errors consistent with the rest of your app.


28-66: Comprehensive pagination and filtering logic.

Queries the organization’s policies with optional search, status filtering, skip/take pagination, and concurrency via Promise.all. This is efficient and clean.


88-88: Returning { policies, total } in data.

This output structure is well-formed and aligns with the PoliciesResponse interface.


94-94: Unified error handling.

Falling back to appErrors.UNEXPECTED_ERROR.message ensures consistent messaging for unhandled errors.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx (1)

71-116: LGTM! Well-configured editor with essential extensions.

The editor configuration is comprehensive, including necessary extensions for rich text editing and tables.

apps/app/src/components/tables/policies/filter-toolbar.tsx (1)

87-108: LGTM! Well-implemented responsive design.

The component handles mobile and desktop layouts appropriately with conditional rendering.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/types/index.ts (1)

32-50: LGTM! Well-structured error handling.

The error types and constants are well-defined with clear messages.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicies.ts (6)

3-4: No immediate issues with the new imports.
All imports look correct.


9-10: Types import structure looks good.
No concerns here; these types are clearly named and presumably well-defined in the referenced file.


36-41: Validate numeric query parameters.
The default values 1 and 10 for page and per_page can cause unexpected behavior if the query strings are non-numeric or negative. Consider adding a quick numeric check or relying on server-side schema validation.


42-50: SWR usage is consistent with the new fetch function.
No issues spotted; the key structure ensures correct caching and invalidation.


57-61: Returned shape aligns with PoliciesResponse.
Exposing policies, total, and the revalidatePolicies function is clear and maintains a straightforward API.


63-63: No further concerns at the end of the hook.
This closing brace appears to reflect a minor structural change with no functional impact.

apps/app/src/components/tables/policies/columns.tsx (5)

3-4: Imports for status and date formatting look correct.
Utilizing StatusPolicies and formatDate is appropriate for clarifying UI components and display logic.


26-30: Accessing fields on row.original.policy is logically consistent.
No issues found; the fallback to policy.name is properly handled.


35-36: Link and mobile display logic are appropriate.
Usage of <Link> for navigation and <StatusPolicies> for mobile layouts ensures responsiveness.

Also applies to: 40-40


47-48: Status column is straightforward.
No concerns; mapping the status to StatusPolicies is a clean approach for consistent rendering.

Also applies to: 50-50, 54-54


60-61: Date column is well-handled with formatDate.
This is a neat approach, preserving localized date formats.

Also applies to: 66-67

apps/app/src/app/[locale]/(app)/(dashboard)/policies/hooks/usePolicy.ts (4)

4-6: Imports for policy details and updates appear correct.
No issues with referencing these action files and their types.


8-15: ContentNode interface is clearly structured.
This interface captures a wide variety of node attributes. No issues spotted.


44-54: SWR setup for policy details looks correct.
Using ["policy-details", policyId] as the key helps differentiate from the overall policies list.


83-83: Exported return structure is intuitive.
No issues with the naming (policy, isLoading, updatePolicy, refreshPolicy); it's straightforward and descriptive.

Also applies to: 86-87

apps/app/src/locales/es.ts (5)

53-54: LGTM! Status translations are clear and consistent.

The translations for "inactive" and "title" are accurate and align well with Spanish language conventions.


76-76: LGTM! Empty state message improved.

The change from "Sin resultados" to "No se encontraron resultados" provides better clarity and natural phrasing in Spanish.


147-153: LGTM! Common translations are accurate and consistent.

The new translations for edit, errors, description, and last_updated are well-structured and natural in Spanish.


257-258: LGTM! Policy-related translations are comprehensive.

The translations for policy statuses, actions, and messages are accurate and maintain consistency with the application's terminology.

Also applies to: 274-291


708-709: LGTM! Sub-page translations are clear.

The translations for policy editor and details pages are accurate and consistent with the application's navigation structure.

apps/app/src/locales/pt.ts (5)

53-54: LGTM! Status translations are accurate.

The translations for "inactive" and "title" are appropriate and follow Portuguese language conventions.


76-76: LGTM! Empty state message improved.

The change to "Nenhum resultado encontrado" provides better clarity in Portuguese.


147-153: LGTM! Common translations are well-structured.

The new translations for edit, errors, description, and last_updated are natural and accurate in Portuguese.


257-258: LGTM! Policy-related translations are comprehensive.

The translations for policy management features are accurate and maintain consistency with the application's terminology in Portuguese.

Also applies to: 274-291


708-709: LGTM! Sub-page translations are clear.

The translations for policy editor and details pages follow Portuguese language conventions.

apps/app/src/locales/no.ts (5)

53-54: LGTM! Status translations are accurate.

The translations for "inactive" and "title" are appropriate and follow Norwegian language conventions.


76-76: LGTM! Empty state message improved.

The change to "Ingen resultater funnet" provides better clarity in Norwegian.


147-153: LGTM! Common translations are well-structured.

The new translations for edit, errors, description, and last_updated are natural and accurate in Norwegian.


257-258: LGTM! Policy-related translations are comprehensive.

The translations for policy management features are accurate and maintain consistency with the application's terminology in Norwegian.

Also applies to: 274-291


708-709: LGTM! Sub-page translations are clear.

The translations for policy editor and details pages follow Norwegian language conventions.

apps/app/src/locales/fr.ts (5)

53-54: LGTM! Status translations are accurate.

The translations for "inactive" and "title" are appropriate and follow French language conventions.


76-76: LGTM! Empty state message improved.

The change to "Aucun résultat trouvé" provides better clarity in French.


147-153: LGTM! Common translations are well-structured.

The new translations for edit, errors, description, and last_updated are natural and accurate in French.


257-258: LGTM! Policy-related translations are comprehensive.

The translations for policy management features are accurate and maintain consistency with the application's terminology in French.

Also applies to: 274-291


708-709: LGTM! Sub-page translations are clear.

The translations for policy editor and details pages follow French language conventions.

apps/app/src/locales/en.ts (2)

150-155: LGTM! Well-structured common translations.

Good addition of commonly used UI strings in the common section. This promotes reusability and consistency across the application.


308-321: LGTM! Comprehensive policy management translations.

Well-structured set of translations covering all necessary UI elements for policy management, including:

  • Creation flows
  • Search functionality
  • Status filtering
  • Empty states
  • Success/error messages


import { env } from "@/env.mjs";
import { Analytics, AnalyticsProvider } from "@bubba/analytics";
import { AnalyticsProvider } from "@bubba/analytics";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the architectural decision to remove Analytics initialization.

The removal of Analytics initialization is consistent across both web and portal apps, suggesting a deliberate architectural change. Please ensure this change is documented and that the new approach to analytics tracking is clearly communicated.

Run the following script to check for documentation about this architectural change:


🏁 Script executed:

#!/bin/bash
# Description: Check for documentation about analytics architecture.

# Search for documentation files
fd -e md -e mdx | xargs rg -i 'analytics'

# Check commit messages for context
git log -S "Analytics" --pickaxe-regex --pretty=format:'%h %s' -p

Length of output: 169


Action: Document the Architectural Change for Analytics Removal

The removal of Analytics initialization in apps/web/src/app/providers.tsx is clearly intentional and consistent with the changes seen in the portal providers. However, our documentation review only surfaced a reference to an analytics image in the README, and there isn’t any explicit documentation detailing this architectural decision. Please ensure that the rationale and the new approach to analytics tracking are clearly documented (for example, by updating relevant architecture documents or commit messages) so that the intent behind these changes is transparent to all developers.

Comment on lines +13 to +72
<div className="space-y-6">
<div className="flex items-center justify-between">
<Skeleton className="h-8 w-32" />
<Skeleton className="h-9 w-32" />
</div>

<div className="flex gap-4 mb-6">
<Skeleton className="h-10 flex-1 max-w-md" />
<Skeleton className="h-10 w-[180px]" />
</div>

<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>
<Skeleton className="h-5 w-32" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-20" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-40" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-28" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: 5 }).map((_, index) => (
<TableRow key={`skeleton-row-${index + 1}`}>
<TableCell>
<Skeleton className="h-5 w-40" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-20" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-full max-w-[300px]" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-24" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>

<div className="flex justify-between items-center px-2 py-4">
<Skeleton className="h-5 w-40" />
<div className="flex gap-2">
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
</div>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add ARIA attributes for better accessibility.

The skeleton loader should be properly announced to screen readers to indicate the loading state.

Add appropriate ARIA attributes:

-    <div className="space-y-6">
+    <div 
+      className="space-y-6"
+      role="status"
+      aria-label="Loading policies"
+      aria-busy="true"
+    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="space-y-6">
<div className="flex items-center justify-between">
<Skeleton className="h-8 w-32" />
<Skeleton className="h-9 w-32" />
</div>
<div className="flex gap-4 mb-6">
<Skeleton className="h-10 flex-1 max-w-md" />
<Skeleton className="h-10 w-[180px]" />
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>
<Skeleton className="h-5 w-32" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-20" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-40" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-28" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: 5 }).map((_, index) => (
<TableRow key={`skeleton-row-${index + 1}`}>
<TableCell>
<Skeleton className="h-5 w-40" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-20" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-full max-w-[300px]" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-24" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex justify-between items-center px-2 py-4">
<Skeleton className="h-5 w-40" />
<div className="flex gap-2">
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
</div>
</div>
</div>
<div
className="space-y-6"
role="status"
aria-label="Loading policies"
aria-busy="true"
>
<div className="flex items-center justify-between">
<Skeleton className="h-8 w-32" />
<Skeleton className="h-9 w-32" />
</div>
<div className="flex gap-4 mb-6">
<Skeleton className="h-10 flex-1 max-w-md" />
<Skeleton className="h-10 w-[180px]" />
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>
<Skeleton className="h-5 w-32" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-20" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-40" />
</TableHead>
<TableHead>
<Skeleton className="h-5 w-28" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Array.from({ length: 5 }).map((_, index) => (
<TableRow key={`skeleton-row-${index + 1}`}>
<TableCell>
<Skeleton className="h-5 w-40" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-20" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-full max-w-[300px]" />
</TableCell>
<TableCell>
<Skeleton className="h-5 w-24" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="flex justify-between items-center px-2 py-4">
<Skeleton className="h-5 w-40" />
<div className="flex gap-2">
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
<Skeleton className="h-8 w-8" />
</div>
</div>
</div>

Comment on lines +26 to +28
className={cn("size-2.5")}
style={{ backgroundColor: STATUS_COLORS[status] ?? " " }}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the empty space fallback.

The fallback for missing status colors uses two spaces which is not a valid color value.

-        style={{ backgroundColor: STATUS_COLORS[status] ?? "  " }}
+        style={{ backgroundColor: STATUS_COLORS[status] ?? "transparent" }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={cn("size-2.5")}
style={{ backgroundColor: STATUS_COLORS[status] ?? " " }}
/>
className={cn("size-2.5")}
style={{ backgroundColor: STATUS_COLORS[status] ?? "transparent" }}
/>

const updateData: Record<string, any> = {};

if (content !== undefined) {
console.log("CONTENT TYPE:", typeof content);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove console.log statements before production deployment.

Debug statements should not be present in production code.

-        console.log("CONTENT TYPE:", typeof content);
-            console.log("Extracted content array from TipTap doc");
-            console.log("Unknown content format - using as is");
-      console.log("Updating policy with data:", JSON.stringify(updateData));

Also applies to: 55-55, 59-59, 78-78

{policies.length > 0 ? (
<DataTable
columnHeaders={columnHeaders}
data={policies as unknown as PolicyType[]}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid type casting with unknown.

Using as unknown as PolicyType[] is a type-safety escape hatch that could hide potential issues.

Consider updating the usePolicies hook to return the correct type directly:

-          data={policies as unknown as PolicyType[]}
+          data={policies}

Update the usePolicies hook return type to match PolicyType[].

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data={policies as unknown as PolicyType[]}
data={policies}

Comment on lines +49 to +69
const debouncedSave = useDebouncedCallback(async (content: any) => {
if (!policy) return;

setSaveStatus("Saving");
try {
const contentToSave =
content.type === "doc" && Array.isArray(content.content)
? content.content
: content;

await updatePolicy({
...policy,
content: contentToSave,
});

setSaveStatus("Saved");
} catch (err) {
console.error("Failed to save policy:", err);
setSaveStatus("Unsaved");
}
}, 1000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling in the save function.

The catch block only logs the error without providing user feedback.

   } catch (err) {
     console.error("Failed to save policy:", err);
     setSaveStatus("Unsaved");
+    // Add user notification
+    toast({
+      title: "Error saving policy",
+      description: "Please try again or contact support if the issue persists.",
+      variant: "destructive",
+    });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const debouncedSave = useDebouncedCallback(async (content: any) => {
if (!policy) return;
setSaveStatus("Saving");
try {
const contentToSave =
content.type === "doc" && Array.isArray(content.content)
? content.content
: content;
await updatePolicy({
...policy,
content: contentToSave,
});
setSaveStatus("Saved");
} catch (err) {
console.error("Failed to save policy:", err);
setSaveStatus("Unsaved");
}
}, 1000);
const debouncedSave = useDebouncedCallback(async (content: any) => {
if (!policy) return;
setSaveStatus("Saving");
try {
const contentToSave =
content.type === "doc" && Array.isArray(content.content)
? content.content
: content;
await updatePolicy({
...policy,
content: contentToSave,
});
setSaveStatus("Saved");
} catch (err) {
console.error("Failed to save policy:", err);
setSaveStatus("Unsaved");
// Add user notification
toast({
title: "Error saving policy",
description: "Please try again or contact support if the issue persists.",
variant: "destructive",
});
}
}, 1000);

Comment on lines +56 to +66
onUpdate: ({ editor }) => {
try {
const json = editor.getJSON().content;
if (json) {
setEditorContent(json as JSONContent[]);
setIsDirty(true);
}
} catch (error) {
console.error("Error updating editor content:", error);
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling in onUpdate callback.

The error is only logged to console without user feedback.

   } catch (error) {
     console.error("Error updating editor content:", error);
+    toast({
+      title: "Error updating content",
+      description: "Changes may not be saved. Please refresh the page.",
+      variant: "destructive",
+    });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onUpdate: ({ editor }) => {
try {
const json = editor.getJSON().content;
if (json) {
setEditorContent(json as JSONContent[]);
setIsDirty(true);
}
} catch (error) {
console.error("Error updating editor content:", error);
}
},
onUpdate: ({ editor }) => {
try {
const json = editor.getJSON().content;
if (json) {
setEditorContent(json as JSONContent[]);
setIsDirty(true);
}
} catch (error) {
console.error("Error updating editor content:", error);
toast({
title: "Error updating content",
description: "Changes may not be saved. Please refresh the page.",
variant: "destructive",
});
}
},

Comment on lines +81 to +90
<Button
variant="outline"
size="sm"
disabled={isSaving || !isDirty}
className={cn("gap-1", isSaving && "opacity-50 cursor-not-allowed")}
>
<Save className="w-4 h-4" />
{isSaving ? "Saving..." : "Save"}
</Button>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add save handler for the save button.

The save button is rendered but lacks an onClick handler.

   <Button
     variant="outline"
     size="sm"
     disabled={isSaving || !isDirty}
+    onClick={handleSave}
     className={cn("gap-1", isSaving && "opacity-50 cursor-not-allowed")}
   >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="outline"
size="sm"
disabled={isSaving || !isDirty}
className={cn("gap-1", isSaving && "opacity-50 cursor-not-allowed")}
>
<Save className="w-4 h-4" />
{isSaving ? "Saving..." : "Save"}
</Button>
</div>
<Button
variant="outline"
size="sm"
disabled={isSaving || !isDirty}
onClick={handleSave}
className={cn("gap-1", isSaving && "opacity-50 cursor-not-allowed")}
>
<Save className="w-4 h-4" />
{isSaving ? "Saving..." : "Save"}
</Button>
</div>

export const policyDetailsSchema = z.object({
id: z.string(),
status: z.enum(["draft", "published", "archived"]),
content: z.array(z.any()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a more specific type for policy content.

Using z.any() for content reduces type safety. Consider defining a specific schema for the content structure.

-  content: z.array(z.any()),
+  content: z.array(z.object({
+    type: z.string(),
+    attrs: z.record(z.unknown()).optional(),
+    content: z.array(z.unknown()).optional(),
+  })),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content: z.array(z.any()),
content: z.array(z.object({
type: z.string(),
attrs: z.record(z.unknown()).optional(),
content: z.array(z.unknown()).optional(),
})),

statuses: {
draft: "Draft",
published: "Published",
archived: "Archived",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Duplicate status translation detected.

The "Archived" status is defined in two locations:

  1. policies.table.statuses.archived
  2. policies.status.archived

This duplication could lead to inconsistencies in translations and maintenance overhead.

Consider consolidating these into a single translation key under common.status since status strings should be reusable across different contexts:

-  policies: {
-    table: {
-      statuses: {
-        archived: "Archived",
-      }
-    },
-    status: {
-      archived: "Archived",
-    }
-  }
+  common: {
+    status: {
+      archived: "Archived"
+    }
+  }

Also applies to: 305-305

@vercel
vercel Bot temporarily deployed to Preview – web February 24, 2025 23:46 Inactive
@vercel
vercel Bot temporarily deployed to Preview – comp-portal February 24, 2025 23:46 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
apps/app/src/components/policies/charts/policies-by-framework.tsx (1)

25-36: Consider enhancing the empty state message.

While the empty state handling is correct, the message "No frameworks have linked policies" could be more descriptive to help users understand what frameworks mean in this context.

-          No frameworks have linked policies
+          No compliance frameworks (SOC 2, ISO 27001, GDPR) have any linked policies
packages/analytics/src/components/provider.tsx (1)

37-42: Consider enhancing the Suspense implementation.

While adding Suspense is good for handling async operations, the current implementation could be improved:

  1. Using null as fallback might cause layout shifts. Consider using a minimal placeholder.
  2. The Suspense boundary around PHProvider might affect analytics collection if the provider suspends.
  3. Missing error handling for potential analytics initialization failures.

Consider this enhanced implementation:

+ import { ErrorBoundary } from 'react-error-boundary';

+ const AnalyticsFallback = () => (
+   <div style={{ width: '100%', height: '100%' }} aria-hidden="true" />
+ );

+ const ErrorFallback = () => {
+   // Silently handle analytics errors while logging them
+   console.error('Analytics failed to load');
+   return null;
+ };

  return (
-   <Suspense fallback={null}>
+   <ErrorBoundary FallbackComponent={ErrorFallback}>
+     <Suspense fallback={<AnalyticsFallback />}>
        <PHProvider client={posthog}>
          <PostHogPageView />
          {children}
        </PHProvider>
+     </Suspense>
+   </ErrorBoundary>
-   </Suspense>
  );
apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesList.tsx (2)

34-35: Extract pagination defaults to constants.

Consider extracting the default values for per_page and page to named constants at the top of the file for better maintainability.

+const DEFAULT_PER_PAGE = 10;
+const DEFAULT_PAGE = 1;
+
 export function PoliciesList({ columnHeaders }: PoliciesListProps) {
   // ...
-  const per_page = Number(searchParams.get("per_page")) || 10;
-  const page = Number(searchParams.get("page")) || 1;
+  const per_page = Number(searchParams.get("per_page")) || DEFAULT_PER_PAGE;
+  const page = Number(searchParams.get("page")) || DEFAULT_PAGE;

43-54: Extract error card to a reusable component.

Consider extracting the error card to a separate reusable component since error states are commonly needed across different parts of the application.

+interface ErrorCardProps {
+  error: Error;
+}
+
+function ErrorCard({ error }: ErrorCardProps) {
+  const t = useI18n();
+  return (
+    <div className="p-6">
+      <Card>
+        <CardContent className="p-6 flex items-center gap-3">
+          <AlertTriangle className="text-red-500 h-5 w-5" />
+          <span>{error.message || t("common.errors.unexpected_error")}</span>
+        </CardContent>
+      </Card>
+    </div>
+  );
+}

 export function PoliciesList({ columnHeaders }: PoliciesListProps) {
   // ...
   if (error) {
-    return (
-      <div className="p-6">
-        <Card>
-          <CardContent className="p-6 flex items-center gap-3">
-            <AlertTriangle className="text-red-500 h-5 w-5" />
-            <span>{error.message || t("common.errors.unexpected_error")}</span>
-          </CardContent>
-        </Card>
-      </div>
-    );
+    return <ErrorCard error={error} />;
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc3adb8 and 2b99c3f.

📒 Files selected for processing (8)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesList.tsx (1 hunks)
  • apps/app/src/components/policies/charts/policies-by-framework.tsx (4 hunks)
  • apps/app/src/components/policies/policy-overview.tsx (0 hunks)
  • apps/app/src/components/tables/policies/data-table.tsx (4 hunks)
  • apps/web/src/app/pitch/page.tsx (0 hunks)
  • packages/analytics/src/components/page-view.tsx (0 hunks)
  • packages/analytics/src/components/provider.tsx (2 hunks)
  • packages/analytics/src/index.ts (0 hunks)
💤 Files with no reviewable changes (4)
  • packages/analytics/src/components/page-view.tsx
  • packages/analytics/src/index.ts
  • apps/web/src/app/pitch/page.tsx
  • apps/app/src/components/policies/policy-overview.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/app/src/components/tables/policies/data-table.tsx
🔇 Additional comments (6)
apps/app/src/components/policies/charts/policies-by-framework.tsx (3)

16-16: LGTM! Improved variable naming.

The change from data to policies provides better semantic meaning and aligns with the domain model.


46-46: LGTM! Consistent variable naming.

The change maintains consistency with the earlier variable renaming while preserving the expected data structure for the BarChart component.


76-78: LGTM! Simplified mapping logic.

The change to use policy.id directly improves code clarity by eliminating the unnecessary entry abstraction.

packages/analytics/src/components/provider.tsx (1)

5-5: LGTM! Clean import addition.

The Suspense import is correctly grouped with existing React imports, following best practices.

apps/app/src/app/[locale]/(app)/(dashboard)/policies/components/PoliciesList.tsx (2)

84-84: Avoid type casting with unknown.

Using as unknown as PolicyType[] is a type-safety escape hatch that could hide potential issues.


85-85: Use Math.ceil for pageCount calculation.

The pageCount calculation should use Math.ceil to handle cases where total is not perfectly divisible by per_page.

Comment on lines +68 to +69
<NoPolicies />
<Loading isEmpty />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant Loading component.

The Loading component with isEmpty prop appears redundant when already showing NoPolicies component. Consider removing it to avoid potential confusion.

         <FilterToolbar isEmpty={true} users={[]} />
         <NoPolicies />
-        <Loading isEmpty />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<NoPolicies />
<Loading isEmpty />
<FilterToolbar isEmpty={true} users={[]} />
<NoPolicies />

This branch was successfully deployed

1 active and 2 inactive deployments
Preview – app — 2b99c3f8 Deployed Feb 24, 2025 by vercel[bot]
Preview – web — 2b99c3f8 Deployed Feb 24, 2025 by vercel[bot]
Preview – comp-portal — 2b99c3f8 Deployed Feb 24, 2025 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants