diff --git a/.gitpod.yml b/.gitpod.yml index f2bf425..9bbccf5 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -5,7 +5,7 @@ # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart tasks: - - init: yarn install && yarn run build - command: yarn run start + - init: yarn + command: yarn dev diff --git a/package.json b/package.json index bde31bc..0b1860f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^3.6.0", + "easymde": "^2.20.0", "embla-carousel-react": "^8.3.0", "framer-motion": "^12.4.7", "input-otp": "^1.2.4", @@ -59,9 +60,11 @@ "react-hook-form": "^7.54.2", "react-markdown": "^10.1.0", "react-resizable-panels": "^2.1.3", + "react-simplemde-editor": "^5.2.0", "react-syntax-highlighter": "^15.6.1", "recharts": "^2.12.7", "remark-gfm": "^4.0.1", + "simplemde": "^1.11.2", "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", diff --git a/src/components/Admin.tsx b/src/components/Admin.tsx new file mode 100644 index 0000000..dab8b49 --- /dev/null +++ b/src/components/Admin.tsx @@ -0,0 +1,160 @@ +import { useState } from "react"; +import { motion } from "framer-motion"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { useToast } from "@/hooks/use-toast"; +import Link from "next/link"; +import { Home, FileText, Calendar, Users, ExternalLink } from "lucide-react"; +import HeroEditor from "@/components/admin/HeroEditor"; +import ProjectsEditor from "@/components/admin/ProjectsEditor"; +import ActivitiesEditor from "@/components/admin/ActivitiesEditor"; +import EventsEditor from "@/components/admin/EventsEditor"; +import TeamEditor from "@/components/admin/TeamEditor"; +import { handleLogout } from "@/lib/utils"; +import PageLayout from "./layout/PageLayout"; + +const Admin = () => { + const { toast } = useToast(); + const [activeTab, setActiveTab] = useState("hero"); + + return ( + +
+ +
+
+

Admin Dashboard

+

Manage website content

+
+ + +
+ + +
+ + + Hero Section + + + Projects + + + Activities + + + Events + + + Team + + +
+ + + + + {activeTab === "hero" && "Hero Section Editor"} + {activeTab === "projects" && "Projects Editor"} + {activeTab === "activities" && "Activities Editor"} + {activeTab === "events" && "Events Editor"} + {activeTab === "team" && "Team Members Editor"} + + + {activeTab === "hero" && "Edit the main heading, text and background images"} + {activeTab === "projects" && "Add, edit or remove projects"} + {activeTab === "activities" && "Manage robotics activities and workshops"} + {activeTab === "events" && "Add or edit upcoming events and competitions"} + {activeTab === "team" && "Manage team members and their information"} + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Preview Pages

+
+ + +

Activities Page

+

View all robotics activities and workshops.

+ +
+
+ + + +

Events Page

+

View upcoming events and competitions.

+ +
+
+ + + +

Team Page

+

View all team members and their profiles.

