diff --git a/frontend/src/Add_Hackathon.jsx b/frontend/src/Add_Hackathon.jsx new file mode 100644 index 0000000..bd4227a --- /dev/null +++ b/frontend/src/Add_Hackathon.jsx @@ -0,0 +1,7 @@ + + +const Add_hackathon = () => { + +} + +export default Add_hackathon \ No newline at end of file diff --git a/frontend/src/Dashboard.jsx b/frontend/src/Dashboard.jsx index 88ab1eb..992d1ae 100644 --- a/frontend/src/Dashboard.jsx +++ b/frontend/src/Dashboard.jsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'; const Dashboard = () => { const [skills, setSkills] = useState([]); + const [hackathons, setHackathons] = useState([]); useEffect(() => { const fetchSkills = async () => { @@ -15,7 +16,17 @@ const Dashboard = () => { } }; + const fetchHackathons = async () => { + try { + const response = await Axios.get('http://localhost:3000/hackathon'); + setHackathons(response.data); + } catch (error) { + console.error('Error fetching hackathons:', error); + } + }; + fetchSkills(); + fetchHackathons(); }, []); const createSkill = async (skillName) => { @@ -72,6 +83,32 @@ const Dashboard = () => { } }; + const createHackathon = async (hackathonData) => { + try { + await Axios.post('http://localhost:3000/hackathon/create', hackathonData); + + Swal.fire({ + title: "Good job!", + text: `${hackathonData.name} created successfully!`, + icon: "success" + }); + setTimeout(() => { + Swal.close(); + }, 3000); + + // Refresh hackathons list + const response = await Axios.get('http://localhost:3000/hackathon'); + setHackathons(response.data); + } catch (error) { + console.error(`Error creating ${hackathonData.name}:`, error); + Swal.fire({ + title: "Error", + text: `There was an error creating ${hackathonData.name}.`, + icon: "error" + }); + } + }; + const handleAddSkill = async () => { const { value: skillName } = await Swal.fire({ title: 'Enter Skill Name', @@ -90,6 +127,35 @@ const Dashboard = () => { } }; + const handleAddHackathon = async () => { + const { value: formValues } = await Swal.fire({ + title: 'Enter Hackathon Details', + html: + '<input id="swal-input1" class="swal2-input" placeholder="Name">' + + '<input id="swal-input2" class="swal2-input" placeholder="Description">' + + '<input id="swal-input3" class="swal2-input" placeholder="Location">' + + '<input id="swal-input4" class="swal2-input" type="date" placeholder="Start Date">' + + '<input id="swal-input5" class="swal2-input" type="date" placeholder="End Date">' + + '<input id="swal-input6" class="swal2-input" placeholder="Image URL">', + focusConfirm: false, + showCancelButton: true, + preConfirm: () => { + return { + name: document.getElementById('swal-input1').value, + description: document.getElementById('swal-input2').value, + location: document.getElementById('swal-input3').value, + startDate: document.getElementById('swal-input4').value, + endDate: document.getElementById('swal-input5').value, + hackathonImage: document.getElementById('swal-input6').value, + }; + } + }); + + if (formValues) { + createHackathon(formValues); + } + }; + const all_user = () => { window.location.href = '/AllUsers'; } @@ -102,77 +168,110 @@ const Dashboard = () => { </h1> {/* Skills Section */} - <div className="bg-white rounded-xl shadow-xl p-8 transform hover:shadow-2xl transition-all duration-300"> - <h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4 relative"> - Skill Management - <span className="absolute bottom-0 left-0 w-1/4 h-1 bg-gradient-to-r from-green-400 to-teal-500"></span> - </h2> - <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> - {skills.map((skill) => ( - <div key={skill.Skill_ID} className="relative group perspective-1000"> - <button - onClick={createSkill} - className="w-full group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-green-500 to-green-600 hover:from-green-400 hover:to-teal-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:rotate-2 hover:shadow-xl backdrop-blur-sm" - > - <span className="text-3xl mb-3 transform group-hover:scale-125 group-hover:rotate-12 transition-transform duration-300"> - {skill.Skill_Name.toLowerCase().includes('python') ? '🐍' : - skill.Skill_Name.toLowerCase().includes('java') ? '☕' : - skill.Skill_Name.toLowerCase().includes('react') ? '⚛️' : - skill.Skill_Name.toLowerCase().includes('node') ? '📦' : - skill.Skill_Name.toLowerCase().includes('sql') ? '🗄️' : - skill.Skill_Name.toLowerCase().includes('html') ? '🌐' : - skill.Skill_Name.toLowerCase().includes('css') ? '🎨' : - skill.Skill_Name.toLowerCase().includes('javascript') ? '💛' : - skill.Skill_Name.toLowerCase().includes('analytics skills') ? '📊' : - skill.Skill_Name.toLowerCase().includes('machine learning') ? '🤖' : - skill.Skill_Name.toLowerCase().includes('data science') ? '📈' : - skill.Skill_Name.toLowerCase().includes('cloud') ? '☁️' : - skill.Skill_Name.toLowerCase().includes('security') ? '🔐' : - skill.Skill_Name.toLowerCase().includes('devops') ? '🚢' : - skill.Skill_Name.toLowerCase().includes('blockchain') ? '⛓️' : - skill.Skill_Name.toLowerCase().includes('ai') ? '🧠' : - skill.Skill_Name.toLowerCase().includes('mobile') ? '📱' : - skill.Skill_Name.toLowerCase().includes('network') ? '🌐' : - skill.Skill_Name.toLowerCase().includes('testing') ? '🧪' : - skill.Skill_Name.toLowerCase().includes('web') ? '🌐' : - skill.Skill_Name.toLowerCase().includes('design') ? '🎨' : - skill.Skill_Name.toLowerCase().includes('problem solving') ? '🧩' : - skill.Skill_Name.toLowerCase().includes('team collaboration ') ? '🤝' : - skill.Skill_Name.toLowerCase().includes('code review ') ? '🔍' : - skill.Skill_Name.toLowerCase().includes('communication') ? '💬' : - skill.Skill_Name.toLowerCase().includes('mentoring ') ? '🧑🏫' : - skill.Skill_Name.toLowerCase().includes('time management ') ? '🧑🏫' : - skill.Skill_Name.toLowerCase().includes('continuous learning ') ? '📚' : - skill.Skill_Name.toLowerCase().includes('community ') ? '🤝' : - '💡'} - </span> - <span className="font-semibold tracking-wide group-hover:text-green-100"> - {skill.Skill_Name} - </span> - </button> - <button - onClick={() => deleteSkill(skill.Skill_ID)} - className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transform scale-75 group-hover:scale-100 transition-all duration-300 hover:bg-red-600 hover:rotate-90 shadow-lg" - > - <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> - <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> - </svg> - </button> - </div> - ))} - <button - onClick={handleAddSkill} - className="group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-teal-500 to-teal-600 hover:from-teal-400 hover:to-green-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:-rotate-2 hover:shadow-xl" - > - <span className="text-3xl mb-3 transform group-hover:rotate-180 transition-transform duration-500">➕</span> - <span className="font-semibold tracking-wide group-hover:text-teal-100"> - Add more skill - </span> - </button> - </div> - </div> - - {/* Users Section */} + <div className="bg-white rounded-xl shadow-xl p-8 transform hover:shadow-2xl transition-all duration-300"> + <h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4 relative"> + Skill Management + <span className="absolute bottom-0 left-0 w-1/4 h-1 bg-gradient-to-r from-green-400 to-teal-500"></span> + </h2> + <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> + {skills.map((skill) => ( + <div key={skill.Skill_ID} className="relative group perspective-1000"> + <button + onClick={createSkill} + className="w-full group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-green-500 to-green-600 hover:from-green-400 hover:to-teal-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:rotate-2 hover:shadow-xl backdrop-blur-sm" + > + <span className="text-3xl mb-3 transform group-hover:scale-125 group-hover:rotate-12 transition-transform duration-300"> + {skill.Skill_Name.toLowerCase().includes('python') ? '🐍' : + skill.Skill_Name.toLowerCase().includes('java') ? '☕' : + skill.Skill_Name.toLowerCase().includes('react') ? '⚛️' : + skill.Skill_Name.toLowerCase().includes('node') ? '📦' : + skill.Skill_Name.toLowerCase().includes('sql') ? '🗄️' : + skill.Skill_Name.toLowerCase().includes('html') ? '🌐' : + skill.Skill_Name.toLowerCase().includes('css') ? '🎨' : + skill.Skill_Name.toLowerCase().includes('javascript') ? '💛' : + skill.Skill_Name.toLowerCase().includes('analytics skills') ? '📊' : + skill.Skill_Name.toLowerCase().includes('machine learning') ? '🤖' : + skill.Skill_Name.toLowerCase().includes('data science') ? '📈' : + skill.Skill_Name.toLowerCase().includes('cloud') ? '☁️' : + skill.Skill_Name.toLowerCase().includes('security') ? '🔐' : + skill.Skill_Name.toLowerCase().includes('devops') ? '🚢' : + skill.Skill_Name.toLowerCase().includes('blockchain') ? '⛓️' : + skill.Skill_Name.toLowerCase().includes('ai') ? '🧠' : + skill.Skill_Name.toLowerCase().includes('mobile') ? '📱' : + skill.Skill_Name.toLowerCase().includes('network') ? '🌐' : + skill.Skill_Name.toLowerCase().includes('testing') ? '🧪' : + skill.Skill_Name.toLowerCase().includes('web') ? '🌐' : + skill.Skill_Name.toLowerCase().includes('design') ? '🎨' : + skill.Skill_Name.toLowerCase().includes('problem solving') ? '🧩' : + skill.Skill_Name.toLowerCase().includes('team collaboration ') ? '🤝' : + skill.Skill_Name.toLowerCase().includes('code review ') ? '🔍' : + skill.Skill_Name.toLowerCase().includes('communication') ? '💬' : + skill.Skill_Name.toLowerCase().includes('mentoring ') ? '🧑🏫' : + skill.Skill_Name.toLowerCase().includes('time management ') ? '🧑🏫' : + skill.Skill_Name.toLowerCase().includes('continuous learning ') ? '📚' : + skill.Skill_Name.toLowerCase().includes('community ') ? '🤝' : + '💡'} + </span> + <span className="font-semibold tracking-wide group-hover:text-green-100"> + {skill.Skill_Name} + </span> + </button> + <button + onClick={() => deleteSkill(skill.Skill_ID)} + className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 transform scale-75 group-hover:scale-100 transition-all duration-300 hover:bg-red-600 hover:rotate-90 shadow-lg" + > + <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" /> + </svg> + </button> + </div> + ))} + <button + onClick={handleAddSkill} + className="group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-teal-500 to-teal-600 hover:from-teal-400 hover:to-green-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:-rotate-2 hover:shadow-xl" + > + <span className="text-3xl mb-3 transform group-hover:rotate-180 transition-transform duration-500">➕</span> + <span className="font-semibold tracking-wide group-hover:text-teal-100"> + Add more skill + </span> + </button> + </div> + </div> + + {/* Hackathons Section */} + <div className="bg-white rounded-xl shadow-xl p-8 transform hover:shadow-2xl transition-all duration-300"> + <h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4 relative"> + Hackathon Management + <span className="absolute bottom-0 left-0 w-1/4 h-1 bg-gradient-to-r from-blue-400 to-indigo-500"></span> + </h2> + <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> + {hackathons.map((hackathon) => ( + <div key={hackathon.HackathonID} className="relative group perspective-1000"> + <button + className="w-full group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-blue-500 to-indigo-600 hover:from-blue-400 hover:to-indigo-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:rotate-2 hover:shadow-xl backdrop-blur-sm" + > + <span className="text-3xl mb-3 transform group-hover:scale-125 group-hover:rotate-12 transition-transform duration-300"> + 🏆 + </span> + <span className="font-semibold tracking-wide group-hover:text-blue-100"> + {hackathon.Name} + </span> + </button> + </div> + ))} + <button + onClick={handleAddHackathon} + className="group flex flex-col items-center justify-center p-6 bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-400 hover:to-blue-500 text-white rounded-xl transition-all duration-500 transform hover:scale-110 hover:-rotate-2 hover:shadow-xl" + > + <span className="text-3xl mb-3 transform group-hover:rotate-180 transition-transform duration-500">➕</span> + <span className="font-semibold tracking-wide group-hover:text-indigo-100"> + Add more hackathon + </span> + </button> + </div> + </div> + + {/* Users Section */} <div className="bg-white rounded-xl shadow-xl p-8"> <h2 className="text-2xl font-semibold mb-6 text-gray-800 border-b pb-4"> User Management diff --git a/frontend/src/Swipe.jsx b/frontend/src/Swipe.jsx index 5c487d6..4a45441 100644 --- a/frontend/src/Swipe.jsx +++ b/frontend/src/Swipe.jsx @@ -1,37 +1,9 @@ -import { useState, useEffect, useRef } from 'react'; -import { X, Heart, MapPin, ChevronLeft } from 'lucide-react'; +import { useState, useEffect, useRef, useCallback } from 'react'; +import { MapPin, ChevronLeft, X, Heart } from 'lucide-react'; +import Axios from 'axios'; const Swipe = () => { - const [profiles] = useState([ - { - name: 'น้องหงหยกหงไทย', - age: 99, - location: 'กรุงเทพมหานคร', - skills: ['Skill1', 'Skill2', 'Skill3'], - bio: 'ประวัติการเข้าร่วม', - imgUrl: 'https://cdn.pixabay.com/photo/2016/11/29/13/14/attractive-1869761_1280.jpg', - iconUrl: '/api/placeholder/50/50', - }, - { - name: 'John Doe', - age: 28, - location: 'Bangkok', - skills: ['JavaScript', 'React', 'Node.js'], - bio: 'Experienced developer looking to contribute to hackathons!', - imgUrl: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTvi7HpQ-_PMSMOFrj1hwjp6LDcI-jm3Ro0Xw&s', - iconUrl: '/api/placeholder/50/50', - }, - { - name: 'Jane Smith', - age: 25, - location: 'Chiang Mai', - skills: ['Python', 'Machine Learning', 'Data Science'], - bio: 'Data scientist eager to bring AI solutions to life.', - imgUrl: '/api/placeholder/400/400', - iconUrl: '/api/placeholder/50/50', - }, - ]); - + const [profiles, setProfiles] = useState([]); const [currentProfileIndex, setCurrentProfileIndex] = useState(0); const [likedProfiles, setLikedProfiles] = useState([]); const [rejectedProfiles, setRejectedProfiles] = useState([]); @@ -40,6 +12,20 @@ const Swipe = () => { const [isDragging, setIsDragging] = useState(false); const cardRef = useRef(null); + useEffect(() => { + const fetchProfiles = async () => { + try { + const userID = localStorage.getItem('UserID'); + const response = await Axios.get(`http://localhost:3000/swipe/${userID}`); + setProfiles(response.data); + } catch (error) { + console.error('Error fetching profiles:', error); + } + }; + + fetchProfiles(); + }, []); + const currentProfile = profiles[currentProfileIndex]; const handleDragStart = (e) => { @@ -49,7 +35,7 @@ const Swipe = () => { setDragStart({ x: clientX, y: clientY }); }; - const handleDragMove = (e) => { + const handleDragMove = useCallback((e) => { if (!isDragging) return; const clientX = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX; const clientY = e.type === 'mousemove' ? e.clientY : e.touches[0].clientY; @@ -58,9 +44,31 @@ const Swipe = () => { y: clientY - dragStart.y }; setDragDelta(delta); - }; + }, [isDragging, dragStart]); + + const handleLike = useCallback(async () => { + const userID = localStorage.getItem('UserID'); + await Axios.post('http://localhost:3000/swipe/action', { + userID, + swipedUserID: currentProfile.UserID, + swipeAction: 'Like' + }); + setLikedProfiles([...likedProfiles, currentProfile]); + setTimeout(showNextProfile, 300); + }, [currentProfile, likedProfiles]); + + const handleReject = useCallback(async () => { + const userID = localStorage.getItem('UserID'); + await Axios.post('http://localhost:3000/swipe/action', { + userID, + swipedUserID: currentProfile.UserID, + swipeAction: 'Dislike' + }); + setRejectedProfiles([...rejectedProfiles, currentProfile]); + setTimeout(showNextProfile, 300); + }, [currentProfile, rejectedProfiles]); - const handleDragEnd = () => { + const handleDragEnd = useCallback(() => { if (!isDragging) return; const threshold = window.innerWidth * 0.3; if (dragDelta.x > threshold) { @@ -71,25 +79,17 @@ const Swipe = () => { setDragDelta({ x: 0, y: 0 }); } setIsDragging(false); - }; + }, [isDragging, dragDelta, handleLike, handleReject]); const showNextProfile = () => { setDragDelta({ x: 0, y: 0 }); if (currentProfileIndex < profiles.length - 1) { setCurrentProfileIndex(currentProfileIndex + 1); + } else { + setProfiles([]); } }; - const handleLike = () => { - setLikedProfiles([...likedProfiles, currentProfile]); - setTimeout(showNextProfile, 300); - }; - - const handleReject = () => { - setRejectedProfiles([...rejectedProfiles, currentProfile]); - setTimeout(showNextProfile, 300); - }; - useEffect(() => { const card = cardRef.current; if (card) { @@ -106,11 +106,11 @@ const Swipe = () => { card.removeEventListener('touchend', dragEndHandler); }; } - }, [isDragging, dragStart]); + }, [isDragging, dragStart, handleDragEnd, handleDragMove]); - const rotation = (dragDelta.x / window.innerWidth) * 45; const opacity = Math.max(1 - Math.abs(dragDelta.x) / (window.innerWidth / 2), 0); const scale = Math.max(1 - Math.abs(dragDelta.x) / (window.innerWidth * 2), 0.9); + const rotation = dragDelta.x / 20; return ( <div className="min-h-screen bg-gray-100"> @@ -144,7 +144,7 @@ const Swipe = () => { {/* Profile Image */} <div className="relative h-96"> <img - src={currentProfile.imgUrl} + src={currentProfile.ProfileImage} alt="profile" className="w-full h-full object-cover" /> @@ -153,27 +153,27 @@ const Swipe = () => { {/* Profile Info */} <div className="p-4"> <div className="flex items-center gap-2 mb-2"> - <h2 className="text-xl font-semibold">{currentProfile.name}</h2> - <span className="text-gray-600">{currentProfile.age}</span> + <h2 className="text-xl font-semibold">{currentProfile.UserName}</h2> + <span className="text-gray-600">{currentProfile.Age}</span> </div> <div className="flex items-center text-gray-600 mb-2"> <MapPin size={16} className="mr-1" /> - <span className="text-sm">{currentProfile.location}</span> + <span className="text-sm">{currentProfile.Location}</span> </div> <div className="flex flex-wrap gap-2 mb-3"> - {currentProfile.skills.map((skill, index) => ( + {currentProfile.UserSkills.map((skill, index) => ( <span key={index} className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm" > - {skill} + {skill.Skill.Skill_Name} </span> ))} </div> - <p className="text-gray-600">{currentProfile.bio}</p> + <p className="text-gray-600">{currentProfile.Bio}</p> </div> {/* Like/Nope Indicators */} @@ -214,7 +214,7 @@ const Swipe = () => { ) : ( <div className="text-center p-8 bg-white rounded-xl shadow-lg"> <h2 className="text-xl font-bold text-gray-800 mb-3">No more profiles!</h2> - <p className="text-gray-600">You ve seen all available profiles.</p> + <p className="text-gray-600">Youve seen all available profiles.</p> </div> )} </div> @@ -222,4 +222,4 @@ const Swipe = () => { ); }; -export default Swipe; \ No newline at end of file +export default Swipe; \ No newline at end of file diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index 1828fcb..ad8bf07 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -50,8 +50,8 @@ function Navbar() { const markAsRead = async (notificationID) => { try { - const userID = localStorage.getItem('UserID'); - await axios.put(`http://localhost:3000/noti/${userID}/unread`, { ReadStatus: true }); + // const userID = localStorage.getItem('UserID'); + await axios.put(`http://localhost:3000/noti/${notificationID}`, { ReadStatus: true }); setNotifications((prev) => prev.filter((notification) => notification.NotificationID !== notificationID) ); @@ -146,15 +146,12 @@ function Navbar() { <a href="/profile" className="text-white hover:text-purple-200 transition duration-300 font-medium"> Profile </a> - <a href="/EventDetail" className="text-white hover:text-purple-200 transition duration-300 font-medium"> - Event hackathon - </a> <a href="/hackathon" className="text-white hover:text-purple-200 transition duration-300 font-medium"> Hackathon </a> <a href="/Rating" className="text-white hover:text-purple-200 transition duration-300 font-medium"> Rating - </a> + </a> <a href="/swipe" className="text-white hover:text-purple-200 transition duration-300 font-medium"> Match </a> @@ -205,6 +202,45 @@ function Navbar() { </> ) : ( <> + + <a href="/profile" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> + Profile + </a> + <a href="/EventDetail" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> + Event hackathon + </a> + <a href="/hackathon" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> + Hackathon + </a> + <a href="/swipe" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> + Match + </a> + {isAdmin && ( + <a href="/dashboard" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> + Dashboard + </a> + )} + + <a href="/hackathon" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + รวม hackathon + </a> + <a href="/EventDetail" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + กิจกรรม hackathon + </a> + <a href="/Rating" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + Rating + </a> + <a href="/Personal" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + personal Type + </a> + <a href="/swipe" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + จับคู่ + </a> + <a href="/profile" className="text-white hover:text-purple-200 transition duration-300 font-medium"> + โปรไฟล์ + </a> + + <a href="/profile" className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300"> Profile </a> @@ -222,6 +258,7 @@ function Navbar() { Dashboard </a> )} + <button onClick={handleLogout} className="text-white hover:bg-white/10 px-4 py-2 rounded-lg transition duration-300" diff --git a/frontend/src/hackathon.css b/frontend/src/hackathon.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/hackathon.jsx b/frontend/src/hackathon.jsx index 2f00cdf..00ae521 100644 --- a/frontend/src/hackathon.jsx +++ b/frontend/src/hackathon.jsx @@ -1,16 +1,12 @@ -import Axios from 'axios' +import Axios from 'axios'; import { Link } from 'react-router-dom'; -import { useEffect } from 'react'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; const Hackathon = () => { const [Hackathonlist, setHackathonlist] = useState([]); - - const getHackathon = async () => { try { - const response = await Axios.get('http://localhost:3000/hackathon'); const data = response.data.map(hackathon => ({ HackathonID: hackathon.HackathonID, @@ -28,11 +24,11 @@ const Hackathon = () => { useEffect(() => { getHackathon(); - }, []); + }, []); return ( <div> - {/* หัวรอใส่รูป */} + {/* Header */} <div className="bg-gray-700 h-64 flex items-center justify-center"> <div className="container mx-auto text-center mt-20"> <h1 className="text-3xl font-bold text-white"> @@ -44,14 +40,17 @@ const Hackathon = () => { </div> </div> - {/* เนื้อหา */} + {/* Content */} <div className="container mx-auto py-6"> - <h2 className="text-2xl font-semibold text-white mb-4"> + <h2 className="text-2xl font-semibold text-white mb-4 text-center"> กิจกรรมแนะนำ </h2> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> {Hackathonlist.map((hackathon) => ( - <div key={hackathon.HackathonID} className="bg-gray-800 rounded shadow overflow-hidden"> + <div + key={hackathon.HackathonID} + className="bg-gray-800 rounded shadow overflow-hidden transform transition-transform duration-300 hover:scale-105" + > <img src={hackathon.HackathonImage} alt={hackathon.Name} @@ -59,22 +58,20 @@ const Hackathon = () => { /> <div className="p-4"> <h3 className="text-3xl font-bold text-white">{hackathon.Name}</h3> - <p className="text-lg text-gray-400">เริ่มรับสมัคร {hackathon.StartDate}</p> - - <p className="text-lg text-gray-400">สิ้นสุดรับสมัคร {hackathon.EndDate}</p> - - <Link to={`/EventDetail/${hackathon.HackathonID}`}className="text-black hover:underline"> - <button className="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"> - ดูรายละเอียด - </button> - </Link> - </div> + <p className="text-lg text-gray-400">เริ่มรับสมัคร {hackathon.StartDate}</p> + <p className="text-lg text-gray-400">สิ้นสุดรับสมัคร {hackathon.EndDate}</p> + <Link to={`/EventDetail/${hackathon.HackathonID}`} className="text-black hover:underline"> + <button className="mt-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transform transition-transform duration-300 hover:scale-110"> + ดูรายละเอียด + </button> + </Link> </div> - ))} + </div> + ))} </div> </div> </div> ); }; -export default Hackathon; \ No newline at end of file +export default Hackathon; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index af4639f..012a378 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -12,14 +12,15 @@ import Swipe from "./Swipe"; import Register from "./Register"; import LoginForm from "./LoginForm"; import Profile from "./profile"; -import EventDetail from './EventDetail'; +import EventDetail from "./EventDetail"; +import Rating from "./Rating"; import Dashboard from "./Dashboard"; import Hackathon from "./hackathon"; import AllUsers from "./all_user"; import Message from "./message"; import About_us from "./about_us"; -import Rating from "./Rating"; +import Add_hackathon from "./Add_Hackathon" const router = createBrowserRouter([ { @@ -143,11 +144,11 @@ const router = createBrowserRouter([ errorElement: <ErrorPage /> }, { - path: "/rating", + path: "/Add_hackathon", element: ( <> <Navbar /> - <Rating /> + <Add_hackathon /> </> ), errorElement: <ErrorPage /> diff --git a/frontend/src/profile.jsx b/frontend/src/profile.jsx index 50b894a..660f8b8 100644 --- a/frontend/src/profile.jsx +++ b/frontend/src/profile.jsx @@ -4,7 +4,7 @@ import { MdEmail } from "react-icons/md"; import Axios from 'axios'; import PropTypes from 'prop-types'; import Swal from 'sweetalert2' -import image from '../../public/uploads/profiles/5psgeommk-b1715b66-2c24-49ee-b16e-37ab0ba43ea0.jpeg' +import image from '../../public/uploads/profiles/5psgeommk-b1715b66-2c24-49ee-b16e-37ab0ba43ea0.jpeg' // EditProfileModal component const EditProfileModal = ({ isOpen, onClose, onSave, user }) => { diff --git a/src/controllers/hackathonController.ts b/src/controllers/hackathonController.ts index 7575702..eeecb63 100644 --- a/src/controllers/hackathonController.ts +++ b/src/controllers/hackathonController.ts @@ -35,7 +35,7 @@ export const hackathonController = new Elysia({ prefix: "/hackathon" }) // Create a new hackathon .post( "/create", - async ({ body: { name, description, location, startDate, endDate }, error }) => { + async ({ body: { name, description, location, startDate, endDate, hackathonImage }, error }) => { // Validate the input if (!name || !description || !location || !startDate || !endDate) { return error(400, "Name and description are required"); @@ -47,7 +47,8 @@ export const hackathonController = new Elysia({ prefix: "/hackathon" }) Description: description, StartDate: startDate, EndDate: endDate, - Location: location, + Location: location, + HackathonImage: hackathonImage, }, }); @@ -60,6 +61,7 @@ export const hackathonController = new Elysia({ prefix: "/hackathon" }) location: t.String(), startDate: t.String(), endDate: t.String(), + hackathonImage: t.String(), }), } ) diff --git a/src/controllers/swipeController.ts b/src/controllers/swipeController.ts index e719968..7e598ea 100644 --- a/src/controllers/swipeController.ts +++ b/src/controllers/swipeController.ts @@ -4,31 +4,31 @@ import { prisma } from "../prisma"; // Controller for handling swipe-related routes export const swipeController = new Elysia({ prefix: "/swipe" }) - // Fetch a profile to swipe on + // Fetch all profiles to swipe on except the current user .get( "/:userID", async ({ params: { userID }, error }) => { - // Fetch a user that the current user hasn't swiped on yet - const potentialMatch = await prisma.user.findFirst({ + // Fetch all users except the current user + const potentialMatches = await prisma.user.findMany({ where: { - // Avoid users the current user has already swiped on - SwipesReceived: { - none: { - SwipingUserID: userID, - }, - }, UserID: { not: userID, // Exclude the current user }, }, - // You can add more complex logic here for filtering based on working style, location, etc. + include: { + UserSkills: { + include: { + Skill: true, + }, + }, + }, }); - if (!potentialMatch) { + if (potentialMatches.length === 0) { return error(404, "No more profiles available to swipe"); } - return potentialMatch; // Return the profile for swiping + return potentialMatches; // Return the profiles for swiping }, { params: t.Object({ @@ -78,7 +78,7 @@ export const swipeController = new Elysia({ prefix: "/swipe" }) NotificationContent: `You have a new match with ${userID}!`, }, ], - }) + }); await prisma.message.createMany({ data: [ @@ -114,4 +114,4 @@ export const swipeController = new Elysia({ prefix: "/swipe" }) swipeAction: t.String(), // "Like" or "Dislike" }), } - ); + ); \ No newline at end of file