Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const App = () => {
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/recycle-bin" element={<Index deletePage={true} />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
Expand Down
10 changes: 8 additions & 2 deletions src/components/NoteCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';

import { useLocation } from 'react-router-dom';
interface NoteCardProps {
note: Note;
isActive: boolean;
Expand All @@ -23,6 +23,9 @@ interface NoteCardProps {
}

export function NoteCard({ note, isActive, onClick, onDelete }: NoteCardProps) {
const path = useLocation()?.pathname
const deletePage = path === '/recycle-bin'

const preview = note.content.slice(0, 100) || 'No content';

return (
Expand Down Expand Up @@ -67,7 +70,10 @@ export function NoteCard({ note, isActive, onClick, onDelete }: NoteCardProps) {
<AlertDialogHeader>
<AlertDialogTitle>Delete Note</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this note? This action cannot be undone.
{
deletePage ? "This note will be permanently deleted and cannot be recovered. Are you sure you want to continue?" :
"This note will be moved to the recycle bin and can be restored later. Are you sure you want to continue?"
}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
Expand Down
8 changes: 7 additions & 1 deletion src/components/NoteEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { useLocation } from 'react-router-dom';

interface NoteEditorProps {
note: Note;
Expand All @@ -25,6 +26,8 @@ interface NoteEditorProps {
export function NoteEditor({ note, onUpdate, onDelete }: NoteEditorProps) {
const [title, setTitle] = useState(note.title);
const [content, setContent] = useState(note.content);
const path = useLocation()?.pathname
const deletePage = path === '/recycle-bin'
Comment on lines +29 to +30
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolons at the end of these statements for consistency with the rest of the codebase.

Suggested change
const path = useLocation()?.pathname
const deletePage = path === '/recycle-bin'
const path = useLocation()?.pathname;
const deletePage = path === '/recycle-bin';

Copilot uses AI. Check for mistakes.

useEffect(() => {
setTitle(note.title);
Expand Down Expand Up @@ -60,7 +63,10 @@ export function NoteEditor({ note, onUpdate, onDelete }: NoteEditorProps) {
<AlertDialogHeader>
<AlertDialogTitle>Delete Note</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this note? This action cannot be undone.
{
deletePage ? "This note will be permanently deleted and cannot be recovered. Are you sure you want to continue?" :
"This note will be moved to the recycle bin and can be restored later. Are you sure you want to continue?"
}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
Expand Down
118 changes: 76 additions & 42 deletions src/components/NotesSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Note } from '@/types/note';
import { NoteCard } from './NoteCard';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { PlusCircle, Search, FileDown } from 'lucide-react';
import { PlusCircle, Search, FileDown, Trash2, Home } from 'lucide-react';
import jsPDF from 'jspdf';
import { useState } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { ThemeToggle } from "@/components/theme/themeToggle";
import { Link, useLocation } from 'react-router-dom';

interface NotesSidebarProps {
notes: Note[];
Expand All @@ -24,62 +25,70 @@ export function NotesSidebar({
onDelete,
}: NotesSidebarProps) {
const [searchQuery, setSearchQuery] = useState('');
const path = useLocation()?.pathname
const deletePage = path === '/recycle-bin'

console.log(notes)
console.log(activeNoteId)

const filteredNotes = notes.filter(
(note) =>
note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
note.content.toLowerCase().includes(searchQuery.toLowerCase())
(note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
note.content.toLowerCase().includes(searchQuery.toLowerCase())) && (!!note?.deleted === deletePage)
);

return (
<div className="w-screen md:w-80 border-r bg-secondary/30 flex flex-col h-screen">
<div className="p-4 border-b space-y-3">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-foreground">Notes</h1>
<h1 className="text-2xl font-bold text-foreground">{ deletePage ? "Recycle Bin" : "Notes"}</h1>
<div className="flex items-center gap-2">
<ThemeToggle/>
<Button
onClick={() => {
if (!activeNoteId) return;
{!deletePage && (
<>
<ThemeToggle />
<Button
onClick={() => {
if (!activeNoteId) return;

const activeNote = notes.find(note => note.id === activeNoteId);
if (!activeNote) return;
const activeNote = notes.find(note => note.id === activeNoteId);
if (!activeNote) return;

const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 10;
const contentWidth = pageWidth - (margin * 2);
const doc = new jsPDF();
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 10;
const contentWidth = pageWidth - (margin * 2);

doc.setFontSize(16);
doc.text(activeNote.title || 'Untitled Note', margin, margin);
doc.setFontSize(16);
doc.text(activeNote.title || 'Untitled Note', margin, margin);

doc.setFontSize(12);
const contentLines = doc.splitTextToSize(activeNote.content, contentWidth);
doc.setFontSize(12);
const contentLines = doc.splitTextToSize(activeNote.content, contentWidth);

let yOffset = margin + 10;
const lineHeight = 7;
let yOffset = margin + 10;
const lineHeight = 7;

contentLines.forEach((line: string) => {
if (yOffset > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
yOffset = margin;
}
doc.text(line, margin, yOffset);
yOffset += lineHeight;
});
contentLines.forEach((line: string) => {
if (yOffset > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
yOffset = margin;
}
doc.text(line, margin, yOffset);
yOffset += lineHeight;
});

doc.save(`${activeNote.title || 'note'}.pdf`);
}}
size="icon"
variant="outline"
className="hover:bg-secondary"
title="Export as PDF"
>
<FileDown className="h-5 w-5" />
</Button>
<Button onClick={onCreateNote} size="icon" variant="default" title="Create Note">
<PlusCircle className="h-5 w-5" />
</Button>
doc.save(`${activeNote.title || 'note'}.pdf`);
}}
size="icon"
variant="outline"
className="hover:bg-secondary"
>
<FileDown className="h-5 w-5" />
</Button>
<Button onClick={onCreateNote} size="icon" variant="default">
<PlusCircle className="h-5 w-5" />
</Button>
</>
)}
</div>
</div>
<div className="relative">
Expand All @@ -96,7 +105,7 @@ export function NotesSidebar({
<div className="p-4 space-y-2">
{filteredNotes.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
{searchQuery ? 'No notes found' : 'No notes yet. Create one!'}
{(searchQuery ? 'No notes found' : (deletePage ? 'Recycle Bin is empty' : 'No notes yet. Create one!'))}
</div>
) : (
filteredNotes.map((note) => (
Expand All @@ -105,12 +114,37 @@ export function NotesSidebar({
note={note}
isActive={note.id === activeNoteId}
onClick={() => onSelectNote(note.id)}
onDelete={() => onDelete(note.id)}
onDelete={onDelete}
/>
))
)}
</div>
</ScrollArea>
<div className="border-t">
{!deletePage ? (
<Link to="/recycle-bin" className="w-full">
<Button
variant="ghost"
size="sm"
className="w-full h-14 pl-4 justify-start text-red-600 hover:text-red-700 hover:bg-red-300 dark:hover:bg-red-950 rounded-none"
>
<Trash2 className="h-4 w-4 mr-2" />
Recycle Bin
</Button>
</Link>
) : (
<Link to="/" className="w-full">
<Button
variant="ghost"
size="sm"
className="w-full h-14 pl-4 justify-start rounded-none"
>
<Home className="h-4 w-4 mr-2" />
Home
</Button>
</Link>
)}
</div>
</div>
);
}
9 changes: 9 additions & 0 deletions src/hooks/useNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,19 @@ export function useNotes() {
setNotes((prev) => prev.filter((note) => note.id !== id));
};

const softDeleteNote = (id: string) => {
setNotes((prev) =>
prev.map((note) =>
note.id === id ? { ...note, deleted: true } : note
)
);
};

return {
notes,
createNote,
updateNote,
deleteNote,
softDeleteNote
};
}
22 changes: 16 additions & 6 deletions src/pages/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import { FileText, ArrowLeft } from "lucide-react";
import { toast } from "sonner";

const Index = () => {
const { notes, createNote, updateNote, deleteNote } = useNotes();
const Index = ({deletePage = false}: {deletePage?: boolean}) => {
const { notes, createNote, updateNote, deleteNote, softDeleteNote } = useNotes();
const [activeNoteId, setActiveNoteId] = useState<string | null>(null);
const [isMobile, setIsMobile] = useState(false);

Expand All @@ -17,6 +17,10 @@
return () => window.removeEventListener("resize", checkMobile);
}, []);

useEffect(() => {
setActiveNoteId(null);
}, [deletePage])
Copy link

Copilot AI Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the useEffect dependency array.

Copilot uses AI. Check for mistakes.

const handleCreateNote = () => {
const newId = createNote();
setActiveNoteId(newId);
Expand All @@ -28,6 +32,12 @@
setActiveNoteId(notes.length > 1 ? notes[0].id : null);
}
};
const handleSoftDeleteNote = (id: string) => {
softDeleteNote(id);
if (activeNoteId === id) {
setActiveNoteId(null);
}
};

const navigateNotes = (direction: "up" | "down") => {
if (notes.length === 0) return;
Expand Down Expand Up @@ -104,7 +114,7 @@
return () => {
document.removeEventListener("keydown", handleKeyDown, true);
};
}, [notes, activeNoteId]);

Check warning on line 117 in src/pages/Index.tsx

View workflow job for this annotation

GitHub Actions / ci (20.x)

React Hook useEffect has missing dependencies: 'handleCreateNote', 'handleDeleteNote', and 'navigateNotes'. Either include them or remove the dependency array

Check warning on line 117 in src/pages/Index.tsx

View workflow job for this annotation

GitHub Actions / ci (22.x)

React Hook useEffect has missing dependencies: 'handleCreateNote', 'handleDeleteNote', and 'navigateNotes'. Either include them or remove the dependency array

Check warning on line 117 in src/pages/Index.tsx

View workflow job for this annotation

GitHub Actions / ci (18.x)

React Hook useEffect has missing dependencies: 'handleCreateNote', 'handleDeleteNote', and 'navigateNotes'. Either include them or remove the dependency array

const activeNote = notes.find((note) => note.id === activeNoteId);

Expand All @@ -118,7 +128,7 @@
activeNoteId={activeNoteId}
onSelectNote={setActiveNoteId}
onCreateNote={handleCreateNote}
onDelete={handleDeleteNote} // ADD THIS LINE
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after opening brace in conditional expression. Should be onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Copilot uses AI. Check for mistakes.
/>
<main className="flex-1 overflow-hidden relative">
{/* Keyboard Shortcuts Helper */}
Expand Down Expand Up @@ -162,7 +172,7 @@
<NoteEditor
note={activeNote}
onUpdate={updateNote}
onDelete={handleDeleteNote}
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after opening brace in conditional expression. Should be onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Suggested change
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Copilot uses AI. Check for mistakes.
/>
</div>
) : (
Expand Down Expand Up @@ -191,7 +201,7 @@
<NoteEditor
note={activeNote}
onUpdate={updateNote}
onDelete={handleDeleteNote}
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after opening brace in conditional expression. Should be onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Copilot uses AI. Check for mistakes.
/>
</div>
) : (
Expand All @@ -200,7 +210,7 @@
activeNoteId={activeNoteId}
onSelectNote={setActiveNoteId}
onCreateNote={handleCreateNote}
onDelete={handleDeleteNote} // ADD THIS LINE TOO
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after opening brace in conditional expression. Should be onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Suggested change
onDelete={ deletePage ? handleDeleteNote : handleSoftDeleteNote}
onDelete={deletePage ? handleDeleteNote : handleSoftDeleteNote}

Copilot uses AI. Check for mistakes.
/>
)}
</main>
Expand Down
1 change: 1 addition & 0 deletions src/types/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface Note {
content: string;
createdAt: Date;
updatedAt: Date;
deleted?: boolean;
}
// Utility function to format timestamps
export const formatTimestamp = (date: Date, locale: string = 'en-US'): string => {
Expand Down
Loading