updated history page
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -868,19 +868,18 @@ def api_delete_post(post_id: int):
|
|||||||
return _error("You don't have permission to delete this post.", 403)
|
return _error("You don't have permission to delete this post.", 403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
add_audit_log({
|
||||||
|
"user_id": user_id,
|
||||||
|
"action": "post.deleted",
|
||||||
|
"details": json.dumps({"deleted_post_id": post_id, "title": post.get("title")})
|
||||||
|
})
|
||||||
|
|
||||||
delete_rag_chunks(post_id)
|
delete_rag_chunks(post_id)
|
||||||
delete_archive_files(post_id)
|
delete_archive_files(post_id)
|
||||||
delete_metadata(post_id)
|
delete_metadata(post_id)
|
||||||
delete_rights(post_id)
|
delete_rights(post_id)
|
||||||
delete_audio_post(post_id)
|
delete_audio_post(post_id)
|
||||||
|
|
||||||
add_audit_log({
|
|
||||||
"post_id": post_id,
|
|
||||||
"user_id": user_id,
|
|
||||||
"action": "post.deleted",
|
|
||||||
"details": json.dumps({"title": post.get("title")})
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify({"message": "Post deleted successfully", "post_id": post_id})
|
return jsonify({"message": "Post deleted successfully", "post_id": post_id})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
BIN
backend/uploads/00ce7e87-614d-44f7-a300-0ac8fb4af2bd_data.m4a
Normal file
BIN
backend/uploads/00ce7e87-614d-44f7-a300-0ac8fb4af2bd_data.m4a
Normal file
Binary file not shown.
BIN
backend/uploads/28b3ced4-4174-4555-ad9c-cb0adff1fbba_harvard.wav
Normal file
BIN
backend/uploads/28b3ced4-4174-4555-ad9c-cb0adff1fbba_harvard.wav
Normal file
Binary file not shown.
BIN
frontend/public/Logo.png
Normal file
BIN
frontend/public/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 421 KiB |
@@ -61,6 +61,7 @@ export default function App() {
|
|||||||
localStorage.removeItem('voicevault_user')
|
localStorage.removeItem('voicevault_user')
|
||||||
setShowLogin(true)
|
setShowLogin(true)
|
||||||
setActiveTab('feed')
|
setActiveTab('feed')
|
||||||
|
setViewingPostId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = async (query) => {
|
const handleSearch = async (query) => {
|
||||||
@@ -91,9 +92,15 @@ export default function App() {
|
|||||||
setViewingPostId(null)
|
setViewingPostId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleTabChange = (tab) => {
|
||||||
|
setActiveTab(tab)
|
||||||
|
setViewingPostId(null)
|
||||||
|
}
|
||||||
|
|
||||||
const handlePostCreated = () => {
|
const handlePostCreated = () => {
|
||||||
// Switch to feed after creating a post
|
// Switch to feed after creating a post
|
||||||
setActiveTab('feed')
|
setActiveTab('feed')
|
||||||
|
setViewingPostId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUserUpdate = (updatedUser) => {
|
const handleUserUpdate = (updatedUser) => {
|
||||||
@@ -202,11 +209,16 @@ export default function App() {
|
|||||||
|
|
||||||
// Main App
|
// Main App
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-gray-50 text-gray-800 flex flex-col overflow-hidden">
|
<div className="h-screen bg-white-50 text-gray-800 flex flex-col overflow-hidden">
|
||||||
<Header onSearch={handleSearch} onLogout={handleLogout} onNavigateToSearch={handleNavigateToSearch} />
|
<Header onSearch={handleSearch} onLogout={handleLogout} onNavigateToSearch={handleNavigateToSearch} />
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
<div className="flex-1 flex overflow-hidden max-w-[1400px] mx-auto w-full">
|
<div className="flex-1 flex overflow-hidden max-w-[1400px] mx-auto w-full">
|
||||||
|
<Sidebar user={user} activeTab={activeTab} onTabChange={handleTabChange} />
|
||||||
|
=======
|
||||||
|
<div className="flex-1 flex overflow-hidden max-w-[1500px] mx-auto w-full">
|
||||||
<Sidebar user={user} activeTab={activeTab} onTabChange={setActiveTab} />
|
<Sidebar user={user} activeTab={activeTab} onTabChange={setActiveTab} />
|
||||||
|
>>>>>>> cd657f37cf76773d9dd6ad7a0cdbe05e19a9eced
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto p-6">
|
<main className="flex-1 overflow-y-auto p-6">
|
||||||
{renderPage()}
|
{renderPage()}
|
||||||
|
|||||||
@@ -27,13 +27,16 @@ class ApiClient {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, config);
|
const response = await fetch(url, config);
|
||||||
const data = await response.json();
|
const contentType = response.headers.get('content-type') || '';
|
||||||
|
const isJson = contentType.includes('application/json');
|
||||||
|
const data = isJson ? await response.json() : null;
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data.error || 'Request failed');
|
const fallbackError = `Request failed with status ${response.status}`;
|
||||||
|
throw new Error((data && data.error) || fallbackError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data || {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('API Error:', error);
|
console.error('API Error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -87,14 +90,16 @@ class ApiClient {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData, // Don't set Content-Type, let browser set it with boundary
|
body: formData, // Don't set Content-Type, let browser set it with boundary
|
||||||
});
|
});
|
||||||
|
const contentType = response.headers.get('content-type') || '';
|
||||||
const data = await response.json();
|
const isJson = contentType.includes('application/json');
|
||||||
|
const data = isJson ? await response.json() : null;
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(data.error || 'Upload failed');
|
const fallbackError = `Upload failed with status ${response.status}`;
|
||||||
|
throw new Error((data && data.error) || fallbackError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data || {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload Error:', error);
|
console.error('Upload Error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -144,10 +149,6 @@ class ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPostMetadata(postId) {
|
|
||||||
return this.request(`/posts/${postId}/metadata`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAudioUrl(postId, expiresIn = 3600) {
|
async getAudioUrl(postId, expiresIn = 3600) {
|
||||||
return this.request(`/posts/${postId}/audio?expires_in=${expiresIn}`);
|
return this.request(`/posts/${postId}/audio?expires_in=${expiresIn}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export default function EditPostModal({ post, user, onClose, onSave }) {
|
|||||||
visibility
|
visibility
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.editPost(post.post_id, updates)
|
const updatedPost = await api.editPost(post.post_id, updates)
|
||||||
onSave?.()
|
onSave?.(updatedPost)
|
||||||
onClose()
|
onClose()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || 'Failed to update post')
|
setError(err.message || 'Failed to update post')
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ export default function Header({ onSearch, onLogout, onNavigateToSearch }) {
|
|||||||
<div className="max-w-[1400px] mx-auto flex items-center justify-between gap-6">
|
<div className="max-w-[1400px] mx-auto flex items-center justify-between gap-6">
|
||||||
{/* Left: Logo */}
|
{/* Left: Logo */}
|
||||||
<div className="flex items-center gap-3 flex-shrink-0">
|
<div className="flex items-center gap-3 flex-shrink-0">
|
||||||
<div className="w-8 h-8 bg-[#f4b840] rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 rounded-lg flex items-center justify-center">
|
||||||
<svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
{/* <svg className="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
|
||||||
</svg>
|
</svg> */}
|
||||||
|
<img
|
||||||
|
src="/Logo.png"
|
||||||
|
alt="VoiceVault Logo"
|
||||||
|
className="w-10 h-10 object-contain rounded-lg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-lg font-bold text-gray-900">VoiceVault</h1>
|
<h1 className="text-lg font-bold text-gray-900">VoiceVault</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function History({ user, onViewPost }) {
|
|||||||
try {
|
try {
|
||||||
await api.deletePost(postId, user.user_id)
|
await api.deletePost(postId, user.user_id)
|
||||||
// Remove from local state immediately
|
// Remove from local state immediately
|
||||||
setPosts(posts.filter(p => p.post_id !== postId))
|
setPosts((prev) => prev.filter((p) => p.post_id !== postId))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Failed to delete post: ' + err.message)
|
alert('Failed to delete post: ' + err.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -58,8 +58,15 @@ export default function History({ user, onViewPost }) {
|
|||||||
setEditingPost(post)
|
setEditingPost(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSaveEdit = () => {
|
const handleSaveEdit = (updatedPost) => {
|
||||||
// Refresh the list after edit
|
if (updatedPost && updatedPost.post_id) {
|
||||||
|
setPosts((prev) =>
|
||||||
|
prev.map((post) =>
|
||||||
|
post.post_id === updatedPost.post_id ? { ...post, ...updatedPost } : post
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
fetchHistory()
|
fetchHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default function PostDetail({ postId, user, onBack }) {
|
|||||||
const [volume, setVolume] = useState(1)
|
const [volume, setVolume] = useState(1)
|
||||||
const [downloading, setDownloading] = useState(false)
|
const [downloading, setDownloading] = useState(false)
|
||||||
const audioRef = useRef(null)
|
const audioRef = useRef(null)
|
||||||
|
const [audioSrc, setAudioSrc] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (postId) {
|
if (postId) {
|
||||||
@@ -34,6 +35,8 @@ export default function PostDetail({ postId, user, onBack }) {
|
|||||||
|
|
||||||
// Load audio URL if available
|
// Load audio URL if available
|
||||||
if (postData.audio_url && audioRef.current) {
|
if (postData.audio_url && audioRef.current) {
|
||||||
|
setPost(postData)
|
||||||
|
setAudioSrc(postData.audio_url)
|
||||||
audioRef.current.src = postData.audio_url
|
audioRef.current.src = postData.audio_url
|
||||||
audioRef.current.load()
|
audioRef.current.load()
|
||||||
}
|
}
|
||||||
@@ -290,12 +293,14 @@ export default function PostDetail({ postId, user, onBack }) {
|
|||||||
|
|
||||||
<audio
|
<audio
|
||||||
ref={audioRef}
|
ref={audioRef}
|
||||||
|
src={audioSrc}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
onLoadedMetadata={handleLoadedMetadata}
|
onLoadedMetadata={handleLoadedMetadata}
|
||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4 mb-4">
|
<div className="flex items-center gap-4 mb-4">
|
||||||
<button
|
<button
|
||||||
onClick={togglePlay}
|
onClick={togglePlay}
|
||||||
|
|||||||
Reference in New Issue
Block a user