@@ -215,7 +237,28 @@ export default function AddForm({
/>
- {/* Add Semester Selection */}
+
+
+
+
+
@@ -303,8 +346,7 @@ export default function AddForm({
value={formData.Photos}
onChange={handleChange}
className="mt-1 w-full rounded-md border p-2"
- placeholder="Url to photo folder"
- required
+ placeholder={DEFAULT_PHOTOS}
/>
diff --git a/src/components/admin/projects/EditForm.tsx b/src/components/admin/projects/EditForm.tsx
index a3c30b5..1be53fc 100644
--- a/src/components/admin/projects/EditForm.tsx
+++ b/src/components/admin/projects/EditForm.tsx
@@ -5,39 +5,66 @@ import {
doc,
updateDoc,
deleteDoc,
+ Timestamp,
} from "firebase/firestore";
import { db } from "@/lib/firebase/firebase";
import { Search, Trash2 } from "lucide-react";
-import { Project, ProjectFormData, Parts } from "@/types/project";
+import { Project, ProjectFormData } from "@/types/project";
import AddForm from "./AddForm";
import { LuLoader2, LuX } from "react-icons/lu";
+const DEFAULT_YOUTUBE = "https://www.youtube.com/@pcbuildinguf";
+const DEFAULT_PHOTOS = "https://photos.app.goo.gl/Mua121F4n2MVZ9wn8";
+
export default function EditForm() {
- const [projects, setProjects] = useState
([]); // Store all projects
- const [searchQuery, setSearchQuery] = useState(""); // Store search input
- const [selectedProject, setSelectedProject] = useState(null); //current selected project for editing
- const [isModalOpen, setIsModalOpen] = useState(false); // Control modal visibility
- const [loading, setLoading] = useState(true); // Loading state for initial data fetch
- const [error, setError] = useState(null); // Error handling
- const [isDeleting, setIsDeleting] = useState(false); // Loading state for delete operation
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); // Control delete confirmation modal
- const [projectToDelete, setProjectToDelete] = useState(null); // Store project to delete
+ const [projects, setProjects] = useState([]);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedProject, setSelectedProject] = useState(null);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const [projectToDelete, setProjectToDelete] = useState(null);
useEffect(() => {
fetchProjects();
}, []);
- // Function to fetch all projects from Firestore
const fetchProjects = async () => {
try {
const querySnapshot = await getDocs(collection(db, "Projects"));
- const projectsData = querySnapshot.docs.map((doc) => ({
- id: doc.id,
- ...doc.data(),
- })) as Project[];
+ const projectsData = querySnapshot.docs.map((doc) => {
+ const data = doc.data();
+
+ // Ensure buildDate is a Timestamp
+ let buildDate;
+ if (data.buildDate instanceof Timestamp) {
+ buildDate = data.buildDate;
+ } else if (data.buildDate) {
+ // If it's a different date format, convert to Timestamp
+ buildDate = Timestamp.fromDate(new Date(data.buildDate));
+ } else {
+ // If no date exists, use current time
+ buildDate = Timestamp.fromDate(new Date());
+ }
+
+ return {
+ id: doc.id,
+ ...data,
+ buildDate,
+ // Ensure default values for links
+ Youtube: data.Youtube || DEFAULT_YOUTUBE,
+ Photos: data.Photos || DEFAULT_PHOTOS,
+ };
+ }) as Project[];
+
+ // Sort projects by date, most recent first
+ const sortedProjects = projectsData.sort(
+ (a, b) => b.buildDate.toMillis() - a.buildDate.toMillis(),
+ );
- console.log("Projects:", projectsData);
- setProjects(projectsData);
+ setProjects(sortedProjects);
setLoading(false);
} catch (err) {
setError("Failed to fetch projects");
@@ -46,18 +73,11 @@ export default function EditForm() {
}
};
- // Filter projects based on search query
- const filteredProjects = projects.filter((project) => {
- return project.Title.toLowerCase().includes(searchQuery.toLowerCase());
- });
-
- // Handler for clicking edit button
const handleEditClick = (project: Project) => {
setSelectedProject(project);
setIsModalOpen(true);
};
- //handler for updating a project
const handleUpdate = async (updatedData: ProjectFormData) => {
if (!selectedProject) return;
@@ -65,11 +85,12 @@ export default function EditForm() {
const projectRef = doc(db, "Projects", selectedProject.id);
const firestoreData = {
- Youtube: updatedData.Youtube,
+ Youtube: updatedData.Youtube || DEFAULT_YOUTUBE,
Description: updatedData.Description,
Title: updatedData.Title,
- Photos: updatedData.Photos,
+ Photos: updatedData.Photos || DEFAULT_PHOTOS,
Image: updatedData.Image,
+ buildDate: updatedData.buildDate,
Builders: updatedData.Builders,
semester: {
term: updatedData.semester.term,
@@ -89,12 +110,15 @@ export default function EditForm() {
await updateDoc(projectRef, firestoreData);
- // Update local state
- setProjects((prevProjects) =>
- prevProjects.map((proj) =>
+ // Update local state and sort
+ setProjects((prevProjects) => {
+ const updatedProjects = prevProjects.map((proj) =>
proj.id === selectedProject.id ? { ...proj, ...updatedData } : proj,
- ),
- );
+ );
+ return updatedProjects.sort(
+ (a, b) => b.buildDate.toMillis() - a.buildDate.toMillis(),
+ );
+ });
setIsModalOpen(false);
setSelectedProject(null);
@@ -122,7 +146,6 @@ export default function EditForm() {
setShowDeleteConfirm(false);
setProjectToDelete(null);
- //alert('Project deleted successfully');
} catch (err) {
console.error("Error deleting project:", err);
setError("Failed to delete project");
@@ -131,7 +154,6 @@ export default function EditForm() {
}
};
- // Render loading state
if (loading) {
return (
@@ -142,119 +164,120 @@ export default function EditForm() {
);
}
- // Render error state
if (error) {
- return
Error: {error}
;
+ return
Error: {error}
;
}
return (
- <>
-
- {/* Search input */}
-
-
- setSearchQuery(e.target.value)}
- className="w-full rounded border p-2 pl-11 text-sm sm:text-base"
- />
-
+
+ {/* Search input */}
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full rounded border p-2 pl-11 text-sm sm:text-base"
+ />
+
- {/* Projects List */}
+ {/* Projects List */}
+
{projects
- .filter(
- (
- proj, // Changed from project to proj
- ) => proj.Title.toLowerCase().includes(searchQuery.toLowerCase()),
+ .filter((project) =>
+ project.Title.toLowerCase().includes(searchQuery.toLowerCase()),
)
- .map(
- (
- proj, // Changed from project to proj
- ) => (
-
-
{proj.Title}
-
-
-
-
+ .map((project) => (
+
+
+
{project.Title}
+
+ Built on:{" "}
+ {new Date(
+ project.buildDate.toDate().setHours(12),
+ ).toLocaleDateString()}
+
- ),
- )}
-
- {/* Edit Modal */}
- {isModalOpen && selectedProject && (
-
-
-
-
Edit Project
-
setIsModalOpen(false)}
- >
-
-
-
-
-
-
- )}
-
- {/* Delete Confirmation Modal */}
- {showDeleteConfirm && (
-
-
-
Confirm Delete
-
- Are you sure you want to delete "{projectToDelete?.Title}"? This
- action cannot be undone.
-
-
+
-
- )}
+ ))}
- >
+
+ {/* Edit Modal */}
+ {isModalOpen && selectedProject && (
+
+
+
+
Edit Project
+
+
+
+
+
+ )}
+
+ {/* Delete Confirmation Modal */}
+ {showDeleteConfirm && (
+
+
+
Confirm Delete
+
+ Are you sure you want to delete "{projectToDelete?.Title}"? This
+ action cannot be undone.
+
+
+
+
+
+
+
+ )}
+
);
}
diff --git a/src/components/projects/ProjectModal.tsx b/src/components/projects/ProjectModal.tsx
index e073787..3a9f7f3 100644
--- a/src/components/projects/ProjectModal.tsx
+++ b/src/components/projects/ProjectModal.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useState, useRef, useEffect } from "react";
import { Project } from "@/types/project";
import { X } from "lucide-react";
@@ -12,9 +12,33 @@ export default function ProjectModal({ project, onClose }: ProjectModalProps) {
"description" | "specs" | "builders"
>("description");
+ const modalRef = useRef
(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ modalRef.current &&
+ !modalRef.current.contains(event.target as Node)
+ ) {
+ onClose();
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [onClose]);
+
return (
-
-
+
onClose()}
+ >
+
e.stopPropagation()}
+ >
{/* Noise background */}
@@ -26,9 +50,9 @@ export default function ProjectModal({ project, onClose }: ProjectModalProps) {
-
+
{/* Left side - Image */}
-
+

{project.Title}
-
- {project.semester.term} {project.semester.year}
-
+
+
+ {project.semester.term} {project.semester.year}
+
+
+ Built on{" "}
+ {new Date(
+ project.buildDate.toDate().getTime() -
+ project.buildDate.toDate().getTimezoneOffset() * 60000,
+ ).toLocaleDateString()}
+
+
{/* Tabs */}
diff --git a/src/components/projects/ProjectsPage.tsx b/src/components/projects/ProjectsPage.tsx
index 0eb8485..4ace09f 100644
--- a/src/components/projects/ProjectsPage.tsx
+++ b/src/components/projects/ProjectsPage.tsx
@@ -15,7 +15,14 @@ const sortProjects = (projects: Project[]) => {
}
// If years are equal, compare terms
- return termOrder[b.semester.term] - termOrder[a.semester.term];
+ const termComparison =
+ termOrder[b.semester.term] - termOrder[a.semester.term];
+ if (termComparison !== 0) {
+ return termComparison;
+ }
+
+ // If terms are equal, compare dates
+ return b.buildDate.toMillis() - a.buildDate.toMillis();
});
};
@@ -44,7 +51,7 @@ export default function ProjectsPage() {
}, []);
return (
-
+
{/* Applied noise background for consistency */}
@@ -110,7 +117,7 @@ export default function ProjectsPage() {
setSelectedProject(project)}
- className="shadow-white-glow group relative cursor-pointer overflow-hidden rounded-lg bg-gray-950 p-4 transition-transform hover:scale-105"
+ className="group relative cursor-pointer overflow-hidden rounded-lg bg-gray-950 p-4 shadow-white-glow transition-transform hover:scale-105"
>
{/* Project Image */}
diff --git a/src/types/project.ts b/src/types/project.ts
index 7dc77c0..1649cf3 100644
--- a/src/types/project.ts
+++ b/src/types/project.ts
@@ -1,3 +1,5 @@
+import { Timestamp } from "firebase/firestore";
+
export interface Parts {
RAM: string;
Cooling: string;
@@ -17,6 +19,7 @@ export interface ProjectFormData {
Photos: string;
Image: string;
Builders: string[];
+ buildDate: Timestamp; // Added buildDate field
semester: {
term: "Spring" | "Summer" | "Fall";
year: number;