-
-
-
- PEC Robotics Society
-
-
- Pushing the boundaries of innovation through robotics and automation
-
-
-
-
-
-
- {/* Navigation dots */}
-
- {images.map((_, index) => (
-
+ {/* Overlay content */}
+
+
+
+
+ PEC Robotics Society
+
+
+ Pushing the boundaries of innovation through robotics and automation
+
+
+
+
+
-
- )}
- >
+ {/* Navigation dots */}
+
+ {images.map((_, index) => (
+
+
+
+
);
};
diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx
index 335776c..4b5c4d9 100644
--- a/src/components/LoginForm.tsx
+++ b/src/components/LoginForm.tsx
@@ -1,12 +1,9 @@
import { useState } from "react";
-import Link from "next/link";
import { Button } from "@/components/ui/button";
-import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Eye, EyeOff, Mail, Lock } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
-import crypto from "crypto";
import { useRouter } from "next/router";
import { loginUser } from "@/lib/supabase/actions/auth.actions";
@@ -49,8 +46,6 @@ const LoginForm = () => {
}
if (data) {
- document.cookie = `sb-access-token=${data.session.access_token}; path=/; Secure`;
- document.cookie = `sb-refresh-token=${data.session.refresh_token}; path=/; Secure`;
router.push("/admin/page");
}
diff --git a/src/components/ProjectSection.tsx b/src/components/ProjectSection.tsx
deleted file mode 100644
index b329ada..0000000
--- a/src/components/ProjectSection.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import ProjectCard from "./ProjectCard";
-import { ProjectType } from "@/types";
-
-const ProjectSection: React.FC<{ projects: ProjectType[] }> = ({ projects }) => {
- return (
-
-
-
-
Our Projects
-
Discover our innovative robotics projects
-
-
-
-
- );
-}
-
-export default ProjectSection;
\ No newline at end of file
diff --git a/src/components/Team.tsx b/src/components/Team.tsx
index 9115a8c..c649564 100644
--- a/src/components/Team.tsx
+++ b/src/components/Team.tsx
@@ -1,17 +1,16 @@
import { motion } from "framer-motion";
import { TeamMember } from "@/types";
import Image from "next/image";
+import PageSection from "@/components/layout/PageSection";
const Team: React.FC<{ teamMembers: TeamMember[] }> = ({ teamMembers }) => {
return (
-
-
-
Our Team
-
Meet the minds behind the innovation
-
-
+
= 4 ? "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-8" : "flex justify-center items-center gap-8"}>
{teamMembers?.map((member: TeamMember, index) => (
= ({ teamMembers }) => {
))}
-
+
);
}
diff --git a/src/components/WrongPage.tsx b/src/components/WrongPage.tsx
new file mode 100644
index 0000000..7c9fc4f
--- /dev/null
+++ b/src/components/WrongPage.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import { AlertTriangle, ArrowLeft } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { motion } from "framer-motion";
+import { useRouter } from "next/router";
+
+const WrongPage = () => {
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+
+ Oops!
+
+ It seems you've wandered to the wrong page.
+ Don't worry, it happens to the best of us!
+
+
+
+
+
+
+
+ );
+};
+
+export default WrongPage;
\ No newline at end of file
diff --git a/src/components/admin/ActivitiesEditor.tsx b/src/components/admin/ActivitiesEditor.tsx
new file mode 100644
index 0000000..5ff32d9
--- /dev/null
+++ b/src/components/admin/ActivitiesEditor.tsx
@@ -0,0 +1,362 @@
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
+import { useToast } from "@/hooks/use-toast";
+import { Plus, Trash, Edit, Save } from "lucide-react";
+import { Card } from "@/components/ui/card";
+
+// Default activity data structure
+const defaultActivities = [
+ {
+ id: "1",
+ title: "Robotics Workshop",
+ shortDescription: "Learn robotics basics in a hands-on environment",
+ description: "A comprehensive workshop covering the fundamentals of robotics including mechanics, electronics, and programming.",
+ duration: "4 hours",
+ frequency: "Monthly",
+ participants: "20-30 students",
+ tags: ["Workshop", "Beginners", "Hands-on"]
+ }
+];
+
+const ActivitiesEditor = () => {
+ const { toast } = useToast();
+ const [activities, setActivities] = useState(defaultActivities);
+ const [newActivity, setNewActivity] = useState({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ duration: "",
+ frequency: "",
+ participants: "",
+ tags: []
+ });
+ const [editingId, setEditingId] = useState
(null);
+ const [tagInput, setTagInput] = useState("");
+
+ // Load activities from localStorage if exists
+ useEffect(() => {
+ const savedActivities = localStorage.getItem("activitiesData");
+ if (savedActivities) {
+ try {
+ setActivities(JSON.parse(savedActivities));
+ } catch (error) {
+ console.error("Failed to parse saved activities data:", error);
+ }
+ }
+ }, []);
+
+ const handleSaveAll = () => {
+ localStorage.setItem("activitiesData", JSON.stringify(activities));
+ toast({
+ title: "Changes saved",
+ description: "Activities have been updated successfully.",
+ });
+ };
+
+ const handleAddActivity = () => {
+ if (!newActivity.title || !newActivity.shortDescription) {
+ toast({
+ title: "Error",
+ description: "Title and short description are required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ const newId = Date.now().toString();
+ setActivities(prev => [...prev, { ...newActivity, id: newId }]);
+ setNewActivity({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ duration: "",
+ frequency: "",
+ participants: "",
+ tags: []
+ });
+ };
+
+ const handleUpdateActivity = () => {
+ if (!editingId) return;
+
+ setActivities(prev =>
+ prev.map(activity =>
+ activity.id === editingId ? newActivity : activity
+ )
+ );
+ setEditingId(null);
+ setNewActivity({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ duration: "",
+ frequency: "",
+ participants: "",
+ tags: []
+ });
+ };
+
+ const handleEditActivity = (activity) => {
+ setNewActivity(activity);
+ setEditingId(activity.id);
+ };
+
+ const handleRemoveActivity = (id) => {
+ setActivities(prev => prev.filter(activity => activity.id !== id));
+ if (editingId === id) {
+ setEditingId(null);
+ setNewActivity({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ duration: "",
+ frequency: "",
+ participants: "",
+ tags: []
+ });
+ }
+ };
+
+ const handleAddTag = () => {
+ if (tagInput.trim()) {
+ setNewActivity(prev => ({
+ ...prev,
+ tags: [...(prev.tags || []), tagInput.trim()]
+ }));
+ setTagInput("");
+ }
+ };
+
+ const handleRemoveTag = (index) => {
+ setNewActivity(prev => ({
+ ...prev,
+ tags: prev.tags.filter((_, i) => i !== index)
+ }));
+ };
+
+ return (
+
+
+
+ {editingId ? "Edit Activity" : "Add New Activity"}
+
+
+
+
+ setNewActivity(prev => ({ ...prev, title: e.target.value }))}
+ placeholder="Activity title"
+ />
+
+
+
+
+ setNewActivity(prev => ({ ...prev, shortDescription: e.target.value }))}
+ placeholder="Brief activity description"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ setTagInput(e.target.value)}
+ placeholder="Add a tag"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleAddTag();
+ }
+ }}
+ />
+
+
+ {newActivity.tags && newActivity.tags.length > 0 && (
+
+ {newActivity.tags.map((tag, index) => (
+
+ {tag}
+
+
+ ))}
+
+ )}
+
+
+ {editingId ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
Current Activities
+
+ {activities.length === 0 ? (
+
No activities added yet.
+ ) : (
+
+ {activities.map((activity) => (
+
+
+
+ {activity.title}
+
+
+
+
+
Short Description:
+
{activity.shortDescription}
+
+
Full Description:
+
{activity.description}
+
+
+
+
Duration:
+
{activity.duration}
+
+
+
Frequency:
+
{activity.frequency}
+
+
+
Participants:
+
{activity.participants}
+
+
+
+ {activity.tags && activity.tags.length > 0 && (
+
+
Tags:
+
+ {activity.tags.map((tag, index) => (
+
+ {tag}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+};
+
+export default ActivitiesEditor;
diff --git a/src/components/admin/EventsEditor.tsx b/src/components/admin/EventsEditor.tsx
new file mode 100644
index 0000000..b6e8a88
--- /dev/null
+++ b/src/components/admin/EventsEditor.tsx
@@ -0,0 +1,462 @@
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
+import { useToast } from "@/hooks/use-toast";
+import { Plus, Trash, Edit, Save, Calendar } from "lucide-react";
+import { Card } from "@/components/ui/card";
+import MarkdownEditor from "./MarkdownEditor";
+import ReactMarkdown from "react-markdown";
+
+// Default events data structure
+const defaultEvents = [
+ {
+ id: "1",
+ title: "Annual Robotics Competition",
+ shortDescription: "Showcase your robotics skills in our yearly competition",
+ description: "Join us for our flagship event where teams compete to build the most innovative and efficient robots to complete a series of challenging tasks.",
+ date: "2023-12-15",
+ time: "10:00 AM - 5:00 PM",
+ location: "PEC Main Auditorium",
+ capacity: "200",
+ imageUrl: "/projects/robot1.jpg",
+ registrationUrl: "https://example.com/register",
+ tags: ["Competition", "Annual Event", "Prizes"]
+ }
+];
+
+interface EventType {
+ id: string;
+ title: string;
+ shortDescription: string;
+ description: string;
+ date: string;
+ time: string;
+ location: string;
+ capacity: string;
+ imageUrl: string;
+ registrationUrl: string;
+ tags: string[];
+}
+
+const EventsEditor = () => {
+ const { toast } = useToast();
+ const [events, setEvents] = useState(defaultEvents);
+ const [newEvent, setNewEvent] = useState({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ date: "",
+ time: "",
+ location: "",
+ capacity: "",
+ imageUrl: "",
+ registrationUrl: "",
+ tags: []
+ });
+ const [editingId, setEditingId] = useState(null);
+ const [tagInput, setTagInput] = useState("");
+
+ // Load events from localStorage if exists
+ useEffect(() => {
+ const savedEvents = localStorage.getItem("eventsData");
+ if (savedEvents) {
+ try {
+ setEvents(JSON.parse(savedEvents));
+ } catch (error) {
+ console.error("Failed to parse saved events data:", error);
+ }
+ }
+ }, []);
+
+ const handleSaveAll = () => {
+ localStorage.setItem("eventsData", JSON.stringify(events));
+ toast({
+ title: "Changes saved",
+ description: "Events have been updated successfully.",
+ });
+ };
+
+ const handleAddEvent = () => {
+ if (!newEvent.title || !newEvent.date) {
+ toast({
+ title: "Error",
+ description: "Title and date are required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ const newId = Date.now().toString();
+ setEvents(prev => [...prev, { ...newEvent, id: newId }]);
+ setNewEvent({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ date: "",
+ time: "",
+ location: "",
+ capacity: "",
+ imageUrl: "",
+ registrationUrl: "",
+ tags: []
+ });
+ };
+
+ const handleUpdateEvent = () => {
+ if (!editingId) return;
+
+ setEvents(prev =>
+ prev.map(event =>
+ event.id === editingId ? newEvent : event
+ )
+ );
+ setEditingId(null);
+ setNewEvent({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ date: "",
+ time: "",
+ location: "",
+ capacity: "",
+ imageUrl: "",
+ registrationUrl: "",
+ tags: []
+ });
+ };
+
+ const handleEditEvent = (event: EventType) => {
+ setNewEvent(event);
+ setEditingId(event.id);
+ };
+
+ const handleRemoveEvent = (id: string) => {
+ setEvents(prev => prev.filter(event => event.id !== id));
+ if (editingId === id) {
+ setEditingId(null);
+ setNewEvent({
+ id: "",
+ title: "",
+ shortDescription: "",
+ description: "",
+ date: "",
+ time: "",
+ location: "",
+ capacity: "",
+ imageUrl: "",
+ registrationUrl: "",
+ tags: []
+ });
+ }
+ };
+
+ const handleAddTag = () => {
+ if (tagInput.trim()) {
+ setNewEvent(prev => ({
+ ...prev,
+ tags: [...(prev.tags || []), tagInput.trim()]
+ }));
+ setTagInput("");
+ }
+ };
+
+ const handleRemoveTag = (index: number) => {
+ setNewEvent(prev => ({
+ ...prev,
+ tags: prev.tags.filter((_, i) => i !== index)
+ }));
+ };
+
+ return (
+
+
+
+ {editingId ? "Edit Event" : "Add New Event"}
+
+
+
+
+ setNewEvent(prev => ({ ...prev, title: e.target.value }))}
+ placeholder="Event title"
+ />
+
+
+
+
+ setNewEvent(prev => ({ ...prev, shortDescription: e.target.value }))}
+ placeholder="Brief event description"
+ />
+
+
+
+
+ setNewEvent(prev => ({ ...prev, description: value }))}
+ placeholder="Write detailed event description using Markdown"
+ minHeight="200px"
+ />
+
+
+
+
+
+
+
+
+ setNewEvent(prev => ({ ...prev, imageUrl: e.target.value }))}
+ placeholder="URL to event image"
+ />
+
+
+
+
+ setNewEvent(prev => ({ ...prev, registrationUrl: e.target.value }))}
+ placeholder="URL for registration"
+ />
+
+
+
+
+
+ setTagInput(e.target.value)}
+ placeholder="Add a tag"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleAddTag();
+ }
+ }}
+ />
+
+
+ {newEvent.tags && newEvent.tags.length > 0 && (
+
+ {newEvent.tags.map((tag, index) => (
+
+ {tag}
+
+
+ ))}
+
+ )}
+
+
+ {editingId ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
Current Events
+
+ {events.length === 0 ? (
+
No events added yet.
+ ) : (
+
+ {events.map((event) => (
+
+
+
+
+
+ {event.title}
+
+
{event.date}
+
+
+
+
+
+ {event.imageUrl && (
+

+ )}
+
+
Short Description:
+
{event.shortDescription}
+
+
Full Description:
+
+ {event.description}
+
+
+
+
+
+
+
+
+
Location:
+
{event.location}
+
+
+
Capacity:
+
{event.capacity}
+
+
+
+
+
+ {event.tags && event.tags.length > 0 && (
+
+
Tags:
+
+ {event.tags.map((tag, index) => (
+
+ {tag}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+};
+
+export default EventsEditor;
diff --git a/src/components/admin/HeroEditor.tsx b/src/components/admin/HeroEditor.tsx
new file mode 100644
index 0000000..98eaa77
--- /dev/null
+++ b/src/components/admin/HeroEditor.tsx
@@ -0,0 +1,173 @@
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import Image from "next/image";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import { useToast } from "@/hooks/use-toast";
+import { Plus, Trash, Save } from "lucide-react";
+import { getImagesFromFolder } from "@/lib/supabase/actions/storage.actions";
+import { Loader } from "@/components/layout/Loader";
+
+/*
+// Default data structure
+const defaultHeroData = {
+ title: "PEC Robotics Society",
+ description: "Pushing the boundaries of innovation through robotics and automation",
+ images: [
+ "/gallery/image1.jpg",
+ "/gallery/image2.jpg",
+ "/gallery/image3.jpg",
+ ]
+};
+*/
+
+interface HeroData {
+ title: string;
+ description: string;
+ images: string[]
+};
+
+const HeroEditor = () => {
+ let defaultHeroData: HeroData = {
+ title: "",
+ description: "",
+ images: []
+ };
+
+ const [loading, setLoading] = useState(true);
+ const [heroData, setHeroData] = useState(defaultHeroData!);
+
+ useEffect(() => {
+ const fetch = async () => {
+ const data = await getImagesFromFolder("hero");
+ defaultHeroData = {
+ title: "PEC Robotics Society",
+ description: "Pushing the boundaries of innovation through robotics and automation",
+ images: data
+ };
+ setHeroData(defaultHeroData);
+ setLoading(false);
+ };
+ fetch();
+ }, []);
+
+ const { toast } = useToast();
+
+ const [newImageUrl, setNewImageUrl] = useState("");
+
+ // Load data from localStorage if exists
+ useEffect(() => {
+ const savedData = localStorage.getItem("heroData");
+ if (savedData) {
+ try {
+ setHeroData(JSON.parse(savedData));
+ } catch (error) {
+ console.error("Failed to parse saved hero data:", error);
+ }
+ }
+ }, []);
+
+ const handleSave = () => {
+ localStorage.setItem("heroData", JSON.stringify(heroData));
+ toast({
+ title: "Changes saved",
+ description: "Hero section has been updated successfully.",
+ });
+ };
+
+ const handleAddImage = () => {
+ if (!newImageUrl.trim()) {
+ toast({
+ title: "Error",
+ description: "Please enter an image URL",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ setHeroData(prev => ({
+ ...prev,
+ images: [...prev.images, newImageUrl]
+ }));
+ setNewImageUrl("");
+ };
+
+ const handleRemoveImage = (index: number) => {
+ setHeroData(prev => ({
+ ...prev,
+ images: prev.images.filter((_, i) => i !== index)
+ }));
+ };
+
+ return (
+
+ < div className="space-y-6" >
+
+
+ setHeroData(prev => ({ ...prev, title: e.target.value }))}
+ placeholder="Enter hero title"
+ />
+
+
+
+
+
+
+
+
+
+
+ {heroData.images.map((image, index) => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
setNewImageUrl(e.target.value)}
+ placeholder="Enter image URL"
+ />
+
+
+
+
+
+ div>
+ Loader >
+ );
+};
+
+export default HeroEditor;
diff --git a/src/components/admin/MarkdownEditor.tsx b/src/components/admin/MarkdownEditor.tsx
new file mode 100644
index 0000000..6ed1b83
--- /dev/null
+++ b/src/components/admin/MarkdownEditor.tsx
@@ -0,0 +1,63 @@
+import { useState, useEffect } from "react";
+// import SimpleMDE from "react-simplemde-editor";
+import dynamic from "next/dynamic";
+import ReactMarkdown from "react-markdown";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import "easymde/dist/easymde.min.css";
+import { Card } from "@/components/ui/card";
+
+interface MarkdownEditorProps {
+ value: string;
+ onChange: (value: string) => void;
+ placeholder?: string;
+ minHeight?: string;
+}
+
+const SimpleMDE = dynamic(() => import("react-simplemde-editor"), { ssr: false });
+
+const MarkdownEditor = ({ value, onChange, placeholder = "Write content using Markdown...", minHeight = "200px" }: MarkdownEditorProps) => {
+ const [activeTab, setActiveTab] = useState("write");
+ const [mounted, setMounted] = useState(false);
+
+ // This is needed because SimpleMDE is client-side only
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const options = {
+ autofocus: false,
+ spellChecker: false,
+ placeholder: placeholder,
+ status: ["lines", "words"],
+ minHeight: minHeight,
+ };
+
+ if (!mounted) {
+ return Loading editor...
;
+ }
+
+ return (
+
+
+ Write
+ Preview
+
+
+
+
+
+
+
+
+ {value ? (
+ {value}
+ ) : (
+ No content to preview
+ )}
+
+
+
+ );
+};
+
+export default MarkdownEditor;
diff --git a/src/components/admin/ProjectsEditor.tsx b/src/components/admin/ProjectsEditor.tsx
new file mode 100644
index 0000000..82af511
--- /dev/null
+++ b/src/components/admin/ProjectsEditor.tsx
@@ -0,0 +1,297 @@
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
+import { useToast } from "@/hooks/use-toast";
+import { Plus, Trash, Edit, Save } from "lucide-react";
+import { Card } from "@/components/ui/card";
+import MarkdownEditor from "./MarkdownEditor";
+import ReactMarkdown from "react-markdown";
+import { getProjects, uploadProject } from "@/lib/supabase/actions/project.actions";
+import { Project } from "@/types";
+import { Loader } from "../layout/Loader";
+import FormField from "../FormField";
+
+// Default project data structure
+
+
+const ProjectsEditor = () => {
+ const { toast } = useToast();
+ const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [fileName, setFileName] = useState("");
+ const [newProject, setNewProject] = useState({
+ id: null,
+ title: "",
+ description: "",
+ image: "",
+ longDescription: "",
+ category: "",
+ technologies: ""
+ });
+ const [editingId, setEditingId] = useState(null);
+
+ // Load projects from localStorage if exists
+ useEffect(() => {
+ const fetch = async () => {
+ const data = await getProjects();
+ setProjects(data);
+ setLoading(false);
+ };
+ fetch();
+
+ const savedProjects = localStorage.getItem("projectsData");
+ if (savedProjects) {
+ try {
+ setProjects(JSON.parse(savedProjects));
+ } catch (error) {
+ console.error("Failed to parse saved projects data:", error);
+ }
+ }
+ }, []);
+
+ const handleSaveAll = () => {
+ localStorage.setItem("projectsData", JSON.stringify(projects));
+ toast({
+ title: "Changes saved",
+ description: "Projects have been updated successfully.",
+ });
+ };
+
+ const handleAddProject = async () => {
+ console.log(newProject);
+ if (!newProject.title ||
+ !newProject.description ||
+ !newProject.longDescription ||
+ !newProject.image ||
+ !newProject.category || !newProject.technologies) {
+ toast({
+ title: "Error",
+ description: "All fields are Required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ setProjects(prev => [...prev, { ...newProject }]);
+ const { error } = await uploadProject(newProject, fileName);
+
+ if (error) {
+ toast({
+ title: error.name,
+ description: error.message,
+ variant: "destructive"
+ });
+ }
+ else {
+ toast({
+ title: "Success",
+ description: "Project has been Uploaded"
+ });
+
+ }
+
+
+ setNewProject({
+ id: null,
+ title: "",
+ description: "",
+ image: "",
+ longDescription: "",
+ category: "",
+ technologies: ""
+ });
+ };
+
+ const handleUpdateProject = () => {
+ if (!editingId) return;
+
+ setProjects(prev =>
+ prev.map(project =>
+ project.id === editingId ? newProject : project
+ )
+ );
+ setEditingId(null);
+ };
+
+ const handleEditProject = (project: any) => {
+ setNewProject(project);
+ setEditingId(project.id);
+ };
+
+ const handleRemoveProject = (id: number) => {
+ setProjects(prev => prev.filter(project => project.id !== id));
+ if (editingId === id) {
+ setEditingId(null);
+ setNewProject({
+ id: null,
+ title: "",
+ description: "",
+ image: "",
+ longDescription: "",
+ category: "",
+ technologies: ""
+ });
+ }
+ };
+
+ return (
+
+
+
+
+ {editingId ? "Edit Project" : "Add New Project"}
+
+
+
+
+
+
+
+
+
+ {editingId ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
Current Projects
+
+ {projects.length === 0 ? (
+
No projects added yet.
+ ) : (
+
+ {projects.map((project) => (
+
+
+
+ {project.title}
+
+
+
+
+
+ {project.image && (
+

+ )}
+
+
{project.description}
+
+ {project.longDescription}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default ProjectsEditor;
diff --git a/src/components/admin/TeamEditor.tsx b/src/components/admin/TeamEditor.tsx
new file mode 100644
index 0000000..f32db39
--- /dev/null
+++ b/src/components/admin/TeamEditor.tsx
@@ -0,0 +1,364 @@
+import { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
+import { useToast } from "@/hooks/use-toast";
+import { Plus, Trash, Edit, Save, User } from "lucide-react";
+import { Card } from "@/components/ui/card";
+
+// Default team members data structure
+const defaultTeamMembers = [
+ {
+ id: "1",
+ firstName: "John",
+ lastName: "Doe",
+ role: "President",
+ bio: "John is a final year student with expertise in AI and robotics control systems.",
+ imageUrl: "/projects/robot1.jpg",
+ email: "john.doe@example.com",
+ github: "https://github.com/johndoe",
+ linkedin: "https://linkedin.com/in/johndoe"
+ }
+];
+
+const TeamEditor = () => {
+ const { toast } = useToast();
+ const [teamMembers, setTeamMembers] = useState(defaultTeamMembers);
+ const [newMember, setNewMember] = useState({
+ id: "",
+ firstName: "",
+ lastName: "",
+ role: "",
+ bio: "",
+ imageUrl: "",
+ email: "",
+ github: "",
+ linkedin: ""
+ });
+ const [editingId, setEditingId] = useState(null);
+
+ // Load team members from localStorage if exists
+ useEffect(() => {
+ const savedTeamMembers = localStorage.getItem("teamMembersData");
+ if (savedTeamMembers) {
+ try {
+ setTeamMembers(JSON.parse(savedTeamMembers));
+ } catch (error) {
+ console.error("Failed to parse saved team members data:", error);
+ }
+ }
+ }, []);
+
+ const handleSaveAll = () => {
+ localStorage.setItem("teamMembersData", JSON.stringify(teamMembers));
+ toast({
+ title: "Changes saved",
+ description: "Team members have been updated successfully.",
+ });
+ };
+
+ const handleAddMember = () => {
+ if (!newMember.firstName || !newMember.lastName || !newMember.role) {
+ toast({
+ title: "Error",
+ description: "First name, last name, and role are required",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ const newId = Date.now().toString();
+ setTeamMembers(prev => [...prev, { ...newMember, id: newId }]);
+ setNewMember({
+ id: "",
+ firstName: "",
+ lastName: "",
+ role: "",
+ bio: "",
+ imageUrl: "",
+ email: "",
+ github: "",
+ linkedin: ""
+ });
+ };
+
+ const handleUpdateMember = () => {
+ if (!editingId) return;
+
+ setTeamMembers(prev =>
+ prev.map(member =>
+ member.id === editingId ? newMember : member
+ )
+ );
+ setEditingId(null);
+ setNewMember({
+ id: "",
+ firstName: "",
+ lastName: "",
+ role: "",
+ bio: "",
+ imageUrl: "",
+ email: "",
+ github: "",
+ linkedin: ""
+ });
+ };
+
+ const handleEditMember = (member) => {
+ setNewMember(member);
+ setEditingId(member.id);
+ };
+
+ const handleRemoveMember = (id) => {
+ setTeamMembers(prev => prev.filter(member => member.id !== id));
+ if (editingId === id) {
+ setEditingId(null);
+ setNewMember({
+ id: "",
+ firstName: "",
+ lastName: "",
+ role: "",
+ bio: "",
+ imageUrl: "",
+ email: "",
+ github: "",
+ linkedin: ""
+ });
+ }
+ };
+
+ return (
+
+
+
+ {editingId ? "Edit Team Member" : "Add New Team Member"}
+
+
+
+
+
+
+ setNewMember(prev => ({ ...prev, role: e.target.value }))}
+ placeholder="e.g., President, Technical Lead"
+ />
+
+
+
+
+
+
+
+
+ setNewMember(prev => ({ ...prev, imageUrl: e.target.value }))}
+ placeholder="URL to profile image"
+ />
+
+
+
+
+ setNewMember(prev => ({ ...prev, email: e.target.value }))}
+ placeholder="Email address"
+ />
+
+
+
+
+ {editingId ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
Current Team Members
+
+ {teamMembers.length === 0 ? (
+
No team members added yet.
+ ) : (
+
+ {teamMembers.map((member) => (
+
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
{member.role}
+
+
+
+
+
+ {member.imageUrl && (
+

+ )}
+
+
Bio:
+
{member.bio || "No bio provided"}
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
+
+ );
+};
+
+export default TeamEditor;
\ No newline at end of file
diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx
new file mode 100644
index 0000000..8fc3405
--- /dev/null
+++ b/src/components/layout/Footer.tsx
@@ -0,0 +1,196 @@
+"use client";
+
+import Link from "next/link";
+import {
+ Github,
+ Instagram,
+ Linkedin,
+ Mail,
+ MapPin,
+ Clock,
+ BookOpen,
+ Users,
+ Calendar,
+ ChevronRight
+} from "lucide-react";
+
+
+const FooterSection: React.FC<{ title: string, description?: string, children: React.ReactNode }> = ({ title, description, children }) => {
+ return (
+
+
{title}
+ {description && (
+
+ {description}
+
)
+ }
+
+
);
+}
+
+const About = () => {
+ const socialLinks = [
+ { href: "https://github.com/Robotics-PEC", icon: Github },
+ { href: "https://www.linkedin.com/company/pec-robotics-society/", icon: Linkedin },
+ { href: "https://www.instagram.com/robotics.society/", icon: Instagram },
+ { href: "mailto:robotics@pec.edu.in", icon: Mail }
+ ];
+ return (
+
+
+ {socialLinks.map(({ href, icon: Icon }, index) => (
+
+
+
+ ))}
+
+
+ );
+}
+
+const QuickLinks = () => {
+ const quickLinks = [
+ { href: "/projects", label: "Projects" },
+ { href: "/activities", label: "Activities" },
+ { href: "/events", label: "Events" },
+ { href: "/contact", label: "Contact" },
+ { href: "/Docify", label: "Docify" },
+ { href: "/pyq", label: "Previous Year Questions" }
+ ];
+ return (
+
+
+ {quickLinks.map(({ href, label }, index) => (
+
+
+
+ {label}
+
+
+ ))}
+
+
+ );
+}
+
+const Resources = () => {
+ const resources = [
+ { href: "/activities", label: "Workshops", icon: BookOpen },
+ { href: "/#team", label: "Our Team", icon: Users },
+ { href: "/events", label: "Upcoming Events", icon: Calendar }
+ ];
+
+ return (
+
+
+ {resources.map(({ href, label, icon: Icon }, index) => (
+
+
+
+ {label}
+
+
+ ))}
+
+
+ );
+}
+
+const ContactInfo = () => {
+ const contactInfo = [
+ {
+ icon: MapPin,
+ text: "Punjab Engineering College, Sector 12, Chandigarh, 160012",
+ isMultiline: true,
+ },
+ {
+ icon: Mail,
+ text: "robotics@pec.edu.in",
+ },
+ {
+ icon: Clock,
+ text: "Mon - Fri: 9:00 AM - 5:00 PM",
+ },
+ ];
+
+ return (
+
+
+ {contactInfo.map(({ icon: Icon, text, isMultiline }, index) => (
+
+
+ {text}
+
+ ))}
+
+
+ );
+}
+
+const Copyright = () => {
+ return (
+
+
+
+ © {new Date().getFullYear()} PEC Robotics Society. All rights reserved.
+
+
+ Privacy Policy
+ Terms of Service
+
+
+
+ );
+}
+
+const Footer = () => {
+
+ return (
+
+ );
+};
+
+export default Footer;
diff --git a/src/components/Header.tsx b/src/components/layout/Header.tsx
similarity index 89%
rename from src/components/Header.tsx
rename to src/components/layout/Header.tsx
index 12ea5f6..ca719c4 100644
--- a/src/components/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
-import Image from "next/image"; // Import Next.js Image component
+import Image from "next/image";
import { Button } from "@/components/ui/button";
import { GitFork, Menu } from "lucide-react";
import Link from "next/link";
@@ -16,7 +16,7 @@ const Header = () => {
const navigation = [
{ name: "Home", path: "/" },
- { name: "Projects", path: "/projects" },
+ { name: "Projects", path: "/project" },
{ name: "Activities", path: "/activities" },
{ name: "Events", path: "/events" },
{ name: "Contact", path: "/contact" },
@@ -30,12 +30,12 @@ const Header = () => {
{/* Logo */}
diff --git a/src/components/Loader.tsx b/src/components/layout/Loader.tsx
similarity index 77%
rename from src/components/Loader.tsx
rename to src/components/layout/Loader.tsx
index 1439cbf..10b4dac 100644
--- a/src/components/Loader.tsx
+++ b/src/components/layout/Loader.tsx
@@ -1,5 +1,5 @@
-import React from "react";
+import React, { ReactNode } from "react";
import { cn } from "@/lib/utils";
export interface LoaderProps {
@@ -7,6 +7,8 @@ export interface LoaderProps {
text?: string;
className?: string;
variant?: "spinner" | "pulse" | "dots";
+ isLoading: Boolean;
+ children: React.ReactNode;
}
export const Loader = ({
@@ -14,6 +16,8 @@ export const Loader = ({
text,
className,
variant = "spinner",
+ isLoading,
+ children
}: LoaderProps) => {
const sizeClasses = {
sm: "w-8 h-8",
@@ -69,9 +73,16 @@ export const Loader = ({
};
return (
-
- {renderLoader()}
- {text &&
{text}
}
-
+ <>{
+ isLoading ? (
+
+
+ {renderLoader()}
+ {text &&
{text}
}
+
+
Loading...
+
+ ) : children
+ }>
);
};
\ No newline at end of file
diff --git a/src/components/layout/PageHead.tsx b/src/components/layout/PageHead.tsx
new file mode 100644
index 0000000..4fe3717
--- /dev/null
+++ b/src/components/layout/PageHead.tsx
@@ -0,0 +1,34 @@
+import Head from "next/head";
+import { useRouter } from "next/router";
+
+interface PageHeadProps {
+ title: string;
+ description?: string;
+}
+
+const PageHead: React.FC = ({ title, description }) => {
+ const router = useRouter();
+ const currentUrl = `https://roboticspec.com${router.asPath}`;
+
+ return (
+
+ {title}
+
+ {description && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default PageHead;
\ No newline at end of file
diff --git a/src/components/layout/PageLayout.tsx b/src/components/layout/PageLayout.tsx
new file mode 100644
index 0000000..6a59f51
--- /dev/null
+++ b/src/components/layout/PageLayout.tsx
@@ -0,0 +1,17 @@
+import Header from "./Header";
+import Footer from "./Footer";
+import { ReactNode } from "react";
+
+const PageLayout: React.FC<{ children: ReactNode }> = ({ children }) => {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+export default PageLayout;
\ No newline at end of file
diff --git a/src/components/layout/PageSection.tsx b/src/components/layout/PageSection.tsx
new file mode 100644
index 0000000..f57bf2c
--- /dev/null
+++ b/src/components/layout/PageSection.tsx
@@ -0,0 +1,34 @@
+import { motion } from "framer-motion";
+
+interface PageSectionProps {
+ title: string;
+ subtitle: string;
+ children: React.ReactNode;
+}
+
+const PageSection: React.FC = ({ title, subtitle, children }) => {
+ return (
+
+
+ {title}
+ {subtitle}
+
+
+
+ {children}
+
+
+ );
+};
+
+export default PageSection;
\ No newline at end of file
diff --git a/src/lib/sessionStorageAdapter.ts b/src/lib/sessionStorageAdapter.ts
new file mode 100644
index 0000000..86fd51c
--- /dev/null
+++ b/src/lib/sessionStorageAdapter.ts
@@ -0,0 +1,18 @@
+export const sessionStorageAdapter = {
+ getItem: (key: string) => {
+ if (typeof window !== 'undefined') {
+ return window.sessionStorage.getItem(key);
+ }
+ return null;
+ },
+ setItem: (key: string, value: string) => {
+ if (typeof window !== 'undefined') {
+ window.sessionStorage.setItem(key, value);
+ }
+ },
+ removeItem: (key: string) => {
+ if (typeof window !== 'undefined') {
+ window.sessionStorage.removeItem(key);
+ }
+ }
+};
\ No newline at end of file
diff --git a/src/lib/supabase/actions/project.actions.ts b/src/lib/supabase/actions/project.actions.ts
index d4fb9a9..3e92845 100644
--- a/src/lib/supabase/actions/project.actions.ts
+++ b/src/lib/supabase/actions/project.actions.ts
@@ -1,4 +1,6 @@
+import { Project } from "@/types";
import { client } from "../supabase";
+import { uploadImage } from "./storage.actions";
export const getProjects = async () => {
const { data, error } = await client.from("projects").select("*");
@@ -11,11 +13,27 @@ export const getProjects = async () => {
};
export const getProjectById = async (id: number) => {
- console.log(id);
const { data, error } = await client.from("projects").select().eq("id", id);
if (error) console.log(error);
if (!data) throw new Error("Project with this id doesn't exist");
return JSON.parse(JSON.stringify(data[0]));
+};
+
+export const uploadProject = async (project: Project, fileName: string) => {
+ await uploadImage("projects", fileName, project.image);
+ const { data } = client.storage.from("media").getPublicUrl(`projects/${fileName}`);
+
+ const { id, ...rest } = project;
+
+ const { error } = await client.from("projects").insert({ ...rest, image: data.publicUrl });
+
+ if (error) {
+ console.log(error);
+ return { error: error };
+ }
+
+ return { error: null };
+
}
\ No newline at end of file
diff --git a/src/lib/supabase/actions/storage.actions.ts b/src/lib/supabase/actions/storage.actions.ts
index 840f8e1..ee10ea0 100644
--- a/src/lib/supabase/actions/storage.actions.ts
+++ b/src/lib/supabase/actions/storage.actions.ts
@@ -1,4 +1,5 @@
-import { client } from "../supabase"
+import { base64ToBlob } from "@/lib/utils";
+import { client } from "../supabase";
export const getFileNames = async (folder: string) => {
const { data, error } = await client.storage.from("media").list(folder);
@@ -23,4 +24,18 @@ export const getImagesFromFolder = async (folder: string) => {
}
return publicUrls;
-};
\ No newline at end of file
+};
+
+export const uploadImage = async (folder: string, name: string, fileData: string) => {
+ const extension = name.split(".")[name.split(".").length - 1]
+ const contentType = `image/${extension}`;
+ const base64String = fileData.replace(/^data:image\/\w+;base64,/, '');
+ const blob = base64ToBlob(base64String, contentType)
+ const filePath = `${folder}/${name}`;
+
+ const { data, error } = await client.storage.from("media").upload(filePath, blob, { contentType, upsert: true });
+
+ if (error) console.log(error);
+
+ return data;
+}
\ No newline at end of file
diff --git a/src/lib/supabase/supabase.ts b/src/lib/supabase/supabase.ts
index 06a1bd4..d44ddb1 100644
--- a/src/lib/supabase/supabase.ts
+++ b/src/lib/supabase/supabase.ts
@@ -1,6 +1,16 @@
import { createClient } from "@supabase/supabase-js";
+import { sessionStorageAdapter } from "../sessionStorageAdapter";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_API_ENDPOINT!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
-export const client = createClient(supabaseUrl, supabaseAnonKey);
+export const client = createClient(
+ supabaseUrl,
+ supabaseAnonKey,
+ {
+ auth: {
+ storage: sessionStorageAdapter,
+ storageKey: `sb-${process.env.NEXT_PUBLIC_PROJECT_REF}-auth-token`
+ }
+ }
+);
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index bd0c391..b458095 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,6 +1,29 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
+import { client } from "./supabase/supabase";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
-}
+};
+
+export const handleLogout = async () => {
+ await client.auth.signOut();
+ localStorage.clear();
+};
+
+export const base64ToBlob = (base64Data: string, contentType = 'image/png') => {
+ const byteCharacters = atob(base64Data)
+ const byteArrays = []
+
+ for (let i = 0; i < byteCharacters.length; i += 512) {
+ const slice = byteCharacters.slice(i, i + 512)
+ const byteNumbers = new Array(slice.length)
+ for (let j = 0; j < slice.length; j++) {
+ byteNumbers[j] = slice.charCodeAt(j)
+ }
+ const byteArray = new Uint8Array(byteNumbers)
+ byteArrays.push(byteArray)
+ }
+
+ return new Blob(byteArrays, { type: contentType })
+}
\ No newline at end of file
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 074066d..3be4eb5 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -3,6 +3,8 @@
import { useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import { Button } from "@/components/ui/button";
+import PageLayout from "@/components/layout/PageLayout";
+import PageHead from "@/components/layout/PageHead";
const NotFound = () => {
const router = useRouter();
@@ -13,18 +15,24 @@ const NotFound = () => {
}, [pathname]);
return (
-
-
-
404
-
Oops! Page not found
-
+
+
+
+
+
404
+
Oops! Page not found
+
+
-
+
);
};
diff --git a/src/pages/activities.tsx b/src/pages/activities.tsx
index 9801dff..cf8b1b2 100644
--- a/src/pages/activities.tsx
+++ b/src/pages/activities.tsx
@@ -1,15 +1,16 @@
import { motion } from "framer-motion";
-import Head from "next/head";
import { Card } from "@/components/ui/card";
import { Calendar, Users } from "lucide-react";
-import Header from "@/components/Header";
-import Footer from "@/components/Footer";
import { useEffect, useState } from "react";
import { getActivites } from "@/lib/supabase/actions/activities.actions";
-import { Loader } from "@/components/Loader";
+import { Loader } from "@/components/layout/Loader";
import { ActivityType } from "@/types";
+import PageHead from "@/components/layout/PageHead";
+import PageLayout from "@/components/layout/PageLayout";
+import PageSection from "@/components/layout/PageSection";
+
const Activities = () => {
const [activities, setActivities] = useState
([]);
@@ -26,88 +27,64 @@ const Activities = () => {
}, []);
return (
- <>
- {loading ? (
-
-
- Loading...
-
- ) : (
-
-
-
-
-
Robotics Society | Punjab Engineering College
-
-
-
-
-
-
-
-
-
-
-
-
-
- {activities.length > 0 ?
- <>
-
-
Our Activities
-
Join us in our robotics journey
-
-
-
- {activities.map((activity) => (
-
-
-
-
-
{activity.title}
-
-
{activity.description}
-
-
-
- {activity.date}
-
-
-
- {activity.participants} participants
-
-
+
+
+
+
+
+ {activities.length > 0 ?
+
+
+ {activities.map((activity) => (
+
+
+
+
+
{activity.title}
+
+
{activity.description}
+
+
+
+ {activity.date}
+
+
+
+ {activity.participants} participants
-
-
+
+
+
+
- ))}
-
-
- >
- :
-
-
No Activities as of now!
+ ))}
-
- }
-
-
-
-
-
- )}
- >
+
+ :
+
+
No Activities as of now!
+
+ }
+
+ section >
+
+
);
};
diff --git a/src/pages/admin.tsx b/src/pages/admin.tsx
deleted file mode 100644
index e102e8d..0000000
--- a/src/pages/admin.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import LoginForm from '@/components/LoginForm'
-import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
-import Link from 'next/link'
-import React from 'react'
-
-const AdminPage = () => {
- return (
-
-
-
-
- Welcome back
-
- Enter your credentials to access your account
-
-
-
-
-
-
-
-
-
- )
-}
-
-export default AdminPage
\ No newline at end of file
diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx
new file mode 100644
index 0000000..f7358d6
--- /dev/null
+++ b/src/pages/admin/index.tsx
@@ -0,0 +1,30 @@
+import LoginForm from '@/components/LoginForm'
+import PageLayout from '@/components/layout/PageLayout'
+import { motion } from "framer-motion";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+
+const AdminPage = () => {
+ return (
+
+
+
+
+
+
+ Welcome back
+
+ Enter your credentials to access your account
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AdminPage
\ No newline at end of file
diff --git a/src/pages/admin/page.tsx b/src/pages/admin/page.tsx
index 1fd4019..51c5bc3 100644
--- a/src/pages/admin/page.tsx
+++ b/src/pages/admin/page.tsx
@@ -1,53 +1,36 @@
-import { Button } from '@/components/ui/button';
+import Admin from '@/components/Admin';
+import WrongPage from '@/components/WrongPage';
import { client } from '@/lib/supabase/supabase';
-import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react'
const AdminPage = () => {
- const router = useRouter();
const [validUser, setValidUser] = useState(false);
+
useEffect(() => {
const checkSession = async () => {
const { data: { session } } = await client.auth.getSession();
- console.log(session);
- console.log(new Date().getTime());
-
if (session) {
setValidUser(true);
return;
}
return;
}
-
+ client.auth.onAuthStateChange((event, session) => {
+ if (event === "SIGNED_OUT") {
+ localStorage.clear();
+ }
+ })
checkSession();
}, []);
- const handleLogout = () => {
- localStorage.clear();
- router.push("/");
- }
-
return (
<>
{
validUser ? (
-
- AdminPage
-
-
+
) : (
-
-
- Oops!! You're not supposed to be here!!
-
-
-
-
+
)
}
>
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index ac54e1c..e8250a4 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -1,15 +1,15 @@
-import { useState, useEffect } from "react";
+import { useEffect } from "react";
import { motion } from "framer-motion";
-import Head from "next/head";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
-import { Mail, MapPin } from "lucide-react";
import { useForm, ValidationError } from "@formspree/react";
import { useToast } from "@/components/ui/use-toast";
-import Header from "@/components/Header";
-import Footer from "@/components/Footer";
+
+import PageSection from "@/components/layout/PageSection";
+import PageLayout from "@/components/layout/PageLayout";
+import PageHead from "@/components/layout/PageHead";
const Contact = () => {
const { toast } = useToast();
@@ -26,20 +26,18 @@ const Contact = () => {
}, [state.succeeded, toast]);
return (
-
-
-
-
Robotics Society | Punjab Engineering College
-
-
-
-
-
-
-
Contact Us
-
Get in touch with our team
-
-
+
+ {/* Meta tags for SEO Optimisations */}
+
+
-
-
-
+
+
+
);
};
diff --git a/src/components/EventCalendar.tsx b/src/pages/events/components/EventCalendar.tsx
similarity index 98%
rename from src/components/EventCalendar.tsx
rename to src/pages/events/components/EventCalendar.tsx
index 0fcc4a9..e429761 100644
--- a/src/components/EventCalendar.tsx
+++ b/src/pages/events/components/EventCalendar.tsx
@@ -170,7 +170,7 @@ const EventCalendar: React.FC = ({ events }) => {
+
+
+
);
};
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 45360ba..422f08a 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,16 +1,14 @@
+import Hero from "@/components/Hero";
+import Team from "@/components/Team";
+import PageHead from "@/components/layout/PageHead";
+import PageLayout from "@/components/layout/PageLayout";
+
import { useState, useEffect } from "react";
-import Head from "next/head";
-import Header from "@/components/Header";
-import Footer from "@/components/Footer";
-import ProjectCard from "@/components/ProjectCard";
import { ProjectType, TeamMember } from "@/types";
import { getProjects } from "@/lib/supabase/actions/project.actions";
import { getTeamMembers } from "@/lib/supabase/actions/team.actions";
-import { Loader } from "@/components/Loader";
-
-import Hero from "@/components/Hero";
-import Team from "@/components/Team";
-import ProjectSection from "@/components/ProjectSection";
+import { Loader } from "@/components/layout/Loader";
+import { ProjectSection } from "@/pages/project/index";
const Index = () => {
const [teamMembers, setTeamMembers] = useState([]);
@@ -36,45 +34,24 @@ const Index = () => {
}, []);
return (
- <>
- {
- loading ? (
-
-
- Loading...
-
- ) :
-
-
-
Robotics Society | Punjab Engineering College
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Hero Section */}
-
+
+
- {/* Projects Section */}
-
+
- {/* Team Section */}
-
+ {/* Hero Section */}
+
-
-
-
+ {/* Projects Section */}
+
- }
- >
+ {/* Team Section */}
+
+
+
);
};
diff --git a/src/pages/project/[id].tsx b/src/pages/project/[id].tsx
index b1cac69..b962523 100644
--- a/src/pages/project/[id].tsx
+++ b/src/pages/project/[id].tsx
@@ -1,25 +1,20 @@
"use client";
-import Footer from '@/components/Footer';
-import Header from '@/components/Header';
import { Button } from '@/components/ui/button';
import { ChevronLeft, ChevronRight } from 'lucide-react';
-import Head from 'next/head';
import { useRouter } from 'next/router';
import { motion, AnimatePresence } from "framer-motion";
import Image from 'next/image';
-import Link from 'next/link';
import React, { useEffect, useState } from 'react';
import { ProjectType } from '@/types';
import { getProjectById, getProjects } from '@/lib/supabase/actions/project.actions';
-import { Loader } from '@/components/Loader';
-import { set } from 'date-fns';
-import { useIsMobile } from '@/hooks/use-mobile';
+import { Loader } from '@/components/layout/Loader';
+import PageHead from '@/components/layout/PageHead';
+import PageLayout from '@/components/layout/PageLayout';
const ProjectPage = () => {
const router = useRouter();
const id = Number(router.query.id);
- const isMobile: boolean = useIsMobile();
const [project, setProject] = useState();
const [projectsLength, setProjectsLength] = useState(0);
const [loading, setLoading] = useState(true);
@@ -59,103 +54,89 @@ const ProjectPage = () => {
};
return (
- <>
- {
- loading ? (
-
-
- Loading...
-
- ) : (
-
-
-
Robotics Society | Punjab Engineering College
-
-
-
-
-
-
- {
- project ? (
-
-
-
-
-
- {project.category}
+
+
+
+ {
+ project ? (
+
+
+
+
+
+ {project.category}
+
+
+ {project.title}
+
+
+ {project.description}
+
+
+ {project.technologies?.map((tech, index) => (
+
+ {tech}
-
- {project.title}
-
-
- {project.description}
-
-
- {project.technologies?.map((tech, index) => (
-
- {tech}
-
- ))}
-
-
-
-
-
-
-
About this project
-
- {project.longDescription}
-
-
-
-
+ ))}
+
+
+
+
+
+
+
About this project
+
+ {project.longDescription}
+
+
+
+
-
+
-
-
-
-
-
- ) : (
-
-
Incorrect Project ID
-
+
+
- )
- }
-
-
- )
- }
- >
+
+
+ ) : (
+
+
Incorrect Project ID
+
+
+ )
+ }
+
+
);
};
diff --git a/src/components/ProjectCard.tsx b/src/pages/project/components/ProjectCard.tsx
similarity index 97%
rename from src/components/ProjectCard.tsx
rename to src/pages/project/components/ProjectCard.tsx
index 17c7588..6216b31 100644
--- a/src/components/ProjectCard.tsx
+++ b/src/pages/project/components/ProjectCard.tsx
@@ -1,5 +1,5 @@
import { motion } from 'framer-motion'
-import { Card } from './ui/card'
+import { Card } from '../../../components/ui/card'
import Image from 'next/image'
import Link from 'next/link';
import { ProjectType } from '@/types';
diff --git a/src/pages/project/index.tsx b/src/pages/project/index.tsx
new file mode 100644
index 0000000..f7797de
--- /dev/null
+++ b/src/pages/project/index.tsx
@@ -0,0 +1,55 @@
+import { useEffect, useState } from "react";
+import { ProjectType } from "@/types";
+import { getProjects } from "@/lib/supabase/actions/project.actions";
+import { Loader } from "@/components/layout/Loader";
+import PageHead from "@/components/layout/PageHead";
+import PageLayout from "@/components/layout/PageLayout";
+import PageSection from "@/components/layout/PageSection";
+import ProjectCard from "@/pages/project/components/ProjectCard";
+
+const ProjectSection: React.FC<{ projects: ProjectType[] }> = ({ projects }) => {
+ return (
+
+ );
+}
+
+
+const Projects = () => {
+
+ const [loading, setLoading] = useState(true);
+ const [projects, setProjects] = useState
([]);
+
+ useEffect(() => {
+ const fetch = async () => {
+ const data = await getProjects();
+ setProjects(data);
+ setLoading(false);
+ };
+ fetch();
+ }, []);
+
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default Projects;
+export { ProjectSection };
diff --git a/src/pages/projects.tsx b/src/pages/projects.tsx
deleted file mode 100644
index 5f24bc7..0000000
--- a/src/pages/projects.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useEffect, useState } from "react";
-import Head from "next/head";
-import Header from "@/components/Header";
-import Footer from "@/components/Footer";
-import ProjectSection from "@/components/ProjectSection";
-import { ProjectType } from "@/types";
-import { getProjects } from "@/lib/supabase/actions/project.actions";
-import { Loader } from "@/components/Loader";
-
-const Projects = () => {
-
- const [loading, setLoading] = useState(true);
- const [projects, setProjects] = useState([]);
-
- useEffect(() => {
- const fetch = async () => {
- const data = await getProjects();
- setProjects(data);
- setLoading(false);
- };
-
- fetch();
-
- }, []);
-
-
- return (
- <>
- {
- loading ? (
-
-
- Loading...
-
- ) : (
-
-
-
-
Robotics Society | Punjab Engineering College
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
- >
- );
-};
-
-export default Projects;
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 1916ea9..c13b98b 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -1,3 +1,5 @@
+import { SetStateAction } from "react";
+
export interface ProjectType {
id: number;
title: string;
@@ -37,4 +39,25 @@ export interface TeamMember {
export interface PublicUrlType {
name: string;
url: string;
-};
\ No newline at end of file
+};
+
+export interface Project {
+ id: number | null;
+ title: string;
+ description: string;
+ image: string;
+ longDescription: string;
+ category: string;
+ technologies: string;
+};
+
+export interface FormFieldProps {
+ htmlFor: string;
+ title: string;
+ id: string;
+ onChange: (value: SetStateAction) => void;
+ placeholder: string;
+ value: string;
+ type: "BLOB" | "TEXT" | "IMAGE" | "MARKDOWN"
+ setFileName?: (value: SetStateAction) => void;
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 7e08f79..099f5df 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14,6 +14,72 @@
dependencies:
regenerator-runtime "^0.14.0"
+"@codemirror/autocomplete@^6.0.0":
+ version "6.18.6"
+ resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz#de26e864a1ec8192a1b241eb86addbb612964ddb"
+ integrity sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.17.0"
+ "@lezer/common" "^1.0.0"
+
+"@codemirror/commands@^6.0.0":
+ version "6.8.0"
+ resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-6.8.0.tgz#92f200b66f852939bd6ebb90d48c2d9e9c813d64"
+ integrity sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==
+ dependencies:
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/state" "^6.4.0"
+ "@codemirror/view" "^6.27.0"
+ "@lezer/common" "^1.1.0"
+
+"@codemirror/language@^6.0.0":
+ version "6.10.8"
+ resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.8.tgz#3e3a346a2b0a8cf63ee1cfe03349eb1965dce5f9"
+ integrity sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==
+ dependencies:
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.23.0"
+ "@lezer/common" "^1.1.0"
+ "@lezer/highlight" "^1.0.0"
+ "@lezer/lr" "^1.0.0"
+ style-mod "^4.0.0"
+
+"@codemirror/lint@^6.0.0":
+ version "6.8.4"
+ resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.8.4.tgz#7d8aa5d1a6dec89ffcc23ad45ddca2e12e90982d"
+ integrity sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==
+ dependencies:
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.35.0"
+ crelt "^1.0.5"
+
+"@codemirror/search@^6.0.0":
+ version "6.5.10"
+ resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.5.10.tgz#7367bfc88094d078b91c752bc74140fb565b55ee"
+ integrity sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==
+ dependencies:
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.0.0"
+ crelt "^1.0.5"
+
+"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.5.0":
+ version "6.5.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.5.2.tgz#8eca3a64212a83367dc85475b7d78d5c9b7076c6"
+ integrity sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==
+ dependencies:
+ "@marijn/find-cluster-break" "^1.0.0"
+
+"@codemirror/view@^6.0.0", "@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0", "@codemirror/view@^6.27.0", "@codemirror/view@^6.35.0":
+ version "6.36.4"
+ resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.36.4.tgz#d47d38b92a22cc40647bfb9cc97944e13d44942d"
+ integrity sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==
+ dependencies:
+ "@codemirror/state" "^6.5.0"
+ style-mod "^4.1.0"
+ w3c-keyname "^2.2.4"
+
"@corex/deepmerge@^4.0.43":
version "4.0.43"
resolved "https://registry.yarnpkg.com/@corex/deepmerge/-/deepmerge-4.0.43.tgz#9bd42559ebb41cc5a7fb7cfeea5f231c20977dca"
@@ -325,6 +391,30 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.3.tgz#138fcddab157d83da557554851017c6c1e5667fd"
+ integrity sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==
+
+"@lezer/highlight@^1.0.0":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.1.tgz#596fa8f9aeb58a608be0a563e960c373cbf23f8b"
+ integrity sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==
+ dependencies:
+ "@lezer/common" "^1.0.0"
+
+"@lezer/lr@^1.0.0":
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.2.tgz#931ea3dea8e9de84e90781001dae30dea9ff1727"
+ integrity sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==
+ dependencies:
+ "@lezer/common" "^1.0.0"
+
+"@marijn/find-cluster-break@^1.0.0":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz#775374306116d51c0c500b8c4face0f9a04752d8"
+ integrity sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==
+
"@next/env@15.2.1":
version "15.2.1"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.2.1.tgz#4b5baade67031fb4cc3c1841c661dde3db24bde4"
@@ -1111,6 +1201,13 @@
dependencies:
"@tanstack/query-core" "5.67.2"
+"@types/codemirror@^5.60.10", "@types/codemirror@~5.60.5":
+ version "5.60.15"
+ resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-5.60.15.tgz#0f82be6f4126d1e59cf4c4830e56dcd49d3c3e8a"
+ integrity sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==
+ dependencies:
+ "@types/tern" "*"
+
"@types/d3-array@^3.0.3":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5"
@@ -1205,6 +1302,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+"@types/marked@^4.0.7":
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac"
+ integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==
+
"@types/mdast@^4.0.0":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
@@ -1268,6 +1370,13 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/tern@*":
+ version "0.23.9"
+ resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.9.tgz#6f6093a4a9af3e6bb8dde528e024924d196b367c"
+ integrity sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==
+ dependencies:
+ "@types/estree" "*"
+
"@types/unist@*", "@types/unist@^3.0.0":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
@@ -1760,6 +1869,31 @@ cmdk@^1.0.0:
"@radix-ui/react-primitive" "^2.0.0"
use-sync-external-store "^1.2.2"
+codemirror-spell-checker@*, codemirror-spell-checker@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e"
+ integrity sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==
+ dependencies:
+ typo-js "*"
+
+codemirror@*:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
+ integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
+ dependencies:
+ "@codemirror/autocomplete" "^6.0.0"
+ "@codemirror/commands" "^6.0.0"
+ "@codemirror/language" "^6.0.0"
+ "@codemirror/lint" "^6.0.0"
+ "@codemirror/search" "^6.0.0"
+ "@codemirror/state" "^6.0.0"
+ "@codemirror/view" "^6.0.0"
+
+codemirror@^5.65.15:
+ version "5.65.18"
+ resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.18.tgz#d7146e4271135a9b4adcd023a270185457c9c428"
+ integrity sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA==
+
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -1808,6 +1942,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+crelt@^1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
+ integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
+
cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
@@ -2045,6 +2184,17 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+easymde@^2.20.0:
+ version "2.20.0"
+ resolved "https://registry.yarnpkg.com/easymde/-/easymde-2.20.0.tgz#88b3161feab6e1900afa9c4dab3f1da352b0a26e"
+ integrity sha512-V1Z5f92TfR42Na852OWnIZMbM7zotWQYTddNaLYZFVKj7APBbyZ3FYJ27gBw2grMW3R6Qdv9J8n5Ij7XRSIgXQ==
+ dependencies:
+ "@types/codemirror" "^5.60.10"
+ "@types/marked" "^4.0.7"
+ codemirror "^5.65.15"
+ codemirror-spell-checker "1.1.2"
+ marked "^4.1.0"
+
electron-to-chromium@^1.5.73:
version "1.5.113"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz#1175b8ba4170541e44e9afa8b992e5bbfff0d150"
@@ -3291,6 +3441,16 @@ markdown-table@^3.0.0:
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==
+marked@*:
+ version "15.0.7"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-15.0.7.tgz#f67d7e34d202ce087e6b879107b5efb04e743314"
+ integrity sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==
+
+marked@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
+ integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
+
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
@@ -4267,6 +4427,13 @@ react-resizable-panels@^2.1.3:
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz#afd29d8a3d708786a9f95183a38803c89f13c2e7"
integrity sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==
+react-simplemde-editor@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-5.2.0.tgz#7a4c8b97e4989cb129b45ba140145d71bdc0684e"
+ integrity sha512-GkTg1MlQHVK2Rks++7sjuQr/GVS/xm6y+HchZ4GPBWrhcgLieh4CjK04GTKbsfYorSRYKa0n37rtNSJmOzEDkQ==
+ dependencies:
+ "@types/codemirror" "~5.60.5"
+
react-smooth@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.4.tgz#a5875f8bb61963ca61b819cedc569dc2453894b4"
@@ -4639,6 +4806,15 @@ simple-swizzle@^0.2.2:
dependencies:
is-arrayish "^0.3.1"
+simplemde@^1.11.2:
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/simplemde/-/simplemde-1.11.2.tgz#a23a35d978d2c40ef07dec008c92f070d8e080e3"
+ integrity sha512-AUXuHJ/tEEDEcN/MTitHIw3AuBcheizJt7SVwtyn00B0UK5RKZ3GB+JndmRcJ5wfYGCIL0O2yJm/uz0sJOFSLg==
+ dependencies:
+ codemirror "*"
+ codemirror-spell-checker "*"
+ marked "*"
+
sonner@^1.5.0:
version "1.7.4"
resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.7.4.tgz#4c39820db86623800a17115c8970796aa862133a"
@@ -4803,6 +4979,11 @@ strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+style-mod@^4.0.0, style-mod@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.2.tgz#ca238a1ad4786520f7515a8539d5a63691d7bf67"
+ integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==
+
style-to-js@^1.0.0:
version "1.1.16"
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a"
@@ -5032,6 +5213,11 @@ typescript@^5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
+typo-js@*:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.2.5.tgz#0aa65e0be9b69036463a3827de8185b4144e3086"
+ integrity sha512-F45vFWdGX8xahIk/sOp79z2NJs8ETMYsmMChm9D5Hlx3+9j7VnCyQyvij5MOCrNY3NNe8noSyokRjQRfq+Bc7A==
+
unbox-primitive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
@@ -5186,6 +5372,11 @@ victory-vendor@^36.6.8:
d3-time "^3.0.0"
d3-timer "^3.0.1"
+w3c-keyname@^2.2.4:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
+ integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"