diff --git a/frontend/src/index.css b/frontend/src/index.css index 2448183..e5110b6 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -273,10 +273,9 @@ font-family: "Inter", "SF Pro Display", system-ui, sans-serif; } - /* Prevent scrollbar flash when panels/modals open */ + /* Ensure full height layout */ html, body { - overflow: hidden; height: 100%; width: 100%; } @@ -284,7 +283,6 @@ #root { height: 100%; width: 100%; - overflow: hidden; } /* Smooth scrolling */ diff --git a/frontend/src/pages/SessionDetail/ContentSection.tsx b/frontend/src/pages/SessionDetail/ContentSection.tsx index e305ce6..b53a69c 100644 --- a/frontend/src/pages/SessionDetail/ContentSection.tsx +++ b/frontend/src/pages/SessionDetail/ContentSection.tsx @@ -2,50 +2,84 @@ import React from "react"; import type { ContentSectionProps } from "./types"; import { CopyButton } from "./CopyButton"; +const wordCount = (text: string) => + text.trim().split(/\s+/).filter(Boolean).length; + export const ContentSection: React.FC = ({ title, content, copyLabel, icon, + variant = "summary", }) => { const isEmpty = !content; + const words = content ? wordCount(content) : 0; + + const isSummary = variant === "summary"; + + const accentClass = isSummary + ? "from-brand-400 to-purple-500" + : "from-emerald-400 to-teal-500"; + + const iconColorClass = isSummary ? "text-brand-500" : "text-emerald-600"; + + const emptyIconBgClass = isSummary ? "bg-brand-50" : "bg-emerald-50"; + const emptyIconColorClass = isSummary ? "text-brand-300" : "text-emerald-300"; + + const emptyHeading = isSummary + ? "Summary not yet generated" + : "No transcript available"; + const emptySub = isSummary + ? "Will appear once the AI processes the transcript" + : "Will appear once the audio is processed"; + + const filename = isSummary ? "summary.txt" : "transcript.txt"; return (
-
-
- {icon} + {/* Accent top bar */} +
+ + {/* Header */} +
+
+ {icon}

{title}

+ {!isEmpty && ( + + {words.toLocaleString()} {words === 1 ? "word" : "words"} + + )}
{!isEmpty && }
-
+ + {/* Body */} +
{isEmpty ? ( -
-
- - - +
+
+ {icon}
-

Not available yet

-

- This will appear once the session is processed -

+

{emptyHeading}

+

{emptySub}

) : ( -
-            {content}
-          
+
+
+
+
+
+ + {filename} + +
+
+              {content}
+            
+
)}
diff --git a/frontend/src/pages/SessionDetail/SessionContent.tsx b/frontend/src/pages/SessionDetail/SessionContent.tsx index 8d90250..de43677 100644 --- a/frontend/src/pages/SessionDetail/SessionContent.tsx +++ b/frontend/src/pages/SessionDetail/SessionContent.tsx @@ -1,65 +1,109 @@ +import React, { useState } from "react"; import { SessionHeader } from "./SessionHeader"; import { ErrorNotice } from "./ErrorNotice"; import { ContentSection } from "./ContentSection"; import type { SummarizerSession } from "../../types/user.types"; +type Tab = "summary" | "transcript"; + interface SessionContentProps { session: SummarizerSession; onDelete: () => void; } +const SummaryIcon = ( + + + +); + +const TranscriptIcon = ( + + + +); + export const SessionContent: React.FC = ({ session, onDelete, -}) => ( -
- +}) => { + const [activeTab, setActiveTab] = useState("summary"); - {session.error && } + const tabs: { id: Tab; label: string; hasContent: boolean }[] = [ + { id: "summary", label: "Summary", hasContent: !!session.summary }, + { id: "transcript", label: "Transcript", hasContent: !!session.transcript }, + ]; - {!session.error && ( - <> - - - - } - /> - - - - } - /> - - )} -
-); + return ( +
+ + + {session.error && ( + + )} + + {/* Tab bar */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Active section — key forces fade-in on tab switch */} +
+ {activeTab === "summary" ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/frontend/src/pages/SessionDetail/types.ts b/frontend/src/pages/SessionDetail/types.ts index 4ac4815..97c2cbf 100644 --- a/frontend/src/pages/SessionDetail/types.ts +++ b/frontend/src/pages/SessionDetail/types.ts @@ -25,6 +25,7 @@ export interface ContentSectionProps { content: string | null; copyLabel: string; icon: React.ReactNode; + variant?: "summary" | "transcript"; } export interface SessionHeaderProps {