diff --git a/src/lib/components/TaskCard.module.css b/src/lib/components/TaskCard.module.css
new file mode 100644
index 0000000..1981cd7
--- /dev/null
+++ b/src/lib/components/TaskCard.module.css
@@ -0,0 +1,221 @@
+/* Card Container */
+.cardContainer {
+ padding: 1.25rem;
+ border-radius: 0.75rem;
+ border: 1px solid #e4e4e7;
+ background-color: #ffffff;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease-in-out;
+}
+
+.archiving {
+ opacity: 0.5;
+}
+
+@media (prefers-color-scheme: dark) {
+ .cardContainer {
+ background-color: #18181b;
+ border-color: #27272a;
+ }
+}
+
+/* Header Section */
+.header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.title {
+ font-weight: 600;
+ font-size: 1.125rem;
+ color: #18181b;
+ margin: 0;
+}
+
+@media (prefers-color-scheme: dark) {
+ .title {
+ color: #f4f4f5;
+ }
+}
+
+/* Status Badges */
+.badge {
+ font-size: 0.75rem;
+ padding: 0.25rem 0.625rem;
+ border-radius: 9999px;
+ font-weight: 500;
+}
+
+.statusComplete {
+ background-color: #d1fae5;
+ color: #065f46;
+}
+
+.statusInProgress {
+ background-color: #fef3c7;
+ color: #92400e;
+}
+
+.statusTodo {
+ background-color: #dbeafe;
+ color: #1e40af;
+}
+
+@media (prefers-color-scheme: dark) {
+ .statusComplete {
+ background-color: #022c22;
+ color: #6ee7b7;
+ }
+ .statusInProgress {
+ background-color: #451a03;
+ color: #fcd34d;
+ }
+ .statusTodo {
+ background-color: #172554;
+ color: #93c5fd;
+ }
+}
+
+/* Description */
+.description {
+ margin-top: 0.5rem;
+ font-size: 0.875rem;
+ color: #52525b;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+@media (prefers-color-scheme: dark) {
+ .description {
+ color: #a1a1aa;
+ }
+}
+
+/* Footer Section */
+.footer {
+ margin-top: 1rem;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ font-size: 0.75rem;
+ color: #71717a;
+ border-top: 1px solid #f4f4f5;
+ padding-top: 0.75rem;
+}
+
+@media (prefers-color-scheme: dark) {
+ .footer {
+ border-color: #27272a;
+ color: #a1a1aa;
+ }
+}
+
+.metaGroup {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.topicTag {
+ background-color: #f4f4f5;
+ padding: 0.125rem 0.5rem;
+ border-radius: 0.25rem;
+ font-family: monospace;
+ color: #3f3f46;
+}
+
+@media (prefers-color-scheme: dark) {
+ .topicTag {
+ background-color: #27272a;
+ color: #d4d4d8;
+ }
+}
+
+/* Overdue Badge */
+.overdueBadge {
+ background-color: #fee2e2;
+ color: #b91c1c;
+ padding: 0.125rem 0.5rem;
+ border-radius: 0.25rem;
+ font-weight: 600;
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+@media (prefers-color-scheme: dark) {
+ .overdueBadge {
+ background-color: #450a0a;
+ color: #fca5a5;
+ }
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: .5; }
+}
+
+/* Actions Section */
+.actions {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.5rem;
+ font-size: 0.75rem;
+}
+
+.btnEdit {
+ padding: 0.375rem 0.75rem;
+ border-radius: 0.25rem;
+ background-color: #f4f4f5;
+ color: #27272a;
+ font-weight: 500;
+ border: none;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.btnEdit:hover {
+ background-color: #e4e4e7;
+}
+
+.btnArchive {
+ padding: 0.375rem 0.75rem;
+ border-radius: 0.25rem;
+ background-color: #fef2f2;
+ color: #dc2626;
+ font-weight: 500;
+ border: none;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.btnArchive:hover {
+ background-color: #fee2e2;
+}
+
+.btnArchive:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+@media (prefers-color-scheme: dark) {
+ .btnEdit {
+ background-color: #27272a;
+ color: #e4e4e7;
+ }
+ .btnEdit:hover {
+ background-color: #3f3f46;
+ }
+ .btnArchive {
+ background-color: rgba(69, 10, 10, 0.5);
+ color: #f87171;
+ }
+ .btnArchive:hover {
+ background-color: rgba(127, 29, 29, 0.5);
+ }
+}
diff --git a/src/lib/components/TaskCard.tsx b/src/lib/components/TaskCard.tsx
index 4ee32e3..ef9df13 100644
--- a/src/lib/components/TaskCard.tsx
+++ b/src/lib/components/TaskCard.tsx
@@ -13,96 +13,89 @@ import { archiveTaskAction } from '../actions';
// Import the pop-up modal component used to edit tasks
import TaskEditModal from './TaskEditModal';
+// Import CSS Modules styling
+import styles from './TaskCard.module.css';
+
// Define what props (inputs) this component expects (it needs 1 Task object)
interface TaskCardProps {
- task: Task;
+ task: Task;
}
export default function TaskCard({ task }: TaskCardProps) {
- // Local state: tracks if the edit modal pop-up is open (true or false)
- const [isEditing, setIsEditing] = useState(false);
+ // Local state: tracks if the edit modal pop-up is open (true or false)
+ const [isEditing, setIsEditing] = useState(false);
- // Local state: tracks if we are currently archiving (used to dim the card)
- const [isArchiving, setIsArchiving] = useState(false);
+ // Local state: tracks if we are currently archiving (used to dim the card)
+ const [isArchiving, setIsArchiving] = useState(false);
- // Function called when the user clicks the "Archive" button
- const handleArchive = async () => {
- // Ask the user to confirm before archiving
- if (confirm(`Are you sure you want to archive "${task.title}"?`)) {
- setIsArchiving(true); // Dim the card visually
- await archiveTaskAction(task.id); // Call the server action to update SQLite
- }
- };
+ // Function called when the user clicks the "Archive" button
+ const handleArchive = async () => {
+ // Ask the user to confirm before archiving
+ if (confirm(`Are you sure you want to archive "${task.title}"?`)) {
+ setIsArchiving(true); // Dim the card visually
+ await archiveTaskAction(task.id); // Call the server action to update SQLite
+ }
+ };
- return (
- // Outer card container div (dims opacity if isArchiving is true)
-
-
- {/* Header section: Title and Status badge side by side */}
-
- {/* Task Title */}
-
{task.title}
-
- {/* Status Badge: Green for Complete, Yellow for In-Progress, Blue for Todo */}
-
- {task.status}
-
-
+ // Helper function to return status badge CSS class based on task.status
+ const getStatusClass = () => {
+ if (task.status === 'Complete') return styles.statusComplete;
+ if (task.status === 'In-Progress') return styles.statusInProgress;
+ return styles.statusTodo;
+ };
- {/* Task Description (only renders if a description was provided) */}
- {task.description && (
-
{task.description}
- )}
+ return (
+ // Outer card container div (dims opacity if isArchiving is true)
+
- {/* Footer section: Topic tag, Due Date, and Dynamic OVERDUE badge */}
-
-
- {/* Topic Badge */}
-
- #{task.topic}
-
- {/* Due Date display */}
-
Due: {task.due_date}
+ {/* Header section: Title and Status badge side by side */}
+
+ {/* Task Title */}
+
{task.title}
+
+ {/* Status Badge: Green for Complete, Yellow for In-Progress, Blue for Todo */}
+
+ {task.status}
+
+
+
+ {/* Task Description (only renders if a description was provided) */}
+ {task.description && (
+
{task.description}
+ )}
+
+ {/* Footer section: Topic tag, Due Date, and Dynamic OVERDUE badge */}
+
+
+ {/* Topic Badge */}
+ #{task.topic}
+ {/* Due Date display */}
+ Due: {task.due_date}
+
+
+ {/* Dynamic Overdue Badge: Only renders if task.is_overdue is calculated as true */}
+ {task.is_overdue && (
+
OVERDUE
+ )}
+
+
+ {/* Action buttons: Edit and Archive */}
+
+ {/* Click to open the Edit Modal */}
+ setIsEditing(true)} className={styles.btnEdit}>
+ Edit
+
+
+ {/* Click to archive the task */}
+
+ Archive
+
+
+
+ {/* Edit Modal Pop-up: Only renders when isEditing === true */}
+ {isEditing && (
+
setIsEditing(false)} />
+ )}
-
- {/* Dynamic Overdue Badge: Only renders if task.is_overdue is calculated as true */}
- {task.is_overdue && (
-
- ⚠️ OVERDUE
-
- )}
-
-
- {/* Action buttons: Edit and Archive */}
-
- {/* Click to open the Edit Modal */}
- setIsEditing(true)}
- className="px-3 py-1.5 rounded bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-800 dark:text-zinc-200 font-medium transition"
- >
- Edit
-
-
- {/* Click to archive the task */}
-
- Archive
-
-
-
- {/* Edit Modal Pop-up: Only renders when isEditing === true */}
- {isEditing && (
-
setIsEditing(false)} />
- )}
-
- );
+ );
}
diff --git a/src/lib/components/TaskEditModal.module.css b/src/lib/components/TaskEditModal.module.css
new file mode 100644
index 0000000..bcf10a9
--- /dev/null
+++ b/src/lib/components/TaskEditModal.module.css
@@ -0,0 +1,180 @@
+/* Modal Backdrop Overlay */
+.overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 50;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ padding: 1rem;
+ backdrop-filter: blur(4px);
+}
+
+/* Modal Dialog Box */
+.dialog {
+ width: 100%;
+ max-width: 32rem;
+ border-radius: 0.75rem;
+ background-color: #ffffff;
+ border: 1px solid #e4e4e7;
+ padding: 1.5rem;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
+}
+
+@media (prefers-color-scheme: dark) {
+ .dialog {
+ background-color: #18181b;
+ border-color: #27272a;
+ }
+}
+
+/* Modal Header */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.title {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: #18181b;
+ margin: 0;
+}
+
+.btnClose {
+ background: none;
+ border: none;
+ font-size: 1.25rem;
+ color: #71717a;
+ cursor: pointer;
+}
+
+.btnClose:hover {
+ color: #27272a;
+}
+
+@media (prefers-color-scheme: dark) {
+ .title {
+ color: #f4f4f5;
+ }
+ .btnClose {
+ color: #a1a1aa;
+ }
+ .btnClose:hover {
+ color: #f4f4f5;
+ }
+}
+
+/* Form Layout */
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.gridTwo {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+}
+
+.field {
+ display: flex;
+ flex-direction: column;
+}
+
+.label {
+ display: block;
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: #52525b;
+ margin-bottom: 0.25rem;
+}
+
+@media (prefers-color-scheme: dark) {
+ .label {
+ color: #a1a1aa;
+ }
+}
+
+.input, .select, .textarea {
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ border-radius: 0.5rem;
+ border: 1px solid #d4d4d8;
+ background-color: transparent;
+ color: #18181b;
+ outline: none;
+ box-sizing: border-box;
+}
+
+.select {
+ background-color: #fafafa;
+}
+
+.input:focus, .select:focus, .textarea:focus {
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
+}
+
+@media (prefers-color-scheme: dark) {
+ .input, .select, .textarea {
+ border-color: #3f3f46;
+ color: #f4f4f5;
+ }
+ .select {
+ background-color: #27272a;
+ }
+}
+
+/* Footer Buttons */
+.footerActions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.5rem;
+ padding-top: 0.5rem;
+}
+
+.btnCancel {
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ border-radius: 0.5rem;
+ border: 1px solid #d4d4d8;
+ background: transparent;
+ color: #27272a;
+ cursor: pointer;
+}
+
+.btnCancel:hover {
+ background-color: #f4f4f5;
+}
+
+.btnSave {
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ border-radius: 0.5rem;
+ border: none;
+ background-color: #2563eb;
+ color: #ffffff;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.btnSave:hover {
+ background-color: #1d4ed8;
+}
+
+@media (prefers-color-scheme: dark) {
+ .btnCancel {
+ border-color: #3f3f46;
+ color: #e4e4e7;
+ }
+ .btnCancel:hover {
+ background-color: #27272a;
+ }
+}
diff --git a/src/lib/components/TaskEditModal.tsx b/src/lib/components/TaskEditModal.tsx
index e5f1f80..9b65647 100644
--- a/src/lib/components/TaskEditModal.tsx
+++ b/src/lib/components/TaskEditModal.tsx
@@ -1,35 +1,26 @@
-// Client component directive for browser interactive pop-up modal
'use client';
-// Import state hook from React
import { useState } from 'react';
-
-// Import Task and TaskStatus types
-import { Task, TaskStatus } from '../types';
-
-// Import the update server action function
+import { Task } from '../types';
import { updateTaskAction } from '../actions';
+import styles from './TaskEditModal.module.css';
-// Props needed for the modal: the task to edit, and an onClose function to close the pop-up
interface TaskEditModalProps {
task: Task;
onClose: () => void;
}
export default function TaskEditModal({ task, onClose }: TaskEditModalProps) {
- // State to track if saving is in progress (shows 'Saving...' on button)
const [isSaving, setIsSaving] = useState(false);
- // Form submit handler to update task details
const handleSubmit = async (e: React.FormEvent
) => {
- e.preventDefault(); // Stop normal page refresh
+ e.preventDefault();
setIsSaving(true);
- const formData = new FormData(e.currentTarget); // Get data entered into form inputs
+ const formData = new FormData(e.currentTarget);
try {
- // Call the server action to update SQLite database using formData directly
await updateTaskAction(task.id, formData);
- onClose(); // Close modal upon successful update
+ onClose();
} catch (err) {
alert('Failed to update task');
} finally {
@@ -38,95 +29,71 @@ export default function TaskEditModal({ task, onClose }: TaskEditModalProps) {
};
return (
- // Modal Overlay: dark background overlay that covers the entire screen
-
- {/* Modal Dialog Box */}
-
-
- {/* Modal Header */}
-
-
Edit Task #{task.id}
- {/* Close button (X) */}
-
✕
+
+
+
+
Edit Task #{task.id}
+ ✕
- {/* Edit Form */}
-