Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@tailwindcss/vite": "^4.1.16",
"axios": "^1.13.2",
"chart.js": "^4.5.1",
"date-fns": "^4.4.0",
"lucide-react": "^0.548.0",
"react": "^19.1.1",
"react-chartjs-2": "^5.3.1",
Expand Down
10 changes: 10 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Summaries from "./pages/Summaries.jsx";
import Reports from "./pages/Reports.jsx";
import AiSearch from "./pages/AiSearch.jsx";
import MeetingRoom from "./pages/MeetingRoom.jsx";
import MeetingDetails from "./pages/MeetingDetails.jsx";

// --- Components ---
import ProtectedRoute from "./components/ProtectedRoute.jsx";
Expand Down Expand Up @@ -210,6 +211,15 @@ const App = () => {

<Route path="/meeting-room" element={<MeetingRoom />} />

<Route
path="/meeting/:id"
element={
<ProtectedRoute>
<MeetingDetails />
</ProtectedRoute>
}
/>

{/* ✅ Fallback route — send unknown routes to Home */}
<Route path="*" element={<Home />} />
</Routes>
Expand Down
261 changes: 261 additions & 0 deletions client/src/components/meeting-details/MeetingActions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";

const MeetingActions = ({ meeting, onDelete, onRename }) => {
const navigate = useNavigate();
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showRenameModal, setShowRenameModal] = useState(false);
const [newTitle, setNewTitle] = useState("");

if (!meeting) return null;

const handleDownloadTranscript = () => {
if (!meeting.transcript) {
alert("No transcript available to download.");
return;
}

const blob = new Blob([meeting.transcript], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${meeting.title || "meeting"}-transcript.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};

const handleDownloadSummary = () => {
const content =
meeting.summary || JSON.stringify(meeting.structuredMoM, null, 2);
if (!content) {
alert("No summary available to download.");
return;
}

const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${meeting.title || "meeting"}-summary.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};

const handleRename = () => {
setNewTitle(meeting.title || "");
setShowRenameModal(true);
};

const confirmRename = () => {
if (newTitle.trim()) {
onRename(meeting._id, newTitle.trim());
setShowRenameModal(false);
}
};

const handleDelete = () => {
setShowDeleteModal(true);
};

const confirmDelete = () => {
onDelete(meeting._id);
setShowDeleteModal(false);
};

const handleBack = () => {
navigate("/summaries");
};

return (
<>
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"
/>
</svg>
Quick Actions
</h2>

<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<button
onClick={handleDownloadTranscript}
className="flex items-center justify-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
Download Transcript
</button>

<button
onClick={handleDownloadSummary}
className="flex items-center justify-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
Download Summary
</button>

<button
onClick={handleRename}
className="flex items-center justify-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Rename Meeting
</button>

<button
onClick={handleDelete}
className="flex items-center justify-center gap-2 px-4 py-2 bg-red-50 hover:bg-red-100 text-red-700 rounded-lg transition-colors text-sm font-medium"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
Delete Meeting
</button>
</div>

<button
onClick={handleBack}
className="mt-4 w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm font-medium"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 19l-7-7m0 0l7-7m-7 7h18"
/>
</svg>
Back to Meeting Repository
</button>
</div>

{/* Delete Confirmation Modal */}
{showDeleteModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Delete Meeting
</h3>
<p className="text-gray-600 mb-6">
Are you sure you want to delete this meeting? This action cannot
be undone.
</p>
<div className="flex gap-3 justify-end">
<button
onClick={() => setShowDeleteModal(false)}
className="px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium"
>
Cancel
</button>
<button
onClick={confirmDelete}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors text-sm font-medium"
>
Delete
</button>
</div>
</div>
</div>
)}

{/* Rename Modal */}
{showRenameModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Rename Meeting
</h3>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
placeholder="Enter new title"
autoFocus
/>
<div className="flex gap-3 justify-end">
<button
onClick={() => setShowRenameModal(false)}
className="px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors text-sm font-medium"
>
Cancel
</button>
<button
onClick={confirmRename}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm font-medium"
>
Save
</button>
</div>
</div>
</div>
)}
</>
);
};

export default MeetingActions;
Loading
Loading