From 3559b9e7c5c6d553755b89ac8be4a74462695756 Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Tue, 25 Feb 2025 14:47:24 +0000 Subject: [PATCH] refactor: Enhance policy details page with improved header and layout - Extracted PolicyHeader component for better separation of concerns - Removed dynamic rendering and caching configurations - Updated editor styling and layout - Added inline editing for policy name - Simplified policy details page structure - Updated status policies component to support custom className - Refreshed logo header design --- .../[policyId]/components/PolicyDetails.tsx | 45 +++------ .../[policyId]/components/PolicyHeader.tsx | 94 +++++++++++++++++++ .../(dashboard)/policies/[policyId]/page.tsx | 10 -- apps/app/src/components/status-policies.tsx | 10 +- apps/web/src/app/(home)/page.tsx | 17 ---- apps/web/src/app/components/logo-header.tsx | 22 +++-- packages/ui/src/components/badge.tsx | 2 +- packages/ui/src/editor.css | 2 +- 8 files changed, 131 insertions(+), 71 deletions(-) create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyHeader.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx index 2847e33e47..2ef99fe9fe 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyDetails.tsx @@ -2,7 +2,6 @@ import { useI18n } from "@/locales/client"; import { Alert, AlertDescription, AlertTitle } from "@bubba/ui/alert"; -import { Button } from "@bubba/ui/button"; import { Card, CardContent, CardHeader } from "@bubba/ui/card"; import { Skeleton } from "@bubba/ui/skeleton"; import Bold from "@tiptap/extension-bold"; @@ -22,13 +21,13 @@ import TableRow from "@tiptap/extension-table-row"; import Text from "@tiptap/extension-text"; import Underline from "@tiptap/extension-underline"; import { EditorContent, useEditor } from "@tiptap/react"; -import { AlertCircle, Save } from "lucide-react"; +import { AlertCircle } from "lucide-react"; import { redirect } from "next/navigation"; -import { EditorRoot } from "novel"; import React, { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; import { usePolicyDetails } from "../../hooks/usePolicy"; +import { PolicyHeader } from "./PolicyHeader"; import "@bubba/ui/editor.css"; -import { useDebouncedCallback } from "use-debounce"; interface PolicyDetailsProps { policyId: string; @@ -45,11 +44,11 @@ export function PolicyDetails({ policyId }: PolicyDetailsProps) { const [currentContent, setCurrentContent] = useState(null); const [initialLoadComplete, setInitialLoadComplete] = useState(false); - // Function to save content with debounce const debouncedSave = useDebouncedCallback(async (content: any) => { if (!policy) return; setSaveStatus("Saving"); + try { const contentToSave = content.type === "doc" && Array.isArray(content.content) @@ -66,7 +65,7 @@ export function PolicyDetails({ policyId }: PolicyDetailsProps) { console.error("Failed to save policy:", err); setSaveStatus("Unsaved"); } - }, 1000); + }, 1500); const editor = useEditor({ extensions: [ @@ -97,7 +96,7 @@ export function PolicyDetails({ policyId }: PolicyDetailsProps) { editorProps: { attributes: { class: - "prose dark:prose-invert focus:outline-none h-full w-full focus:outline-none text-foreground px-16 py-16 max-w-[900px] mx-auto", + "prose dark:prose-invert focus:outline-none h-full w-full focus:outline-none text-foreground max-w-none", }, }, onUpdate: ({ editor }) => { @@ -137,12 +136,6 @@ export function PolicyDetails({ policyId }: PolicyDetailsProps) { } }, [editor, policy, initialLoadComplete]); - // Manual save function - const handleManualSave = () => { - if (!editor || !policy) return; - debouncedSave.flush(); - }; - if (error) { if (error.code === "NOT_FOUND") { redirect("/policies"); @@ -184,25 +177,17 @@ export function PolicyDetails({ policyId }: PolicyDetailsProps) { ); } - if (!policy) return null; + if (!policy) return redirect("/policies"); return ( -
-
-
- {saveStatus} -
-
- {wordCount} Words -
-
- - - - +
+ +
); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyHeader.tsx new file mode 100644 index 0000000000..faafed1161 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/components/PolicyHeader.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { StatusPolicies, type StatusType } from "@/components/status-policies"; +import type { PolicyStatus } from "@bubba/db"; +import { Badge } from "@bubba/ui/badge"; +import { formatDistanceToNow } from "date-fns"; +import { Calendar, Clock } from "lucide-react"; +import { useState } from "react"; +import type { PolicyDetails } from "../types"; + +interface PolicyHeaderProps { + policy: PolicyDetails; + saveStatus: "Saved" | "Saving" | "Unsaved"; + wordCount: number; + status: PolicyStatus; +} + +export function PolicyHeader({ + policy, + saveStatus, + wordCount, + status, +}: PolicyHeaderProps) { + const [isEditing, setIsEditing] = useState(false); + const [policyName, setPolicyName] = useState(policy.policy.name); + + const handleNameChange = (e: React.FocusEvent) => { + setIsEditing(false); + }; + + const lastUpdated = formatDistanceToNow(new Date(policy.updatedAt), { + addSuffix: true, + }); + + return ( +
+
+
+
+
+ {isEditing ? ( +

{ + if (e.key === "Enter") { + e.preventDefault(); + e.currentTarget.blur(); + } + }} + > + {policyName} +

+ ) : ( +

setIsEditing(true)} + > + {policy.policy.name} +

+ )} +
+ +
+
+
+ + {lastUpdated} +
+
+
+ +
+
+ + + {saveStatus} + + + {wordCount} words + +
+
+
+
+
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx index eed7650d75..17f8aa1dbf 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/[policyId]/page.tsx @@ -5,9 +5,6 @@ import { setStaticParamsLocale } from "next-international/server"; import { redirect } from "next/navigation"; import { PolicyDetails } from "./components/PolicyDetails"; -export const dynamic = "force-dynamic"; // Force dynamic rendering -export const revalidate = 0; // Disable caching - export default async function PolicyDetailsPage({ params, }: { @@ -26,7 +23,6 @@ export default async function PolicyDetailsPage({ return ; } -// Add these headers to prevent caching export async function generateMetadata({ params, }: { @@ -39,11 +35,5 @@ export async function generateMetadata({ return { title: t("sub_pages.policies.policy_details"), - // Add cache control headers - other: { - "Cache-Control": "no-cache, no-store, must-revalidate", - Pragma: "no-cache", - Expires: "0", - }, }; } diff --git a/apps/app/src/components/status-policies.tsx b/apps/app/src/components/status-policies.tsx index 7c8676c4f6..0ac3dfa2a4 100644 --- a/apps/app/src/components/status-policies.tsx +++ b/apps/app/src/components/status-policies.tsx @@ -17,11 +17,17 @@ const STATUS_COLORS: Record = { needs_review: "#ff0000", } as const; -export function StatusPolicies({ status }: { status: StatusType }) { +export function StatusPolicies({ + status, + className, +}: { + status: StatusType; + className?: string; +}) { const t = useI18n(); return ( -
+
- -
-
- -
-
diff --git a/apps/web/src/app/components/logo-header.tsx b/apps/web/src/app/components/logo-header.tsx index 514e727196..3592cef8b6 100644 --- a/apps/web/src/app/components/logo-header.tsx +++ b/apps/web/src/app/components/logo-header.tsx @@ -2,19 +2,21 @@ import type * as React from "react"; const LogoHeader = (props: React.SVGProps) => ( - + + + + + + + + ); diff --git a/packages/ui/src/components/badge.tsx b/packages/ui/src/components/badge.tsx index e5c76b2f64..0250c3e474 100644 --- a/packages/ui/src/components/badge.tsx +++ b/packages/ui/src/components/badge.tsx @@ -3,7 +3,7 @@ import type * as React from "react"; import { cn } from "../utils"; const badgeVariants = cva( - "inline-flex items-center rounded-sm border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + "inline-flex items-center rounded-none border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { variants: { variant: { diff --git a/packages/ui/src/editor.css b/packages/ui/src/editor.css index 60dd070ff9..f48ef8732f 100644 --- a/packages/ui/src/editor.css +++ b/packages/ui/src/editor.css @@ -67,7 +67,7 @@ pre { /* Base editor styles */ .ProseMirror { - @apply w-full px-8 py-6 text-sm leading-normal; + @apply w-full py-6 text-sm leading-normal; height: 100%; min-height: 350px; overflow-y: auto;