added individual entry delete option

This commit is contained in:
2026-03-26 14:55:40 +05:30
parent 0ea8038f15
commit 625e4709d3
2 changed files with 231 additions and 8 deletions

View File

@@ -642,9 +642,9 @@
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
width: 100%;
margin: 0 auto;
padding: 0 1.875rem;
padding: 0.875rem 1.5rem;
min-height: 66px;
border-radius: 100px;
background: #f0fdf4;
@@ -653,11 +653,29 @@
font-size: 1.3125rem;
font-weight: 600;
color: #15803d;
white-space: nowrap;
text-align: center;
white-space: normal;
word-break: break-word;
opacity: 0;
animation: save-inline-quote-in 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
@media (max-width: 479px) {
.save-inline-quote {
font-size: 1rem;
min-height: 52px;
border-radius: 20px;
}
}
@media (min-width: 480px) {
.save-inline-quote {
width: fit-content;
white-space: nowrap;
padding: 0 1.875rem;
}
}
@keyframes save-inline-quote-in {
0% { opacity: 0; transform: scale(0.88) translateY(4px); }
100% { opacity: 1; transform: scale(1) translateY(0); }
@@ -1098,6 +1116,32 @@
box-shadow: 0 5px 16px rgba(0, 0, 0, 0.09);
}
.entry-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.entry-delete-btn {
display: flex;
align-items: center;
justify-content: center;
width: 1.625rem;
height: 1.625rem;
border-radius: 7px;
border: 1px solid #fee2e2;
background: #fff5f5;
color: #ef4444;
cursor: pointer;
transition: background 0.18s ease, border-color 0.18s ease;
padding: 0;
flex-shrink: 0;
}
.entry-delete-btn:hover {
background: #fee2e2;
border-color: #fca5a5;
}
.entry-header {
display: flex;
align-items: center;
@@ -1775,6 +1819,80 @@
font-family: "Sniglet", system-ui;
}
/* Delete confirmation modal */
.delete-confirm-modal {
text-align: center;
padding: 2rem 1.5rem;
}
.delete-confirm-icon {
display: flex;
align-items: center;
justify-content: center;
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
background: #fee2e2;
color: #ef4444;
margin: 0 auto 1rem;
}
.delete-confirm-title {
margin: 0 0 0.5rem;
font-size: 1.25rem;
color: #111827;
font-family: "Sniglet", system-ui;
}
.delete-confirm-body {
color: #6b7280;
font-size: 0.9rem;
font-family: "Sniglet", system-ui;
margin: 0 0 1.5rem;
line-height: 1.5;
}
.delete-confirm-actions {
display: flex;
gap: 0.75rem;
justify-content: center;
}
.delete-confirm-cancel,
.delete-confirm-delete {
flex: 1;
max-width: 10rem;
padding: 0.625rem 1rem;
border-radius: 12px;
font-size: 0.9375rem;
font-family: "Sniglet", system-ui;
cursor: pointer;
transition: all 0.18s ease;
border: none;
}
.delete-confirm-cancel {
background: #f3f4f6;
color: #374151;
}
.delete-confirm-cancel:hover:not(:disabled) {
background: #e5e7eb;
}
.delete-confirm-delete {
background: #ef4444;
color: #fff;
}
.delete-confirm-delete:hover:not(:disabled) {
background: #dc2626;
}
.delete-confirm-cancel:disabled,
.delete-confirm-delete:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* ---- Responsive: tablet+ (≥ 768px) ---- */
@media (min-width: 768px) {
.entry-modal-overlay {
@@ -2449,6 +2567,33 @@
color: #f87171;
}
[data-theme="dark"] .entry-delete-btn {
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.2);
color: #f87171;
}
[data-theme="dark"] .entry-delete-btn:hover {
background: rgba(239, 68, 68, 0.18);
border-color: rgba(239, 68, 68, 0.35);
}
[data-theme="dark"] .delete-confirm-modal {
background: #1a1a1a;
}
[data-theme="dark"] .delete-confirm-title {
color: #e8f5e8;
}
[data-theme="dark"] .delete-confirm-body {
color: #7a8a7a;
}
[data-theme="dark"] .delete-confirm-cancel {
background: #252525;
color: #b0b8b0;
}
[data-theme="dark"] .delete-confirm-cancel:hover:not(:disabled) {
background: #303030;
}
[data-theme="dark"] .entry-modal-overlay {
background: rgba(0, 0, 0, 0.7);
}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { getUserEntries, type JournalEntry } from '../lib/api'
import { getUserEntries, deleteEntry, type JournalEntry } from '../lib/api'
import { decryptEntry } from '../lib/crypto'
import { formatIST, getISTDateComponents } from '../lib/timezone'
import BottomNav from '../components/BottomNav'
@@ -19,6 +19,8 @@ export default function HistoryPage() {
const [entries, setEntries] = useState<DecryptedEntry[]>([])
const [loadingEntries, setLoadingEntries] = useState(false)
const [selectedEntry, setSelectedEntry] = useState<DecryptedEntry | null>(null)
const [entryToDelete, setEntryToDelete] = useState<DecryptedEntry | null>(null)
const [deleting, setDeleting] = useState(false)
const { continueTourOnHistory } = useOnboardingTour()
@@ -175,6 +177,22 @@ export default function HistoryPage() {
setSelectedDate(new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day))
}
const handleDeleteConfirm = async () => {
if (!entryToDelete || !user || !userId) return
setDeleting(true)
try {
const token = await user.getIdToken()
await deleteEntry(userId, entryToDelete.id, token)
setEntries((prev) => prev.filter((e) => e.id !== entryToDelete.id))
if (selectedEntry?.id === entryToDelete.id) setSelectedEntry(null)
} catch (error) {
console.error('Failed to delete entry:', error)
} finally {
setDeleting(false)
setEntryToDelete(null)
}
}
if (loading) {
return (
<div className="history-page" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -267,21 +285,38 @@ export default function HistoryPage() {
</p>
) : (
selectedDateEntries.map((entry) => (
<button
<div
key={entry.id}
type="button"
className="entry-card"
role="button"
tabIndex={0}
onClick={() => setSelectedEntry(entry)}
onKeyDown={(e) => e.key === 'Enter' && setSelectedEntry(entry)}
>
<div className="entry-header">
<span className="entry-date">{formatDate(entry.createdAt)}</span>
<div className="entry-header-right">
<span className="entry-time">{formatTime(entry.createdAt)}</span>
<button
type="button"
className="entry-delete-btn"
title="Delete entry"
onClick={(e) => { e.stopPropagation(); setEntryToDelete(entry) }}
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
<path d="M10 11v6M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
</button>
</div>
</div>
<h4 className="entry-title">{entry.decryptedTitle || entry.title || '[Untitled]'}</h4>
{entry.decryptedContent && (
<p className="entry-preview">{entry.decryptedContent}</p>
)}
</button>
</div>
))
)}
</div>
@@ -352,6 +387,49 @@ export default function HistoryPage() {
</div>
)}
{/* Delete Confirmation Modal */}
{entryToDelete && (
<div
className="entry-modal-overlay"
onClick={(e) => {
if (e.target === e.currentTarget && !deleting) setEntryToDelete(null)
}}
>
<div className="entry-modal delete-confirm-modal">
<div className="delete-confirm-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
<path d="M10 11v6M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
</div>
<h2 className="delete-confirm-title">Delete entry?</h2>
<p className="delete-confirm-body">
"{entryToDelete.decryptedTitle || entryToDelete.title || 'Untitled'}" will be permanently deleted and cannot be recovered.
</p>
<div className="delete-confirm-actions">
<button
type="button"
className="delete-confirm-cancel"
onClick={() => setEntryToDelete(null)}
disabled={deleting}
>
Cancel
</button>
<button
type="button"
className="delete-confirm-delete"
onClick={handleDeleteConfirm}
disabled={deleting}
>
{deleting ? 'Deleting…' : 'Delete'}
</button>
</div>
</div>
</div>
)}
<BottomNav />
</div>
)