+ +
+
+
+
+
+
+
+ ); +}; + +export default Admin; diff --git a/src/components/Blob.tsx b/src/components/Blob.tsx new file mode 100644 index 0000000..52c3573 --- /dev/null +++ b/src/components/Blob.tsx @@ -0,0 +1,76 @@ +import React, { SetStateAction, useRef, useState } from 'react' +import { useToast } from './ui/use-toast'; +import { Button } from './ui/button'; +import { Project } from '@/types'; + +const Blob = ({ id, onChange, setFileName }: { id: string, onChange: (value: SetStateAction) => void, setFileName: (value: SetStateAction) => void }) => { + const [isLoading, setIsLoading] = useState(false); + const fileInputRef = useRef(null); + const { toast } = useToast(); + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + // Check if the file is an image + if (!file.type.match('image.*')) { + toast({ + title: "Invalid file type", + description: "Please select an image file", + variant: "destructive", + }); + return; + } + + setIsLoading(true); + const reader = new FileReader(); + + reader.onload = (e) => { + const result = e.target?.result as string; + onChange(prev => ({ ...prev, [id]: result })); + setFileName(file.name); + setIsLoading(false); + toast({ + title: "Success!", + description: "Image successfully converted to base64", + }); + }; + + reader.onerror = () => { + setIsLoading(false); + toast({ + title: "Error", + description: "Failed to read the image file", + variant: "destructive", + }); + }; + + reader.readAsDataURL(file); + }; + + const handleButtonClick = () => { + fileInputRef.current?.click(); + }; + + return ( +
+ + + +
+ ) +} + +export default Blob; \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx deleted file mode 100644 index e149004..0000000 --- a/src/components/Footer.tsx +++ /dev/null @@ -1,165 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { Github, Instagram, Linkedin, Mail, MapPin, Phone, Clock, BookOpen, Users, Calendar, ChevronRight } from "lucide-react"; -import { Button } from "@/components/ui/button"; - -const Footer = () => { - return ( -
-
-
- {/* About Section */} -
-

PEC Robotics

-

- Pushing the boundaries of innovation through robotics and automation at Punjab Engineering College, Chandigarh. -

- -
- - {/* Quick Links Section */} -
-

Quick Links

-
    -
  • - - - Projects - -
  • -
  • - - - Activities - -
  • -
  • - - - Events - -
  • -
  • - - - Contact - -
  • -
  • - - - Docify - -
  • -
  • - - - Previous Year Questions - -
  • -
-
- - {/* Resources Section */} -
-

Resources

-
    -
  • - - - Workshops - -
  • -
  • - - - Our Team - -
  • -
  • - - - Upcoming Events - -
  • -
-
- - {/* Contact Info Section */} -
-

Contact Info

-
    -
  • - - - Punjab Engineering College, Sector 12, Chandigarh, 160012 - -
  • - {/*
  • - - +91 123-456-7890 -
  • */} -
  • - - robotics@pec.edu.in -
  • -
  • - - Mon - Fri: 9:00 AM - 5:00 PM -
  • -
-
-
- - {/* Newsletter Subscription */} - {/*
-
-

Subscribe to Our Newsletter

-

- Stay updated with our latest projects and events -

-
e.preventDefault()}> - - -
-
-
*/} - - {/* Copyright */} -
-
-

- © {new Date().getFullYear()} PEC Robotics Society. All rights reserved. -

-
- Privacy Policy - Terms of Service -
-
-
-
-
- ); -}; - -export default Footer; diff --git a/src/components/FormField.tsx b/src/components/FormField.tsx new file mode 100644 index 0000000..8b97393 --- /dev/null +++ b/src/components/FormField.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { Label } from './ui/label' +import { Input } from './ui/input' +import { FormFieldProps } from '@/types' +import MarkdownEditor from './admin/MarkdownEditor' +import Blob from './Blob' + +const FormField = ({ htmlFor, title, id, onChange, placeholder, value, type, setFileName }: FormFieldProps) => { + switch (type) { + case "TEXT": + return ( +
+ + onChange(prev => ({ ...prev, [id]: e.target.value }))} + placeholder={placeholder} + /> +
+ ) + case "IMAGE": + return ( +
+ + +
+ ) + case "MARKDOWN": + return ( +
+ + onChange(prev => ({ ...prev, [id]: value }))} + placeholder={placeholder} + minHeight="200px" + /> +
+ ) + default: + return ( +
+ + onChange(prev => ({ ...prev, image: e.target.value }))} + placeholder={placeholder} + /> +
+ ) + + } +} + +export default FormField \ No newline at end of file diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index d7a3306..3cee335 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from "framer-motion"; import { Button } from "@/components/ui/button"; import { ChevronRight } from "lucide-react"; import { getImagesFromFolder } from "@/lib/supabase/actions/storage.actions"; -import { Loader } from "./Loader"; +import { Loader } from "./layout/Loader"; const Hero: React.FC<{ handleClick: () => void }> = ({ handleClick }) => { const [currentIndex, setCurrentIndex] = useState(0); @@ -29,81 +29,73 @@ const Hero: React.FC<{ handleClick: () => void }> = ({ handleClick }) => { }, [images]); return ( - <> - {loading ? ( -
- - Loading... + +
+ {/* Full-width image slider */} +
+ + +
+ +
- ) : ( -
- {/* Full-width image slider */} -
- - -
- - -
- - {/* Overlay content */} -
-
- -

- 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" + /> +
+ +
+ +