From 85477e549980e625fe20f22258c231771255a9f1 Mon Sep 17 00:00:00 2001 From: Jeet Debnath Date: Thu, 16 Apr 2026 15:06:40 +0530 Subject: [PATCH] edit entry option added --- src/App.css | 160 ++++++++++++++++++++++++++++++++++ src/pages/HistoryPage.tsx | 175 +++++++++++++++++++++++++++++++++++--- 2 files changed, 322 insertions(+), 13 deletions(-) diff --git a/src/App.css b/src/App.css index 420340e..50271f3 100644 --- a/src/App.css +++ b/src/App.css @@ -1141,6 +1141,26 @@ gap: 0.5rem; } +.entry-edit-btn { + display: flex; + align-items: center; + justify-content: center; + width: 1.625rem; + height: 1.625rem; + border-radius: 7px; + border: 1px solid #dbeafe; + background: #eff6ff; + color: #3b82f6; + cursor: pointer; + transition: background 0.18s ease, border-color 0.18s ease; + padding: 0; + flex-shrink: 0; +} +.entry-edit-btn:hover { + background: #dbeafe; + border-color: #93c5fd; +} + .entry-delete-btn { display: flex; align-items: center; @@ -1922,6 +1942,115 @@ cursor: not-allowed; } +/* Edit entry modal */ +.entry-modal-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.entry-modal-edit { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + border: none; + background: #eff6ff; + color: #3b82f6; + cursor: pointer; + transition: background 0.2s, color 0.2s; + flex-shrink: 0; +} +.entry-modal-edit:hover { + background: #dbeafe; + color: #2563eb; +} + +.edit-entry-modal { + padding: 1.5rem; +} + +.edit-entry-fields { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.25rem; +} + +.edit-entry-title-input { + width: 100%; + padding: 0.625rem 0.875rem; + border: 1.5px solid #e5e7eb; + border-radius: 10px; + font-size: 1rem; + font-weight: 600; + font-family: inherit; + color: #111827; + background: #f9fafb; + outline: none; + box-sizing: border-box; + transition: border-color 0.2s; +} +.edit-entry-title-input:focus { + border-color: #6ee7b7; + background: #fff; +} + +.edit-entry-content-input { + width: 100%; + padding: 0.625rem 0.875rem; + border: 1.5px solid #e5e7eb; + border-radius: 10px; + font-size: 0.9375rem; + font-family: "Sniglet", system-ui; + color: #374151; + background: #f9fafb; + outline: none; + box-sizing: border-box; + resize: vertical; + line-height: 1.6; + transition: border-color 0.2s; +} +.edit-entry-content-input:focus { + border-color: #6ee7b7; + background: #fff; +} +.edit-entry-content-input:disabled, +.edit-entry-title-input:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.edit-entry-actions { + display: flex; + gap: 0.75rem; + justify-content: flex-end; +} + +.edit-entry-save { + flex: 1; + max-width: 10rem; + padding: 0.625rem 1rem; + border: none; + border-radius: 10px; + font-size: 0.9375rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; + font-family: inherit; + background: #10b981; + color: #fff; +} +.edit-entry-save:hover:not(:disabled) { + background: #059669; +} +.edit-entry-save:disabled { + opacity: 0.6; + cursor: not-allowed; +} + /* ---- Responsive: tablet+ (≥ 768px) ---- */ @media (min-width: 768px) { .entry-modal-overlay { @@ -2698,6 +2827,25 @@ color: #f87171; } +[data-theme="dark"] .entry-edit-btn { + background: rgba(59, 130, 246, 0.08); + border-color: rgba(59, 130, 246, 0.2); + color: #60a5fa; +} +[data-theme="dark"] .entry-edit-btn:hover { + background: rgba(59, 130, 246, 0.18); + border-color: rgba(59, 130, 246, 0.35); +} + +[data-theme="dark"] .entry-modal-edit { + background: rgba(59, 130, 246, 0.1); + color: #60a5fa; +} +[data-theme="dark"] .entry-modal-edit:hover { + background: rgba(59, 130, 246, 0.2); + color: #93c5fd; +} + [data-theme="dark"] .entry-delete-btn { background: rgba(239, 68, 68, 0.08); border-color: rgba(239, 68, 68, 0.2); @@ -2708,6 +2856,18 @@ border-color: rgba(239, 68, 68, 0.35); } +[data-theme="dark"] .edit-entry-title-input, +[data-theme="dark"] .edit-entry-content-input { + background: #1a1a1a; + border-color: #2d2d2d; + color: #e8f5e8; +} +[data-theme="dark"] .edit-entry-title-input:focus, +[data-theme="dark"] .edit-entry-content-input:focus { + border-color: #4ade80; + background: #151515; +} + [data-theme="dark"] .delete-confirm-modal { background: var(--color-surface); } diff --git a/src/pages/HistoryPage.tsx b/src/pages/HistoryPage.tsx index 530b021..b5229f2 100644 --- a/src/pages/HistoryPage.tsx +++ b/src/pages/HistoryPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { useAuth } from '../contexts/AuthContext' -import { getUserEntries, deleteEntry, type JournalEntry } from '../lib/api' -import { decryptEntry } from '../lib/crypto' +import { getUserEntries, deleteEntry, updateEntry, type JournalEntry } from '../lib/api' +import { decryptEntry, encryptEntry } from '../lib/crypto' import { formatIST, getISTDateComponents } from '../lib/timezone' import BottomNav from '../components/BottomNav' import { useOnboardingTour, hasPendingTourStep, clearPendingTourStep } from '../hooks/useOnboardingTour' @@ -22,6 +22,10 @@ export default function HistoryPage() { const [selectedEntry, setSelectedEntry] = useState(null) const [entryToDelete, setEntryToDelete] = useState(null) const [deleting, setDeleting] = useState(false) + const [entryToEdit, setEntryToEdit] = useState(null) + const [editTitle, setEditTitle] = useState('') + const [editContent, setEditContent] = useState('') + const [saving, setSaving] = useState(false) const { continueTourOnHistory } = useOnboardingTour() @@ -178,6 +182,58 @@ export default function HistoryPage() { setSelectedDate(new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)) } + const isEntryFromToday = (createdAt: string): boolean => { + const nowIST = new Date(new Date().getTime() + 5.5 * 60 * 60 * 1000) + const components = getISTDateComponents(createdAt) + return ( + components.year === nowIST.getUTCFullYear() && + components.month === nowIST.getUTCMonth() && + components.date === nowIST.getUTCDate() + ) + } + + const openEditModal = (entry: DecryptedEntry) => { + setEntryToEdit(entry) + setEditTitle(entry.decryptedTitle || '') + setEditContent(entry.decryptedContent || '') + } + + const handleEditSave = async () => { + if (!entryToEdit || !user || !userId || !secretKey) return + setSaving(true) + try { + const token = await user.getIdToken() + const combined = `${editTitle.trim()}\n\n${editContent.trim()}` + const { ciphertext, nonce } = await encryptEntry(combined, secretKey) + + await updateEntry(userId, entryToEdit.id, { + title: undefined, + content: undefined, + encryption: { + encrypted: true, + ciphertext, + nonce, + algorithm: 'XSalsa20-Poly1305', + }, + }, token) + + const updatedEntry: DecryptedEntry = { + ...entryToEdit, + encryption: { encrypted: true, ciphertext, nonce, algorithm: 'XSalsa20-Poly1305' }, + decryptedTitle: editTitle.trim(), + decryptedContent: editContent.trim(), + } + + setEntries((prev) => prev.map((e) => e.id === entryToEdit.id ? updatedEntry : e)) + if (selectedEntry?.id === entryToEdit.id) setSelectedEntry(updatedEntry) + setEntryToEdit(null) + } catch (error) { + console.error('Failed to update entry:', error) + } finally { + setSaving(false) + } + } + const handleDeleteConfirm = async () => { if (!entryToDelete || !user || !userId) return setDeleting(true) @@ -291,6 +347,19 @@ export default function HistoryPage() { {formatDate(entry.createdAt)}
{formatTime(entry.createdAt)} + {isEntryFromToday(entry.createdAt) && ( + + )}
- +
+ {isEntryFromToday(selectedEntry.createdAt) && ( + + )} + +

@@ -381,6 +465,71 @@ export default function HistoryPage() { )} + {/* Edit Entry Modal */} + {entryToEdit && ( +
{ + if (e.target === e.currentTarget && !saving) setEntryToEdit(null) + }} + > +
+
+ Edit Entry + +
+
+ setEditTitle(e.target.value)} + disabled={saving} + maxLength={200} + /> +