From 631a0b7a9ac04bdbbd8dad23f4abfc973a76c2cb Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 24 Oct 2026 15:18:34 +0200 Subject: [PATCH 1/2] Fixed partner message reader --- .../components/dashboard/DashboardPage.tsx | 34 ++-- .../notifications/MessageViewer.tsx | 186 ++++++++++++++++++ .../src/lib/markdownUtils.ts | 51 +++++ 3 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx diff --git a/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx b/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx index c885008..e1a93ce 100644 --- a/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx +++ b/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx @@ -61,6 +61,8 @@ import { NotificationStatus, } from '../../types'; import { formatZAR } from '../../lib/formatters'; +import { MessageViewer } from '../notifications/MessageViewer'; +import { stripMarkdown } from '../../lib/markdownUtils'; interface EnrichedPartnerProject { project: Project; @@ -1321,7 +1323,7 @@ export function DashboardPage() {

- {msg.message} + {stripMarkdown(msg.message)}

@@ -1393,7 +1395,7 @@ export function DashboardPage() { )} -

{msg.message}

+

{stripMarkdown(msg.message)}

@@ -2120,11 +2122,11 @@ export function DashboardPage() { MODAL 3: MESSAGE READER (Clicking an Inbox item) ===================================================================== */} {selectedNotification && ( -
-
-
-
-

+
+
+
+
+

{selectedNotification.subject || 'Message'}

@@ -2134,20 +2136,22 @@ export function DashboardPage() {

-
-

- {selectedNotification.message} -

-
+
+ +
@@ -2163,7 +2167,7 @@ export function DashboardPage() { setMessageSubject(subj.startsWith('Re:') ? subj : `Re: ${subj}`); setMessageBody(''); }} - className="px-4 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs flex items-center gap-1.5 cursor-pointer" + className="px-4 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs flex items-center gap-1.5 cursor-pointer transition shadow-lg shadow-emerald-500/20" > Reply diff --git a/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx b/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx new file mode 100644 index 0000000..9d22279 --- /dev/null +++ b/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx @@ -0,0 +1,186 @@ +import React, { useState, useMemo } from 'react'; +import { + Copy, + Check, + Mail, + MessageSquare, + Code2, + Eye, + ShieldCheck, +} from 'lucide-react'; +import { NotificationPlatform } from '../../types'; +import { parseMarkdownToHtml, isHtmlContent, stripMarkdown } from '../../lib/markdownUtils'; + +export interface MessageViewerProps { + content: string; + platform?: NotificationPlatform | number | string; + subject?: string; + className?: string; + showFormatHeader?: boolean; +} + +export const MessageViewer: React.FC = ({ + content, + platform, + subject, + className = '', + showFormatHeader = true, +}) => { + const [copied, setCopied] = useState(false); + const [showRaw, setShowRaw] = useState(false); + + // Determine whether this message represents an Email (HTML) or In-App (Markup) + const isEmail = useMemo(() => { + if ( + platform === NotificationPlatform.Email || + platform === 1 || + platform === '1' || + String(platform).toLowerCase() === 'email' + ) { + return true; + } + // If platform is not explicitly InApp, detect if content is structured HTML + if ( + platform !== NotificationPlatform.InApp && + platform !== 5 && + platform !== '5' && + String(platform).toLowerCase() !== 'inapp' && + isHtmlContent(content) + ) { + return true; + } + return false; + }, [platform, content]); + + const isInApp = useMemo(() => { + if ( + platform === NotificationPlatform.InApp || + platform === 5 || + platform === '5' || + String(platform).toLowerCase() === 'inapp' + ) { + return true; + } + return !isEmail; + }, [platform, isEmail]); + + // Convert the message to its respective format: + // - Email: HTML rendered + // - In-App: Markup (Markdown) parsed to HTML + const renderedHtml = useMemo(() => { + if (!content) return '

No message content

'; + + // Format 1: Email => HTML + if (isEmail) { + if (isHtmlContent(content)) { + return content.replace(/)<[^<]*)*<\/script>/gi, ''); + } + return parseMarkdownToHtml(content); + } + + // Format 2: In-App => Markup (Markdown) + return parseMarkdownToHtml(content); + }, [content, isEmail]); + + const handleCopy = () => { + const textToCopy = showRaw ? content : stripMarkdown(content) || content; + navigator.clipboard.writeText(textToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+ {/* Format Header & Controls Bar */} + {showFormatHeader && ( +
+
+ {isEmail ? ( + + + Email (HTML) + + ) : ( + + + In-App (Markup) + + )} + + + {isEmail ? 'Rendered as HTML Email' : 'Converted from Markup to Formatted View'} + +
+ +
+ {/* View Mode Toggle: Formatted vs Raw */} + + + {/* Copy Button */} + +
+
+ )} + + {/* Message Body Content */} +
+ {showRaw ? ( +
+            {content}
+          
+ ) : ( +
+ )} +
+ + {/* Footer Info Stamp */} +
+
+ + PFM Communications • {isEmail ? 'Email HTML Format' : 'In-App Markup Format'} +
+ + {stripMarkdown(content).length} characters + +
+
+ ); +}; + diff --git a/PostFundManagement.PartnerUI/src/lib/markdownUtils.ts b/PostFundManagement.PartnerUI/src/lib/markdownUtils.ts index ba3ccb3..c1ae9af 100644 --- a/PostFundManagement.PartnerUI/src/lib/markdownUtils.ts +++ b/PostFundManagement.PartnerUI/src/lib/markdownUtils.ts @@ -62,11 +62,62 @@ export function parseMarkdownToHtml(markdown: string): string { return `${escaped}`; }); + // Tables (| A | B |\n|---|---|\n| C | D |) + html = html.replace(/((?:\|[^\n]+\|\n?)+)/g, (match) => { + const rows = match.trim().split('\n'); + if (rows.length < 2) return match; + + const hasSeparator = rows[1] && rows[1].includes('---'); + if (!hasSeparator) return match; + + const parseCells = (row: string) => + row + .split('|') + .slice(1, -1) + .map((c) => c.trim()); + + const headers = parseCells(rows[0]); + const bodyRows = rows.slice(2); + + let tableHtml = `
`; + tableHtml += ``; + headers.forEach((h) => { + tableHtml += ``; + }); + tableHtml += ``; + + bodyRows.forEach((row, i) => { + const cells = parseCells(row); + const bg = i % 2 === 0 ? '#0f172a' : '#1e293b/40'; + tableHtml += ``; + cells.forEach((c) => { + tableHtml += ``; + }); + tableHtml += ``; + }); + + tableHtml += `
${h}
${c}
`; + return tableHtml; + }); + html = html.replace(/^#### (.*?)$/gm, '

$1

'); html = html.replace(/^### (.*?)$/gm, '

$1

'); html = html.replace(/^## (.*?)$/gm, '

$1

'); html = html.replace(/^# (.*?)$/gm, '

$1

'); + // Blockquotes and GitHub alerts + html = html.replace(/^>\s*\[!NOTE\]\s*(.*?)$/gim, '
NOTE: $1
'); + html = html.replace(/^>\s*\[!IMPORTANT\]\s*(.*?)$/gim, '
IMPORTANT: $1
'); + html = html.replace(/^>\s*\[!WARNING\]\s*(.*?)$/gim, '
WARNING: $1
'); + html = html.replace(/^>\s*\[!TIP\]\s*(.*?)$/gim, '
TIP: $1
'); + html = html.replace(/^>\s*(.*?)$/gm, '
$1
'); + + // Horizontal Rules (---) + html = html.replace(/^(?:---|\*\*\*|___)\s*$/gm, '
'); + + // Strikethrough + html = html.replace(/~~(.*?)~~/g, '$1'); + html = html.replace(/\*\*\*(.*?)\*\*\*/g, '$1'); html = html.replace(/\*\*(.*?)\*\*/g, '$1'); html = html.replace(/__(.*?)__/g, '$1'); -- 2.47.3 From 385c76510b5030db228a4cd974b690b4d5bee17c Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 24 Oct 2026 15:23:12 +0200 Subject: [PATCH 2/2] Fixed partnerui message viewer --- .../components/dashboard/DashboardPage.tsx | 61 ++++++++++--------- .../notifications/MessageViewer.tsx | 18 +++--- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx b/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx index e1a93ce..57163cc 100644 --- a/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx +++ b/PostFundManagement.PartnerUI/src/components/dashboard/DashboardPage.tsx @@ -2123,7 +2123,8 @@ export function DashboardPage() { ===================================================================== */} {selectedNotification && (
-
+
+ {/* Modal Header */}

@@ -2141,38 +2142,42 @@ export function DashboardPage() {

-
+ + {/* Scrollable Modal Body containing the Message Reading Pane */} +
-
- - -
+
+ + {/* Sticky Modal Footer */} +
+ +
diff --git a/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx b/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx index 9d22279..a001d08 100644 --- a/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx +++ b/PostFundManagement.PartnerUI/src/components/notifications/MessageViewer.tsx @@ -17,6 +17,7 @@ export interface MessageViewerProps { subject?: string; className?: string; showFormatHeader?: boolean; + maxHeightClass?: string; } export const MessageViewer: React.FC = ({ @@ -25,6 +26,7 @@ export const MessageViewer: React.FC = ({ subject, className = '', showFormatHeader = true, + maxHeightClass = 'max-h-[60vh]', }) => { const [copied, setCopied] = useState(false); const [showRaw, setShowRaw] = useState(false); @@ -90,10 +92,10 @@ export const MessageViewer: React.FC = ({ }; return ( -
+
{/* Format Header & Controls Bar */} {showFormatHeader && ( -
+
{isEmail ? ( @@ -112,7 +114,7 @@ export const MessageViewer: React.FC = ({
-
+
{/* View Mode Toggle: Formatted vs Raw */}
)} - {/* Message Body Content */} -
+ {/* Message Body Content - with explicit max height, max width, word wrapping, and automatic scrollbars */} +
{showRaw ? ( -
+          
             {content}
           
) : (
)}
{/* Footer Info Stamp */} -
+
PFM Communications • {isEmail ? 'Email HTML Format' : 'In-App Markup Format'} -- 2.47.3