diff --git a/.env b/.env index 53e3e47..2589e4b 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ PORT = 3000 -DATABASE_URL="mysql://root:@localhost:3307/hack_db" +DATABASE_URL="mysql://root:@localhost:3306/hack_db" # --- For Docker-compose # DATABASE_URL="mysql://root:root@localhost:3307/hack_db" \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index a67c7a0..fd32bba 100644 Binary files a/bun.lockb and b/bun.lockb differ 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: + '' + + '' + + '' + + '' + + '' + + '', + 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 = () => { {/* Skills Section */} -
-

- Skill Management - -

-
- {skills.map((skill) => ( -
- - -
- ))} - -
-
- - {/* Users Section */} +
+

+ Skill Management + +

+
+ {skills.map((skill) => ( +
+ + +
+ ))} + +
+
+ + {/* Hackathons Section */} +
+

+ Hackathon Management + +

+
+ {hackathons.map((hackathon) => ( +
+ +
+ ))} + +
+
+ + {/* Users Section */}

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 (
@@ -144,7 +144,7 @@ const Swipe = () => { {/* Profile Image */}
profile @@ -153,27 +153,27 @@ const Swipe = () => { {/* Profile Info */}
-

{currentProfile.name}

- {currentProfile.age} +

{currentProfile.UserName}

+ {currentProfile.Age}
- {currentProfile.location} + {currentProfile.Location}
- {currentProfile.skills.map((skill, index) => ( + {currentProfile.UserSkills.map((skill, index) => ( - {skill} + {skill.Skill.Skill_Name} ))}
-

{currentProfile.bio}

+

{currentProfile.Bio}

{/* Like/Nope Indicators */} @@ -214,7 +214,7 @@ const Swipe = () => { ) : (

No more profiles!

-

You ve seen all available profiles.

+

Youve seen all available profiles.

)}
@@ -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 dd5ba99..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() { Profile - - Event hackathon - Hackathon Rating - + Match @@ -223,7 +220,7 @@ function Navbar() { Dashboard )} -========= + รวม hackathon @@ -243,6 +240,25 @@ function Navbar() { โปรไฟล์ + + + Profile + + + Event hackathon + + + Hackathon + + + Match + + {isAdmin && ( + + Dashboard + + )} +

Team {val.TeamName}

- +
+ + +
))} + {/* leave team popup */} + {isleaveTeamModalOpen[0] == true && ( +
+
+

Show member

+ + {member.filter(val => val.userID !== UserID).map((val,key) => ( +
+
+

{val.teamID}

+

UserID: {val.userID}

+

Username: {val.userName}

+
+ {currentUserRole === 'head' && ( + + )} + +
+ ))} + + {/* Buttons */} + +
+ + +
+
+
+ )} + + {/* jointeam popup */} {isjoinTeamModalOpen[0] == true && (
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 (
- {/* หัวรอใส่รูป */} + {/* Header */}

@@ -44,14 +40,17 @@ const Hackathon = () => {

- {/* เนื้อหา */} + {/* Content */}
-

+

กิจกรรมแนะนำ

{Hackathonlist.map((hackathon) => ( -
+
{hackathon.Name} { />

{hackathon.Name}

-

เริ่มรับสมัคร {hackathon.StartDate}

- -

สิ้นสุดรับสมัคร {hackathon.EndDate}

- - - - -
+

เริ่มรับสมัคร {hackathon.StartDate}

+

สิ้นสุดรับสมัคร {hackathon.EndDate}

+ + +
- ))} +
+ ))}
); }; -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 4ebca51..012a378 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -12,18 +12,15 @@ import Swipe from "./Swipe"; import Register from "./Register"; import LoginForm from "./LoginForm"; import Profile from "./profile"; -import EventDetail from './EventDetail'; - -import Dashboard from "./Dashboard"; - 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([ { @@ -147,11 +144,11 @@ const router = createBrowserRouter([ errorElement: }, { - path: "/rating", + path: "/Add_hackathon", element: ( <> - + ), errorElement: 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/public/uploads/profiles/2a4xlniyn-391db290-98da-45c5-b290-1f00757c26a4.png b/public/uploads/profiles/2a4xlniyn-391db290-98da-45c5-b290-1f00757c26a4.png new file mode 100644 index 0000000..5898271 Binary files /dev/null and b/public/uploads/profiles/2a4xlniyn-391db290-98da-45c5-b290-1f00757c26a4.png differ diff --git a/public/uploads/profiles/oion3wsp1-1835c8d5-3c92-4c40-b483-e419862d1e18.png b/public/uploads/profiles/oion3wsp1-1835c8d5-3c92-4c40-b483-e419862d1e18.png new file mode 100644 index 0000000..921d423 Binary files /dev/null and b/public/uploads/profiles/oion3wsp1-1835c8d5-3c92-4c40-b483-e419862d1e18.png differ 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 diff --git a/src/controllers/teamController.ts b/src/controllers/teamController.ts index be30f46..8c7883b 100644 --- a/src/controllers/teamController.ts +++ b/src/controllers/teamController.ts @@ -74,6 +74,28 @@ export const teamController = new Elysia({ prefix: "/team" }) TeamName: t.String(), }), }) +.get("/finduserteam/:teamID", async ({ params, error }) => { + const teamID = parseInt(params.teamID, 10); // Ensure teamID is a number + if (isNaN(teamID)) { + return error(400, "Invalid teamID"); + } + + const team = await prisma.userTeam.findMany({ + where: { + TeamID: teamID, + }, + }); + + if (!team || team.length === 0) { + return error(404, "Team not found"); + } + + return team; +}, { + params: t.Object({ + teamID: t.String(), // teamID is a string in the URL params + }), +}) .get("/hackathon/:id", async ({ params: { id }, error }) => { const teams = await prisma.team.findMany({ @@ -97,7 +119,7 @@ export const teamController = new Elysia({ prefix: "/team" }) .post("/create", async ({ body, error }) => { const { teamName, hackathonID, maxMember } = body; - + console.log('Creating team with:', { teamName, hackathonID, maxMember }); // Check if team and hackathonID exists const hackathon = await prisma.hackathon.findUnique({ where: { HackathonID: hackathonID },