)
@@ -200,6 +274,7 @@ const ImageContainer = styled.div`
align-items: center;
width: 100%;
height: 50%;
+
img {
width: 100%;
height: 300px;
@@ -222,7 +297,7 @@ const OverlayTextContainer = styled.div`
const OverlayTextTop = styled.div`
border-radius: 5px;
text-align: center;
-
+
h2 {
font-size: 32px;
font-weight: 700;
@@ -233,7 +308,7 @@ const OverlayTextTop = styled.div`
const OverlayTextBottom = styled.div`
border-radius: 5px;
text-align: center;
-
+
h2 {
font-size: 28px;
font-weight: 600;
@@ -246,7 +321,7 @@ const BottomAboutContainer = styled.div`
flex: 1;
flex-direction: column;
justify-content: center;
-
+
`;
const BottomCenterContainer = styled.div`
@@ -265,6 +340,7 @@ const BottomCenterContainer = styled.div`
color: orange;
font-weight: 700;
}
+
h3 {
font-size: 20px;
font-weight: 600;
@@ -312,17 +388,20 @@ const FirstFeatureContainer = styled.div`
border-radius: 8px;
text-align: center;
margin: 1rem;
+
img {
width: 20rem;
height: 15rem;
border-radius: 10rem;
margin: 0 auto;
}
+
h2 {
margin-top: 5px;
font-size: 24px;
font-weight: 700;
}
+
h3 {
font-size: 18px;
font-weight: 600;
@@ -363,14 +442,14 @@ const FlowBigContainer = styled.div`
grid-template-rows: auto 1fr auto; /* Three rows for TopText, FlowCenterContainer, and FlowButtonContainer */
width: 100%;
height: auto;
-
+
`;
const TopText = styled.div`
display: flex;
justify-content: center;
align-items: center;
-
+
h2 {
font-size: 28px;
font-weight: 700;
@@ -389,6 +468,7 @@ const FlowCenterContainer = styled.div`
margin-top: 2rem;
margin-bottom: 3rem;
/* 각 요소에 일정한 간격을 위해 마진 추가 */
+
.Image1, .Image2, .Image3, .Image4 {
margin: 10px;
}
@@ -417,7 +497,7 @@ const FlowButton = styled.button`
color: orange;
font-weight: 700;
font-size: 24px;
-
+
&:hover {
background-color: #FBCEB1;
color: #f7e8cb;
diff --git a/hansoyeon/src/Pages/AboutPolicyPage.js b/hansoyeon/src/Pages/AboutPolicyPage.js
old mode 100644
new mode 100755
index 71173cc80..8fd5a7348
--- a/hansoyeon/src/Pages/AboutPolicyPage.js
+++ b/hansoyeon/src/Pages/AboutPolicyPage.js
@@ -1,6 +1,7 @@
import React from "react";
import styled from "styled-components";
import aboutPolicy from "../imgs/aboutPolicy.jpg";
+import Footer from "../Components/Footer";
const AboutPolicyPage = () => {
return (
@@ -31,11 +32,13 @@ const AboutPolicyPage = () => {
+
)
}
@@ -45,6 +48,7 @@ const Container = styled.div`
position: absolute;
width: 100%;
height: 100vh;
+ flex-direction: column;
`
const CenterContainer = styled.div`
display: flex;
@@ -99,7 +103,6 @@ const BottomContainer = styled.div`
`
const BottomContentContainer = styled.div`
background-color: #FDF9EA;
- margin-bottom: 3rem;
width: 90%;
height: auto;
margin-top: 1rem;
diff --git a/hansoyeon/src/Pages/AdminMatchingPage.js b/hansoyeon/src/Pages/AdminMatchingPage.js
new file mode 100644
index 000000000..abac744bc
--- /dev/null
+++ b/hansoyeon/src/Pages/AdminMatchingPage.js
@@ -0,0 +1,589 @@
+import React, {useEffect, useState} from "react";
+import styled from "styled-components";
+import {useNavigate, useParams} from "react-router-dom";
+import {useCookies} from "react-cookie";
+import {useUserStore} from "../stores";
+import axios from "axios";
+import defaultProfilePic from '../imgs/default_profile.png';
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+import Footer from "../Components/Footer";
+
+const CompanyMatchingPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [announcements, setAnnouncements] = useState([]);
+
+ const [applicants, setApplicants] = useState([]);
+ const [selectedJobId, setSelectedJobId] = useState(null);
+
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
+
+ const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
+ const [selectedUser, setSelectedUser] = useState(null);
+
+ const [providerPhone, setProviderPhone] = useState('');
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ fetchJobAnnouncements();
+ }
+ }, [user]);
+
+ const fetchJobAnnouncements = async () => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/recruitments`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ console.log(response.data)
+ setAnnouncements(response.data);
+ } catch (error) {
+ console.error("Error fetching job announcements:", error);
+ }
+ };
+
+ const handleJobView = (jobId) => {
+ navigate(`/recruit/${jobId}`);
+ };
+
+ const fetchApplicants = async (jobId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/recruitments/${jobId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ if (response.data && response.data.data) {
+ console.log(response.data)
+ setApplicants(response.data.data);
+ }
+ } catch (error) {
+ console.error("Error fetching applicants:", error);
+ setApplicants([]);
+ }
+ };
+
+ const handleCheckApplicants = async (announcement) => {
+ setSelectedAnnouncement(announcement);
+ setSelectedJobId(announcement.job_id);
+ fetchApplicants(announcement.job_id);
+ setIsModalOpen(true);
+ };
+
+ const handleModalClose = () => {
+ setIsModalOpen(false);
+ };
+
+ const handleUserClick = (user) => {
+ setSelectedUser(user);
+ setIsDetailModalOpen(true);
+ };
+
+ const acceptMatching = async (recruitment, user) => {
+ const confirmSelection = window.confirm(`${user.userId}님을 선발하시겠습니까?`);
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+
+ if (confirmSelection) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('선발 처리가 완료되었습니다.');
+
+ fireNotificationWithTimeout('매칭 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}이 매칭되었습니다. `
+ });
+
+ fetchApplicants(recruitmentId);
+
+ const providerResponse = await axios.get(`http://localhost:8050/api/auth/provider/${recruitment.jobProviders}`);
+ if (providerResponse.status === 200) {
+ console.log(providerResponse.data);
+ setProviderPhone(providerResponse.data.companyTel);
+ }
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingComplete", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCompanyComplete", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error accepting the matching:", error);
+ alert('선발 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const cancelApproval = async (recruitment, user) => {
+ const confirmCancel = window.confirm(`${user.userId}님의 선발을 취소하시겠습니까?`);
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+
+ if (confirmCancel) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings/cancelApproval', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('선발 취소 처리가 완료되었습니다.');
+
+ fireNotificationWithTimeout('매칭 취소 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}의 매칭이 취소되었습니다. `
+ });
+
+ fetchApplicants(recruitmentId);
+
+ const providerResponse = await axios.get(`http://localhost:8050/api/auth/provider/${recruitment.jobProviders}`);
+ if (providerResponse.status === 200) {
+ console.log(providerResponse.data);
+ setProviderPhone(providerResponse.data.companyTel);
+ }
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCancel", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCompanyCancel", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error canceling the approval:", error);
+ alert('선발 취소 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const handleDeleteAllMatchings = async (recruitment) => {
+ const confirmDeletion = window.confirm(`공고를 정말 삭제하시겠습니까?`);
+ if (confirmDeletion) {
+ try {
+ await axios.delete(`http://localhost:8050/api/matchings/byRecruitment/${recruitment.job_id}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ await axios.delete(`http://localhost:8050/api/recruitments/${recruitment.job_id}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ alert('공고가 삭제되었습니다.');
+ if (user && cookies.token) {
+ fetchJobAnnouncements(user.providerId);
+ }
+
+ fireNotificationWithTimeout('매칭 삭제 완료', 5000, {
+ body: `[${recruitment.title}] 공고가 삭제되었습니다. `
+ });
+
+ // SMS 전송 로직
+ try {
+ // Provider에게도 SMS 전송
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingDelete", {
+ phone: user.companyTel,
+ jobTitle: recruitment.title
+ });
+ console.log(smsResponse.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ } catch (error) {
+ console.error("Error deleting all matchings:", error);
+ alert('매칭 삭제 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const hasDatePassed = (endDate) => {
+ const today = new Date();
+ const end = new Date(endDate);
+ return end < today;
+ };
+
+ const pastAnnouncements = announcements.filter(announcement => hasDatePassed(announcement.startDate));
+ const upcomingAnnouncements = announcements.filter(announcement => !hasDatePassed(announcement.startDate));
+
+ return (
+
+ )
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 50px;
+ gap: 30px;
+`;
+
+const Title = styled.h2`
+ font-size: 40px;
+ font-family: 'omyu_pretty';
+`
+
+const AnnouncementCard = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #FFFFFF;
+ border-radius: 10px;
+ padding: 20px;
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
+ width: 70%;
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0px 6px 15px rgba(0, 0, 0, 0.15);
+ transform: translateY(-3px);
+ }
+`;
+
+const AnnouncementTitle = styled.div`
+ cursor: pointer;
+
+ h3 {
+ font-size: 1.2em;
+ color: #333;
+ margin: 0;
+ }
+`;
+
+const Button = styled.button`
+ padding: 10px 15px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+ font-weight: bold;
+ margin-left: 10px;
+
+ &:first-child {
+ background-color: #4CAF50;
+ color: white;
+
+ &:hover {
+ background-color: #45A049;
+ }
+ }
+
+ &:last-child {
+ background-color: #F44336;
+ color: white;
+
+ &:hover {
+ background-color: #D32F2F;
+ }
+ }
+`;
+
+const ApplicantsList = styled.div`
+ margin-top: 20px;
+ padding: 10px;
+ background: #fff;
+ border: 1px solid #ddd;
+`;
+
+const Modal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const ModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+`;
+
+const ModalHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ button {
+ padding: 5px 10px;
+ margin-left: 10px;
+ }
+`;
+
+const StyledTable = styled.table`
+ width: 100%;
+ border-collapse: collapse;
+`;
+
+const TableHeader = styled.th`
+ background-color: #f4f4f4;
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const TableRow = styled.tr`
+ &:nth-child(even) {
+ background-color: #f9f9f9;
+ }
+`;
+
+const TableCell = styled.td`
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const NoApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: #ff6b6b;
+ color: white;
+ &:hover {
+ background-color: #ff4747;
+ }
+`;
+
+const ApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: green;
+ color: white;
+ &:hover {
+ background-color: darkgreen;
+ }
+`;
+
+const DetailModal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const DetailModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+ font-size: 18px;
+`;
+
+const ProfileRequestPic = styled.img`
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ object-fit: cover;
+ margin-top: 10px;
+ margin-bottom: 20px;
+`;
+
+const DisabledButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ background-color: #cccccc;
+ color: white;
+ cursor: not-allowed;
+`;
+
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
+
+
+export default CompanyMatchingPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/AnnouncementContentPage.js b/hansoyeon/src/Pages/AnnouncementContentPage.js
old mode 100644
new mode 100755
index f7ff2d344..ad0c5ba4e
--- a/hansoyeon/src/Pages/AnnouncementContentPage.js
+++ b/hansoyeon/src/Pages/AnnouncementContentPage.js
@@ -3,6 +3,8 @@ import {useNavigate, useParams} from 'react-router-dom';
import styled from 'styled-components';
import {responsivePropType} from "react-bootstrap/createUtilityClasses";
import axios from "axios";
+import {useCookies} from "react-cookie";
+import Footer from "../Components/Footer";
const AnnouncementContentPage = () => {
const { anno_id } = useParams();
@@ -11,7 +13,9 @@ const AnnouncementContentPage = () => {
const navigate = useNavigate();
const [isEditing, setIsEditing] = useState(false);
const [modifiedContent, setModifiedContent] = useState('');
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
const [updateFlag, setUpdateFlag] = useState(false);
+ const [isAdmin, setIsAdmin] = useState(false);
// 수정 버튼 클릭 시 수정 모드 활성화
const activateEditMode = () => {
@@ -29,6 +33,26 @@ const AnnouncementContentPage = () => {
setModifiedContent(e.target.value);
};
+ //상세 페이지 불러오는 함수
+ const fetchAnnouncement = async () => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/announcements/${anno_id}`)
+ if (response.status !== 200 ) {
+ throw new Error('Failed to fetch announcement content');
+ }
+ const data = response.data;
+ setAnnouncement(data);
+ setModifiedContent(data.anno_content);
+ console.log('Announcement Content: ', data);
+ } catch (error) {
+ console.error('Error fetching announcement content: ', error);
+ }
+ };
+
+ useEffect(() => {
+ fetchAnnouncement();
+ }, [anno_id]);
+
//삭제 물어보는메서드
const confirmDelete = () => {
const isConfirmed = window.confirm('삭제하시겠습니까?');
@@ -52,6 +76,28 @@ const AnnouncementContentPage = () => {
}
};
+ //admin구분
+ useEffect(() => {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then((response) => {
+ console.log(response.data);
+ const user = response.data;
+ const isAdminUser = user.userId === 'admin';
+ setIsAdmin(isAdminUser);
+ })
+ .catch(error => {
+ console.error('Error fetching user info: ', error);
+ if (error.response) {
+ console.error('Status Code: ', error.response.status);
+ console.error('Response Data: ', error.response.data);
+ }
+ })
+ }, []);
+
//수정 메서드
const saveEdit = async () => {
const modifiedData = {
@@ -83,26 +129,6 @@ const AnnouncementContentPage = () => {
setUpdateFlag((prevFlag) => !prevFlag);
};
- const fetchAnnouncement = async () => {
- try {
- const response = await axios.get(`http://localhost:8050/api/announcements/${anno_id}`)
- if (response.status !== 200 ) {
- throw new Error('Failed to fetch announcement content');
- }
- const data = response.data;
- setAnnouncement(data);
- setModifiedContent(data.anno_content);
- console.log('Announcement Content: ', data);
- } catch (error) {
- console.error('Error fetching announcement content: ', error);
- }
- };
-
- useEffect(() => {
- fetchAnnouncement();
- }, [anno_id]);
-
-
if (!announcement) {
return
Loading...
);
};
const Container = styled.div`
display: flex;
- flex: 1;
- height: 700px;
flex-direction: column;
- justify-content: center;
+ min-height: 100vh; /* Set minimum height to 100% of the viewport height */
+ justify-content: space-between; /* Space between children elements */
align-items: center;
`;
@@ -166,24 +191,28 @@ const MiddleContainer = styled.div`
flex-direction: column;
width: 900px;
height: 500px;
+ font-weight: 600;
background-color: #f8f8f8; /* Light gray background */
border-radius: 10px;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Soft shadow */
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5); /* Soft shadow */
padding: 20px; /* Add some padding */
margin-top: 20px; /* Add space at the top */
+ font-family: 'SUITE-Light';
+
`;
const NoticeTitleContainer = styled.div`
font-size: 40px;
- font-weight: 700;
- width: 800px;
+ font-weight: 500;
+ width: 900px;
display: flex;
flex-direction: row;
align-items: center;
- margin-bottom: 1rem;
+ margin-top: 9rem;
+ font-family: 'omyu_pretty';
`;
const EditButton = styled.button`
- background-color: #007bff;
+ background-color: #2582fa;
border: none;
color: white;
border-radius: 10px;
@@ -196,7 +225,6 @@ const Title = styled.div`
flex: 1;
font-size: 20px;
align-items: center;
- margin-left: 2rem;
margin-right: 2rem;
justify-content: space-between;
`;
@@ -206,14 +234,18 @@ const WriterContainer = styled.div`
font-size: 20px;
flex: 3;
align-items: center;
- margin-left: 1rem;
+ margin-left: 0.3rem;
+
+ h6 {
+ font-size: 20px;
+}
`;
const ContentContainer = styled.div`
display: flex;
flex: 4;
- font-size: 20px;
- margin-left: 1rem;
+ margin-left: 0.3rem;
+ font-size: 24px;
`;
const ButtonContainer = styled.div`
@@ -242,21 +274,19 @@ const CancelButton = styled.button`
border-radius: 10px;
width: 80px;
height: 30px;
- margin-right: 1rem;
`;
const DeleteButton = styled.button`
- background-color: #ff3b30; /* Red color */
+ background-color: tomato; /* Red color */
border: none;
color: white;
border-radius: 10px;
- margin-right: 1rem;
width: 80px;
height: 30px;
`;
const ModifyButton = styled.button`
- background-color: #007bff; /* Blue color */
+ background-color: orange; /* Blue color */
border: none;
color: white;
border-radius: 10px;
@@ -264,4 +294,4 @@ const ModifyButton = styled.button`
height: 30px;
`;
-export default AnnouncementContentPage;
+export default AnnouncementContentPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/AnnouncementListPage.js b/hansoyeon/src/Pages/AnnouncementListPage.js
old mode 100644
new mode 100755
index 38b021356..5ac2468bb
--- a/hansoyeon/src/Pages/AnnouncementListPage.js
+++ b/hansoyeon/src/Pages/AnnouncementListPage.js
@@ -1,55 +1,68 @@
-import React, {useEffect, useState} from 'react';
+import React, { useEffect, useState, useMemo } from 'react';
import styled from 'styled-components';
-import navigate from "../Components/Navigate";
-import {useNavigate} from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import axios from "axios";
-import data from "bootstrap/js/src/dom/data";
-const AnnouncementListPage = (props) => {
+import { useCookies } from "react-cookie";
+import Footer from "../Components/Footer";
+
+const AnnouncementListPage = () => {
const navigate = useNavigate();
const [announcements, setAnnouncements] = useState([]);
- //페이지 시작번호
+ const [isAdmin, setIsAdmin] = useState(false);
+ const [cookies, setCookie, removeCookie] = useCookies(["token"]);
const [currentPage, setCurrentPage] = useState(1);
- //최대 5개까지만 목록에 표시
const itemsPerPage = 5;
- //글 작성 버튼 클릭시 작성페이지로 이동
+
+ useEffect(() => {
+ axios.get('http://localhost:8050/api/announcements')
+ .then(response => {
+ const reversedAnnouncements = [...response.data].reverse();
+ setAnnouncements(reversedAnnouncements);
+ })
+ .catch(error => console.error('Error fetching announcements:', error));
+ }, []);
+
const WritingNews = () => {
navigate("/WritingNewsPage");
}
- // 전체 공지사항 목록에서 현재 페이지에 표시할 목록만 추출
- const indexOfLastItem = currentPage * itemsPerPage;
- const indexOfFirstItem = indexOfLastItem - itemsPerPage;
- const currentItems = announcements.slice(indexOfFirstItem, indexOfLastItem);
- // 글 번호 고유Id값
- // 글 제목 클릭시 상세내용 페이지 이동
+
+ useEffect(() => {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then((response) => {
+ const user = response.data;
+ const isAdminUser = user.userId === 'admin';
+ setIsAdmin(isAdminUser);
+ })
+ .catch(error => {
+ console.error('Error fetching user info: ', error);
+ if (error.response) {
+ console.error('Status Code: ', error.response.status);
+ console.error('Response Data: ', error.response.data);
+ }
+ })
+ }, [cookies.token]);
+
const viewAnnouncement = async (annoId) => {
try {
- const response = await axios.get(`http://localhost:8050/api/announcements/${annoId}`);
- const data = response.data;
-
await axios.put(`http://localhost:8050/api/announcements/${annoId}/increaseViews`);
-
navigate(`/AnnouncementContent/${annoId}`);
} catch (error) {
console.error('Error fetching or updating announcement:', error);
}
};
+ const currentItems = useMemo(() => {
+ const indexOfLastItem = currentPage * itemsPerPage;
+ const indexOfFirstItem = indexOfLastItem - itemsPerPage;
+ return announcements.slice(indexOfFirstItem, indexOfLastItem);
+ }, [currentPage, announcements]);
- //글 목록 띄우기
- useEffect(() => {
- // API 호출을 통해 공지사항 목록 가져오기
- axios.get('http://localhost:8050/api/announcements')
- .then(response => {
-
- // 받아온 목록을 오름차순으로 정렬
- const reversedAnnouncements = [...response.data].reverse();
- setAnnouncements(reversedAnnouncements);
- })
- .catch(error => console.error('Error fetching announcements:', error));
- }, []);
-
- // 페이지 번호 클릭 시 호출되는 함수
const paginate = (pageNumber) => setCurrentPage(pageNumber);
+
return (
);
};
const Container = styled.div`
display: flex;
- flex: 1;
- height: 700px;
+ flex-direction: column;
+ min-height: 100vh; /* Set minimum height to 100% of the viewport height */
+ justify-content: space-between; /* Space between children elements */
align-items: center;
- justify-content: center;
-`
+`;
const MiddleContainer = styled.div`
display: flex;
- height: 500px;
- width: 1000px;
flex-direction: column;
-`
+ width: 1000px;
+ margin-top: 9rem;
+ flex-grow: 1; /* Expand to fill available space */
+`;
const AnnouncementTitle = styled.div`
display: flex;
- flex: 1;
+ height: 50px;
align-items:center;
- font-size: 40px;
- font-weight: 700;
+ font-size: 44px;
+ font-weight: 500;
+ font-family: 'omyu_pretty';
`
const NewsTitle = styled.div`
display: flex;
- flex: 2;
+ flex: 1;
flex-direction: row;
- align-items: center;
+ font-family: 'omyu_pretty';
`
const SmallNewsTitle = styled.div`
display: flex;
@@ -132,7 +152,7 @@ const LeftNewsTitle = styled.div`
display: flex;
flex: 5;
height: 60px;
- font-size: 20px;
+ font-size: 24px;
align-items: center;
margin-left: 0.5rem;
`
@@ -146,33 +166,19 @@ const RightNewsTitle = styled.div`
justify-content: right;
`
const WritingButton = styled.button`
- background-color: skyblue;
+ background-color: #FFC107;
display: flex;
width: 100px;
height: 40px;
text-align: center;
color: white;
- margin-right: 1rem;
border-radius: 10px;
- font-size: 20px;
+ font-size: 24px;
align-items: center;
justify-content: center;
border: none;
`
-const ExitButton = styled.div`
- display: flex;
- width: 100px;
- height: 40px;
- text-align: center;
- color: white;
- border-radius: 10px;
- background-color: mediumseagreen;
- font-size: 20px;
- float: right;
- align-items: center;
- justify-content: center;
-`
const MainTitle = styled.div`
display: flex;
flex: 7;
@@ -181,8 +187,8 @@ const MainTitle = styled.div`
const TopMainTitle = styled.div`
display: flex;
flex-direction: row;
- border-bottom: 2px solid hotpink;
- border-top: 2px solid gray;
+ border-bottom: 2px solid #495057;
+ border-top: 2px solid black;
height: 50px;
align-items: center;
`
@@ -196,7 +202,7 @@ const BottomContent = styled.div`
display: flex;
flex-direction: row;
height: 50px;
- border-bottom: 1px solid skyblue;
+ border-bottom: 1px groove gray;
`
const Pagination = styled.div`
@@ -217,5 +223,11 @@ const PageNumber = styled.div`
color: white;
}
`;
+const FinalContainer = styled.div`
+ width: 100%;
+ margin-top: auto; /* Set margin-top to auto to push the Footer to the bottom */
+`;
+
+
export default AnnouncementListPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/ApprovalPage.js b/hansoyeon/src/Pages/ApprovalPage.js
old mode 100644
new mode 100755
index 1b76f315e..6143ddd00
--- a/hansoyeon/src/Pages/ApprovalPage.js
+++ b/hansoyeon/src/Pages/ApprovalPage.js
@@ -4,6 +4,7 @@ import styled, { keyframes } from "styled-components";
import logo from "../imgs/logo2.png";
import {Button} from "react-bootstrap";
import {useNavigate} from "react-router-dom";
+import Footer from "../Components/Footer";
const ApprovalPage = () => {
const navigate = useNavigate();
diff --git a/hansoyeon/src/Pages/BlackListCompanyPage.js b/hansoyeon/src/Pages/BlackListCompanyPage.js
new file mode 100644
index 000000000..425a9c5c8
--- /dev/null
+++ b/hansoyeon/src/Pages/BlackListCompanyPage.js
@@ -0,0 +1,430 @@
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { useNavigate } from 'react-router-dom';
+import axios from 'axios';
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
+import Footer from "../Components/Footer";
+
+
+const BlackListCompanyPage = () => {
+ const navigate = useNavigate();
+ const [blacklist, setBlacklist] = useState([]);
+ const [providerId, setProviderId] = useState();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const { user, setUser } = useUserStore();
+
+ const isLoggedIn = cookies.token && user;
+ const userType = cookies.userType;
+
+ useEffect(() => {
+ if (cookies.token) {
+ if (userType === 'company') {
+ axios
+ .get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then((response) => {
+ const fetchedUser = response.data;
+ setUser(fetchedUser);
+ })
+ .catch((error) => {
+ console.error('토큰 검증 실패:', error);
+ handleLogout();
+ });
+ } else {
+ axios
+ .get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then((response) => {
+ const fetchedUser = response.data;
+ setUser(fetchedUser);
+ })
+ .catch((error) => {
+ console.error('토큰 검증 실패:', error);
+ handleLogout();
+ });
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ if (user) {
+ fetchBlacklist(user);
+ }
+ }, [user]);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate('/');
+ };
+
+ const openModal = () => {
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ };
+
+ const fetchBlacklist = (fetchUser) => {
+ const updatedProviderId = user.providerId;
+
+ setProviderId(updatedProviderId);
+
+ axios
+ .get(`http://localhost:8050/api/blacklists/users?providerId=${updatedProviderId}`)
+ .then((response) => {
+ console.log(response.data);
+
+ const blacklistData = response.data.data; // 'data' 필드를 추가하여 실제 블랙리스트 데이터를 가져옵니다.
+
+ if (Array.isArray(blacklistData)) {
+ setBlacklist(blacklistData);
+ } else {
+ console.error('데이터 형식이 배열이 아닙니다.');
+ }
+ })
+ .catch((error) => {
+ console.error('블랙리스트 불러오기 실패: ', error);
+ });
+ };
+
+
+ const handleTabClick = (tabNumber) => {
+ setProviderId(tabNumber);
+ };
+
+ const handleDelete = (blacklistId) => {
+ console.log(user.providerId)
+ console.log(blacklistId);
+
+ // 삭제 API 호출
+ axios
+ .delete('http://localhost:8050/api/blacklists', {
+ data: {
+ providerId: user.providerId,
+ userId: blacklistId
+ }
+ })
+ .then((response) => {
+ fetchBlacklist();
+ })
+ .catch((error) => {
+ console.error("Error deleting blacklist: ", error);
+ });
+ };
+
+
+ const handleAddUser = () => {
+ openModal();
+ };
+
+ const UserAddModal = ({closeModal}) => {
+ const [searchTerm, setSearchTerm] = useState('');
+ const [searchResults, setSearchResults] = useState([]);
+ const [selectedUser, setSelectedUser] = useState(null);
+
+ const handleSearch = () => {
+ axios
+ .get(`http://localhost:8050/api/auth/users/search?search=${searchTerm}`)
+ .then((response) => {
+ const filteredResults = response.data.filter(user => user.userId !== "admin");
+ setSearchResults(filteredResults);
+ })
+ .catch((error) => {
+ console.error('Failed to search users: ', error);
+ });
+ };
+
+ const handleUserSelect = (user) => {
+ setSelectedUser(user);
+ };
+
+ const handleAddUserToBlacklist = () => {
+ if (selectedUser) {
+ axios
+ .post(`http://localhost:8050/api/blacklists?providerId=${user.providerId}&userId=${selectedUser.userId}`)
+ .then((response) => {
+ console.log('User added to the blacklist:', selectedUser);
+ fetchBlacklist(user);
+ closeModal();
+ })
+ .catch((error) => {
+ console.error('Error adding user to the blacklist:', error);
+ });
+ }
+ };
+
+ return (
+
+ );
+};
+
+const StyledContainer = styled.div`
+ background-color: #fafafa;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 50px;
+ min-height: 100vh;
+ font-family: 'Segoe UI', sans-serif;
+`;
+
+const Title = styled.h1`
+ color: #333;
+ font-size: 40px;
+ margin-bottom: 20px;
+ text-align: center;
+ font-family: 'omyu_pretty';
+`;
+
+const ListContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 80%; // Adjust the width as per your layout
+ margin: 0 auto; // Center align the container
+`;
+
+const BlackListItem = styled.div`
+ background-color: #fff;
+ border-radius: 10px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+ padding: 15px;
+ margin-bottom: 20px;
+ transition: box-shadow 0.3s ease;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ }
+`;
+
+const UserHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+const UserAvatar = styled.img`
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ margin-right: 15px;
+`;
+
+const UserInfo = styled.div`
+ flex-grow: 1;
+ margin-right: 20px;
+`;
+
+const ItemTitle = styled.h3`
+ font-size: 1.1em;
+ font-weight: 600;
+ margin-bottom: 5px;
+ color: #333;
+`;
+
+const ItemContent = styled.p`
+ font-size: 0.9em;
+ color: #555;
+`;
+
+const DeleteButton = styled.button`
+ background-color: #dc3545;
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: #c82333;
+ }
+`;
+
+const AddButton = styled.button`
+ background-color: #aab5bd;
+ color: white;
+ border: none;
+ font-weight: 600;
+ padding: 10px 15px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 24px;
+ margin-bottom: 20px;
+ width: 500px;
+
+ &:hover {
+ background-color: #868e96;
+ }
+`;
+const Add11Button = styled.button`
+ background-color: #aab5bd;
+ color: white;
+ border: none;
+ font-weight: 600;
+ padding: 10px 15px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 18px;
+ margin-bottom: 20px;
+ width: 250px;
+
+ &:hover {
+ background-color: #868e96;
+ }
+`
+
+const ModalWrapper = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.3);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+const ModalContent = styled.div`
+ background: white;
+ padding: 20px;
+ border-radius: 8px;
+ width: 400px;
+ position: relative;
+`;
+
+const CloseButton = styled.div`
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ font-size: 20px;
+ cursor: pointer;
+`;
+
+const UserSearchModal = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-top: 20px;
+
+ input {
+ padding: 8px;
+ margin-bottom: 10px;
+ }
+
+ button {
+ background-color: #007bff;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0056b3;
+ }
+ }
+
+`;
+
+
+const SelectedUserContainer = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const SelectedUserInfo = styled.div`
+ flex: 1;
+`;
+
+const SearchResultItem = styled.div`
+ background-color: #f8f9fa;
+ padding: 10px;
+ margin-bottom: 10px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #e2e6ea;
+ }
+`;
+
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
+export default BlackListCompanyPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/BlackListPage.js b/hansoyeon/src/Pages/BlackListPage.js
new file mode 100644
index 000000000..351456150
--- /dev/null
+++ b/hansoyeon/src/Pages/BlackListPage.js
@@ -0,0 +1,246 @@
+import React, {useEffect, useState} from "react";
+import axios from "axios";
+import styled from "styled-components";
+import { Table, Modal, Button } from "react-bootstrap";
+import defaultProfilePic from '../imgs/default_profile.png';
+import Footer from "../Components/Footer";
+
+const BlackListPage = () => {
+ const [blacklist, setBlacklist] = useState([]);
+
+ const [selectedUser, setSelectedUser] = useState(null);
+ const [showModal, setShowModal] = useState(false);
+
+ const handleUserClick = (user) => {
+ setSelectedUser(user);
+ setShowModal(true);
+ };
+
+
+ useEffect(() => {
+ fetchBlacklist();
+ }, []);
+
+ const fetchBlacklist = () => {
+ // 탭에 따라 다른 URL에서 데이터를 가져올 수 있음
+ axios.get(`http://localhost:8050/api/blacklists`)
+ .then(response => {
+ setBlacklist(response.data.data);
+ })
+ .catch(error => {
+ console.error("Error fetching blacklist: ", error);
+ });
+ };
+
+ const handleDelete = (blacklistId) => {
+ // 삭제 API 호출
+ axios
+ .delete(`http://localhost:8050/api/blacklists/${blacklistId}`)
+ .then((response) => {
+ fetchBlacklist();
+ alert("삭제 되었습니다.");
+ })
+ .catch((error) => {
+ console.error("Error deleting blacklist: ", error);
+ });
+ };
+
+ const UserDetailModal = ({ show, onHide, user }) => {
+ return (
+
+ );
+};
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 50px;
+ min-height: 100vh;
+`;
+
+const Header = styled.h2`
+ color: #333;
+ text-align: center;
+ margin-top: 50px;
+ margin-bottom: 40px;
+ //font-size: 48px;
+ font-weight: 500;
+ //font-family: 'omyu_pretty';
+`;
+
+const TabButton = styled.button`
+ margin: 10px;
+ padding: 10px 20px;
+ background-color: #f8f9fa;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #e2e6ea;
+ }
+
+ &.active {
+ background-color: #007bff;
+ color: white;
+ }
+`;
+
+const StyledTable = styled(Table)`
+ background-color: white;
+ width: 80%;
+
+ thead {
+ background-color: #f8f9fa;
+ }
+
+ th, td {
+ text-align: center;
+ vertical-align: middle;
+ }
+
+ td {
+ font-size: 17px;
+ }
+
+ th:first-child, td:first-child {
+ width: 180px;
+ }
+
+ th:nth-child(2), td:nth-child(2) {
+ width: 200px;
+ }
+
+ th:nth-child(3), td:nth-child(3) {
+ width: 250px;
+ }
+
+ th:nth-child(4), td:nth-child(4) {
+ width: 150px;
+ }
+
+ @media (max-width: 768px) {
+ width: 100%;
+ font-size: 14px; /* 화면이 작을 때 폰트 크기 줄임 */
+
+ th, td {
+ padding: 10px; /* 셀의 패딩 조정 */
+ }
+
+ /* 필요한 경우 여기에 더 많은 스타일 규칙 추가 */
+ }
+`;
+
+
+const ButtonContainer = styled.div`
+
+`;
+
+const DeleteButton = styled.button`
+ background-color: #dc3545;
+ color: white;
+ width: 60px;
+ border-radius: 8px;
+ border: none;
+ padding: 5px 10px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #c82333;
+ }
+`;
+
+const UserProfileImage = styled.img`
+ display: block;
+ margin: 0 auto;
+ width: 100px;
+ height: 100px;
+ border-radius: 50%;
+ margin-bottom: 10px;
+`;
+
+const UserInfo = styled.div`
+ text-align: center;
+`;
+
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
+
+export default BlackListPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/CompanyInfoChangePage.js b/hansoyeon/src/Pages/CompanyInfoChangePage.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/Pages/CompanyManagePage.js b/hansoyeon/src/Pages/CompanyManagePage.js
old mode 100644
new mode 100755
index f94139a03..5814f54f2
--- a/hansoyeon/src/Pages/CompanyManagePage.js
+++ b/hansoyeon/src/Pages/CompanyManagePage.js
@@ -4,6 +4,7 @@ import { Table, Button, Modal } from "react-bootstrap";
import styled from 'styled-components';
import defaultProfile from '../imgs/default_profile.png';
import approvalCheck from '../imgs/approvalCheck.png'
+import Footer from "../Components/Footer";
const CompanyManagePage = () => {
const [companies, setCompanies] = useState([]);
@@ -180,11 +181,16 @@ const CompanyManagePage = () => {
+ {selectedCompany.companyLicense.length === 10 ?
+
+ }
>
)}
@@ -216,7 +222,11 @@ const CompanyManagePage = () => {
);
};
@@ -344,6 +354,14 @@ const StyledLicenseCheckImage = styled.img`
width: 15px;
height: 15px;
`;
+const StyledFooter = styled.footer`
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ padding: 10px;
+ text-align: center;
+ margin-bottom: -10px;
+`;
export default CompanyManagePage;
diff --git a/hansoyeon/src/Pages/CompanyMatchingPage.js b/hansoyeon/src/Pages/CompanyMatchingPage.js
new file mode 100755
index 000000000..26631313d
--- /dev/null
+++ b/hansoyeon/src/Pages/CompanyMatchingPage.js
@@ -0,0 +1,592 @@
+import React, {useEffect, useState} from "react";
+import styled from "styled-components";
+import {useNavigate, useParams} from "react-router-dom";
+import {useCookies} from "react-cookie";
+import {useUserStore} from "../stores";
+import axios from "axios";
+import defaultProfilePic from '../imgs/default_profile.png';
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+import Footer from "../Components/Footer";
+
+const CompanyMatchingPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [announcements, setAnnouncements] = useState([]);
+
+ const [applicants, setApplicants] = useState([]);
+ const [selectedJobId, setSelectedJobId] = useState(null);
+
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
+
+ const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
+ const [selectedUser, setSelectedUser] = useState(null);
+
+ const [providerPhone, setProviderPhone] = useState('')
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ setProviderPhone(user.companyTel);
+ fetchJobAnnouncements(user.providerId);
+ }
+ }, [user]);
+
+ const fetchJobAnnouncements = async (jobProviders) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/recruitments/byProvider/${jobProviders}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ console.log(response.data)
+ setAnnouncements(response.data);
+ } catch (error) {
+ console.error("Error fetching job announcements:", error);
+ }
+ };
+
+ const handleJobView = (jobId) => {
+ navigate(`/recruit/${jobId}`);
+ };
+
+ const fetchApplicants = async (jobId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/recruitments/${jobId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ if (response.data && response.data.data) {
+ console.log(response.data)
+ setApplicants(response.data.data);
+ }
+ } catch (error) {
+ console.error("Error fetching applicants:", error);
+ setApplicants([]);
+ }
+ };
+
+ const handleCheckApplicants = async (announcement) => {
+ setSelectedAnnouncement(announcement);
+ setSelectedJobId(announcement.job_id);
+ fetchApplicants(announcement.job_id);
+ setIsModalOpen(true);
+ };
+
+ const handleModalClose = () => {
+ setIsModalOpen(false);
+ };
+
+ const handleUserClick = (user) => {
+ setSelectedUser(user);
+ setIsDetailModalOpen(true);
+ };
+
+ const acceptMatching = async (recruitment, user) => {
+ const confirmSelection = window.confirm(`${user.userId}님을 선발하시겠습니까?`);
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+
+ if (confirmSelection) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('선발 처리가 완료되었습니다.');
+
+ fireNotificationWithTimeout('매칭 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}이 매칭되었습니다. `
+ });
+
+ fetchApplicants(recruitmentId);
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingComplete", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCompanyComplete", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error accepting the matching:", error);
+ alert('선발 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const cancelApproval = async (recruitment, user) => {
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+ const confirmCancel = window.confirm(`${user.userId}님의 선발을 취소하시겠습니까?`);
+ if (confirmCancel) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings/cancelApproval', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('선발 취소 처리가 완료되었습니다.');
+ fireNotificationWithTimeout('매칭 취소 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}의 매칭이 취소되었습니다. `
+ });
+ fetchApplicants(recruitmentId);
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCancel", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingCompanyCancel", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error canceling the approval:", error);
+ alert('선발 취소 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const handleDeleteAllMatchings = async (recruitment) => {
+ console.log(recruitment)
+ const confirmDeletion = window.confirm(`공고를 정말 삭제하시겠습니까?`);
+ if (confirmDeletion) {
+ try {
+ await axios.delete(`http://localhost:8050/api/matchings/byRecruitment/${recruitment.job_id}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ await axios.delete(`http://localhost:8050/api/recruitments/${recruitment.job_id}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ alert('공고가 삭제되었습니다.');
+ fireNotificationWithTimeout('매칭 삭제 완료', 5000, {
+ body: `[${recruitment.title}] 공고가 삭제되었습니다. `
+ });
+ if (user && cookies.token) {
+ fetchJobAnnouncements(user.providerId);
+ }
+
+ // SMS 전송 로직
+ try {
+ // Provider에게도 SMS 전송
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingDelete", {
+ phone: user.companyTel,
+ jobTitle: recruitment.title
+ });
+ console.log(smsResponse.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ } catch (error) {
+ console.error("Error deleting all matchings:", error);
+ alert('매칭 삭제 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const hasDatePassed = (endDate) => {
+ const today = new Date();
+ const end = new Date(endDate);
+ return end < today;
+ };
+
+ const pastAnnouncements = announcements.filter(announcement => hasDatePassed(announcement.startDate));
+ const upcomingAnnouncements = announcements.filter(announcement => !hasDatePassed(announcement.startDate));
+
+ return (
+
+ )
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 30px;
+ margin-top: 50px;
+ min-height: 100vh;
+`;
+
+const Title = styled.h2`
+ font-size: 40px;
+ font-family: 'omyu_pretty';
+`
+
+const AnnouncementCard = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #FFFFFF;
+ border-radius: 10px;
+ padding: 20px;
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
+ width: 70%;
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0px 6px 15px rgba(0, 0, 0, 0.15);
+ transform: translateY(-3px);
+ }
+`;
+
+const AnnouncementTitle = styled.div`
+ cursor: pointer;
+
+ h3 {
+ font-size: 1.2em;
+ color: #333;
+ margin: 0;
+ }
+`;
+
+const Button = styled.button`
+ padding: 10px 15px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+ font-weight: bold;
+ margin-left: 10px;
+
+ &:first-child {
+ background-color: #4CAF50;
+ color: white;
+
+ &:hover {
+ background-color: #45A049;
+ }
+ }
+
+ &:last-child {
+ background-color: #F44336;
+ color: white;
+
+ &:hover {
+ background-color: #D32F2F;
+ }
+ }
+`;
+
+const ApplicantsList = styled.div`
+ margin-top: 20px;
+ padding: 10px;
+ background: #fff;
+ border: 1px solid #ddd;
+`;
+
+const Modal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const ModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+`;
+
+const ModalHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ h2 {
+ display: flex;
+ text-align: center;
+ font-size: 24px;
+ }
+ button {
+ width: 80px;
+ border: none;
+ color: white;
+ height: 40px;
+ font-weight: 700;
+ background-color: green;
+ border-radius: 10px;
+ padding: 5px 10px;
+ margin-left: 10px;
+ }
+`;
+
+const StyledTable = styled.table`
+ width: 100%;
+ border-collapse: collapse;
+`;
+
+const TableHeader = styled.th`
+ background-color: #f4f4f4;
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const TableRow = styled.tr`
+ &:nth-child(even) {
+ background-color: #f9f9f9;
+ }
+`;
+
+const TableCell = styled.td`
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const NoApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: #ff6b6b;
+ color: white;
+ &:hover {
+ background-color: #ff4747;
+ }
+`;
+
+const ApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: orangered;
+ color: white;
+ &:hover {
+ background-color: red;
+ }
+`;
+
+const DetailModal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const DetailModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+ font-family: 'omyu_pretty';
+ font-size: 24px;
+ h2 {
+ font-size: 40px;
+
+
+ }
+
+`;
+
+const ProfileRequestPic = styled.img`
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ object-fit: cover;
+ margin-top: 10px;
+ margin-bottom: 20px;
+`;
+
+const DisabledButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ background-color: #cccccc;
+ color: white;
+ cursor: not-allowed;
+`;
+
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
+
+export default CompanyMatchingPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/FriendListPage.js b/hansoyeon/src/Pages/FriendListPage.js
new file mode 100755
index 000000000..72beedd20
--- /dev/null
+++ b/hansoyeon/src/Pages/FriendListPage.js
@@ -0,0 +1,466 @@
+import React, {useEffect, useState} from "react";
+import styled from "styled-components";
+import {useNavigate} from 'react-router-dom';
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
+import axios from "axios";
+import Footer from "../Components/Footer";
+
+const FriendListPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [friends, setFriends] = useState([]);
+
+ const [showModal, setShowModal] = useState(false);
+ const [searchUserId, setSearchUserId] = useState('');
+ const [searchResults, setSearchResults] = useState([]);
+
+ const [showRequestModal, setShowRequestModal] = useState(false);
+ const [sentRequests, setSentRequests] = useState([]); // 보낸 요청 목록
+ const [receivedRequests, setReceivedRequests] = useState([]); // 받은 요청 목록
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ fetchFriends(fetchedUser.userId);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ const fetchFriends = (userId) => {
+ axios.get(`http://localhost:8050/api/friendships/friends?userId=${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ if (Array.isArray(response.data.data)) {
+ setFriends(response.data.data);
+ } else {
+ console.error("Expected an array for friends, but got:", response.data.data);
+ setFriends([]);
+ }
+ }).catch(error => {
+ console.error("Error fetching friends:", error);
+ setFriends([]);
+ });
+ };
+
+
+ const handleSearch = () => {
+ axios.get(`http://localhost:8050/api/auth/users/search?search=${searchUserId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ const filteredResults = response.data.filter(user => user.userId !== "admin");
+ setSearchResults(filteredResults);
+ }).catch(error => {
+ console.error("Error searching users:", error);
+ });
+ };
+
+ const handleAddFriend = (friendId) => {
+ // 현재 사용자의 ID와 친구의 ID를 이용하여 친구 요청 보내기
+ const requestBody = {
+ userId: user.userId, // 현재 로그인한 사용자의 ID
+ friendId: friendId // 추가할 친구의 ID
+ };
+
+ axios.post('http://localhost:8050/api/friendships', requestBody, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ alert("친구 요청을 보냈습니다"); // 성공 메시지 표시
+ }).catch(error => {
+ console.error("Error adding friend:", error);
+ alert("친구 추가 실패"); // 실패 메시지 표시
+ });
+ };
+
+ const handleCloseModal = () => {
+ setShowModal(false);
+ setSearchUserId('');
+ setSearchResults([]);
+ };
+
+ const fetchFriendRequests = () => {
+ // 보낸 요청 목록을 불러오는 로직
+ axios.get(`http://localhost:8050/api/friends/sentRequests/${user.userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ setSentRequests(response.data);
+ }).catch(error => {
+ console.error("Error fetching sent friend requests:", error);
+ });
+
+ // 받은 요청 목록을 불러오는 로직
+ axios.get(`http://localhost:8050/api/friends/receivedRequests/${user.userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ setReceivedRequests(response.data);
+ }).catch(error => {
+ console.error("Error fetching received friend requests:", error);
+ });
+ };
+
+ // 요청 목록 모달을 여는 함수
+ const handleOpenRequestModal = () => {
+ setShowRequestModal(true);
+ fetchFriendRequests();
+ };
+
+ // 요청 목록 모달을 닫는 함수
+ const handleCloseRequestModal = () => {
+ setShowRequestModal(false);
+ };
+
+ const handleAcceptRequest = (friendId) => {
+ const requestBody = {
+ userId: friendId,
+ friendId: user.userId
+ };
+
+ axios.put('http://localhost:8050/api/friendships', requestBody, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then(response => {
+ alert("친구 요청이 수락되었습니다");
+ const updatedReceivedRequests = receivedRequests.filter(request => request.userId !== friendId);
+ setReceivedRequests(updatedReceivedRequests);
+ fetchFriends(user.userId);
+
+ })
+ .catch(error => {
+ console.error("Error accepting friend request:", error);
+ alert("친구 요청 수락 실패");
+ });
+ };
+
+ const handleDeleteFriend = (friendId) => {
+ axios.delete(`http://localhost:8050/api/friendships?userId=${user.userId}&friendId=${friendId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ })
+ .then(response => {
+ alert("친구가 삭제되었습니다");
+ setFriends(friends.filter(friend => friend.userId !== friendId));
+ })
+ .catch(error => {
+ console.error("Error deleting friend:", error);
+ alert("친구 삭제 실패");
+ });
+ };
+
+
+ return(
+
+ )
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 90vh;
+ margin-top: 30px;
+`;
+
+const Title = styled.h1`
+ font-size: 40px;
+ font-family: 'omyu_pretty';
+`;
+
+const FriendsContainer = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ width: 100%;
+ max-width: 800px;
+ margin-top: 20px;
+`;
+
+const Friend = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ background-color: #f9f9f9;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ transition: box-shadow 0.3s ease-in-out;
+
+ &:hover {
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
+ }
+`;
+
+const Button = styled.button`
+ margin: 10px;
+ padding: 10px 20px;
+ background-color: #007bff;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 19px;
+ transition: background-color 0.2s;
+ font-family: 'omyu_pretty';
+
+ &:hover {
+ background-color: #0056b3;
+ }
+`;
+
+const Modal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const ModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+`;
+
+const SearchResults = styled.div`
+ margin-top: 20px;
+ // 스타일 정의
+`;
+
+const UserCard = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+ padding: 10px;
+ background-color: #f3f3f3;
+ border-radius: 8px;
+`;
+
+const ProfilePic = styled.img`
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ object-fit: cover;
+`;
+
+const UserInfo = styled.div`
+ margin-left: 10px;
+ flex-grow: 1;
+`;
+
+const UserName = styled.h2`
+ margin-top: 10px;
+ font-size: 20px;
+ color: #333;
+`;
+
+const UserId = styled.p`
+ font-size: 16px;
+ color: #666;
+`;
+
+const AddFriendButton = styled.button`
+ padding: 5px 10px;
+ background-color: #963;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+`;
+
+const Section = styled.div`
+ margin-bottom: 20px;
+`;
+
+const SectionTitle = styled.h3`
+ margin-bottom: 10px;
+ font-family: 'omyu_pretty';
+`;
+
+const Divider = styled.hr`
+ border: 0;
+ height: 1px;
+ background-color: #ccc;
+ margin: 20px 0;
+`;
+
+const RequestItem = styled.div`
+ display: flex;
+ align-items: center;
+ padding: 10px;
+ background-color: #f3f3f3;
+ border-radius: 8px;
+ margin-bottom: 10px;
+`;
+
+const ProfileRequestPic = styled.img`
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ object-fit: cover;
+ margin-right: 10px;
+`;
+
+const AcceptButton = styled.button`
+ padding: 5px 10px;
+ background-color: #28a745;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-left: auto; // 오른쪽에 버튼 위치
+`;
+
+const DeleteButton = styled.button`
+ padding: 5px 10px;
+ background-color: #d9534f;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-top: 10px;
+ font-size: 18px;
+ font-family: 'omyu_pretty';
+`;
+
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
+export default FriendListPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/GeneralManagePage.js b/hansoyeon/src/Pages/GeneralManagePage.js
old mode 100644
new mode 100755
index 885416915..8361622a1
--- a/hansoyeon/src/Pages/GeneralManagePage.js
+++ b/hansoyeon/src/Pages/GeneralManagePage.js
@@ -3,6 +3,7 @@ import axios from "axios";
import { Table, Button, Modal } from "react-bootstrap";
import styled from 'styled-components';
import defaultProfile from '../imgs/default_profile.png';
+import Footer from "../Components/Footer";
const GeneralManagePage = () => {
const [users, setUsers] = useState([]);
@@ -79,7 +80,7 @@ const GeneralManagePage = () => {
handleShowModal(user)}>
- {user.userProfile === 'hansoyeon/src/imgs/default_profile.png' ?
+ {user.userProfile === 'hansoyeon/src/imgs/default_profile.png' || user.userProfile === null ?
<>
{user.userName}
@@ -151,6 +152,7 @@ const GeneralManagePage = () => {
+
);
};
diff --git a/hansoyeon/src/Pages/InfoChangePage.js b/hansoyeon/src/Pages/InfoChangePage.js
old mode 100644
new mode 100755
index 7deab843c..8f7a8d029
--- a/hansoyeon/src/Pages/InfoChangePage.js
+++ b/hansoyeon/src/Pages/InfoChangePage.js
@@ -11,6 +11,7 @@ import iu from "../imgs/iu3.png"
import logoSmall from "../imgs/logo2.png"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {faArrowLeft, faCamera} from '@fortawesome/free-solid-svg-icons';
+import Footer from "../Components/Footer";
const InfoChangePage = (props) => {
const navigate = useNavigate();
@@ -348,15 +349,19 @@ const InfoChangePage = (props) => {
)}
+
+
+
);
};
const StyledContainer = styled.div`
display: flex;
+ flex-direction: column;
justify-content: center;
align-items: center;
- height: 100vh;
+ height: 91.6vh;
`;
const BoxContainer = styled.div`
@@ -641,4 +646,11 @@ const PhoneNumberInput = styled.input`
}
`;
+const StyledFooter = styled.footer`
+ padding: 10px;
+ text-align: center;
+ width: 100%;
+ margin-top: auto; /* 다른 요소 아래에 위치하도록 설정 */
+`;
+
export default InfoChangePage;
diff --git a/hansoyeon/src/Pages/MainPage.js b/hansoyeon/src/Pages/MainPage.js
old mode 100644
new mode 100755
index e2041625f..1c318e0bb
--- a/hansoyeon/src/Pages/MainPage.js
+++ b/hansoyeon/src/Pages/MainPage.js
@@ -1,374 +1,511 @@
-import React, {useState, useCallback, useEffect} from 'react';
-import { Link } from 'react-router-dom';
+import React, { useEffect, useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
-
-//swiper App.css에 추가한후 import
-import {Swiper, SwiperSlide} from 'swiper/react'; //swiper 사용할 import
-import {Autoplay, Pagination} from 'swiper/modules';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import { Autoplay, Pagination, Navigation } from 'swiper/modules';
import 'swiper/css';
+import 'swiper/css/autoplay'
+import 'swiper/css/navigation'
import 'swiper/css/pagination';
-import '../App.css';
-import new1 from '../imgs/newcourse-1.png';
-import new2 from '../imgs/newcourse-2.png';
-import new3 from '../imgs/newcourse-3.png';
-import new4 from '../imgs/newcourse-4.png';
+import axios from 'axios';
+import SwiperCore from 'swiper';
+import theme1 from '../imgs/themecourse1.jpg';
+import theme2 from '../imgs/themecourse2.jpg';
+import theme3 from '../imgs/themecourse3.jpg';
+import theme4 from '../imgs/themecourse4.jpg';
+import theme5 from '../imgs/themecourse5.jpg';
+import theme6 from '../imgs/themecourse6.jpg';
+import theme7 from '../imgs/themecourse7.jpg';
+import theme8 from '../imgs/themecourse8.jpg';
+import region1 from '../imgs/regioncourse1.jpg';
+import region2 from '../imgs/regioncourse2.jpg';
+import region3 from '../imgs/regioncourse3.jpg';
+import region4 from '../imgs/regioncourse4.jpg';
+import region5 from '../imgs/regioncourse5.jpg';
+import region6 from '../imgs/regioncourse6.jpg';
+import region7 from '../imgs/regioncourse7.jpg';
+import region8 from '../imgs/regioncourse8.jpg';
+import recruitment1 from '../imgs/recruitment1.png';
import recommend1 from '../imgs/recommendcourse-1.png';
import recommend2 from '../imgs/recommendcourse-2.png';
import Footer from '../Components/Footer';
-import logo from "../imgs/logo-removebg.png";
-//리뷰 Test 데이터
-const dummyReviews = [
- {
- id: 1,
- title: "멋진 경험이었습니다!",
- content: "가이드분이 너무 친절하셨고, 경치도 환상적이었어요. 다음에 또 방문하고 싶습니다.",
- author: "홍길동",
- date: "2023-01-01"
- },
- {
- id: 2,
- title: "가족과 함께한 최고의 여행",
- content: "아이들과 함께 갔는데 모두가 즐거워했습니다. 추천해요!",
- author: "김철수",
- date: "2023-02-15"
- },
- {
- id: 3,
- title: "다시 오고 싶은 곳",
- content: "서비스도 좋았고, 특히 음식이 맛있었습니다. 여행의 즐거움을 더해주는 곳이었어요.",
- author: "이영희",
- date: "2023-03-20"
- }
-];
+import logo from '../imgs/logo-removebg.png';
+import { getRecruitmentsData } from './RecruitPage';
+import { fetchReviewsData } from './ReviewPage';
+import banner1 from '../imgs/banner4.png';
+import banner2 from '../imgs/banner5.png';
+import banner3 from '../imgs/banner6.png';
+import noImage from '../imgs/noImage.png';
+import '../Components/cover.css';
+
+SwiperCore.use([Autoplay, Pagination]);
+
+const MainContainer = styled.div`
+ font-family: 'Apple SD Gothic Neo', 'Arial', sans-serif;
+ width: 100%;
+ margin: 0 auto;
+ background-color: #ffffff;
+`;
+
+const SectionContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: auto;
+ //border: 1px solid #e1e1e1;
+ margin-bottom: 40px;
+ background-color: #ffffff;
+ //overflow: hidden;
+ border-radius: 12px;
+ //box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ position: relative;
+`;
+
+const TitleStyle = styled.h1`
+ color: #764b36;
+ font-size: 54px;
+ font-family: 'omyu_pretty';
+ margin: 60px 0 5px;
+ position: relative;
+ padding-left: 20px;
+
+ &::after {
+ position: absolute;
+ left: 0;
+ top: 10px;
+ content: "";
+ display: block;
+ width: 6px;
+ height: 90px;
+ background: #764b36;
+ }
+`;
+
+const SubTitleStyle = styled.div`
+ font-weight: bolder;
+ margin: 0 0 16px;
+ color: #555555;
+ font-size: 20px;
+`;
+
+const SwiperContainer = styled.div`
+ margin-bottom: 20px;
+ height: 450px;
+
+ @media (max-width: 1400px) {
+ height: 350px;
+ }
+ @media (max-width: 1200px) {
+ height: 280px;
+ }
+`;
+
+const SlideImage = styled.img`
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ //box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+`;
+
+const ReviewContainer = styled.div`
+ border-radius: 12px;
+ overflow: hidden;
+ background-color: #ffffff;
+ //box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+`;
+
+const ReviewCardContainer = styled.div`
+ border-radius: 12px;
+ overflow: hidden;
+ margin-bottom: 20px;
+ background-color: #ffffff;
+ display: grid;
+ //grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); // 카드의 최소 너비를 250px로 설정
+ gap: 60px;
+ position: relative;
+`;
+
+const DetailButton = styled.button`
+ text-align: right;
+ background-color: transparent;
+ color: black; // 버튼 텍스트 색상
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s;
+ &:hover {
+ color: #764b36;
+ font-weight: bold;
+ }
+`;
+
+const ReviewItem = styled.div`
+ background: #fff;
+ border: 1px solid #e1e1e1;
+ border-radius: 12px;
+ overflow: hidden;
+ cursor: pointer;
+ padding: 15px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ transition: transform 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ }
+`;
+
+const ReviewItem2 = styled.div`
+ background: #fff;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: transform 0.3s ease;
+
+ img {
+ height: 450px;
+ }
+
+ h3 {
+ font-size: 34px;
+ margin-top: 10px;
+ margin-bottom: 12px;
+ }
+ p {
+ font-size: 22px;
+ }
+`;
+
+
+const ReviewImage = styled.img`
+ width: 100%;
+ height: 200px; // 또는 원하는 높이
+ object-fit: cover;
+ border-radius: 12px;
+ margin-bottom: 15px;
+`;
+
+const ReviewTitle = styled.h3`
+ font-weight: bold;
+ color: #333;
+`;
+
+const ReviewContent = styled.p`
+ color: #666;
+`;
+const ReviewInfo = styled.p`
+ margin: 0;
+ font-size: 18px;
+ color: #777777;
+
+ span {
+ font-size: 18px;
+ }
+`;
+const Author = styled.span`
+ //display: block; // 블록 요소로 만들어 줄바꿈 효과 적용
+`;
+
+const Date = styled.span`
+ //display: block; // 블록 요소로 만들어 줄바꿈 효과 적용
+`;
-//===============================페이지 UI========================================
const MainPage = () => {
+ const [recruitments, setRecruitments] = useState([]);
+ const [themeCourses, setThemeCourses] = useState([]); // 추가된 부분
+ const [reviews, setReviews] = useState([]);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ getRecruitmentsData()
+ .then((response) => {
+ const reversedRecruitments = [...response];
+ setRecruitments(reversedRecruitments);
+ console.log(reversedRecruitments);
+ })
+ .catch((error) => console.error('Error fetching recruitments:', error));
+
+ fetchReviewsData()
+ .then((response) => {
+ setReviews(response.slice(0, 3)); // 상위 3개의 리뷰만 저장
+ })
+ .catch((error) => console.error('Error fetching reviews:', error));
+
+ // 추가된 부분: 테마별 코스 데이터 가져오기
+ fetchThemeCourses();
+ }, []);
+
+ // 추가된 함수: 테마별 코스 데이터 가져오기
+
+ const fetchThemeCourses = async () => {
+ try {
+ // 테마별 코스를 불러오는 API 요청 수행
+ const themeCoursesUrl = 'your-theme-courses-api-url';
+ const themeCoursesResponse = await axios.get(themeCoursesUrl);
+ const themeCoursesData = themeCoursesResponse.data;
+
+ // 받아온 데이터를 상태에 업데이트
+ setThemeCourses(themeCoursesData);
+ } catch (error) {
+ console.error('Error fetching theme courses:', error);
+ }
+ };
+
+ const handleReviewClick = (reviewId) => {
+ navigate(`/reviewContent/${reviewId}`);
+ }
+
+ const handleReview = () => {
+ navigate(`/review`);
+ }
+
+ const handleRecruit = () => {
+ navigate(`/recruit`);
+ }
+
+ const handleCourse = () => {
+ navigate(`/newcourse`);
+ }
return (
-
- 신규코스
- 이달의 추천코스
-
- <>
-
-
-
-
-
-
- >
-
-
-
- 추천 코스
- 일자리 및 여행 추천
-
- <>
-
-
-
- 3
- 4
- 5
-
-
- >
-
-
-
- 테마별 코스
- 원하는 테마별로 분류된 코스
-
-
- <>
-
-
-
-
-
-
- >
-
-
-
-
-
- 지역별 코스
- 원하는 테마별로 분류된 코스
-
-
- <>
-
-
-
-
-
-
-
- >
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 테마별 코스
+
+ 원하는 테마를 선택하세요
+ 더보기 >
+
+
+
+
+
+
+
+
+ 지역별 코스
+
+ 지역별로 분류된 코스
+ 더보기 >
+
+
+
+
+
+
+
+ 모집일정
+
+ 여행 계획에 맞는 일자리를 찾아보세요
+ 더보기 >
+
+ ({
+ id: recruitment.job_id,
+ title: recruitment.title.length > 25
+ ? `${recruitment.title.substring(0, 25)}...`
+ : recruitment.title,
+ date: recruitment.startDate,
+ image: recruitment.image[0]
+ }))}
+ />
+
+
- 체험 후기
- 다양한 체험 후기를 들어보세요
-
-
- {dummyReviews.map(review => (
-
+ 체험 후기
+
+ 다양한 체험 후기를 들어보세요
+ 더보기 >
+
+
+ {reviews.map((review) => (
+ handleReviewClick(review.reviewId)}>
+
+ {review.reviewTitle}
+
+ {review.reviewContent}
+
+
+ 작성자: {review.userId}
+ 작성일: {review.reviewWriteDate}
+
+
+
+
))}
-
-
+
-
+
+
);
};
-//==============================================페이지 CSS===================================================
-//=================페이지전체 컨테이너==============
-const MainContainer = styled.div`
- font-family: 'Arial', sans-serif;
-`
-
-//==================구글맵 css===================
-const containerStyle = { //지도크기 css
- width: '100%',
- // height: '400px',
- flex:1
+const SwiperSection = ({ images, sec }) => {
+ const SwiperSlideInner = styled.div`
+ overflow: hidden;
+ height: 450px;
+ border-radius: 12px;
+ transition: all 0.3s;
+
+ &:hover {
+ transform: translateY(-10px);
+ }
+
+ @media (max-width: 1400px) {
+ height: 350px;
+ }
+ @media (max-width: 1200px) {
+ height: 280px;
+ }
+ `;
+
+ return (
+
+
+ {images.map((image, index) => (
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+const RecruitmentSchedule = ({ schedule }) => {
+ const navigate = useNavigate();
+
+ const handleRecruitmentClick = (recruitmentId) => {
+ navigate(`/recruit/${recruitmentId}`);
+ };
+
+ return (
+
+ {schedule.map((item) => (
+ handleRecruitmentClick(item.id)}>
+ 0 ? item.image : noImage} alt={item.title} />
+
+
+ {item.title}
+ {item.date}
+
+
+
+ ))}
+
+ );
};
-const GoogleMapContainer = styled.div`
+
+const RecruitmentItem = styled.div`
display: flex;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
- height: 800px;
-`
-// 제목 스타일 공통으로 적용
-const TitleStyle = styled.div`
- font-size: 24px; // 통일된 글씨 크기
- margin-left: 20px; // 왼쪽 마진 추가
- background-color: white;
+ padding: 10px;
+ border-bottom: 1px solid #e1e1e1;
`;
-const SubTitleStyle = styled.div`
- font-size: 18px; // 부제목 글씨 크기
- background-color: white;
- margin-top: -10px; // 제목과의 간격 조정
+const RecruitmentTitle = styled.h3`
+ font-weight: bold;
+ color: #333;
+ width:100%;
+ overflow:hidden;
+ text-overflow:ellipsis;
+ white-space:nowrap;
`;
-// =============================================
-//==================신규코스 css==================
-const NewCourseContainer = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- height: 500px;
- border: 1px solid gray;
- padding-left: 50px;
-`
-const NewCourseTitle = styled.div`
- display: flex;
- flex: 2;
- background-color: white;
- margin-top: 30px;
- margin-bottom: -40px;
- color: #D1774C;
-`
-const NewCourseSubTitle = styled.div`
- display: flex;
- flex: 1;
- background-color: white;
- padding-top: 0;
- font-weight: bolder;
-`
+const RecruitmentDate = styled.div`
+ color: #666;
+ font-size: 20px;
+ margin-top: 1rem;
+`;
-const NewCourseImage = styled.div`
- display: flex;
- flex: 7;
- background-color: white;
-`
-//==============================================
-//==================추천코스 css===================
-const RecommendCourseContainer = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- height: 300px;
- padding-left: 50px;
-`
-const RecommendCourseTitle = styled.div`
- display: flex;
- flex: 2;
- background-color: white;
- margin-top: 30px;
- margin-bottom: -40px;
- color: #D1774C;
-`
-const RecommendCourseSubTitle = styled.div`
- display: flex;
- flex: 1;
- background-color: white;
- font-weight: bolder;
-`
-const RecommendCourseImage = styled.div`
- display: flex;
- flex: 4;
-`
-//==============================================
-//==============테마별코스=========================
-const ThemaCourseContainer = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- height: 500px;
- border: 1px solid gray;
- padding-left: 50px;
-`
-const ThemaCourseTitle = styled.div`
+const RecruitmentGrid = styled.div`
display: flex;
- flex: 2;
- background-color: white;
- margin-top: 30px;
- margin-bottom: -40px;
- color: #D1774C;
-`
-const ThemaCourseSubTitle = styled.div`
- display: flex;
- flex: 1;
- background-color: white;
- font-weight: bolder;
-`
-const ThemaCouseImage = styled.div`
- display: flex;
- flex: 7;
-`
-//==============================================
-//==================지역별 코스 css=================
-const RegionalCourseContainer = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- height: 500px;
- border: 1px solid gray;
- padding-left: 50px;
-`
-const RegionalCourseTitle = styled.div`
- display: flex;
- flex: 2;
- background-color: white;
- margin-top: 30px;
- margin-bottom: -40px;
- color: #D1774C;
-`
-const RegionalCourseSubTitle = styled.div`
- display: flex;
- flex: 1;
- background-color: white;
- font-weight: bolder;
-`
-const RegionalCouseImage = styled.div`
- display: flex;
- flex: 7;
-`
-//==============================================
-//====================체험후기====================
-const ReviewContainer = styled.div`
- display: flex;
- flex: 1;
- flex-direction: column;
- height: 800px;
- background-color: white;
- border: 1px solid grey;
- padding-left: 50px;
-`
-const ReviewTitle = styled.div`
- display: flex;
- flex: 2;
- background-color: white;
- margin-top: 30px;
- margin-bottom: -40px;
- font-size: 40px;
- color: #D1774C;
-`
-const ReviewSubTitle = styled.div`
- display: flex;
- flex: 1;
- background-color: white;
- font-weight: bolder;
-`
-const ReviewContext = styled.div`
- display: flex;
- flex-direction: column;
- flex: 7;
- background-color: white;
-`
-const Review = ({ title, content, author, date }) => {
- return (
-
- {title}
- {content}
- 작성자: {author} | 작성일: {date}
-
- );
-}
+ gap: 40px;
+`;
+const RecruitmentCard = styled.div`
+ background: #fff;
+ border-radius: 15px;
+ overflow: hidden;
+ box-shadow: 0 3px 10px 2px rgba(0, 0, 0, 0.1);
+ transition: transform 0.3s ease;
+ padding: 20px; // 내부 여백 추가
+ cursor : pointer;
+ flex: 1 0 calc((100% - 80px) / 3);
+
+ &:hover {
+ transform: translateY(-5px) scale(1.04);
+`;
-//==============================================
+const CardImage = styled.img`
+ width: 100%;
+ height: 300px; // 이미지 높이 조정
+ margin-bottom: 8px;
+ object-fit: cover;
+ border-radius: 12px;
+ transition: all 0.3s;
+`;
+
+const CardContent = styled.div`
+ padding: 15px 0;
+`;
+
+const SwiperStyle = styled(Swiper)`
+ height: 500px;
+`;
+
+const StyledImage = styled.img`
+ width: 100%;
+ object-fit: cover;
+`;
-export default MainPage;
\ No newline at end of file
+export default MainPage;
diff --git a/hansoyeon/src/Pages/MemberManagePage.js b/hansoyeon/src/Pages/MemberManagePage.js
old mode 100644
new mode 100755
index 1253a496a..3f38311dc
--- a/hansoyeon/src/Pages/MemberManagePage.js
+++ b/hansoyeon/src/Pages/MemberManagePage.js
@@ -3,6 +3,8 @@ import styled from 'styled-components';
import {useNavigate} from 'react-router-dom';
import company from "../imgs/company.png"
import member from "../imgs/member.png"
+import blacklist from "../imgs/Blacklist_logo.png"
+import Footer from "../Components/Footer";
const SignupPage = (props) => {
const navigate = useNavigate();
@@ -14,6 +16,11 @@ const SignupPage = (props) => {
navigate("/generalManage")
};
+ const handleBlackListImageClick = () => {
+ navigate("/BlackListManage")
+ };
+
+
return (
회원 관리
@@ -24,7 +31,13 @@ const SignupPage = (props) => {
+
+
+
+
+
+
);
};
@@ -34,8 +47,9 @@ const StyledContainer = styled.div`
flex-direction: column;
justify-content: center;
align-items: center;
- height: 80vh;
+ height: 90vh;
background-color: #ffffff;
+ margin-top: 50px;
`;
const Title = styled.div`
@@ -43,8 +57,9 @@ const Title = styled.div`
margin-bottom: 80px;
align-items:center;
justify-content: center;
- font-size: 30px;
+ font-size: 44px;
font-weight: 700;
+ font-family: 'omyu_pretty';
`;
const ImageContainer = styled.div`
@@ -100,4 +115,9 @@ const FormBox = styled.div`
position: relative;
`;
+const StyledFooter = styled.footer`
+ width: 100%;
+ margin-top: auto;
+`;
+
export default SignupPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/MyInfoPage.js b/hansoyeon/src/Pages/MyInfoPage.js
old mode 100644
new mode 100755
index ea5d644eb..064900525
--- a/hansoyeon/src/Pages/MyInfoPage.js
+++ b/hansoyeon/src/Pages/MyInfoPage.js
@@ -8,6 +8,7 @@ import axios from "axios";
import {Badge} from "react-bootstrap";
import logo from "../imgs/seungjun.jpg";
import iu from "../imgs/iu2.jpeg"
+import Footer from "../Components/Footer";
const MyInfoPage = (props) => {
const navigate = useNavigate();
@@ -133,15 +134,20 @@ const MyInfoPage = (props) => {
+
+
+
);
};
const StyledContainer = styled.div`
display: flex;
+ flex-direction: column; /* 수직 방향으로 컨테이너 아이템 정렬 */
justify-content: center;
align-items: center;
- height: 100vh;
+ height: 90vh;
+ margin-top: 50px;
`;
const BoxContainer = styled.div`
@@ -149,7 +155,7 @@ const BoxContainer = styled.div`
justify-content: space-around;
align-items: center;
width: 100%;
- max-width: 1000px;
+ max-width: 1000px;
margin: 0 auto;
`;
@@ -176,7 +182,7 @@ const ImageBox = styled.div`
`;
const LargeImage = styled.img`
- max-width: 100%;
+ max-width: 124%;
max-height: 90%;
border-radius: 10px;
object-fit: cover;
@@ -228,8 +234,9 @@ const EditProfileButton = styled.button`
color: #381E1F;
border: none;
cursor: pointer;
- font-size: 15px;
+ font-size: 18px;
margin-top: 5px;
+ font-family: 'omyu_pretty';
&:hover {
background-color: #e6d700;
}
@@ -284,5 +291,12 @@ const SelfIntroductionTextarea = styled.textarea`
}
`;
+const StyledFooter = styled.footer`
+ padding: 10px;
+ text-align: center;
+ width: 100%;
+ margin-top: auto; /* 다른 요소 아래에 위치하도록 설정 */
+`;
+
export default MyInfoPage;
diff --git a/hansoyeon/src/Pages/NewPage.js b/hansoyeon/src/Pages/NewPage.js
old mode 100644
new mode 100755
index 52f5b2f05..ea1c55af2
--- a/hansoyeon/src/Pages/NewPage.js
+++ b/hansoyeon/src/Pages/NewPage.js
@@ -10,6 +10,7 @@ import banner from "../imgs/banner.png"
import banner2 from "../imgs/banner2.png"
import banner3 from "../imgs/banner3.png"
import {useNavigate} from "react-router-dom";
+import Footer from "../Components/Footer";
const NewPage = (props) => {
const navigate = useNavigate();
@@ -481,9 +482,11 @@ const NewPage = (props) => {
{spot.title}
-
- {isFavorited ? '♥' : '♡'}
-
+ {user &&
+
+ {isFavorited ? '♥' : '♡'}
+
+ }
주소 : {spot.addr1}
@@ -663,7 +666,7 @@ const NewPage = (props) => {
- 테마별 코스
+
@@ -678,12 +681,14 @@ const NewPage = (props) => {
+
{renderRadioButtons()}
검색
+
{Array.isArray(searchResults) && searchResults.length > 0 ? (
searchResults.map((spot, index) => (
@@ -705,6 +710,7 @@ const NewPage = (props) => {
setContentPageNo(prev => Math.max(prev - 1, 1))}>이전
setContentPageNo(prev => prev + 1)}>다음
+
>
);
@@ -715,7 +721,7 @@ const NewPage = (props) => {
- 지역별 코스
+
@@ -773,6 +779,7 @@ const NewPage = (props) => {
setPageNo(prev => Math.max(prev - 1, 1))}>이전
setPageNo(prev => prev + 1)}>다음
+
>
);
@@ -783,7 +790,7 @@ const NewPage = (props) => {
- 나만의 코스
+
@@ -823,6 +830,7 @@ const NewPage = (props) => {
{myModalVisible && selectedMySpot && (
setMyModalVisible(false)} />
)}
+
>
);
@@ -907,11 +915,13 @@ const NewCourseContainer = styled.div`
align-items: center;
`;
const NewCourseTitle = styled.div`
- font-size: 38px;
+ font-size: 44px;
color: #663399;
+ height: 190px;
font-weight: 800;
margin-top: 120px;
margin-bottom: 180px;
+ font-family: 'omyu_pretty';
//background-color: green;
`;
@@ -942,9 +952,10 @@ const TabButton = styled.button`
background: none;
border: none;
cursor: pointer;
- font-size: 20px;
+ font-size: 26px;
margin: 0 10px;
position: relative;
+ font-family: 'omyu_pretty';
// hover 효과
&:hover {
@@ -964,7 +975,7 @@ const TabButton = styled.button`
// 선택된 탭의 스타일
&.active {
color: orange;
- font-size: 24px;
+ font-size: 28px;
font-weight: 700;
&::after {
content: "";
@@ -986,12 +997,20 @@ const AreaContentContainer = styled.div`
align-items: center;
`;
+const SearchSContainer = styled.div`
+display: flex;
+ flex-direction: row;
+ justify-content: center;
+ width: 100%;
+`
const GridContainer = styled.div`
display: grid;
+ place-items: center;
grid-template-columns: repeat(3, 1fr);
- grid-gap: 20px;
- width: 80%;
+ grid-gap: -20px;
+ width: 65%;
+ height: 82%;
margin: 0 auto;
`;
@@ -1011,37 +1030,42 @@ const GridTitle = styled.h2`
`;
const GridItem = styled.div`
- border: 1px solid #ddd;
- padding: 15px;
- text-align: center;
- background-color: #f9f9f9;
- box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.1);
- border-radius: 10px;
- cursor: pointer;
+ width: 320px; // 신용카드 가로 크기에 맞춤
+ height: 400px; // 신용카드 세로 크기에 맞춤 (320px 가로에 대한 비율적 세로 크기)
+ border-radius: 15px; // 신용카드 모서리의 일반적인 둥근 정도
+ box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); // 신용카드에 그림자 효과 적용
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: white; // 배경색은 하얀색으로 설정
+ color: #333; // 텍스트 색상은 어두운 회색으로 설정
+ font-family: 'Arial', sans-serif; // 신용카드에 자주 사용되는 글꼴 설정
+ padding: 20px; // 내부 여백
+ margin: 20px; // 외부 여백
+ box-sizing: border-box; // 박스 크기 계산 방법을 border-box로 설정
`;
const StyledImage = styled.img`
width: 100%;
- height: 150px;
+ height: 280px;
object-fit: cover;
margin-bottom: 10px;
border-radius: 5px;
`;
const SearchBoxContainer = styled.div`
- margin-bottom: 20px;
- padding: 20px;
+ padding: 10px;
border-radius: 10px;
background-color: #f5f5f5;
box-shadow: 0px 2px 4px rgba(0,0,0,0.1);
- width: 80%;
+ width: 60%;
justify-content: center;
align-items: center;
`;
const SearchContainer = styled.div`
display: flex;
- margin-bottom: 10px;
justify-content: center;
`;
@@ -1062,6 +1086,9 @@ const SearchButton = styled.button`
background-color: #4CAF50;
color: white;
cursor: pointer;
+ font-size: 18px;
+ font-family: 'omyu_pretty';
+
&:hover {
background-color: #45a049;
}
@@ -1089,7 +1116,8 @@ const PageButton = styled.button`
const SearchResultText = styled.div`
color: #333;
- font-size: 17px;
+ font-size: 20px;
+ font-family: 'omyu_pretty';
`;
const StyledSelect = styled.select`
@@ -1100,6 +1128,8 @@ const StyledSelect = styled.select`
box-shadow: 0px 2px 4px rgba(0,0,0,0.1);
margin-right: 10px;
cursor: pointer;
+ font-size: 18px;
+ font-family: 'omyu_pretty';
&:hover {
border-color: #aaa;
@@ -1115,6 +1145,8 @@ const RadioButtonLabel = styled.label`
border: 2px solid #ddd;
cursor: pointer;
font-weight: 500;
+ font-size: 18px;
+ font-family: 'omyu_pretty';
&:hover {
background-color: #e8e8e8;
@@ -1128,6 +1160,8 @@ const RadioButton = styled.input`
color: #0000ff;
font-weight: bold;
border-color: #4CAF50;
+ font-size: 20px;
+ font-family: 'omyu_pretty';
}
`;
diff --git a/hansoyeon/src/Pages/Payment.js b/hansoyeon/src/Pages/Payment.js
new file mode 100644
index 000000000..c86bbfa0b
--- /dev/null
+++ b/hansoyeon/src/Pages/Payment.js
@@ -0,0 +1,344 @@
+import React, { useEffect, useState } from "react";
+import { useCookies } from "react-cookie";
+import { useLocation } from "react-router-dom";
+import axios from "axios";
+import styled from "styled-components";
+import { useNavigate } from "react-router-dom";
+import banner2 from "../imgs/banner2.png";
+import {useUserStore} from "../stores";
+const Payment = () => {
+ // 값 가져오기
+ const { state } = useLocation();
+ const [paymentData, setPaymentData] = useState(null);
+ const [money, setMoney] = useState([]);
+ const [selectedLevel, setSelectedLevel] = useState(null);
+ const [fetchedUser, setFetchedUser] = useState();
+ const [IMP, setIMP] = useState(null);
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const navigate = useNavigate();
+ const userType = cookies.userType;
+
+ const levels = [
+ {price: 100, order: 1},
+ {price: 120, order: 2},
+ {price: 130, order: 3},
+ {price: 140, order: 4},
+ {price: 150, order: 5},
+ {price: 160, order: 6}
+ ]
+
+ const handlePayment = (order) => {
+ const selectedPrice = levels.find((level) => level.order === order)?.price;
+
+ // 가맹점 식별
+ const { IMP } = window;
+
+ const data = {
+ pg: "html5_inicis",
+ pay_method: "card",
+ merchant_uid: `mid_${new Date().getTime()}`, // 주문번호
+ amount: selectedPrice,
+ name: "한소연 매칭서비스 금액",
+ buyer_name: user.companyName,
+ buyer_email: user.providerEmail,
+ };
+
+ IMP.request_pay(data, (response) => callback(response, data));
+ };
+ // 결제창 띄우는 기능
+ useEffect(() => {
+ const script = document.createElement("script");
+ script.src = "https://cdn.iamport.kr/js/iamport.payment-1.1.8.js";
+ script.async = true;
+
+ script.onload = () => {
+ setIMP(window.IMP);
+ window.IMP.init("imp64486410");
+ };
+
+ document.body.appendChild(script);
+
+ return () => {
+ document.body.removeChild(script);
+ };
+
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ setUser(fetchedUser)
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ fetchCompanyPayments(user.providerEmail)
+ }, [user]);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ // 결제 성공 여부
+ const callback = async (response, paymentData) => {
+ const { success, error_msg } = response;
+ if (success) {
+ alert("결제 성공");
+ if (paymentData) {
+ try {
+ const paid = new Date(response.paid_at * 1000);
+
+ console.log(paid);
+ let pointsEarned;
+
+ if (paymentData.amount === 100) {
+ pointsEarned = 100;
+ }
+ else if (paymentData.amount === 120) {
+ pointsEarned = 550;
+ }
+ else if (paymentData.amount === 130) {
+ pointsEarned = 1200;
+ }
+ await axios.post(`http://localhost:8050/api/payment/saveClass`, {
+ email: String(paymentData.buyer_email), //회사이메일
+ company: String(paymentData.buyer_name), // 회사이름
+ amount: paymentData.amount, //구매 가격
+ merchant_uid: String(paymentData.merchant_uid), //주문번호
+ apply_num: String(response.apply_num), //신용카드 승인번호
+ paid_at: paid, //결제시간
+ points: pointsEarned //적립된 포인트 추가
+
+ }).then((res) => {
+ console.log("서버로 부터 받는 데이터: ", res.data);
+ if (res.data !== "400") {
+ fetchCompanyPayments(paymentData.buyer_email);
+ }
+ });
+ const updatedPoints = money && money.length > 0 ? money[0].points + pointsEarned : pointsEarned;
+ setMoney([{ points: updatedPoints }]);
+ } catch (error) {
+ console.log(error);
+ }
+ } else {
+ console.error("결제 정보가 부족합니다.");
+ }
+ } else {
+ alert(`결제 실패: ${error_msg}`);
+ }
+ console.log(response);
+ };
+
+
+ // 해당 기업 결제 포인트
+ const fetchCompanyPayments = async (email) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/payment/company/${email}`);
+ const pointPayments = response.data;
+ console.log(pointPayments);
+ setMoney(pointPayments);
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+
+
+ 결제 (현재 포인트: {money && money.length > 0 ? money[0].points : 0} Point)
+
+
+
+
+
+
+ 일반
+ (공고 전용)
+
+
+
+
+
+
+ 10000Point / 1만원
+
+
+
+
+
+
+ 55000Point / 5만원
+
+
+
+
+
+
+ 120000Point / 10만원
+
+
+
+
+
+
+
+
+
+
+ 급여
+ (급여 전용)
+
+
+
+
+
+
+ 10만원
+
+
+
+
+
+
+ 50만원
+
+
+
+
+
+
+ 100만원
+
+
+
+
+
+
+
+ );
+};
+
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ width: 100%;
+
+`;
+
+const TitleContainer = styled.div`
+ display: flex;
+ height: 50px;
+ width: 100%;
+ justify-content: center;
+ margin-top: 5rem;
+
+`
+const Title = styled.div`
+ display: flex;
+ width: 70%;
+ height: 50px;
+ font-size: 32px;
+ font-weight: bold;
+ align-items: center;
+ margin-left: 3rem;
+`
+const NormalContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 2rem;
+ height: 300px;
+ width: 100%;
+`
+const NormalCenter = styled.div`
+ background-color: #f0f0f0;
+ border-radius: 1rem;
+ width: 70%;
+ display: flex;
+ flex-direction: column;
+ height: 300px;
+ justify-content: center;
+ align-items: center;
+`
+const NormalMain = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 280px;
+ width: 100%;
+`
+const NormalTitle = styled.div`
+ display: flex;
+ flex-direction: row;
+ height: 50px;
+ width: 100%;
+ font-size: 28px;
+ font-weight: 600;
+ align-items: center;
+ margin-left: 2rem;
+
+ h2 {
+ font-size: 28px;
+ font-weight: 600;
+ }
+ h3 {
+ display: flex;
+ font-size: 20px;
+ }
+`
+const NormalContent = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ width: 100%;
+ height: 230px;
+`
+const NormalFirst = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 230px;
+ width: 30%;
+
+ img {
+ margin-top: 10px;
+ width: 100%;
+ height: 150px;
+ }
+ h2 {
+ margin-top: 10px;
+ font-size: 24px;
+ }
+`
+const ImgContainer = styled.div`
+`
+const FirstContainer = styled.div`
+ background-color: black;
+`
+const Button = styled.button`
+ display: flex;
+ height: 30px;
+ background-color: orange;
+ width: 120px;
+ font-size: 18px;
+ border: 1px solid gray;
+ border-radius: 5px;
+ justify-content: center;
+ margin-bottom: 10px;
+`
+
+export default Payment;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/RecommendPage.js b/hansoyeon/src/Pages/RecommendPage.js
old mode 100644
new mode 100755
index ce2e2ee55..7fa1c53d1
--- a/hansoyeon/src/Pages/RecommendPage.js
+++ b/hansoyeon/src/Pages/RecommendPage.js
@@ -1,15 +1,15 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { XMLParser } from 'fast-xml-parser';
-import styled from "styled-components";
-import noImage from "../imgs/noImage.png"
+import styled from 'styled-components';
+import noImage from '../imgs/noImage.png';
+import Footer from '../Components/Footer';
const RecommendPage = () => {
const [touristSpots, setTouristSpots] = useState([]);
- const [keyword, setKeyword] = useState("");
+ const [keyword, setKeyword] = useState('');
const [pageNo, setPageNo] = useState(1);
-
- const serviceKey = 'YRUALCBQQWvU6w/tG7ZkUtWtjAeaO9bJjyummGjvfF9SjR0QYO+CRveierZlwe97v5toXybLb6aoFCl1sZ8q4Q==';
+ const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
fetchData(keyword, pageNo);
@@ -17,8 +17,8 @@ const RecommendPage = () => {
const fetchData = async (searchKeyword, pageNo, retryCount = 0) => {
try {
- const encodedServiceKey = encodeURIComponent(serviceKey);
- const encodedKeyword = encodeURIComponent(searchKeyword || "강원");
+ const encodedServiceKey = encodeURIComponent('YRUALCBQQWvU6w/tG7ZkUtWtjAeaO9bJjyummGjvfF9SjR0QYO+CRveierZlwe97v5toXybLb6aoFCl1sZ8q4Q==');
+ const encodedKeyword = encodeURIComponent(searchKeyword || '강원');
const url = `http://apis.data.go.kr/B551011/KorService1/searchKeyword1?serviceKey=${encodedServiceKey}&MobileApp=AppTest&MobileOS=ETC&pageNo=${pageNo}&numOfRows=12&listYN=Y&keyword=${encodedKeyword}`;
const response = await axios.get(url);
@@ -28,12 +28,12 @@ const RecommendPage = () => {
if (jsonObj && jsonObj.response && jsonObj.response.body && jsonObj.response.body.items) {
const spots = jsonObj.response.body.items.item;
setTouristSpots(spots);
+ setTotalPages(jsonObj.response.body.totalCount);
} else {
- // 유효하지 않은 응답일 경우 재시도
- if (retryCount < 3) { // 최대 3번 재시도
+ if (retryCount < 3) {
fetchData(searchKeyword, pageNo, retryCount + 1);
} else {
- console.error("Invalid API response");
+ console.error('Invalid API response');
}
}
} catch (error) {
@@ -45,49 +45,155 @@ const RecommendPage = () => {
};
return (
-
+
+ setPageNo((prev) => Math.max(prev - 1, 1))} direction="prev">
+ 이전
+
+
+ {pageNo} 페이지 / 총 {totalPages} 페이지
+
+ setPageNo((prev) => prev + 1)} direction="next">
+ 다음
+
+
+
+
+
+
);
};
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 100vh;
+ padding: 20px;
+ margin-bottom: -20px; /* Remove the margin at the bottom */
+`;
+
+const SearchContainer = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+
+ input {
+ padding: 10px;
+ font-size: 16px;
+ margin-right: 10px;
+ }
+
+ button {
+ padding: 10px 20px;
+ background-color: #0095f6;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ }
+`;
+
const GridContainer = styled.div`
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- grid-gap: 10px;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-gap: 20px;
+ flex-grow: 1;
`;
const GridItem = styled.div`
- border: 1px solid #ddd;
- padding: 10px;
- text-align: center;
+ border: 1px solid #dbdbdb;
+ border-radius: 8px;
+ overflow: hidden;
+`;
+
+const SpotTitle = styled.h3`
+ padding: 10px;
+ font-size: 18px;
+ color: #262626;
+`;
+
+const SpotImage = styled.img`
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+`;
+
+const LoadingMessage = styled.p`
+ text-align: center;
+ font-size: 18px;
+ color: #262626;
+`;
+
+const PageButtons = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 20px;
+`;
+
+const PageButton = styled.button`
+ padding: 10px 20px;
+ background-color: #0095f6;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0077cc;
+ }
+ margin-right: ${(props) => (props.direction === 'next' ? '0' : '15px')};
+`;
+
+const PageInfo = styled.div`
+ font-size: 16px;
+ color: #333;
+ margin: 0 15px;
+`;
+
+
+const StyledFooter = styled.div`
+ width: 104%;
+ display: flex;
+ justify-content: center;
+ padding: 10px;
+ box-sizing: border-box;
`;
-const StyledImage = styled.img`
- width: 100%;
- height: 200px;
- object-fit: cover;
+const SearchButton = styled.button`
+ padding: 10px 20px;
+ background-color: #0095f6;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
`;
export default RecommendPage;
diff --git a/hansoyeon/src/Pages/RecruitApplicationHistoryPage.js b/hansoyeon/src/Pages/RecruitApplicationHistoryPage.js
new file mode 100644
index 000000000..28cf3ed00
--- /dev/null
+++ b/hansoyeon/src/Pages/RecruitApplicationHistoryPage.js
@@ -0,0 +1,289 @@
+import React, {useEffect, useRef, useState} from 'react';
+import axios from "axios"
+import styled, { keyframes } from "styled-components";
+import logo from "../imgs/logo2.png";
+import {Button} from "react-bootstrap";
+import {useNavigate, useLocation} from "react-router-dom";
+import {useCookies} from "react-cookie";
+import {useUserStore} from "../stores";
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+
+const RecruitApplicationPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [matchings, setMatchings] = useState([]);
+ const [providerPhone, setProviderPhone] = useState('');
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ fetchMatchings(user.userId);
+ }
+ }, [user]);
+
+ const fetchMatchings = async (userId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ const currentDate = new Date();
+ const validMatchings = response.data.data.filter(matching => {
+ const endDate = new Date(matching.recruitment.jobEndDate);
+ return endDate >= currentDate;
+ });
+ console.log(validMatchings)
+ setMatchings(validMatchings);
+ } catch (error) {
+ console.error("Error fetching matchings:", error);
+ }
+ };
+
+ const handlePrev = () => {
+ navigate("/recruit")
+ };
+
+ const handleMain = () => {
+ navigate("/")
+ };
+
+ const handleJobView = (jobId) => {
+ navigate(`/recruit/${jobId}`);
+ };
+
+ const handleCancelApplication = async (matching) => {
+ if (window.confirm("신청을 취소하시겠습니까?")) {
+ try {
+ const requestBody = {
+ recruitmentId: matching.recruitment.jobId,
+ userId: user.userId
+ };
+
+ // 공고 올린 사람의 정보를 가져옵니다.
+ const providerResponse = await axios.get(`http://localhost:8050/api/auth/provider/${matching.recruitment.jobProviders}`);
+ if (providerResponse.status === 200) {
+ console.log(providerResponse.data);
+ setProviderPhone(providerResponse.data.companyTel);
+
+ // 신청 취소 요청을 보냅니다.
+ const response = await axios({
+ method: 'delete',
+ url: 'http://localhost:8050/api/matchings',
+ data: requestBody,
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ fireNotificationWithTimeout('공고 취소 완료', 5000, {
+ body: `[${matching.recruitment.jobTitle}] 취소 완료`
+ });
+
+ if (response.status === 200) {
+ setMatchings(prevMatchings => prevMatchings.filter(m => m.matchingId !== matching.matchingId));
+ alert("신청이 취소되었습니다.");
+ }
+ }
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationCancel", {
+ phone: user.userPhone,
+ jobTitle: matching.recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationCompanyCancel", {
+ phone: providerPhone,
+ jobTitle: matching.recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ } catch (error) {
+ console.error("Error cancelling application:", error);
+ alert("신청 취소에 실패했습니다.");
+ }
+ }
+ };
+
+ return (
+
+
+ 공고 신청 내역
+
+ {user?.userName}님의 공고신청내역입니다.
+
+ {matchings.map(matching => (
+ handleJobView(matching.recruitment.jobId)}
+ >
+ {matching.recruitment.jobTitle}
+ {matching.status === "REQUESTED" ?
+ <>
+ 승인 여부: 비승인
+ {
+ e.stopPropagation(); // Prevents triggering the onClick of MatchingItem
+ handleCancelApplication(matching);
+ }}>
+ 신청 취소
+
+ >
+ :
+ 승인 여부: 승인
+ }
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+const fadeIn = keyframes`
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+`;
+
+const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background-color: #f4f4f4;
+ animation: ${fadeIn} 1s ease-out;
+`;
+
+const FormBox = styled.div`
+ padding: 40px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: #ffffff;
+ border-radius: 15px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ width: 90%;
+ max-width: 450px;
+`;
+
+const LogoImg = styled.img`
+ height: 24vh;
+ width: auto;
+ margin-bottom: 20px;
+`;
+
+const Title = styled.h3`
+ color: #333;
+ margin-bottom: 10px;
+ font-weight: bold;
+`;
+
+const SubTitle = styled.h6`
+ color: #666;
+ text-align: center;
+ margin: 5px 0;
+`;
+
+const ButtonContainer = styled.div`
+
+`;
+
+const MatchingList = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ margin-top: 20px;
+`;
+
+const MatchingItem = styled.div`
+ background: #fff;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ padding: 15px;
+ transition: transform 0.2s ease-in-out;
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+`;
+
+const JobTitle = styled.h5`
+ font-size: 1.2rem;
+ color: #333;
+ margin-bottom: 10px;
+`;
+
+const Status = styled.span`
+ font-size: 1rem;
+ color: #666;
+`;
+
+const CancelButton = styled.button`
+ padding: 8px 15px;
+ background-color: #ff6b6b;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-weight: bold;
+ margin-left: 10px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #ff4747;
+ }
+
+ &:focus {
+ outline: none;
+ }
+`;
+
+
+export default RecruitApplicationPage;
diff --git a/hansoyeon/src/Pages/RecruitApplicationPage.js b/hansoyeon/src/Pages/RecruitApplicationPage.js
new file mode 100644
index 000000000..7d386c5d6
--- /dev/null
+++ b/hansoyeon/src/Pages/RecruitApplicationPage.js
@@ -0,0 +1,289 @@
+import React, {useEffect, useRef, useState} from 'react';
+import axios from "axios"
+import styled, { keyframes } from "styled-components";
+import logo from "../imgs/logo2.png";
+import {Button} from "react-bootstrap";
+import {useNavigate, useLocation} from "react-router-dom";
+import {useCookies} from "react-cookie";
+import {useUserStore} from "../stores";
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+
+const RecruitApplicationPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [matchings, setMatchings] = useState([]);
+ const [providerPhone, setProviderPhone] = useState('');
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ fetchMatchings(user.userId);
+ }
+ }, [user]);
+
+ const fetchMatchings = async (userId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ const currentDate = new Date();
+ const validMatchings = response.data.data.filter(matching => {
+ const endDate = new Date(matching.recruitment.jobEndDate);
+ return endDate >= currentDate;
+ });
+ console.log(validMatchings)
+ setMatchings(validMatchings);
+ } catch (error) {
+ console.error("Error fetching matchings:", error);
+ }
+ };
+
+ const handlePrev = () => {
+ navigate("/recruit")
+ };
+
+ const handleMain = () => {
+ navigate("/")
+ };
+
+ const handleJobView = (jobId) => {
+ navigate(`/recruit/${jobId}`);
+ };
+
+ const handleCancelApplication = async (matching) => {
+ if (window.confirm("신청을 취소하시겠습니까?")) {
+ try {
+ const requestBody = {
+ recruitmentId: matching.recruitment.jobId,
+ userId: user.userId
+ };
+
+ // 공고 올린 사람의 정보를 가져옵니다.
+ const providerResponse = await axios.get(`http://localhost:8050/api/auth/provider/${matching.recruitment.jobProviders}`);
+ if (providerResponse.status === 200) {
+ console.log(providerResponse.data);
+ setProviderPhone(providerResponse.data.companyTel);
+
+ // 신청 취소 요청을 보냅니다.
+ const response = await axios({
+ method: 'delete',
+ url: 'http://localhost:8050/api/matchings',
+ data: requestBody,
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ fireNotificationWithTimeout('공고 취소 완료', 5000, {
+ body: `[${matching.recruitment.jobTitle}] 취소 완료`
+ });
+
+ if (response.status === 200) {
+ setMatchings(prevMatchings => prevMatchings.filter(m => m.matchingId !== matching.matchingId));
+ alert("신청이 취소되었습니다.");
+ }
+ }
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationCancel", {
+ phone: user.userPhone,
+ jobTitle: matching.recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationCompanyCancel", {
+ phone: providerPhone,
+ jobTitle: matching.recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ } catch (error) {
+ console.error("Error cancelling application:", error);
+ alert("신청 취소에 실패했습니다.");
+ }
+ }
+ };
+
+ return (
+
+
+ 공고 신청 내역
+
+ {user?.userName}님의 공고신청내역입니다.
+
+ {matchings.map(matching => (
+ handleJobView(matching.recruitment.jobId)}
+ >
+ {matching.recruitment.jobTitle}
+ {matching.status === "REQUESTED" ?
+ <>
+ 승인 여부: 비승인
+ {
+ e.stopPropagation(); // Prevents triggering the onClick of MatchingItem
+ handleCancelApplication(matching);
+ }}>
+ 신청 취소
+
+ >
+ :
+ 승인 여부: 승인
+ }
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+const fadeIn = keyframes`
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+`;
+
+const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background-color: #f4f4f4;
+ animation: ${fadeIn} 1s ease-out;
+`;
+
+const FormBox = styled.div`
+ padding: 40px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background: #ffffff;
+ border-radius: 15px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+ width: 90%;
+ max-width: 450px;
+`;
+
+const LogoImg = styled.img`
+ height: 24vh;
+ width: auto;
+ margin-bottom: 20px;
+`;
+
+const Title = styled.h3`
+ color: #333;
+ margin-bottom: 10px;
+ font-weight: bold;
+`;
+
+const SubTitle = styled.h6`
+ color: #666;
+ text-align: center;
+ margin: 5px 0;
+`;
+
+const ButtonContainer = styled.div`
+
+`;
+
+const MatchingList = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ gap: 20px;
+ margin-top: 20px;
+`;
+
+const MatchingItem = styled.div`
+ background: #fff;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ padding: 15px;
+ transition: transform 0.2s ease-in-out;
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+`;
+
+const JobTitle = styled.h5`
+ font-size: 1.2rem;
+ color: #333;
+ margin-bottom: 10px;
+`;
+
+const Status = styled.span`
+ font-size: 1rem;
+ color: #666;
+`;
+
+const CancelButton = styled.button`
+ padding: 8px 15px;
+ background-color: #ff6b6b;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-weight: bold;
+ margin-left: 10px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #ff4747;
+ }
+
+ &:focus {
+ outline: none;
+ }
+`;
+
+
+export default RecruitApplicationPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/RecruitPage.js b/hansoyeon/src/Pages/RecruitPage.js
old mode 100644
new mode 100755
index 2d67ae7bb..a5fa0af06
--- a/hansoyeon/src/Pages/RecruitPage.js
+++ b/hansoyeon/src/Pages/RecruitPage.js
@@ -1,121 +1,678 @@
-import React, { useEffect, useState } from "react";
-import { useNavigate } from 'react-router-dom'; // useNavigate 훅 추가
-
-import {useCookies} from "react-cookie";
+import React, { useEffect, useState, useCallback } from "react";
+import { useNavigate } from 'react-router-dom';
+import { useCookies } from "react-cookie";
import axios from "axios";
+import Pagination from '../Components/Pagination';
+import Footer from "../Components/Footer";
+import App from "../App.css";
+import { useRecruitments } from '../hooks/useRecruitments';
+import { useUserStore } from "../stores";
import styled from "styled-components";
+import { Bars } from 'react-loader-spinner';
-const RecruitPage = () => {
+const RecruitPage = (props) => {
const navigate = useNavigate();
- const [cookies, sestCookie, removeCookie] = useCookies(['token']);
- const [recruitments, setRecruitments] = useState([]);
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const { user, setUser } = useUserStore();
+ const userType = cookies.userType;
+ const { recruitments, isLoading } = useRecruitments();
+ const [isCompany, setIsCompany] = useState(false);
+ const [detailData, setDetailData] = useState(null);
+
+
+
+
+ // 페이지네이션
const [currentPage, setCurrentPage] = useState(1);
- const itemsPerPage = 10;
+ const itemsPerPage = 8; // 변경: 페이지당 아이템 개수를 8로 설정
+
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = recruitments.slice(indexOfFirstItem, indexOfLastItem);
- const [isAdmin, setIsAdmin] = useState(false);
- const [detailData, setDetailData] = useState(null);
+
+ const regions = ["지역 선택", "서울특별시", "인천광역시", "대전광역시", "광주광역시",
+ "대구광역시", "부산광역시", "경기도", "강원도", "충청북도",
+ "충청남도", "전라북도", "전라남도", "경상북도", "경상남도", "제주특별자치도"];
+
+ const [selectedRegion, setSelectedRegion] = useState(regions[0]);
+
+ const handleRegionChange = (event) => {
+ setSelectedRegion(event.target.value);
+ };
+
+ const handlePageChange = (pageNumber) => {
+ setCurrentPage(pageNumber);
+ };
+
+ //라디오버튼
+ const [selectedCategory, setSelectedCategory] = useState("");
+
+ const handleCategoryChange = (category) => {
+ setSelectedCategory(category);
+ }
+
+
+ const handleLogout = useCallback(() => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ }, [removeCookie, setUser, navigate]);
+
//글 목록 띄우기
useEffect(() => {
- axios.get('http://localhost:8050/api/recruitments')
- .then(response => {
- // 받아온 목록을 오름차순으로 정렬
- const reversedRecruitments = [...response.data];
- setRecruitments(reversedRecruitments);
- console.log(reversedRecruitments)
- })
- .catch(error => console.error('Error fetching recruitments:', error));
- }, []);
+ if (cookies.token) {
+
+ if(userType === "company"){
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+
+ setUser(fetchedUser);
+ setIsCompany(true)
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }else{
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }
+ }, [cookies.token, userType, setUser, navigate, removeCookie]);
+
+ useEffect(() => {
+ const currentDate = new Date();
+
+ if (user && userType !== "company") {
+ axios.get(`http://localhost:8050/api/blacklists/isUserInBlacklist`, {
+ params: { userId: user.userId }
+ }).then(response => {
+ if (response.data.data) {
+ // 사용자가 블랙리스트에 있는 경우
+ axios.get(`http://localhost:8050/api/blacklists/user/${user.userId}`)
+ .then(response => {
+ const blacklistedProviders = response.data.data.map(blacklist => blacklist.provider.providerId);
+
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+ for(let i=0; i<100; i++) {
+ console.log(i);
+ }
+ console.log("start");
+ const filteredRecruitments = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate && !blacklistedProviders.includes(recruitment.providers);
+
+ }).reverse();
+ console.log("end");
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ })
+ .catch(error => console.error('Error fetching blacklisted providers:', error));
+ } else {
+ // 사용자가 블랙리스트에 없는 경우
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+
+ console.log('2');
+ const validRecruits = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ }
+ }).catch(error => {
+ console.error('Error checking user blacklist status:', error);
+ });
+ } else {
+ // 회사 사용자의 경우 블랙리스트 필터링 없이 모든 공고 표시
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+
+ console.log('3');
+ const validRecruits = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ }
+ }, [user]);
+
+ const fetchRecruitments = useCallback(async () => {
+
+ const currentDate = new Date();
+ if (user && userType !== "company") {
+ axios.get(`http://localhost:8050/api/blacklists/isUserInBlacklist`, {
+ params: { userId: user.userId }
+ }).then(response => {
+ if (response.data.data) {
+ // 사용자가 블랙리스트에 있는 경우
+ axios.get(`http://localhost:8050/api/blacklists/user/${user.userId}`)
+ .then(response => {
+ const blacklistedProviders = response.data.data.map(blacklist => blacklist.provider.providerId);
+
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+
+ console.log('4')
+ const filteredRecruitments = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate && !blacklistedProviders.includes(recruitment.providers);
+ }).reverse();
+
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ })
+ .catch(error => console.error('Error fetching blacklisted providers:', error));
+ } else {
+ // 사용자가 블랙리스트에 없는 경우
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+
+ console.log('5')
+ const validRecruits = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ }
+ }).catch(error => {
+ console.error('Error checking user blacklist status:', error);
+ });
+ } else {
+ // 회사 사용자의 경우 블랙리스트 필터링 없이 모든 공고 표시
+ axios.get('http://localhost:8050/api/recruitments')
+ .then(response => {
+
+ console.log('6')
+ const validRecruits = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+
+ })
+ .catch(error => console.error('Error fetching recruitments:', error));
+ }
+ }, [user, userType]);
+
+ useEffect(() => {
+ fetchRecruitments();
+ }, [fetchRecruitments]);
+
+
// 글 제목 클릭시 상세내용 페이지 이동
const viewRecruitment = async (Id) => {
try {
const response = await axios.get(`http://localhost:8050/api/recruitments/${Id}`);
+ console.log('7');
setDetailData(response.data); // 가져온 데이터를 state에 저장
navigate(`/recruit/${Id}`);
- console.log("111"+response);
+
} catch (error) {
console.error('Error fetching or updating recruitmnet:', error);
}
};
- //이미지 가져오기
- function getImageMimeType(imageData) {
- if (!imageData) {
- return ''; // or any default MIME type you want to use when imageData is null
+ //글쓰기 버튼
+ const WritingBtn = () => {
+ navigate("/recruit/write")
+ }
+
+ const handleHistoryApplication = () => {
+ navigate("/recruitHistory")
+ }
+
+ const handleSearch = () => {
+ if(selectedRegion !== "지역 선택"){
+ const validRecruits = recruitments.filter(recruitment => {
+ const region = recruitment.region;
+ return selectedRegion === region;
+ }).reverse();
+ }else if(selectedRegion === "지역 선택"){
+ fetchRecruitments();
}
+ };
+
+
+ // 라디오 버튼 UI
+ const renderRadioButtons = () => {
+ const contentTypeOptions = [
+ { label: "# 숙소", value: "12" },
+ { label: "# 농업", value: "14" },
+ { label: "# 1주일 이내 모집", value: "32" },
+ { label: "# 2주일 이내 모집", value: "38" },
+ { label: "# 10만원이상", value: "39" }
+ ];
+
+ return (
+
+
+ {regions.map((region, index) => (
+
+ ))}
+
- const mimeType = imageData.split(';')[0].split(':')[1];
- return mimeType;
+ 검색
+
+
+ );
+ };
+
+ if (isLoading) {
+ return ;
}
- //admin구분
- useEffect(() => {
- axios.get('http://localhost:8050/api/auth/currentUser', {
- headers: {
- Authorization: `Bearer ${cookies.token}`
- }
- })
- .then((response) => {
- console.log(response.data);
- const user = response.data;
- const isAdminUser = user.userId === 'admin';
- setIsAdmin(isAdminUser);
- })
- .catch(error => {
- console.error('Error fetching user info:', error);
- if (error.response) {
- console.error('Status Code:', error.response.status);
- console.error('Response Data:', error.response.data);
- }
- });
- }, []);
+
return (
-
-
- {/*asd */}
- {currentItems.map((recruitments) => (
-
-
- {recruitments.content}
-
-
-
-
-
- ))}
-
-
+ }>
+
+
+ 모집 일정
+ 앞으로의 일정
+
+
+
+
+ {renderRadioButtons()}
+ {isCompany ?
+ null
+ :
+ 신청 내역
+ }
+ {isCompany && user.providerApproval === "true" ?
+ 글 쓰기
+ :
+ null
+ }
+
+
+
+
+ {currentItems.map((recruitments) => (
+
+ viewRecruitment(recruitments.job_id)}
+ onMouseOver={(e) => (e.target.style.textDecoration="underline")}
+ onMouseOut={(e) => (e.target.style.textDecoration="none")}>
+
+
+
+
+
+ {recruitments.title.length > 18
+ ? `${recruitments.title.substring(0, 18)}...`
+ : recruitments.title}
+
+
+ {`${recruitments.region} ${recruitments.address}`.substring(0, 18)}...
+
+
+
+ {recruitments.startDate} ~ {recruitments.endDate}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
);
};
+
const Container = styled.div`
- background-color: #61dafb;
- display: flex;
- flex-direction: column;
- height: 100vh;
-
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ width: 100%;
+ align-items: center;
`
const TopContainer = styled.div`
display: flex;
- flex-wrap: wrap; // 여기에 추가
- height: 300px;
- background-color: yellow;
+ flex-wrap: wrap;
+ width: 80%;
+ flex-direction: column;
+ height: auto; // 높이를 자동으로 조정
+ margin-top: 8rem;
+ margin-left: 2.2rem;
+ justify-content: center;
+ align-items: center;
+`;
+const MoTopContainer = styled.div`
+ display: flex;
+ font-size: 48px;
+ font-weight: 500;
+ width: 100%;
+ font-family: 'omyu_pretty';
+`
+const RadioButtonSpan = styled.span`
+ display: flex;
+`;
+
+const MoBottomContainer = styled.div`
+ display: flex;
+ font-size: 24px;
+ margin-top: 0.2rem;
+ width: 100%;
+ margin-left: 0.4rem;
+ font-family: 'omyu_pretty';
`
+const AlgoContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ width: 80%;
+
+`
+const SmallAlgoContainer = styled.div`
+ display: flex;
+ width: 100%;
+ height: 100px;
+ align-items: center;
+ border-radius: 10px;
+`
+
+const RadioContainer = styled.div`
+ display: flex;
+ height: 50px;
+ width: 100%;
+ font-size: 14px;
+ flex-direction: row; // 가로로 배치
+ justify-content: space-between;
+ align-items: center; // 세로 중앙 정렬
+`
+const RadioButtonLabel = styled.label`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: auto;
+ height: 40px;
+ margin: 5px;
+ padding: 10px;
+ border-radius: 20px;
+ border: 2px solid #ddd;
+ cursor: pointer;
+ font-weight: 500;
+ flex-direction: row;
+
+ &:hover {
+ background-color: #e8e8e8;
+ }
+`;
+
+const RadioButton = styled.input`
+ display: none;
+
+ &:checked + span {
+ color: #0000ff;
+ font-weight: bold;
+ border-color: #4CAF50;
+ }
+`;
+const Bottom = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 20px;
+ margin-top: -1rem;
+ width: 80%;
+ margin-bottom: 1rem;
+ max-height: 100vh; /* Increase the max-height value */
+ padding: 20px;
+
+`;
+
+const TitleContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ font-family: GmarketSansTTFLight;
+`;
const BottomContent = styled.div`
+ border-radius: 10px;
+ width: 100%;
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
+ transition: background-color 0.1s ease;
+ height: 100%;
+
+
+ &:hover {
+ background-color: #eee;
+ }
+
+ h3,
+ h4 {
+ font-size: 18px;
+ margin: 0;
+ white-space: nowrap; /* 변경: 텍스트가 다음 줄로 넘어가지 않도록 설정 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ }
+
+ img {
+ width: 100%;
+ height: 60%;
+ border-radius: 10px;
+ object-fit: cover;
+ transition: transform 1s ease;
+ }
+
+ &:hover img {
+ transform: scale(1.05);
+ }
+`;
+
+
+const BottomMain = styled.div`
display: flex;
- flex-direction: column; // 여기에 수정
+ flex: 1;
+ flex-direction: column;
+ border-radius: 10px;
+ border: 2px solid #d6d6d6;
+ width: 100%;
+ height: 100%;
+ padding: 10px;
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
+ transition: background-color 0.1s ease;
+
+ &:hover {
+ background-color: #eee;
+ }
+
+ h3,
+ h4 {
+ font-size: 10px;
+ white-space: nowrap; /* 변경: 텍스트가 다음 줄로 넘어가지 않도록 설정 */
+ overflow: hidden;
+ text-overflow: ellipsis;
+ transition: background-color 0.3s ease;
+ }
+
+ img {
+ width: 100%;
+ height: 50%;
+ border-radius: 10px;
+ transition: transform 1s ease;
+ }
+
+ &:hover img {
+ transform: scale(1.05);
+ }
+`;
+
+const ImgContainer = styled.div`
+ display: flex;
+ flex: 5;
+ height: 200px;
+`
+const WritingButton = styled.button`
+ width: 230px;
+ color: white;
+ margin-right: 1.3rem;
+ border-radius: 10px;
+ font-size: 28px;
+ font-weight: 500;
+ border: none;
+ background-color: orange;
+
+ &:hover {
+ background-color: darkorange;
+ }
+
+`;
+
+const PaginationContainer = styled.div`
+`;
+
+const Upimage = styled.div`
+ display: flex;
+ flex : 1;
+ height: 100px;
+ align-items: center;
+ width: 100%; /* Change to 100% to take the full width */
+ margin-bottom: -25px;
+ object-fit: cover; /* 이미지 비율 유지를 위한 설정 */
+`;
+const Footer1Container = styled.div`
+ display: flex;
+ flex : 3;
+`
+const Footer2Container = styled.div`
+ display: flex;
+ height: 100px;
+ flex : 7;
+ margin-bottom: -50px;
+`
+
+const Footer1Image = styled.div`
+ display: flex;
+ img {
+ width: 50px;
+ height: auto;
+ }
+`;
+
+const Footer2Image = styled.div`
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ img {
+ width: 60px;
+ height: auto; /* 높이 자동 조절 */
+ }
+`;
+const Aa = styled.div`
height: 50px;
- width: 50%; // 여기에 추가
- box-sizing: border-box; // 여기에 추가
- padding: 10px; // 여기에 추가
- border-bottom: 1px solid skyblue;
+ flex: 5;
+ display: flex;
+ justify-content: flex-start;
+ margin-top: -1.3rem;
+`
+const Bb = styled.div`
+ flex: 5;
+ height: 100px;
+ display: flex;
+ justify-content: flex-end;
+ margin-top: -3.5rem;
`
-export default RecruitPage;
\ No newline at end of file
+const StyledSelect = styled.select`
+ padding: 10px;
+ margin-right: 10px;
+ border-radius: 5px;
+ border: 1px solid #ddd;
+ font-size: 16px;
+ cursor: pointer;
+`;
+const ButtonContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ margin-left: 1.5rem;
+`
+const StyledButton = styled.button`
+ padding: 10px 20px;
+ border-radius: 5px;
+ width: 80px;
+ border: none;
+ background-color: orange;
+ color: white;
+ font-size: 16px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: darkorange;
+ }
+`;
+
+
+
+export default RecruitPage;
+
+//====================
+export const getRecruitmentsData = async () => {
+ try {
+ const currentDate = new Date();
+ const response = await axios.get('http://localhost:8050/api/recruitments');
+ return response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+
+ } catch (error) {
+ console.error('Error fetching recruitments:', error);
+ throw error; // 예외를 호출자에게 전파
+ }
+};
+
+//====================
+
+// 로딩 컴포넌트
+const LoadingComponent = () => (
+
+
+
+);
diff --git a/hansoyeon/src/Pages/RecruitViewPage.js b/hansoyeon/src/Pages/RecruitViewPage.js
old mode 100644
new mode 100755
index 1902790fe..23b635887
--- a/hansoyeon/src/Pages/RecruitViewPage.js
+++ b/hansoyeon/src/Pages/RecruitViewPage.js
@@ -1,16 +1,40 @@
import React, { useEffect, useState } from "react";
-import {useNavigate, useParams} from 'react-router-dom'; // useNavigate 훅 추가
-import Container from 'react-bootstrap/Container';
-import Row from 'react-bootstrap/Row';
-import Col from 'react-bootstrap/Col';
-import Card from 'react-bootstrap/Card';
+import {useNavigate, useParams} from 'react-router-dom';
import axios from "axios";
+import styled from "styled-components";
+
+import location from "../imgs/location.png";
+import {useUserStore} from "../stores";
+import {useCookies} from "react-cookie";
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+import sunnny from "../imgs/sunnny.png";
+import moon from "../imgs/moon.png";
+import morning from "../imgs/morning.png";
+import carrerPeople from "../imgs/carrerPeople.png";
+import together from "../imgs/together.png";
+import work from "../imgs/work.png";
const RecruitViewPage = ( props ) => {
+ const {isUser, setIsUser} = useState(false);
const { id } = useParams();
const [isCompanyUser, setIsCompanyUser] = useState(false);
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
const navigate = useNavigate(); // navigate 함수 초기화
const [recruitments, setRecruitments] = useState([]);
+ const [recruitmentId, setRecruitmentId] = useState('');
+ const [job_id, setJobId] = useState('');
+ const [userId, setUserId] = useState('');
+ const [providerId , setProviderId] = useState('');
+ const {user, setUser} = useUserStore();
+ const userType = cookies.userType;
+ const userID = user ? user.userId : '';
+ const [hasApplied, setHasApplied] = useState(false);
+ const [providerPhone, setProviderPhone] = useState('');
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
//상세 페이지 불러오는 함수
const fetchAnnouncement = async () => {
@@ -21,7 +45,15 @@ const RecruitViewPage = ( props ) => {
}
const data = response.data;
setRecruitments(data);
+
console.log('Announcement Content: ', data);
+
+
+ const providerResponse = await axios.get(`http://localhost:8050/api/auth/provider/${data.providers}`);
+ if (providerResponse.status === 200) {
+ setProviderPhone(providerResponse.data.companyTel);
+ }
+
} catch (error) {
console.error('Error fetching announcement content: ', error);
}
@@ -31,11 +63,709 @@ const RecruitViewPage = ( props ) => {
fetchAnnouncement();
}, [id]);
+ useEffect(() => {
+ if (cookies.token) {
+ console.log(userType)
+ if(userType === "company"){
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }else{
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ if(userType !== "company"){
+ const checkApplicationStatus = async () => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${user.userId}`);
+ const matchings = response.data.data;
+ console.log(matchings);
+
+ const hasApplied = matchings.some(matching => matching.recruitment.jobId === parseInt(id));
+ console.log(id)
+ console.log(hasApplied)
+ setHasApplied(hasApplied);
+ } catch (error) {
+ console.error('Error retrieving matchings: ', error);
+ }
+ };
+
+ checkApplicationStatus();
+ }
+ }, [id, user]);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ //공고 지원
+ const applyBtn = async () => {
+ console.log(recruitments.job_id);
+ console.log(user.userId);
+ try {
+ const response = await axios.post(
+ `http://localhost:8050/api/matchings`,
+ {
+ recruitmentId: recruitments.job_id,
+ userId: user.userId
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`,
+ 'Content-Type': 'application/json',
+ },
+ }
+ );
+
+ if (response.status === 200) {
+ alert("정상 신청 되었습니다.");
+
+ navigate(`/recruit`);
+
+ fireNotificationWithTimeout('신청 완료', 5000, {
+ body: `${recruitments.title} 신청 완료`
+ });
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationComplete", {
+ phone: user.userPhone,
+ jobTitle: recruitments.title
+ });
+ console.log(smsResponse.data);
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationCompanyComplete", {
+ phone: providerPhone,
+ jobTitle: recruitments.title
+ });
+ console.log(smsResponse.data);
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ console.log(recruitments)
+ navigate('/recruitApply', { state: { jobDetails: recruitments } });
+
+ }
+ // 서버 응답에 대한 처리 (예: 성공 여부에 따라 다른 동작 수행)
+ console.log(response.data);
+
+ } catch (error) {
+ // 오류 처리
+ console.error('API 요청 중 오류 발생: ', error);
+ }
+ };
+
return (
-
+
+
+ {recruitments.image && recruitments.image.length >= 4 && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+ {` ${recruitments.region}`} {recruitments.address}
+
+
+ {recruitments.title}
+ {/*{recruitments.providers}*/}
+
+
+ 모집 일정
+
+
+
+
+ 일정 1
+ {recruitments.startDate} ~ {recruitments.endDate}
+
+
+
+
+
+
+
+
+ 첫날
+
+
+ {recruitments.content}
+
+
+
+
+
+
+
+
+
+ 잠시 동안
+
+
+ {recruitments.second}
+
+
+
+
+
+
+
+
+
+ 마지막 날
+
+
+ {recruitments.third}
+
+
+
+
+
+ 총 금액 : {recruitments.money} 10시간 도와드릴경우의 금액입니다.
+ ※날씨나 당일의 스케줄의 변경에 의해 변동하는 경우가 있습니다.
+
+
+
+
+
+ 한소연 개요
+
+
+
+
+
+ 도움 내용
+ {recruitments.schedule}
+
+
+ 한소연 모집배경
+ {recruitments.background}
+
+
+ 식사
+
+
+ 조식
+
+
+ {recruitments.morning}
+
+
+
+ 점심
+
+
+ {recruitments.lunch}
+
+
+
+ 저녁
+
+
+ {recruitments.dinner}
+
+
+
+
+ 필요한 소지품, 복장
+
+
+ {recruitments.need}
+
+
+
+
+
+ {!hasApplied && userType !== "company" && user && (
+ 지원하기
+ )}
+
+
);
};
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+`
+
+const ImgContainer = styled.div`
+ display: flex;
+ height: 400px;
+ width: 100%;
+ margin-top: 1rem;
+ justify-content: center;
+`
+
+const ImgSmallContainer = styled.div`
+ display: flex;
+ width: 100%;
+ flex-direction: row;
+ justify-content: space-evenly;
+ img {
+ width: 24%;
+ border-radius: 0.5rem;
+ height: auto;
+ }
+`
+
+const LocationContainer = styled.div`
+ display: flex;
+ margin-top: 2rem;
+ align-items: center;
+ h2 {
+ margin-left: 16rem;
+ font-size: 24px;
+ color: orangered;
+ }
+
+ img {
+ height: 24px;
+
+ }
+`
+const ScheduleContainer = styled.div`
+ width: 100%;
+ display: flex;
+ height: 50px;
+ justify-content: center;
+`
+const TitleContainer = styled.div`
+ display: flex;
+ height: auto;
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ h2 {
+ margin-left: 16rem;
+ font-size: 32px;
+ }
+`
+const TopMainContainer = styled.div`
+ display: flex;
+ height: 600px;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+`
+const TopContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid orangered;
+ background-color: #FDF9EA;
+ border-radius: 5px;
+ width: 70%;
+ height: 600px;
+ align-items: center;
+`
+const TopSchedule = styled.div`
+ display: flex;
+ height: 50px;
+ width: 70%;
+ font-size: 24px;
+ font-weight: 700;
+ align-items: center;
+`
+const DeyContainer = styled.div`
+ display: flex;
+ height: 380px;
+ background-color: #FDF9EA;
+ width: 100%;
+ flex-direction: column;
+ align-items: center;
+`
+const FirstContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ height: 120px;
+ width: 95%;
+ background-color: #FDF9EA;
+`
+const FirstLeft = styled.div`
+ display: flex;
+ height: auto;
+ width: 8%;
+ background-color: #FDF9EA;
+ justify-content: center;
+ align-items: center;
+ img {
+ width: 70px;
+ height: 70px;
+ }
+`
+const FirstRight = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: auto;
+ width: 92%;
+ background-color: #FDF9EA;
+`
+
+const FirstTop = styled.div`
+ display: flex;
+ height: 50px;
+ background-color: #FDF9EA;
+ width: auto;
+ align-items: center;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+const FirstBottom = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 80px;
+ width: auto;
+ background-color: #FDF9EA;
+ justify-content: flex-start;
+ text-align: start;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+
+const SecondContainer = styled.div`
+ display: flex;
+ height: 130px;
+ width: 95%;
+ background-color: #FDF9EA;
+`
+const SecondLeft = styled.div`
+ display: flex;
+ height: auto;
+ width: 8%;
+ background-color: #FDF9EA;
+ justify-content: center;
+ align-items: center;
+ img {
+ margin-left: 5px;
+ width: 50px;
+ height: 60px;
+ }
+`
+const SecondTop = styled.div`
+ display: flex;
+ height: 50px;
+ background-color: #FDF9EA;
+ width: auto;
+ align-items: center;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+
+const SecondBottom = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 120px;
+ width: auto;
+ background-color: #FDF9EA;
+ justify-content: flex-start;
+ text-align: start;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+const ThirdContainer = styled.div`
+ display: flex;
+ height: 100px;
+ width: 95%;
+ background-color: #FDF9EA;
+`
+const ThirdLeft = styled.div`
+ display: flex;
+ height: auto;
+ width: 8%;
+ background-color: #FDF9EA;
+ justify-content: center;
+ align-items: center;
+ img {
+ width: 50px;
+ height: 60px;
+ }
+`
+const ThirdTop = styled.div`
+ display: flex;
+ height: 40px;
+ background-color: #FDF9EA;
+ width: auto;
+ align-items: center;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+const ThirdBottom = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 60px;
+ width: auto;
+ background-color: #FDF9EA;
+ justify-content: flex-start;
+ text-align: start;
+ h2 {
+ font-size: 20px;
+ font-weight: 600;
+ }
+`
+const MoneyContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 95%;
+ height: 120px;
+ background-color: #FDF9EA;
+ border: 1px solid red;
+ margin-bottom: 2rem;
+ text-align: start;
+ h2 {
+ margin-left: 1rem;
+ margin-top: 1rem;
+ font-size: 24px;
+ }
+ h3 {
+ margin-left: 1rem;
+ font-size: 20px;
+ }
+`
+const MiddleSchedule = styled.div`
+`
+const DayContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ background-color: blue;
+ margin-top: 1rem;
+ width: 100%;
+ height: 50px;
+`
+const DayFirstContainer = styled.div`
+ display: flex;
+ width: 10%;
+ height: auto;
+ background-color: #FDF9EA;
+ font-size: 20px;
+ font-weight: 600;
+ color: gray;
+ justify-content: center;
+ align-items: center;
+`
+const DayCondContainer = styled.div`
+ display: flex;
+ width: 90%;
+ height: auto;
+ background-color: #FDF9EA;
+ font-size: 24px;
+ font-weight: 500;
+ color: black;
+ align-items: center;
+`
+const HanSoContainer = styled.div`
+ display: flex;
+ margin-top: 2rem;
+ width: 100%;
+ height: 50px;
+ justify-content: center;
+`
+
+const HanSoText = styled.div`
+ width: 70%;
+ height: 50px;
+ text-align: start;
+ font-size: 32px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+`
+const MainContainer = styled.div`
+ display: flex;
+ margin-top: 2rem;
+ height: 800px;
+ width: 100%;
+ justify-content: center;
+ margin-bottom: 1rem;
+`
+const SoJiPoomContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: #FDF9EA;
+ border-bottom: 1px solid orangered;
+ width: 100%;
+ height: 110px;
+ justify-content: center;
+ align-items: center;
+`
+const MainCenterContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid orangered;
+ border-radius: 5px;
+ width: 70%;
+ height: auto;
+`
+
+const MainContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 150px;
+ width: 100%;
+ background-color: #FDF9EA;
+ align-items: center;
+ border-bottom: 1px solid #FBCEB1;
+`
+const BackgroundContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 220px;
+ width: 100%;
+ background-color: #FDF9EA;
+ align-items: center;
+ border-bottom: 1px solid #FBCEB1;
+`
+const FoodContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 320px;
+ width: 100%;
+ background-color: #FDF9EA;
+ align-items: center;
+ border-bottom: 2px solid #FBCEB1;
+ text-align: start;
+`
+const Background = styled.div`
+ display: flex;
+ background-color: #FDF9EA;
+ width: 95%;
+ height: 170px;
+ align-items: center;
+ text-align: start;
+ font-size: 18px;
+ font-weight: 500;
+`
+const HelpContent = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: 1rem;
+ width: 95%;
+ height: 40px;
+ font-size: 24px;
+ font-weight: 700;
+ background-color: #FDF9EA;
+`
+const HelpBottom = styled.div`
+ display: flex;
+ background-color: #FDF9EA;
+ width: 95%;
+ height: 70px;
+ align-items: center;
+ text-align: start;
+ font-size: 24px;
+ font-weight: 500;
+
+`
+const FoodDayContainer = styled.div`
+ display: flex;
+ width: 95%;
+ height: 30px;
+ font-size: 20px;
+ font-weight: 600;
+ background-color: #FDF9EA;
+ align-items: center;
+ h2 {
+ margin-top: 10px;
+ font-size: 20px;
+ }
+`
+const FoodContent = styled.div`
+ display: flex;
+ align-items: center;
+ width: 87%;
+ height: 70px;
+ background-color: #FDF9EA;
+
+ h2 {
+ font-size: 18px;
+ }
+`
+const FoodDinnerContainer = styled.div`
+ display: flex;
+ width: 95%;
+ height: 30px;
+ font-size: 20px;
+ font-weight: 600;
+ align-items: center;
+ h2 {
+ margin-top: 10px;
+ margin-left: 1rem;
+ font-size: 20px;
+ }
+`
+const HelpSchedule = styled.div`
+ display: flex;
+ width: 95%;
+ height: 100px;
+ align-items: center;
+ text-align: start;
+ font-size: 18px;
+ font-weight: 500;
+`
+const ButtonContainer = styled.div`
+ display: flex;
+ width: 100%;
+ justify-content: center;
+`
+const ApplyButton = styled.button`
+ display: flex;
+ background-color: #FDF9EA;
+ width: 20%;
+ border-radius: 10rem;
+ height: 50px;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid darkkhaki;
+ transition: background-color 0.3s ease; /* 호버 효과를 위한 전환 효과 */
+
+ &:hover {
+ background-color: #FCE999; /* 호버 시 배경색 변경 */
+ cursor: pointer; /* 호버 시 커서 모양 변경 */
+ }
+`
+
export default RecruitViewPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/RegionPage.js b/hansoyeon/src/Pages/RegionPage.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/Pages/ReviewChangePage.js b/hansoyeon/src/Pages/ReviewChangePage.js
new file mode 100644
index 000000000..02a4d1caf
--- /dev/null
+++ b/hansoyeon/src/Pages/ReviewChangePage.js
@@ -0,0 +1,173 @@
+import React, { useEffect, useState } from 'react';
+import { Row, Col, Button, Form, Container } from 'react-bootstrap';
+import styled from 'styled-components';
+import { useNavigate, useLocation } from 'react-router-dom';
+import axios from 'axios';
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+
+const ReviewChangePage = () => {
+ const navigate = useNavigate();
+ const [cookies] = useCookies(['token']);
+ const { user } = useUserStore();
+ const [postTitle, setPostTitle] = useState('');
+ const [postContent, setPostContent] = useState('');
+ const [postImage, setPostImage] = useState(null);
+ const [postImagePreview, setPostImagePreview] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [errorMessage, setErrorMessage] = useState('');
+
+ const location = useLocation();
+ const { reviewId } = location.state || {};
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get(`http://localhost:8050/api/reviews/${reviewId}`)
+ .then((response) => {
+ const reviewData = response.data;
+ setPostTitle(reviewData.reviewTitle);
+ setPostContent(reviewData.reviewContent);
+ setPostImagePreview(reviewData.reviewImage);
+ })
+ .catch((error) => {
+ console.error('Error fetching review:', error);
+ // 에러 처리
+ });
+ }
+ }, [cookies.token, reviewId]);
+
+ const handlePostImageChange = (e) => {
+ if (e.target.files && e.target.files[0]) {
+ let selectedFile = e.target.files[0];
+ setPostImage(selectedFile);
+
+ let reader = new FileReader();
+ reader.onload = (event) => {
+ setPostImagePreview(event.target.result);
+ };
+ reader.readAsDataURL(selectedFile);
+ }
+ };
+
+ const handleUpdatePost = async () => {
+ if (!postTitle || !postContent) {
+ alert('제목과 내용을 모두 작성해주세요.');
+ return;
+ }
+
+ setIsLoading(true);
+ setErrorMessage('');
+
+ const formData = new FormData();
+ formData.append('reviewTitle', postTitle);
+ formData.append('reviewContent', postContent);
+
+ if (postImage) {
+ formData.append('reviewImage', postImage);
+ }
+
+ try {
+ const response = await axios.put(
+ `http://localhost:8050/api/reviews/${reviewId}`,
+ formData,
+ {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`,
+ 'Content-Type': 'multipart/form-data',
+ },
+ }
+ );
+
+ alert('게시글 수정이 완료되었습니다.');
+ navigate('/review');
+ } catch (error) {
+ setErrorMessage('게시글 수정 중 오류가 발생했습니다.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ 제목
+ setPostTitle(e.target.value)}
+ />
+
+
+
+ 내용
+ setPostContent(e.target.value)}
+ />
+
+
+ {postImagePreview && (
+
+ )}
+
+
+
+
+
+ {errorMessage && (
+ {errorMessage}
+ )}
+
+
+
+ {isLoading ? '수정 중...' : '수정'}
+
+
+
+
+
+
+ );
+};
+
+const StyledContainer = styled(Container)`
+ max-width: 800px;
+ background-color: white;
+ border-radius: 8px;
+ padding: 20px;
+ margin-top: 150px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+`;
+
+const ImagePreview = styled.img`
+ width: 100%;
+ max-height: 300px;
+ margin-bottom: 10px;
+`;
+
+const ButtonContainer = styled.div`
+ display: flex;
+ justify-content: flex-end;
+`;
+
+const SubmitButton = styled.button`
+ background-color: #007bff;
+ color: white;
+ padding: 10px 20px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0056b3;
+ }
+`;
+
+export default ReviewChangePage;
diff --git a/hansoyeon/src/Pages/ReviewContentPage.js b/hansoyeon/src/Pages/ReviewContentPage.js
old mode 100644
new mode 100755
index 6350edb2a..e0fa77f3a
--- a/hansoyeon/src/Pages/ReviewContentPage.js
+++ b/hansoyeon/src/Pages/ReviewContentPage.js
@@ -1,54 +1,392 @@
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
-import { useParams } from 'react-router-dom';
+import {useNavigate, useParams} from 'react-router-dom';
+import {Badge} from 'react-bootstrap';
import axios from 'axios';
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
const ReviewContentPage = () => {
const { id } = useParams(); // URL에서 리뷰 ID를 가져옴
const [review, setReview] = useState(null);
- useEffect(() => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const userType = cookies.userType;
+
+ const [comments, setComments] = useState([]); // 댓글 리스트
+ const [comment, setComment] = useState('');
+
+ useEffect( () => {
+ if (cookies.token) {
+ if (userType === "company") {
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ setUser(fetchedUser)
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ } else {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }
axios.get(`http://localhost:8050/api/reviews/${id}`)
.then(response => {
- console.log(response.data); // 로그로 데이터 확인
setReview(response.data);
})
.catch(error => console.error('Error fetching review:', error));
+
+ axios.get(`http://localhost:8050/api/comments/${id}`)
+ .then(response => {
+ setComments(response.data);
+ })
+ .catch(error => console.error('Error fetching comment', error));
}, [id]);
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
const formatDate = (dateString) => {
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
};
+ const handleEdit = (reviewId) => {
+ navigate(`/reviewEdit/${reviewId}`, { state: { reviewId }});
+ };
+
+ const handleDelete = async (reviewId) => {
+ if (window.confirm('리뷰를 삭제하시겠습니까?')) {
+ try {
+ await axios.delete(`http://localhost:8050/api/reviews/${reviewId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ navigate("/review");
+ } catch (error) {
+ console.error('Error deleting review:', error);
+ }
+ }
+ };
+
+ const handleBackToList = () => {
+ navigate("/review");
+ };
+
+ // 댓글을 서버에 전송하는 함수
+ const handlePostComment = async () => {
+ try {
+ let commentData = null;
+ if(userType === "company"){
+ commentData = {
+ reviewId: id,
+ userId: user.providerId,
+ userType: userType,
+ commentContent: comment
+ };
+ }else{
+ commentData = {
+ reviewId: id,
+ userId: user.userId,
+ userType: userType,
+ commentContent: comment
+ };
+ }
+ console.log(commentData)
+ const reviewId = commentData.reviewId
+
+ const response = await axios.post(`http://localhost:8050/api/comments/${reviewId}`, commentData);
+ setComments([...comments, response.data]);
+ setComment('');
+ } catch (error) {
+ console.error("Error posting comment:", error);
+ }
+ };
+ const handleHeart = async (review) => {
+ if (userType !== "company") {
+ if (review.userId === user.userId) {
+ alert("본인 리뷰에는 좋아요를 누를 수 없어요!");
+ return;
+ }
+ }
+ try {
+ if (user.userId !== review.userId) { // 작성자와 현재 사용자가 다른 경우에만
+ await axios.post(`http://localhost:8050/api/reviews/${review.reviewId}/incrementLike`);
+ }
+ alert("좋아요가 반영되었습니다!")
+ } catch (error) {
+ console.error("Error incrementing view count:", error);
+ }
+ };
+
+
if (!review) {
- return Loading... ;
+ return Loading...;
}
return (
-
- 리뷰 상세 페이지
-
- Review ID: {review.id}
- Job ID: {review.jobId}
- User ID: {review.userId}
- Review Content: {review.reviewContent}
- Review Recommend: {review.reviewRecommend}
- Date: {formatDate(review.reviewDate)}
-
-
+
+
+
+
+ {review.reviewTitle}
+
+
+ handleHeart(review)}>
+ 좋아요❤️
+
+
+
+
+ {review.userId}
+ {formatDate(review.reviewWriteDate)}
+ {review.reviewContent}
+
+
+ {user && userType !== "company" && user.userId === review.userId && (
+ <>
+ handleEdit(review.reviewId)}>수정
+ handleDelete(review.reviewId)}>삭제
+ >
+ )}
+ 목록
+
+
+
+ 댓글 ({comments.length})
+
+ {comments.map((comment) => (
+
+
+ {comment.userId}
+ {comment.userType === 'company' ?
+ 기업 회원
+ :
+ 일반 회원
+ }
+
+ {comment.commentContent}
+ {comment.commentWriteDate}
+
+ ))}
+
+ setComment(e.target.value)}
+ />
+ 댓글 작성
+
+
+
);
};
const Container = styled.div`
- width: 80%;
- margin: auto;
+ max-width: 800px;
+ margin: 50px auto;
padding: 20px;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ border-radius: 10px;
+ box-shadow: 0 0 50px rgba(0, 0, 0,0.3);
+ background-color: white;
+ position: relative;
+`;
+
+const Title = styled.div`
+ display: flex;
+ flex-direction: row;
+ text-align: center;
+ color: #333;
+ margin-bottom: 5px;
+ font-weight: bold;
+`;
+const ReviewTitle = styled.div`
+ display: flex;
+ font-family: 'omyu_pretty';
+ width:90%;
+ justify-content: center;
+ align-items: center;
+ h2 {
+
+ font-weight: 500;
+ font-size: 40px;
+ }
+`
+
+const ReviewImage = styled.img`
+ display: block;
+ max-width: 100%;
+ height: auto;
+ margin: 0 auto 20px;
+ border-radius: 10px;
`;
const ReviewDetails = styled.div`
- margin-top: 20px;
+ font-size: 18px;
+`;
+
+const DetailItem = styled.p`
+ margin: 10px 0;
+`;
+
+const Label = styled.span`
+ font-weight: bold;
+`;
+
+const LoadingContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ font-size: 24px;
+`;
+
+const ButtonContainer = styled.div`
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+`;
+
+const ActionButton = styled.button`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #ff9933;
+ color: white;
+ font-size: 18px;
+ width: 70px;
+ height: 40px;
+ padding: 10px 15px;
+ margin-left: 10px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ &:hover {
+ background-color: darkorange;
+ }
+`;
+
+const CommentSection = styled.div`
+ max-width: 800px;
+ background-color: #f5f5f5;
+ padding: 20px;
+ border-radius: 10px;
+ margin: 10px auto;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`;
+
+
+const CommentList = styled.ul`
+ list-style: none;
+ justify-content: center;
+ padding: 0;
+`;
+
+const CommentItem = styled.li`
+ display: flex;
+ width: 90%;
+ justify-content: space-between; // 이 부분을 유지하되, 각 요소의 너비를 조절하여 위치를 정렬합니다.
+ align-items: center;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ padding: 10px;
+ margin-bottom: 10px;
+`;
+
+const CommentAuthor = styled.div`
+ font-weight: bold;
+ margin-right: 5px;
+`;
+
+const CommentAuthorAndBadge = styled.div`
+ flex: 1; // 왼쪽 끝에 위치하도록 설정
+ display: flex;
+ align-items: center;
+`;
+
+const CommentContent = styled.div`
+ flex: 2; // 중앙에 위치하도록 설정
+ text-align: center; // 내용을 중앙 정렬합니다.
+ margin-top: 5px;
+`;
+
+const CommentWriteDate = styled.div`
+ flex: 1; // 오른쪽 끝에 위치하도록 설정
+ text-align: right; // 날짜를 오른쪽 정렬합니다.
+`;
+
+
+const CommentInput = styled.textarea`
+ width: 100%;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ margin-top: 10px;
+`;
+
+const PostCommentButton = styled.button`
+ background-color: #ffffff;
+ color: #333333;
+ font-weight: 600;
+ border: 1px solid #dcdcdc;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ margin-top: 10px;
+ transition: background-color 0.3s, color 0.3s, border-color 0.3s; /* hover 효과를 부드럽게 만들기 위한 트랜지션 설정 */
+
+ &:hover {
+ background-color: #f5f5f5; /* hover 시 배경 색상 변경 */
+ color: #555555; /* hover 시 글자 색상 변경 */
+ border-color: #bfbfbf; /* hover 시 테두리 색상 변경 */
+ }
+`;
+
+
+// 하트 아이콘 스타일링
+const HeartIcon = styled.div`
+ display: flex;
+ flex-direction: row;
+ font-size: 20px;
+ width: 70px;
+ cursor: pointer;
+ margin-left: 10px;
+ align-items: flex-end;
+ justify-content: center;
+ h2 {
+ margin-right: 0.3rem;
+ display: flex;
+ align-items: flex-end;
+ font-size: 12px;
+ }
`;
export default ReviewContentPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/ReviewPage.js b/hansoyeon/src/Pages/ReviewPage.js
old mode 100644
new mode 100755
index a6331afb6..4bd24aab6
--- a/hansoyeon/src/Pages/ReviewPage.js
+++ b/hansoyeon/src/Pages/ReviewPage.js
@@ -1,33 +1,166 @@
-import React, { useEffect, useState } from 'react';
+import React, {useEffect, useState} from 'react';
import styled from 'styled-components';
-import { useNavigate } from 'react-router-dom';
+import {Modal} from 'react-bootstrap';
+import {useNavigate} from 'react-router-dom';
import axios from 'axios';
+import noImage from '../imgs/noImage.png'
+import {useCookies} from 'react-cookie';
+import {useUserStore} from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
+import useThrottle from "../Components/useThrottle";
+import usePushNotification from "../Components/usePushNotification";
+import Footer from "../Components/Footer";
+import GoogleLogin from "react-google-login";
const ReviewPage = () => {
const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const userType = cookies.userType;
+ const [isUser, setIsUser] = useState(false)
+ const [isCompanyUser, setIsCompanyUser] = useState(false)
const [reviews, setReviews] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
- const ITEMS_PER_PAGE = 8;
+ const ITEMS_PER_PAGE = 4;
+
+ const [matchings, setMatchings] = useState([]);
+ const [showModal, setShowModal] = useState(false);
+
+ const [showCompanyModal, setShowCompanyModal] = useState(false);
+ const [announcements, setAnnouncements] = useState([]);
+ const [applicants, setApplicants] = useState([]);
+ const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
+ const [selectedJobId, setSelectedJobId] = useState(null);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isDetailModalOpen, setIsDetailModalOpen] = useState(false);
+ const [selectedUser, setSelectedUser] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const [providerPhone, setProviderPhone] = useState('')
+
+ const { fireNotificationWithTimeout } = usePushNotification();
+ const { throttle } = useThrottle();
+
+ const handleOpenModal = () => {
+ setShowModal(true);
+ fetchMatchings(user.userId);
+ };
+
+ const handleCloseModal = () => setShowModal(false);
+
+ const handleOpenCompanyModal = () => {
+ setShowCompanyModal(true);
+ };
+
+ const handleCloseCompanyModal = () => setShowCompanyModal(false);
useEffect(() => {
- axios.get('http://localhost:8050/api/reviews')
- .then(response => {
- setReviews(response.data.reverse());
- })
- .catch(error => console.error('Error fetching reviews:', error));
+ if (cookies.token) {
+ if(userType === "company"){
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser)
+ setIsCompanyUser(true)
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }else{
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ setIsUser(true);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }
+
+ const fetchReviewsWithCommentCount = async () => {
+ try {
+ // 리뷰 데이터 가져오기
+ const reviewResponse = await axios.get('http://localhost:8050/api/reviews');
+ console.log(reviewResponse.data)
+ const reviewsWithCommentCount = await Promise.all(reviewResponse.data.map(async review => {
+ // 각 리뷰에 대해 댓글 수 가져오기
+ const commentResponse = await axios.get(`http://localhost:8050/api/comments/${review.reviewId}`);
+ return {
+ ...review,
+ commentCount: commentResponse.data.length // 댓글 수를 리뷰 객체에 추가
+ };
+ }));
+ reviewsWithCommentCount.sort((a, b) => b.reviewLikeCount - a.reviewLikeCount);
+ setReviews(reviewsWithCommentCount);
+ } catch (error) {
+ console.error('Error fetching reviews and comment counts:', error);
+ }
+ setIsLoading(false);
+ };
+
+ fetchReviewsWithCommentCount();
}, []);
- const handleWriteButtonClick = () => {
- navigate('/writeReview');
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token && userType === "company") {
+ setProviderPhone(user.companyTel);
+ fetchJobAnnouncements(user.providerId);
+ }
+ }, [user]);
+
+ const fetchJobAnnouncements = async (jobProviders) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/recruitments/byProvider/${jobProviders}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ console.log(response.data)
+ setAnnouncements(response.data);
+ } catch (error) {
+ console.error("Error fetching job announcements:", error);
+ }
};
- const handleReviewClick = (reviewId) => {
- navigate(`/reviewContent/${reviewId}`);
+ const handleWriteButtonClick = () => {
+ navigate('/writeReview');
};
- const formatDate = (dateString) => {
- const date = new Date(dateString);
- return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+ const handleReviewClick = async (review) => {
+ if(user === null){
+ alert("로그인이 필요한 서비스입니다.");
+ return;
+ }
+ try {
+ if (user.userId !== review.userId) { // 작성자와 현재 사용자가 다른 경우에만
+ await axios.post(`http://localhost:8050/api/reviews/${review.reviewId}/incrementView`);
+ }
+ navigate(`/reviewContent/${review.reviewId}`);
+ } catch (error) {
+ console.error("Error incrementing view count:", error);
+ }
};
const indexOfLastItem = currentPage * ITEMS_PER_PAGE;
@@ -36,67 +169,418 @@ const ReviewPage = () => {
const paginate = (pageNumber) => setCurrentPage(pageNumber);
+ const fetchMatchings = async (userId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ const currentDate = new Date();
+ const validMatchings = response.data.data.filter(matching => {
+ const endDate = new Date(matching.recruitment.jobEndDate);
+ return endDate < currentDate;
+ });
+ const validWritingMatchings = validMatchings.filter(matching => {
+ const status = matching.status;
+ return status === "COMPLETED"
+ });
+ console.log(validMatchings)
+ setMatchings(validWritingMatchings);
+ } catch (error) {
+ console.error("Error fetching matchings:", error);
+ }
+ };
+
+ const fetchApplicants = async (jobId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/recruitments/${jobId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ if (response.data && response.data.data) {
+ const validWritingMatchings = response.data.data.filter(matching => {
+ const status = matching.status;
+ return status === "ACCEPTED" || status === "COMPLETED";
+ });
+ console.log(validWritingMatchings)
+ setApplicants(validWritingMatchings);
+ }
+ } catch (error) {
+ console.error("Error fetching applicants:", error);
+ setApplicants([]);
+ }
+ };
+
+ const handleSelectMatching = (selectedMatching) => {
+ // 선택된 matching 정보를 가지고 글 쓰기 페이지로 이동
+ navigate('/writeReview', { state: { selectedMatching } });
+ };
+
+ const handleCheckApplicants = async (announcement) => {
+ setSelectedAnnouncement(announcement);
+ setSelectedJobId(announcement.job_id);
+ fetchApplicants(announcement.job_id);
+ setIsModalOpen(true);
+ };
+
+ const handleModalClose = () => {
+ setIsModalOpen(false);
+ };
+
+ const handleDetailModalClose = () => {
+ setIsDetailModalOpen(false);
+ };
+
+ const handleUserClick = (user) => {
+ setSelectedUser(user);
+ setIsDetailModalOpen(true);
+ };
+
+ const hasDatePassed = (endDate) => {
+ const today = new Date();
+ const end = new Date(endDate);
+ return end < today;
+ };
+
+ const pastAnnouncements = announcements.filter(announcement => hasDatePassed(announcement.endDate));
+
+ const acceptMatching = async (recruitment, user) => {
+ const confirmSelection = window.confirm(`${user.userId}님의 리뷰 권한을 허용하시겠습니까?`);
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+
+ if (confirmSelection) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings/completed', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('권한 허용 처리가 완료되었습니다.');
+ fetchApplicants(recruitmentId);
+
+ fireNotificationWithTimeout('권한 허용 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}의 리뷰 권한이 허용되었습니다. `
+ });
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingWriteBoard", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingWriteBoardCompany", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error accepting the matching:", error);
+ alert('권한 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
+ const cancelApproval = async (recruitment, user) => {
+ const userId = user.userId;
+ const recruitmentId = recruitment.jobId;
+ const userPhone = user.userPhone;
+ const confirmCancel = window.confirm(`${user.userId}님의 리뷰 권한을 취소하시겠습니까?`);
+ if (confirmCancel) {
+ try {
+ const response = await axios.put('http://localhost:8050/api/matchings/completedCancel', {
+ recruitmentId,
+ userId
+ }, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+
+ if (response.status === 200) {
+ alert('권한 취소 처리가 완료되었습니다.');
+ fetchApplicants(recruitmentId);
+
+ fireNotificationWithTimeout('권한 취소 완료', 5000, {
+ body: `[${recruitment.jobTitle}]에 ${user.userName}의 리뷰 권한이 취소되었습니다. `
+ });
+
+ // SMS 전송 로직
+ try {
+ const smsResponse = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingWriteBoardCancel", {
+ phone: userPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(smsResponse.data);
+
+ // Provider에게도 SMS 전송
+ const sms2Response = await axios.post("http://localhost:8050/api/sms/sendApplicationMatchingWriteBoardCancelCompany", {
+ phone: providerPhone,
+ jobTitle: recruitment.jobTitle
+ });
+ console.log(sms2Response.data);
+ } catch (smsError) {
+ console.error("SMS 전송 중 오류 발생:", smsError);
+ }
+ }
+ } catch (error) {
+ console.error("Error canceling the approval:", error);
+ alert('권한 취소 처리 중 오류가 발생했습니다.');
+ }
+ }
+ };
+
return (
-
- 후기 체험담
-
-
-
- {currentItems.map(item => (
- handleReviewClick(item.reviewId)}>
-
- Review ID: {item.reviewId}
- Job ID: {item.jobId}
- User ID: {item.userId}
- Review Content: {item.reviewContent}
- Review Recommend: {item.reviewRecommend}
- Date: {formatDate(item.reviewDate)}
-
-
- ))}
-
-
- {Array.from({ length: Math.ceil(reviews.length / ITEMS_PER_PAGE) }, (_, i) => (
- paginate(i + 1)}>
- {i + 1}
-
- ))}
-
+
+
+ 체험 후기 게시판
+
+ {isUser && user.userId !== "admin" &&
+
+ }
+ {isCompanyUser &&
+
+ }
+
+
+
+
+ {isLoading &&
+ Loading...
+ }
+ {currentItems.map(item => (
+ handleReviewClick(item)}>
+
+
+
+
+
+ {item.reviewTitle} ({item.commentCount})
+ 작성자: {item.userId}
+ 날짜: {item.reviewWriteDate}
+ 조회수: {item.reviewClickCount}, 좋아요: {item.reviewLikeCount}
+
+
+
+ ))}
+
+
+
+ {Array.from({ length: Math.ceil(reviews.length / ITEMS_PER_PAGE) }, (_, i) => (
+ paginate(i + 1)}>
+ {i + 1}
+
+ ))}
+
+
+
+
+ 체험 후기 작성
+
+
+ {matchings.map(matching => (
+
+ {matching.recruitment.jobTitle}
+
+
+ ))}
+
+
+
+
+
+
+
+ 후기 작성 권한 허용
+
+
+ {pastAnnouncements.map(announcement => (
+
+ {announcement.title}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ {selectedAnnouncement &&
+ {selectedAnnouncement.title} 신청 명단
+ }
+
+
+
+ {applicants.length > 0 ? (
+
+
+
+ 이름
+ 전화번호
+ 권한상태
+
+
+
+ {applicants.map(applicant => (
+
+ handleUserClick(applicant.user)}>
+ {applicant.user.userName}
+
+ {applicant.user.userPhone}
+
+ {applicant.status === "ACCEPTED" &&
+ acceptMatching(applicant.recruitment, applicant.user)}>
+ 비허용
+
+ }
+ {applicant.status === "COMPLETED" &&
+ cancelApproval(applicant.recruitment, applicant.user)}>
+ 허용
+
+ }
+
+
+ ))}
+
+
+ ) : (
+ 신청자가 아직 없습니다.
+ )}
+
+
+
+
+
+ 회원 상세 정보
+ {selectedUser &&
+ <>
+ {selectedUser.userProfile === "hansoyeon/src/imgs/default_profile.png" ?
+
+ :
+
+ }
+ 이름: {selectedUser.userName}
+ 전화번호: {selectedUser.userPhone}
+ 이메일: {selectedUser.userEmail}
+ 주소: {selectedUser.userAddress}
+ >
+ }
+
+
+
+
);
};
+const LoadingContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+ font-size: 24px;
+`;
+
const Container = styled.div`
- width: 80%;
- margin: auto;
- padding: 20px;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex: 1;
+ height: auto;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`
+
+const HeaderContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 20px; // 여백 추가
+ margin-left: 3rem;
+`;
+
+const MiddleContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 2rem; // 상단 여백 조정
+ margin-bottom: 2rem; // 하단 여백 조정
+ width: 1000px; // 너비 조정
+ height: auto; // 높이 자동 조절
`;
+const NewsTitle = styled.div`
+ display: flex;
+ flex: 2;
+ flex-direction: row;
+ align-items: center;
+`
+
const ReviewPageTitle = styled.div`
+ font-size: 44px;
+ font-weight: 500;
+ font-family: 'omyu_pretty';
+`;
+
+const RightNewsTitle = styled.div`
display: flex;
- justify-content: space-between;
align-items: center;
- margin-bottom: 20px;
+ justify-content: flex-end;
+ margin-right: 50px;
`;
const Button = styled.button`
- background-color: #4CAF50;
+ background-color: orange;
color: white;
- padding: 10px 20px;
+ font-size: 24px;
+ width: 100px;
+ padding: 5px 10px;
+ font-weight: 500;
border: none;
border-radius: 5px;
cursor: pointer;
+ font-family: 'omyu_pretty';
&:hover {
- background-color: #45a049;
+ background-color: darkorange;
}
`;
const ReviewPageContentContainer = styled.div`
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 20px;
+ grid-template-columns: repeat(2, 1fr); // 2열 그리드 레이아웃
+ gap: 20px; // 그리드 사이 간격
+ width: 100%; // 전체 너비 사용
+ // 필요한 경우 여기에 추가 스타일을 적용
`;
const ReviewPageContentItem = styled.div`
@@ -127,4 +611,172 @@ const PageNumber = styled.span`
}
`;
-export default ReviewPage;
\ No newline at end of file
+const StyledModal = styled(Modal)`
+ .modal-dialog {
+ margin-top: 28vh; // 모달의 위치를 조정
+ }
+`;
+
+const StyledSelectContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px; // 아이템 간 간격 조정
+
+ p {
+ margin: 0;
+ flex-grow: 1; // 제목이 더 많은 공간을 차지하도록 설정
+ }
+
+ button {
+ flex-shrink: 0; // 버튼 크기가 내용에 따라 축소되지 않도록 설정
+ }
+`;
+
+
+const ImgContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 10px; // 이미지와 텍스트 사이 간격
+`;
+
+const Image = styled.img`
+ max-height: 250px;
+ border-radius: 10px;
+`;
+
+const TextInfo = styled.div`
+ text-align: center;
+`;
+
+const Title = styled.p`
+ font-size: 18px;
+ font-weight: bold;
+ margin: 5px 0; // 여백 조정
+`;
+
+const AdditionalInfo = styled.p`
+ font-size: 14px;
+ color: #666;
+ margin: 3px 0; // 여백 조정
+`;
+
+const ModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+`;
+
+const ModalHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ button {
+ padding: 5px 10px;
+ margin-left: 10px;
+ }
+`;
+
+const StyledTable = styled.table`
+ width: 100%;
+ border-collapse: collapse;
+`;
+
+const TableHeader = styled.th`
+ background-color: #f4f4f4;
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const TableRow = styled.tr`
+ &:nth-child(even) {
+ background-color: #f9f9f9;
+ }
+`;
+
+const TableCell = styled.td`
+ border: 1px solid #ddd;
+ padding: 8px;
+ text-align: center;
+`;
+
+const ApplicantsList = styled.div`
+ margin-top: 20px;
+ padding: 10px;
+ background: #fff;
+ border: 1px solid #ddd;
+`;
+
+const DetailModal = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const DetailModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ max-width: 500px;
+ width: 100%;
+ font-size: 18px;
+ text-align: center;
+`;
+
+const ProfileRequestPic = styled.img`
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ object-fit: cover;
+ margin-top: 10px;
+ margin-bottom: 20px;
+`;
+
+const NoApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: #ff6b6b;
+ color: white;
+ &:hover {
+ background-color: #ff4747;
+ }
+`;
+
+const ApproveButton = styled.button`
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ background-color: green;
+ color: white;
+ &:hover {
+ background-color: darkgreen;
+ }
+`;
+
+export default ReviewPage;
+
+export const fetchReviewsData = async () => {
+ try {
+ // 리뷰 데이터 가져오기
+ const reviewResponse = await axios.get('http://localhost:8050/api/reviews');
+ return reviewResponse.data.sort((a, b) => b.reviewLikeCount - a.reviewLikeCount);
+ } catch (error) {
+ console.error('Error fetching reviews and comment counts:', error);
+ }
+};
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/SchedulerPage.js b/hansoyeon/src/Pages/SchedulerPage.js
new file mode 100644
index 000000000..e5d9907ce
--- /dev/null
+++ b/hansoyeon/src/Pages/SchedulerPage.js
@@ -0,0 +1,395 @@
+import React, {useEffect, useState} from "react";
+import styled from "styled-components";
+import {useNavigate} from 'react-router-dom';
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
+import axios from "axios";
+import Calendar from 'react-calendar';
+import 'react-calendar/dist/Calendar.css';
+
+const SchedulerPage = () => {
+ const navigate = useNavigate();
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [date, setDate] = useState(new Date());
+ const [events, setEvents] = useState({});
+
+ const [eventTitle, setEventTitle] = useState('');
+ const [eventContent, setEventContent] = useState('');
+ const [selectedEvent, setSelectedEvent] = useState('');
+ const [selectedDateEvents, setSelectedDateEvents] = useState([]);
+
+ const [friends, setFriends] = useState([]);
+ const [matchings, setMatchings] = useState([]);
+
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedFriend, setSelectedFriend] = useState(null);
+ const [selectedFriendName, setSelectedFriendName] = useState(null);
+ const [friendSchedule, setFriendSchedule] = useState([]);
+
+
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ fetchFriends(fetchedUser.userId)
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const fetchFriends = (userId) => {
+ axios.get(`http://localhost:8050/api/friendships/friends?userId=${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ if (Array.isArray(response.data.data)) {
+ setFriends(response.data.data);
+ } else {
+ console.error("Expected an array for friends, but got:", response.data.data);
+ setFriends([]);
+ }
+ }).catch(error => {
+ console.error("Error fetching friends:", error);
+ setFriends([]);
+ });
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ fetchMatchings(user.userId);
+ }
+ }, [user]);
+
+ const fetchMatchings = async (userId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${userId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ const acceptedMatchings = response.data.data.filter(matching => matching.status === "ACCEPTED").map(matching => ({
+ ...matching,
+ recruitment: {
+ ...matching.recruitment,
+ startDate: matching.recruitment.jobStartDate,
+ endDate: matching.recruitment.jobEndDate,
+ jobStartDate: new Date(matching.recruitment.jobStartDate),
+ jobEndDate: new Date(matching.recruitment.jobEndDate)
+ }
+ }));
+ console.log(acceptedMatchings)
+ setMatchings(acceptedMatchings);
+ } catch (error) {
+ console.error("Error fetching matchings:", error);
+ }
+ };
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ const onChange = (newDate) => {
+ setDate(newDate);
+
+ const formattedDate = new Date(newDate.setHours(0, 0, 0, 0)); // Remove time part for comparison
+ const filteredEvents = matchings.filter(matching => {
+ const startDate = new Date(matching.recruitment.jobStartDate.setHours(0, 0, 0, 0));
+ const endDate = new Date(matching.recruitment.jobEndDate.setHours(0, 0, 0, 0));
+ return formattedDate >= startDate && formattedDate <= endDate;
+ });
+
+ setSelectedDateEvents(filteredEvents);
+ };
+ const isDateInRange = (date, startDate, endDate) => {
+ const start = new Date(startDate.setHours(0, 0, 0, 0));
+ const end = new Date(endDate.setHours(0, 0, 0, 0));
+ const current = new Date(date.setHours(0, 0, 0, 0));
+
+ return current >= start && current <= end;
+ };
+
+ const renderCellContent = ({ date, view }) => {
+ const isMatchingDate = matchings.some(matching => {
+ return isDateInRange(date, matching.recruitment.jobStartDate, matching.recruitment.jobEndDate);
+ });
+
+ return (
+
+ {isMatchingDate ? : }
+
+ );
+ };
+
+ const handleJobView = (jobId) => {
+ navigate(`/recruit/${jobId}`);
+ };
+
+ const handleFriendClick = async (friendId, friendName) => {
+ setSelectedFriend(friendId);
+ setSelectedFriendName(friendName)
+ fetchFriendMatchings(friendId)
+ setIsModalOpen(true);
+ };
+
+ const fetchFriendMatchings = async (friendId) => {
+ try {
+ const response = await axios.get(`http://localhost:8050/api/matchings/user/${friendId}`, {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ });
+ const acceptedMatchings = response.data.data.filter(matching => matching.status === "ACCEPTED").map(matching => ({
+ ...matching,
+ recruitment: {
+ ...matching.recruitment,
+ startDate: matching.recruitment.jobStartDate,
+ endDate: matching.recruitment.jobEndDate,
+ jobStartDate: new Date(matching.recruitment.jobStartDate),
+ jobEndDate: new Date(matching.recruitment.jobEndDate)
+ }
+ }));
+ const currentDate = new Date();
+ const validMatchings = acceptedMatchings.filter(matching => {
+ const startDate = new Date(matching.recruitment.startDate);
+ return startDate >= currentDate;
+ });
+ setFriendSchedule(validMatchings);
+ } catch (error) {
+ console.error("Error fetching matchings:", error);
+ }
+ };
+
+ const FriendSchedulerModal = ({ isOpen, onClose, schedule }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+ {selectedFriendName}의 스케줄
+
+ {friendSchedule.map(matching => (
+ handleJobView(matching.recruitment.jobId)}
+ >
+ {matching.recruitment.jobTitle}
+ {matching.recruitment.startDate} ~ {matching.recruitment.endDate}
+
+ ))}
+
+
+
+
+ );
+ };
+
+ return (
+
+
+ 스케줄러
+
+
+
+ 일정 목록
+
+ {selectedDateEvents.map(matching => (
+ handleJobView(matching.recruitment.jobId)}
+ >
+ {matching.recruitment.jobTitle}
+ {matching.recruitment.startDate} ~ {matching.recruitment.endDate}
+
+ ))}
+
+
+ 친구 목록
+ {friends.map((friend, index) => (
+ handleFriendClick(friend.userId, friend.userName)}>
+ {friend.userName}({friend.userId})
+
+ ))}
+
+
+ setIsModalOpen(false)}
+ schedule={friendSchedule}
+ />
+
+
+ );
+}
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20px;
+`;
+
+const Title = styled.h1`
+ font-size: 44px;
+ font-family: 'omyu_pretty';
+`;
+
+const StyledCalendar = styled(Calendar)`
+ width: 100%;
+ max-width: 800px;
+
+ .react-calendar__tile {
+ max-height: 120px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+`;
+
+const CellContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const CircleMarker = styled.div`
+ width: 10px;
+ height: 10px;
+ background-color: gray;
+ border-radius: 50%;
+ margin-top: 5px;
+`;
+
+const MainContainer = styled.div`
+ display: flex;
+ justify-content: space-around;
+ align-items: start;
+ padding: 20px;
+ flex-direction: row;
+
+ @media (max-width: 1100px) {
+ flex-direction: column;
+ }
+`;
+
+const EventList = styled.div`
+ width: 30%; // Adjust width as needed
+ padding: 20px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ margin-top: 70px;
+`;
+
+const FriendsList = styled.div`
+ width: 100%;
+ padding: 20px;
+ margin-top: 20px;
+ border-top: 1px solid #ccc;
+`;
+
+const Friend = styled.div`
+ margin: 10px 0;
+ padding: 10px;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ // Add more styling as needed
+`;
+
+const FriendName = styled.h3`
+ margin: 0;
+ // Add more styling as needed
+`;
+
+const MatchingList = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`;
+
+const MatchingItem = styled.div`
+ background: #fff;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ padding: 15px;
+ transition: transform 0.2s ease-in-out;
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+`;
+
+const JobTitle = styled.h5`
+ font-size: 1.2rem;
+ color: #333;
+ margin-bottom: 10px;
+`;
+
+const ModalBackdrop = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000; // Ensure it's above other content
+`;
+
+const ModalContent = styled.div`
+ background-color: white;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+ width: 60%; // Adjust as needed
+ max-width: 800px; // Adjust as needed
+ z-index: 1001; // Ensure it's above the backdrop
+`;
+
+const Button = styled.button`
+ padding: 10px 20px;
+ background-color: #007bff;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 16px;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: #0056b3;
+ }
+`;
+
+const EventItem = styled.div`
+ padding: 10px;
+ border-bottom: 1px solid #ddd;
+ margin-bottom: 10px;
+`;
+
+
+export default SchedulerPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/SigninPage.js b/hansoyeon/src/Pages/SigninPage.js
old mode 100644
new mode 100755
index d396af68a..980e81259
--- a/hansoyeon/src/Pages/SigninPage.js
+++ b/hansoyeon/src/Pages/SigninPage.js
@@ -1,20 +1,21 @@
-import React, {useEffect, useState} from 'react';
-import {Form, Button, InputGroup} from 'react-bootstrap';
+import React, { useEffect, useState } from 'react';
+import { Form, Button, InputGroup } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import styled from 'styled-components';
-import {useNavigate} from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import logo from "../imgs/logo2.png";
-import kakao from "../imgs/kakao.png"
-import google from "../imgs/google.png"
-import company from "../imgs/company.png"
-import member from "../imgs/member.png"
+import kakao from "../imgs/kakao.png";
+import google from "../imgs/google.png";
+import company from "../imgs/company.png";
+import member from "../imgs/member.png";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import {faArrowLeft, faEye, faEyeSlash} from '@fortawesome/free-solid-svg-icons';
+import { faArrowLeft, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import axios from "axios";
-import {useCookies} from "react-cookie";
-import {useUserStore} from "../stores";
+import { useCookies } from "react-cookie";
+import { useUserStore } from "../stores";
+import Footer from "../Components/Footer";
-const SigninPage = (props) => {
+const SigninPage = () => {
const [showPassword, setShowPassword] = useState(false);
const [showMemberForm, setShowMemberForm] = useState(false);
const [showCompanyForm, setShowCompanyForm] = useState(false);
@@ -26,7 +27,7 @@ const SigninPage = (props) => {
const [providerPassword, setProviderPassword] = useState('');
const [cookies, setCookies] = useCookies();
- const {user, setUser} = useUserStore();
+ const { user, setUser } = useUserStore();
const [isKakaoSdkLoaded, setIsKakaoSdkLoaded] = useState(false);
const [userEmail, setUserEmail] = useState('');
@@ -52,13 +53,12 @@ const SigninPage = (props) => {
}, []);
const handleKakaoLoginImageClick = () => {
-
- if (window.Kakao && window.Kakao.isInitialized()) { // Kakao SDK 초기화 확인
+ if (window.Kakao && window.Kakao.isInitialized()) {
window.Kakao.Auth.login({
- success: function(authObj) {
+ success: function (authObj) {
window.Kakao.API.request({
url: '/v2/user/me',
- success: function(response) {
+ success: function (response) {
const userEmail = response.kakao_account?.email;
if (userEmail) {
setUserEmail("(kakao)" + userEmail);
@@ -67,12 +67,12 @@ const SigninPage = (props) => {
console.log("이메일 정보를 가져올 수 없습니다.");
}
},
- fail: function(error) {
+ fail: function (error) {
console.log(error);
},
});
},
- fail: function(err) {
+ fail: function (err) {
console.log(err);
},
});
@@ -82,7 +82,7 @@ const SigninPage = (props) => {
};
const handleLogin = () => {
- if(userId.length === 0 || userPassword.length === 0){
+ if (userId.length === 0 || userPassword.length === 0) {
alert('아이디와 비밀번호를 입력하세요');
return;
}
@@ -90,11 +90,11 @@ const SigninPage = (props) => {
const data = {
userId,
userPassword
- }
+ };
axios.post("http://localhost:8050/api/auth/signIn", data).then((response) => {
const responseData = response.data;
console.log(responseData);
- if(!responseData.result){
+ if (!responseData.result) {
alert('아이디 또는 비밀번호가 일치하지 않습니다. ');
return;
}
@@ -102,17 +102,17 @@ const SigninPage = (props) => {
const expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + exprTime);
- setCookies('token', token, {expires});
- setCookies('userType', userType, {expires});
+ setCookies('token', token, { expires });
+ setCookies('userType', userType, { expires });
setUser(user);
navigate("/");
}).catch((error) => {
- alert('로그인에 실패했습니다. ')
- })
+ alert('로그인에 실패했습니다. ');
+ });
};
const handleCompanyLogin = () => {
- if(providerId.length === 0 || providerPassword.length === 0){
+ if (providerId.length === 0 || providerPassword.length === 0) {
alert('아이디와 비밀번호를 입력하세요');
return;
}
@@ -120,11 +120,11 @@ const SigninPage = (props) => {
const data = {
providerId,
providerPassword
- }
+ };
axios.post("http://localhost:8050/api/auth/signIn/company", data).then((response) => {
const responseData = response.data;
console.log(responseData);
- if(!responseData.result){
+ if (!responseData.result) {
alert('아이디 또는 비밀번호가 일치하지 않습니다. ');
return;
}
@@ -132,13 +132,13 @@ const SigninPage = (props) => {
const expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + exprTime);
- setCookies('token', token, {expires});
- setCookies('userType', userType, {expires});
+ setCookies('token', token, { expires });
+ setCookies('userType', userType, { expires });
setUser(user);
- navigate("/")
+ navigate("/");
}).catch((error) => {
- alert('로그인에 실패했습니다. ')
- })
+ alert('로그인에 실패했습니다. ');
+ });
};
const handleKakaoLogin = (userEmail) => {
@@ -163,9 +163,8 @@ const SigninPage = (props) => {
});
};
-
const handleSignUp = () => {
- navigate("/register")
+ navigate("/register");
};
const togglePasswordVisibility = () => {
@@ -185,122 +184,146 @@ const SigninPage = (props) => {
setShowMemberForm(false);
};
-
- if (!showMemberForm && !showCompanyForm) {
- return (
-
- 로그인
-
-
-
-
-
-
-
-
-
- );
- }else if(showMemberForm && !showCompanyForm){
- return (
-
-
-
-
-
-
-
-
- 아이디 찾기
- •
- 비밀번호 찾기
- •
- 회원가입
-
-
-
- )
- }else if(!showMemberForm && showCompanyForm){
- return (
-
-
-
-
-
-
-
-
- 아이디 찾기
- •
- 비밀번호 찾기
- •
- 회원가입
-
-
-
-
- )
-
- }
+ return (
+
+
+ {(!showMemberForm && !showCompanyForm) && (
+ <>
+ 로그인
+
+
+
+
+
+
+
+
+ >
+ )}
+ {(showMemberForm && !showCompanyForm) && (
+ <>
+
+
+
+
+
+
+
+ 아이디 찾기
+ •
+ 비밀번호 찾기
+ •
+ 회원가입
+
+
+ >
+ )}
+ {(!showMemberForm && showCompanyForm) && (
+ <>
+
+
+
+
+
+
+
+ 아이디 찾기
+ •
+ 비밀번호 찾기
+ •
+ 회원가입
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
};
+const FooterContainer = styled.footer`
+ /* Footer 컨테이너 스타일 */
+ margin-top: auto; /* 화면 하단으로 이동 */
+`;
+
+const PageContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+`;
+
+const ContentContainer = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin-top: 80px;
+`;
+
+// 나머지 스타일 및 컴포넌트 정의
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
@@ -316,21 +339,21 @@ const FormBox = styled.div`
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 100%;
- max-width: 450px;
+ max-width: 450px;
margin-top: 20px;
position: relative;
`;
const StyledFormGroup = styled(Form.Group)`
- margin-bottom: 20px;
+ margin-bottom: 20px;
`;
const StyledFormControl = styled(Form.Control)`
- font-size: 16px;
- padding: 10px;
-
+ font-size: 16px;
+ padding: 10px;
+
::placeholder {
- color: #666;
+ color: #666;
}
`;
@@ -402,7 +425,7 @@ const NavigationLinks = styled.div`
const NavLink = styled.a`
text-decoration: none;
- color: #000;
+ color: #000;
margin: 0 10px;
&:hover {
text-decoration: underline;
@@ -410,7 +433,7 @@ const NavLink = styled.a`
`;
const NavLinkDivider = styled.span`
- color: #000;
+ color: #000;
`;
const BackButton = styled.button`
@@ -428,8 +451,9 @@ const Title = styled.div`
display: flex;
margin-bottom: 80px;
align-items:center;
- font-size: 40px;
+ font-size: 44px;
font-weight: 700;
+ font-family: 'omyu_pretty';
`;
const ImageContainer = styled.div`
@@ -439,5 +463,4 @@ const ImageContainer = styled.div`
`;
-
-export default SigninPage;
+export default SigninPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/SignupPage.js b/hansoyeon/src/Pages/SignupPage.js
old mode 100644
new mode 100755
index dc74f1936..57aa1e2bf
--- a/hansoyeon/src/Pages/SignupPage.js
+++ b/hansoyeon/src/Pages/SignupPage.js
@@ -1,16 +1,18 @@
-import React, {useEffect, useState} from 'react';
+import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
-import {useNavigate} from 'react-router-dom';
-import company from "../imgs/company.png"
-import member from "../imgs/member.png"
+import { useNavigate } from 'react-router-dom';
+import company from "../imgs/company.png";
+import member from "../imgs/member.png";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import {faArrowLeft, faEye, faEyeSlash} from '@fortawesome/free-solid-svg-icons';
+import { faArrowLeft, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import kakao from "../imgs/kakaoRegister.png";
import google from "../imgs/googleRegister.png";
-import email from "../imgs/emailRegister.png"
+import email from "../imgs/emailRegister.png";
import GoogleLogin from "react-google-login";
import logo from "../imgs/logo2.png";
-import {Form, InputGroup} from "react-bootstrap";
+import { Form, InputGroup } from "react-bootstrap";
+import Footer from "../Components/Footer";
+
const SignupPage = (props) => {
const [showMemberForm, setShowMemberForm] = useState(false);
const [isKakaoSdkLoaded, setIsKakaoSdkLoaded] = useState(false);
@@ -37,7 +39,7 @@ const SignupPage = (props) => {
}, []);
const handleCompanyImageClick = () => {
- navigate("/companyRegister")
+ navigate("/companyRegister");
};
const handleMemberImageClick = () => {
@@ -54,7 +56,7 @@ const SignupPage = (props) => {
};
const handleEmailRegisterImageClick = () => {
- navigate("/memberRegister")
+ navigate("/memberRegister");
};
const handleBack = () => {
@@ -62,15 +64,14 @@ const SignupPage = (props) => {
};
const handleKakaoRegisterImageClick = () => {
-
if (window.Kakao && window.Kakao.isInitialized()) { // Kakao SDK 초기화 확인
window.Kakao.Auth.login({
- success: function(authObj) {
+ success: function (authObj) {
console.log(authObj); // 인증 객체 확인
window.Kakao.API.request({
url: '/v2/user/me',
- success: function(response) {
+ success: function (response) {
console.log(response);
if (response.kakao_account && response.kakao_account.email) {
const email = "(kakao)" + response.kakao_account.email;
@@ -79,12 +80,12 @@ const SignupPage = (props) => {
console.log("이메일 정보를 가져올 수 없습니다.");
}
},
- fail: function(error) {
+ fail: function (error) {
console.log(error);
},
});
},
- fail: function(err) {
+ fail: function (err) {
console.log(err);
},
});
@@ -93,25 +94,29 @@ const SignupPage = (props) => {
}
};
-
if (!showMemberForm) {
return (
- 회원가입
-
-
-
-
-
-
-
-
+
+
+ 회원가입
+
+
+
+
+
+
+
+
+
+
+
);
- }else if(showMemberForm){
+ } else if (showMemberForm) {
return (
-
+
@@ -125,19 +130,18 @@ const SignupPage = (props) => {
-
+
- )
+ );
}
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
+ min-height: 100vh;
justify-content: center;
align-items: center;
- height: 100vh;
- background-color: #ffffff;
`;
const Title = styled.div`
@@ -145,8 +149,9 @@ const Title = styled.div`
margin-bottom: 80px;
align-items:center;
justify-content: center;
- font-size: 30px;
+ font-size: 44px;
font-weight: 700;
+ font-family: 'omyu_pretty';
`;
const ImageContainer = styled.div`
@@ -196,10 +201,40 @@ const FormBox = styled.div`
background: #ffffff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
- width: 100%;
+ width: 100%; /* Set width to 100% */
max-width: 600px;
margin-top: 150px;
position: relative;
`;
+const FormBoxUser = styled.div`
+ padding: 40px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: #ffffff;
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ width: 100%;
+ max-width: 600px;
+ position: relative;
+`;
+
+const PageContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+`;
+
+const ContentContainer = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 60px;
+ width: 100%;
+`;
+
export default SignupPage;
\ No newline at end of file
diff --git a/hansoyeon/src/Pages/ThemaPage.js b/hansoyeon/src/Pages/ThemaPage.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/Pages/WritingNewsPage.js b/hansoyeon/src/Pages/WritingNewsPage.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/Pages/WritingRecruitPage.js b/hansoyeon/src/Pages/WritingRecruitPage.js
old mode 100644
new mode 100755
index a0103b498..addf27f52
--- a/hansoyeon/src/Pages/WritingRecruitPage.js
+++ b/hansoyeon/src/Pages/WritingRecruitPage.js
@@ -1,98 +1,182 @@
-import React, { useState } from 'react';
+import React, {useEffect, useState} from 'react';
import axios from 'axios';
import { useNavigate } from "react-router-dom";
+import {useCookies} from "react-cookie";
+import {useUserStore} from "../stores";
const WritingRecruitPage = () => {
const [title, setTitle] = useState('');
- const [content, setContent] = useState('');
+ const [schedule, setSchedule] = useState('');
+ const [selectedRegion, setSelectedRegion] = useState('');
+ const [background, setBackground] = useState('');
+ const [morning, setMorning] = useState('');
+ const [lunch, setLunch] = useState('');
+ const [dinner, setDinner] = useState('');
+ const [need, setNeed] = useState('');
const [region, setRegion] = useState('');
+ const [content, setContent] = useState('');
+ const [second, setSecond] = useState('');
+ const [third, setThird] = useState('');
+ const [address, setAddress] = useState('');
const [providers, setProviders] = useState('');
const [money, setMoney] = useState('');
- const [image, setImage] = useState(null);
+ const [image, setImage] = useState([]);
const navigate = useNavigate();
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentCompany', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
+ };
+
+ useEffect(() => {
+ if (user && cookies.token) {
+ setProviders(user.providerId)
+ }
+ }, [user]);
+
+
const handleInputTitle = (e) => {
setTitle(e.target.value);
};
const handleInputContent = (e) => {
setContent(e.target.value);
};
- const handleImageChange = async (e) => {
- // 이미지 선택 시
- setImage(e.target.files[0]);
+ const handleRegionChange = (e) => {
+ setSelectedRegion(e.target.value);
+ };
+ const handleImageChange = (e) => {
+ // 파일 선택 시
+ setImage([...e.target.files]);
};
const handleSubmit = async (e) => {
e.preventDefault();
- // 이미지를 base64로 인코딩
- let base64Image = '';
- if (image) {
- const reader = new FileReader();
- reader.readAsDataURL(image);
-
- // Promise를 사용하기 위해 await를 사용하려면 handleImageChange도 async 함수여야 합니다.
- base64Image = await new Promise((resolve) => {
- reader.onloadend = () => {
- resolve(reader.result);
- };
- });
- }
-
- // 이미지를 포함한 recruitnewspost 객체 생성
- const recruitnewspost = {
- title,
- content,
- startDate,
- endDate,
- region,
- providers,
- money,
- image: base64Image, // 이미지를 추가
-
- };
- console.log("Form Data:", {
- title,
- content,
- region,
- providers,
- money,
- startDate,
- endDate,
- image: base64Image,
- });
- // 서버에 전송
try {
+ const formData = new FormData();
+
+ for (let i = 0; i< image.length; i++) {
+ formData.append('profileImages', image[i]);
+ }
+ console.log(image)
+
const response = await axios.post(
- 'http://localhost:8050/api/createRecruitment',
- recruitnewspost,
+ 'http://localhost:8050/api/uploadProfileImages',
+ formData,
{
headers: {
- 'Content-Type': 'application/json',
+ 'Content-Type': 'multipart/form-data',
},
}
);
- if (response.status === 201) {
- console.log(response.data);
- // 글 작성 성공 시, 페이지를 이동
- navigate('/recruit');
- } else {
- console.error(`Http 오류! 상태 코드: ${response.status}`);
+ // 이미지를 포함한 recruitnewspost 객체 생성
+ const recruitnewspost = {
+ title,
+ schedule,
+ content,
+ second,
+ third,
+ background,
+ morning,
+ lunch,
+ dinner,
+ need,
+ startDate,
+ endDate,
+ region: selectedRegion,
+ address,
+ providers,
+ money,
+ image: response.data.imageUrls, // 이미지를 추가
+ };
+ console.log("Form Data:", {
+ title,
+ schedule,
+ content,
+ second,
+ third,
+ background,
+ morning,
+ lunch,
+ dinner,
+ need,
+ region: selectedRegion,
+ address,
+ providers,
+ money,
+ startDate,
+ endDate,
+ image: response.data.imageUrls
+ });
+
+ // 서버에 전송
+ try {
+ const response = await axios.post(
+ 'http://localhost:8050/api/createRecruitment',
+ recruitnewspost,
+
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+
+ },
+
+ }
+ );
+
+ if (response.status === 201) {
+ console.log(response.data);
+ // 글 작성 성공 시, 페이지를 이동
+ navigate('/recruit');
+ } else {
+ console.error(`Http 오류! 상태 코드: ${response.status}`);
+ }
+ } catch (error) {
+ console.log('API 요청 중 오류 발생: ', error);
}
} catch (error) {
- console.log('API 요청 중 오류 발생: ', error);
+ console.log('이미지 업로드 중 오류 발생: ', error);
}
};
+
+ const regions = ["서울특별시", "인천광역시", "대전광역시", "광주광역시",
+ "대구광역시", "부산광역시", "경기도", "강원도", "충청북도",
+ "충청남도", "전라북도", "전라남도", "경상북도", "경상남도", "제주특별자치도"];
+
return (
@@ -189,4 +367,6 @@ const WritingRecruitPage = () => {
);
};
-export default WritingRecruitPage;
\ No newline at end of file
+
+
+export default WritingRecruitPage;
diff --git a/hansoyeon/src/Pages/WritingReviewPage.js b/hansoyeon/src/Pages/WritingReviewPage.js
old mode 100644
new mode 100755
index a6896f739..588edd0fc
--- a/hansoyeon/src/Pages/WritingReviewPage.js
+++ b/hansoyeon/src/Pages/WritingReviewPage.js
@@ -1,170 +1,268 @@
-import React, { useState } from "react";
+import React, {useEffect, useState} from "react";
+import {Row, Col, Button, Form, Container} from 'react-bootstrap';
import styled from "styled-components";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, useLocation } from "react-router-dom";
import axios from 'axios';
-import { useUserStore } from '../stores'; // useUserStore 훅의 실제 경로
+import { useCookies } from 'react-cookie';
+import { useUserStore } from '../stores';
+import defaultProfilePic from '../imgs/default_profile.png';
const WritingReviewPage = () => {
const navigate = useNavigate();
- const { user } = useUserStore(); // 현재 로그인한 사용자 정보 가져오기
- const [reviewTitle, setReviewTitle] = useState('');
- const [reviewContent, setReviewContent] = useState('');
+ const [cookies, setCookie, removeCookie] = useCookies(['token']);
+ const {user, setUser} = useUserStore();
+ const [postTitle, setPostTitle] = useState('');
+ const [postContent, setPostContent] = useState('');
+ const [postImage, setPostImage] = useState(null);
+ const [postImagePreview, setPostImagePreview] = useState(null);
const writer = user ? user.userName : "익명"; // 현재 로그인한 사용자의 이름이나 "익명" 사용
const [showPopup, setShowPopup] = useState(false); // 팝업 상태 관리
+ const [isLoading, setIsLoading] = useState(false);
+ const [errorMessage, setErrorMessage] = useState('');
- const handleInputTitle = e => {
- setReviewTitle(e.target.value);
+ const location = useLocation();
+ const { selectedMatching } = location.state || {};
+
+ useEffect(() => {
+ if (cookies.token) {
+ axios.get('http://localhost:8050/api/auth/currentUser', {
+ headers: {
+ Authorization: `Bearer ${cookies.token}`
+ }
+ }).then(response => {
+ console.log(cookies.token)
+ // 토큰이 유효한 경우
+ const fetchedUser = response.data;
+ console.log(fetchedUser)
+ setUser(fetchedUser);
+ }).catch(error => {
+ // 토큰이 유효하지 않은 경우
+ console.error("Token verification failed:", error);
+ handleLogout();
+ });
+ }
+ }, []);
+
+ const handleLogout = () => {
+ removeCookie('token');
+ setUser(null);
+ navigate("/");
};
- const handleInputContent = e => {
- setReviewContent(e.target.value);
+
+ const handlePostImageChange = (e) => {
+ if (e.target.files && e.target.files[0]) {
+ let selectedFile = e.target.files[0];
+ setPostImage(selectedFile);
+
+ let reader = new FileReader();
+ reader.onload = (event) => {
+ setPostImagePreview(event.target.result);
+ }
+ reader.readAsDataURL(selectedFile);
+ }
};
- const handleSubmit = async (e) => {
- e.preventDefault();
- const reviewData = {
+ const handleCreatePost = async () => {
+ if (!postTitle || !postContent) {
+ alert('제목과 내용을 모두 작성해주세요.');
+ return;
+ }
+
+ setIsLoading(true);
+ setErrorMessage('');
- jobId: 1, // 임시로 설정된 값, 실제 애플리케이션에서는 사용자가 선택하거나 다른 방법으로 결정해야 합니다.
- userId: user ? user.userId : 999, // 만약 사용자 정보가 없으면 임시 ID를 사용
- reviewTitle: reviewTitle, // 사용자가 입력한 리뷰 제목
- reviewContent: reviewContent, // 사용자가 입력한 리뷰 내용
- reviewDate: new Date(), // 현재 날짜와 시간
- reviewRecommend: 0, // 초기 추천 수, 0으로 설정
- writer: writer // 현재 로그인한 사용자의 이름이나 "익명"
- };
+ const formData = new FormData();
+ formData.append('reviewTitle', postTitle);
+ formData.append('userId', user.userId);
+ formData.append('jobId', selectedMatching.recruitment.jobId)
+ formData.append('reviewContent', postContent);
+
+ if (postImage) {
+ formData.append('postImage', postImage);
+ }
try {
- const response = await axios.post('http://localhost:8050/api/reviews', reviewData, {
+ const response = await axios.post('http://localhost:8050/api/createReview', formData, {
headers: {
- 'Content-Type': 'application/json',
- },
+ 'Content-Type': 'multipart/form-data'
+ }
});
- if (response.status === 200) {
- setShowPopup(true); // 팝업 표시
- setTimeout(() => setShowPopup(false), 3000); // 3초 후에 팝업 숨김
- navigate("/review");
+ if (response.data.success) {
+ alert('게시글 작성이 완료되었습니다.');
+ navigate('/review');
} else {
- console.error(`HTTP error! Status: ${response.status}`);
+ setErrorMessage('게시글 작성 중 오류가 발생했습니다.');
}
} catch (error) {
- console.error('Error posting review:', error);
+ setErrorMessage('게시글 작성 중 오류가 발생했습니다.');
+ } finally {
+ setIsLoading(false);
}
};
return (
-
-
- 리뷰 작성
-
-
-
+
+
+
+
+ 제목
+ setPostTitle(e.target.value)}
+ />
+
+
+
+ 내용
+ setPostContent(e.target.value)}
+ />
+
+
+ {postImagePreview && (
+
+ )}
+
+
+
+
+
+ {errorMessage && (
+ {errorMessage}
+ )}
+
+
+
+
+
+
);
};
-const Container = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 20px;
- height: 100vh;
-`;
-
const TitleContainer = styled.div`
font-size: 30px;
font-weight: bold;
margin-bottom: 20px;
`;
-const Form = styled.form`
- width: 100%;
- max-width: 600px;
-`;
-
const MiddleContainer = styled.div`
display: flex;
flex-direction: column;
+ gap: 20px;
`;
const Title = styled.div`
margin-bottom: 15px;
+ &:before {
+ content: "*";
+ display: inline;
+ color: red;
+ }
`;
+
const Label = styled.label`
- display: block;
- margin-bottom: 5px;
+ margin-right: 10px;
+ font-weight: bold;
`;
const Input = styled.input`
width: 100%;
- padding: 10px;
- border: 1px solid #ccc;
+ padding: 15px;
+ border: 1px solid #dbdbdb;
border-radius: 5px;
`;
const WriterContainer = styled.div`
+ display: flex;
+ align-items: center;
margin-bottom: 15px;
`;
const ContentContainer = styled.div`
margin-bottom: 15px;
+ &:before {
+ content: "*";
+ display: inline;
+ color: red;
+ }
`;
const Textarea = styled.textarea`
width: 100%;
- height: 150px;
+ height: 200px; // 더 큰 입력 필드
+ padding: 15px;
+ border: 1px solid #dbdbdb;
+ border-radius: 5px;
+`;
+
+const FileInput = styled.input`
padding: 10px;
- border: 1px solid #ccc;
+ border: 1px solid #dbdbdb;
border-radius: 5px;
+ cursor: pointer;
`;
const ButtonContainer = styled.div`
display: flex;
- justify-content: flex-end;
+ justify-content: center; // 가운데로 보내기 위해 수정
`;
const SubmitButton = styled.button`
- background-color: #007bff;
+ background-color: #0095f6;
color: white;
- padding: 10px 20px;
+ padding: 15px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
+ font-weight: bold;
+ transition: background-color 0.5s; // 부드러운 색상 전환
+ width: 40%;
&:hover {
- background-color: #0056b3;
+ background-color: #0077cc;
}
-`
+`;
const Popup = styled.div`
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
- background-color: #4CAF50;
+ background-color: #4caf50;
color: white;
padding: 10px 20px;
border-radius: 5px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
`;
+const StyledContainer = styled(Container)`
+ // 여기에 커스텀 CSS 스타일을 추가하세요
+ max-width: 800px;
+ background-color: white;
+ border-radius: 8px;
+ padding: 20px;
+ margin-top: 150px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+`;
+
+const ImagePreview = styled.img`
+ width: 100%;
+ max-height: 300px;
+ margin-bottom: 10px;
+`;
-export default WritingReviewPage;
\ No newline at end of file
+export default WritingReviewPage;
diff --git a/hansoyeon/src/hooks/useRecruitments.js b/hansoyeon/src/hooks/useRecruitments.js
new file mode 100644
index 000000000..aa56c74ef
--- /dev/null
+++ b/hansoyeon/src/hooks/useRecruitments.js
@@ -0,0 +1,31 @@
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+
+export const useRecruitments = () => {
+ const [recruitments, setRecruitments] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const fetchRecruitments = async () => {
+ setIsLoading(true);
+ try {
+ const currentDate = new Date();
+ const response = await axios.get('http://localhost:8050/api/recruitments');
+ const validRecruits = response.data.filter(recruitment => {
+ const startDate = new Date(recruitment.startDate);
+ return startDate >= currentDate;
+ }).reverse();
+ setRecruitments(validRecruits);
+ } catch (error) {
+ console.error('Error fetching recruitments:', error);
+ // 에러 처리 로직 추가
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchRecruitments();
+ }, []);
+
+ return { recruitments, isLoading };
+};
diff --git a/hansoyeon/src/imgs/Blacklist_logo.png b/hansoyeon/src/imgs/Blacklist_logo.png
new file mode 100755
index 000000000..a21bc767a
Binary files /dev/null and b/hansoyeon/src/imgs/Blacklist_logo.png differ
diff --git a/hansoyeon/src/imgs/Flow1.png b/hansoyeon/src/imgs/Flow1.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/Flow2.png b/hansoyeon/src/imgs/Flow2.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/Flow3.png b/hansoyeon/src/imgs/Flow3.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/Flow4.png b/hansoyeon/src/imgs/Flow4.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/about1.png b/hansoyeon/src/imgs/about1.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/about3.png b/hansoyeon/src/imgs/about3.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/about4.jpg b/hansoyeon/src/imgs/about4.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/about5.jpg b/hansoyeon/src/imgs/about5.jpg
index 4cc8b6a75..e7eb320cf 100644
Binary files a/hansoyeon/src/imgs/about5.jpg and b/hansoyeon/src/imgs/about5.jpg differ
diff --git a/hansoyeon/src/imgs/about6.jpg b/hansoyeon/src/imgs/about6.jpg
index 3ccacb53a..f111f6f88 100644
Binary files a/hansoyeon/src/imgs/about6.jpg and b/hansoyeon/src/imgs/about6.jpg differ
diff --git a/hansoyeon/src/imgs/aboutLogo.jpg b/hansoyeon/src/imgs/aboutLogo.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/aboutPolicy.jpg b/hansoyeon/src/imgs/aboutPolicy.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/aespaBanner.png b/hansoyeon/src/imgs/aespaBanner.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/approvalCheck.png b/hansoyeon/src/imgs/approvalCheck.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/banner.png b/hansoyeon/src/imgs/banner.png
index ae8b57d2d..f084eddcc 100644
Binary files a/hansoyeon/src/imgs/banner.png and b/hansoyeon/src/imgs/banner.png differ
diff --git a/hansoyeon/src/imgs/banner2.png b/hansoyeon/src/imgs/banner2.png
index f48998ca7..663667077 100644
Binary files a/hansoyeon/src/imgs/banner2.png and b/hansoyeon/src/imgs/banner2.png differ
diff --git a/hansoyeon/src/imgs/banner3.png b/hansoyeon/src/imgs/banner3.png
index 2db921792..1b50eb143 100644
Binary files a/hansoyeon/src/imgs/banner3.png and b/hansoyeon/src/imgs/banner3.png differ
diff --git a/hansoyeon/src/imgs/banner4.png b/hansoyeon/src/imgs/banner4.png
new file mode 100644
index 000000000..ca78900a7
Binary files /dev/null and b/hansoyeon/src/imgs/banner4.png differ
diff --git a/hansoyeon/src/imgs/banner5.png b/hansoyeon/src/imgs/banner5.png
new file mode 100644
index 000000000..145ccd793
Binary files /dev/null and b/hansoyeon/src/imgs/banner5.png differ
diff --git a/hansoyeon/src/imgs/banner6.png b/hansoyeon/src/imgs/banner6.png
new file mode 100644
index 000000000..a9672cb27
Binary files /dev/null and b/hansoyeon/src/imgs/banner6.png differ
diff --git a/hansoyeon/src/imgs/bookmark.png b/hansoyeon/src/imgs/bookmark.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/carina.jpg b/hansoyeon/src/imgs/carina.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/carrerPeople.png b/hansoyeon/src/imgs/carrerPeople.png
new file mode 100644
index 000000000..084b40914
Binary files /dev/null and b/hansoyeon/src/imgs/carrerPeople.png differ
diff --git a/hansoyeon/src/imgs/company.png b/hansoyeon/src/imgs/company.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/default_profile.png b/hansoyeon/src/imgs/default_profile.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/emailRegister.png b/hansoyeon/src/imgs/emailRegister.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/footer1.png b/hansoyeon/src/imgs/footer1.png
new file mode 100644
index 000000000..f37663359
Binary files /dev/null and b/hansoyeon/src/imgs/footer1.png differ
diff --git a/hansoyeon/src/imgs/footer2.png b/hansoyeon/src/imgs/footer2.png
new file mode 100644
index 000000000..55a3e6cc9
Binary files /dev/null and b/hansoyeon/src/imgs/footer2.png differ
diff --git a/hansoyeon/src/imgs/google.png b/hansoyeon/src/imgs/google.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/googleRegister.png b/hansoyeon/src/imgs/googleRegister.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/gwanghui.jpg b/hansoyeon/src/imgs/gwanghui.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/hart.png b/hansoyeon/src/imgs/hart.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/iu2.jpeg b/hansoyeon/src/imgs/iu2.jpeg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/iu3.png b/hansoyeon/src/imgs/iu3.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/iuBanner.jpg b/hansoyeon/src/imgs/iuBanner.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/jijele.jpeg b/hansoyeon/src/imgs/jijele.jpeg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/kakao.png b/hansoyeon/src/imgs/kakao.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/kakaoRegister.png b/hansoyeon/src/imgs/kakaoRegister.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/license.jpg b/hansoyeon/src/imgs/license.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/location.png b/hansoyeon/src/imgs/location.png
new file mode 100755
index 000000000..6ae8254ca
Binary files /dev/null and b/hansoyeon/src/imgs/location.png differ
diff --git a/hansoyeon/src/imgs/logo-removebg.png b/hansoyeon/src/imgs/logo-removebg.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/logo.png b/hansoyeon/src/imgs/logo.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/logo2.png b/hansoyeon/src/imgs/logo2.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/logo3.png b/hansoyeon/src/imgs/logo3.png
new file mode 100644
index 000000000..d4a57e41d
Binary files /dev/null and b/hansoyeon/src/imgs/logo3.png differ
diff --git a/hansoyeon/src/imgs/logo4.png b/hansoyeon/src/imgs/logo4.png
new file mode 100644
index 000000000..240186c68
Binary files /dev/null and b/hansoyeon/src/imgs/logo4.png differ
diff --git a/hansoyeon/src/imgs/logo5.png b/hansoyeon/src/imgs/logo5.png
new file mode 100644
index 000000000..68e8e8a0d
Binary files /dev/null and b/hansoyeon/src/imgs/logo5.png differ
diff --git a/hansoyeon/src/imgs/member.png b/hansoyeon/src/imgs/member.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/moon.png b/hansoyeon/src/imgs/moon.png
new file mode 100644
index 000000000..fcc9d4b30
Binary files /dev/null and b/hansoyeon/src/imgs/moon.png differ
diff --git a/hansoyeon/src/imgs/morning.png b/hansoyeon/src/imgs/morning.png
new file mode 100644
index 000000000..721b7e3ed
Binary files /dev/null and b/hansoyeon/src/imgs/morning.png differ
diff --git a/hansoyeon/src/imgs/newcourse-1.png b/hansoyeon/src/imgs/newcourse-1.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/newcourse-2.png b/hansoyeon/src/imgs/newcourse-2.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/newcourse-3.png b/hansoyeon/src/imgs/newcourse-3.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/newcourse-4.png b/hansoyeon/src/imgs/newcourse-4.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/ningning.jpeg b/hansoyeon/src/imgs/ningning.jpeg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/noImage.png b/hansoyeon/src/imgs/noImage.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/notebookPeople.png b/hansoyeon/src/imgs/notebookPeople.png
new file mode 100644
index 000000000..73f3f2664
Binary files /dev/null and b/hansoyeon/src/imgs/notebookPeople.png differ
diff --git a/hansoyeon/src/imgs/question.png b/hansoyeon/src/imgs/question.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/recommendcourse-1.png b/hansoyeon/src/imgs/recommendcourse-1.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/recommendcourse-2.png b/hansoyeon/src/imgs/recommendcourse-2.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/recruitment1.png b/hansoyeon/src/imgs/recruitment1.png
new file mode 100644
index 000000000..77a959291
Binary files /dev/null and b/hansoyeon/src/imgs/recruitment1.png differ
diff --git a/hansoyeon/src/imgs/regioncourse1.jpg b/hansoyeon/src/imgs/regioncourse1.jpg
new file mode 100644
index 000000000..949632f54
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse1.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse2.jpg b/hansoyeon/src/imgs/regioncourse2.jpg
new file mode 100644
index 000000000..0266da606
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse2.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse3.jpg b/hansoyeon/src/imgs/regioncourse3.jpg
new file mode 100644
index 000000000..58b959799
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse3.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse4.jpg b/hansoyeon/src/imgs/regioncourse4.jpg
new file mode 100644
index 000000000..0d54b4d2e
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse4.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse5.jpg b/hansoyeon/src/imgs/regioncourse5.jpg
new file mode 100644
index 000000000..bb791a819
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse5.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse6.jpg b/hansoyeon/src/imgs/regioncourse6.jpg
new file mode 100644
index 000000000..a29122ac9
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse6.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse7.jpg b/hansoyeon/src/imgs/regioncourse7.jpg
new file mode 100644
index 000000000..821ff9445
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse7.jpg differ
diff --git a/hansoyeon/src/imgs/regioncourse8.jpg b/hansoyeon/src/imgs/regioncourse8.jpg
new file mode 100644
index 000000000..636fe5139
Binary files /dev/null and b/hansoyeon/src/imgs/regioncourse8.jpg differ
diff --git a/hansoyeon/src/imgs/seungjun.jpg b/hansoyeon/src/imgs/seungjun.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/sharing.png b/hansoyeon/src/imgs/sharing.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/smalllogo.png b/hansoyeon/src/imgs/smalllogo.png
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/sunnny.png b/hansoyeon/src/imgs/sunnny.png
new file mode 100644
index 000000000..92cb401fe
Binary files /dev/null and b/hansoyeon/src/imgs/sunnny.png differ
diff --git a/hansoyeon/src/imgs/themecourse1.jpg b/hansoyeon/src/imgs/themecourse1.jpg
new file mode 100644
index 000000000..26dc98efb
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse1.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse2.jpg b/hansoyeon/src/imgs/themecourse2.jpg
new file mode 100644
index 000000000..522b694c6
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse2.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse3.jpg b/hansoyeon/src/imgs/themecourse3.jpg
new file mode 100644
index 000000000..cdb48e9cc
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse3.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse4.jpg b/hansoyeon/src/imgs/themecourse4.jpg
new file mode 100644
index 000000000..96a5c03de
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse4.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse5.jpg b/hansoyeon/src/imgs/themecourse5.jpg
new file mode 100644
index 000000000..d4e1d7f19
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse5.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse6.jpg b/hansoyeon/src/imgs/themecourse6.jpg
new file mode 100644
index 000000000..0ab7b10b2
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse6.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse7.jpg b/hansoyeon/src/imgs/themecourse7.jpg
new file mode 100644
index 000000000..089151176
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse7.jpg differ
diff --git a/hansoyeon/src/imgs/themecourse8.jpg b/hansoyeon/src/imgs/themecourse8.jpg
new file mode 100644
index 000000000..2eff9fa95
Binary files /dev/null and b/hansoyeon/src/imgs/themecourse8.jpg differ
diff --git a/hansoyeon/src/imgs/together.png b/hansoyeon/src/imgs/together.png
new file mode 100644
index 000000000..36dc3ac94
Binary files /dev/null and b/hansoyeon/src/imgs/together.png differ
diff --git a/hansoyeon/src/imgs/winter.jpg b/hansoyeon/src/imgs/winter.jpg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/imgs/work.png b/hansoyeon/src/imgs/work.png
new file mode 100644
index 000000000..edc6f830c
Binary files /dev/null and b/hansoyeon/src/imgs/work.png differ
diff --git a/hansoyeon/src/index.css b/hansoyeon/src/index.css
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/index.js b/hansoyeon/src/index.js
old mode 100644
new mode 100755
index d563c0fb1..26a2c715f
--- a/hansoyeon/src/index.js
+++ b/hansoyeon/src/index.js
@@ -6,9 +6,7 @@ import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-
-
);
// If you want to start measuring performance in your app, pass a function
diff --git a/hansoyeon/src/logo.svg b/hansoyeon/src/logo.svg
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/reportWebVitals.js b/hansoyeon/src/reportWebVitals.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/setupTests.js b/hansoyeon/src/setupTests.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/stores/index.js b/hansoyeon/src/stores/index.js
old mode 100644
new mode 100755
diff --git a/hansoyeon/src/stores/user.store.js b/hansoyeon/src/stores/user.store.js
old mode 100644
new mode 100755
diff --git a/build/classes/java/main/com/example/demo/HansoYeonApplication$1.class b/out/production/classes/com/example/demo/HansoYeonApplication$1.class
similarity index 56%
rename from build/classes/java/main/com/example/demo/HansoYeonApplication$1.class
rename to out/production/classes/com/example/demo/HansoYeonApplication$1.class
index 623da392f..c9e3bc807 100644
Binary files a/build/classes/java/main/com/example/demo/HansoYeonApplication$1.class and b/out/production/classes/com/example/demo/HansoYeonApplication$1.class differ
diff --git a/out/production/classes/com/example/demo/HansoYeonApplication.class b/out/production/classes/com/example/demo/HansoYeonApplication.class
new file mode 100644
index 000000000..71870ed3e
Binary files /dev/null and b/out/production/classes/com/example/demo/HansoYeonApplication.class differ
diff --git a/build/classes/java/main/com/example/demo/config/GcsConfig.class b/out/production/classes/com/example/demo/config/GcsConfig.class
similarity index 69%
rename from build/classes/java/main/com/example/demo/config/GcsConfig.class
rename to out/production/classes/com/example/demo/config/GcsConfig.class
index 397dd10a3..5274fff9f 100644
Binary files a/build/classes/java/main/com/example/demo/config/GcsConfig.class and b/out/production/classes/com/example/demo/config/GcsConfig.class differ
diff --git a/build/classes/java/main/com/example/demo/config/WebConfig.class b/out/production/classes/com/example/demo/config/WebConfig.class
similarity index 68%
rename from build/classes/java/main/com/example/demo/config/WebConfig.class
rename to out/production/classes/com/example/demo/config/WebConfig.class
index c148ee7ad..fa36cef7b 100644
Binary files a/build/classes/java/main/com/example/demo/config/WebConfig.class and b/out/production/classes/com/example/demo/config/WebConfig.class differ
diff --git a/out/production/classes/com/example/demo/config/WebSecurityConfig.class b/out/production/classes/com/example/demo/config/WebSecurityConfig.class
new file mode 100644
index 000000000..f27584fa1
Binary files /dev/null and b/out/production/classes/com/example/demo/config/WebSecurityConfig.class differ
diff --git a/out/production/classes/com/example/demo/controller/AnnouncementController.class b/out/production/classes/com/example/demo/controller/AnnouncementController.class
new file mode 100644
index 000000000..15380ca4f
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/AnnouncementController.class differ
diff --git a/out/production/classes/com/example/demo/controller/AuthController.class b/out/production/classes/com/example/demo/controller/AuthController.class
new file mode 100644
index 000000000..9781693ff
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/AuthController.class differ
diff --git a/out/production/classes/com/example/demo/controller/BlacklistController.class b/out/production/classes/com/example/demo/controller/BlacklistController.class
new file mode 100644
index 000000000..2ff0c1116
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/BlacklistController.class differ
diff --git a/out/production/classes/com/example/demo/controller/CommentController.class b/out/production/classes/com/example/demo/controller/CommentController.class
new file mode 100644
index 000000000..cc5d55b65
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/CommentController.class differ
diff --git a/out/production/classes/com/example/demo/controller/CourseController.class b/out/production/classes/com/example/demo/controller/CourseController.class
new file mode 100644
index 000000000..626c4536e
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/CourseController.class differ
diff --git a/out/production/classes/com/example/demo/controller/FriendshipController.class b/out/production/classes/com/example/demo/controller/FriendshipController.class
new file mode 100644
index 000000000..3c4098e39
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/FriendshipController.class differ
diff --git a/out/production/classes/com/example/demo/controller/ImageController.class b/out/production/classes/com/example/demo/controller/ImageController.class
new file mode 100644
index 000000000..af32a8fc8
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/ImageController.class differ
diff --git a/out/production/classes/com/example/demo/controller/ImageResponse.class b/out/production/classes/com/example/demo/controller/ImageResponse.class
new file mode 100644
index 000000000..11a558dbb
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/ImageResponse.class differ
diff --git a/out/production/classes/com/example/demo/controller/MatchingController.class b/out/production/classes/com/example/demo/controller/MatchingController.class
new file mode 100644
index 000000000..7bd0a178e
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/MatchingController.class differ
diff --git a/out/production/classes/com/example/demo/controller/MultipleImageResponse.class b/out/production/classes/com/example/demo/controller/MultipleImageResponse.class
new file mode 100644
index 000000000..6f40cf152
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/MultipleImageResponse.class differ
diff --git a/out/production/classes/com/example/demo/controller/PayController.class b/out/production/classes/com/example/demo/controller/PayController.class
new file mode 100644
index 000000000..7f7a87c83
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/PayController.class differ
diff --git a/out/production/classes/com/example/demo/controller/RecruitmentController.class b/out/production/classes/com/example/demo/controller/RecruitmentController.class
new file mode 100644
index 000000000..b1cf7287b
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/RecruitmentController.class differ
diff --git a/out/production/classes/com/example/demo/controller/ReviewController.class b/out/production/classes/com/example/demo/controller/ReviewController.class
new file mode 100644
index 000000000..98ff72ac8
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/ReviewController.class differ
diff --git a/out/production/classes/com/example/demo/controller/SmsController.class b/out/production/classes/com/example/demo/controller/SmsController.class
new file mode 100644
index 000000000..7a45fcc04
Binary files /dev/null and b/out/production/classes/com/example/demo/controller/SmsController.class differ
diff --git a/out/production/classes/com/example/demo/develop/DevelopController.class b/out/production/classes/com/example/demo/develop/DevelopController.class
new file mode 100644
index 000000000..c75805c1b
Binary files /dev/null and b/out/production/classes/com/example/demo/develop/DevelopController.class differ
diff --git a/out/production/classes/com/example/demo/develop/DevelopService.class b/out/production/classes/com/example/demo/develop/DevelopService.class
new file mode 100644
index 000000000..f08b1c2c4
Binary files /dev/null and b/out/production/classes/com/example/demo/develop/DevelopService.class differ
diff --git a/out/production/classes/com/example/demo/develop/requestBody/CreateUserRequestBody.class b/out/production/classes/com/example/demo/develop/requestBody/CreateUserRequestBody.class
new file mode 100644
index 000000000..ea5c4981d
Binary files /dev/null and b/out/production/classes/com/example/demo/develop/requestBody/CreateUserRequestBody.class differ
diff --git a/out/production/classes/com/example/demo/dto/AnnouncementDto.class b/out/production/classes/com/example/demo/dto/AnnouncementDto.class
new file mode 100644
index 000000000..6d49d3e82
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/AnnouncementDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CommentDto.class b/out/production/classes/com/example/demo/dto/CommentDto.class
new file mode 100644
index 000000000..f3b0181e2
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CommentDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CompanySignInDto.class b/out/production/classes/com/example/demo/dto/CompanySignInDto.class
new file mode 100644
index 000000000..308126c85
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CompanySignInDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CompanySignInResponseDto.class b/out/production/classes/com/example/demo/dto/CompanySignInResponseDto.class
new file mode 100644
index 000000000..90beeec64
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CompanySignInResponseDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CompanySignUpDto.class b/out/production/classes/com/example/demo/dto/CompanySignUpDto.class
new file mode 100644
index 000000000..3dfcdbbda
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CompanySignUpDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CompanyUpdateDto.class b/out/production/classes/com/example/demo/dto/CompanyUpdateDto.class
new file mode 100644
index 000000000..cb91b5123
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CompanyUpdateDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/CourseDto.class b/out/production/classes/com/example/demo/dto/CourseDto.class
new file mode 100644
index 000000000..5638040e9
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/CourseDto.class differ
diff --git a/build/classes/java/main/com/example/demo/dto/GoogleOAuth2TokenResponse.class b/out/production/classes/com/example/demo/dto/GoogleOAuth2TokenResponse.class
similarity index 50%
rename from build/classes/java/main/com/example/demo/dto/GoogleOAuth2TokenResponse.class
rename to out/production/classes/com/example/demo/dto/GoogleOAuth2TokenResponse.class
index f8a5a60a9..296863c9d 100644
Binary files a/build/classes/java/main/com/example/demo/dto/GoogleOAuth2TokenResponse.class and b/out/production/classes/com/example/demo/dto/GoogleOAuth2TokenResponse.class differ
diff --git a/out/production/classes/com/example/demo/dto/GoogleUserInfo.class b/out/production/classes/com/example/demo/dto/GoogleUserInfo.class
new file mode 100644
index 000000000..53f3daa50
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/GoogleUserInfo.class differ
diff --git a/out/production/classes/com/example/demo/dto/JobProvidersDto.class b/out/production/classes/com/example/demo/dto/JobProvidersDto.class
new file mode 100644
index 000000000..fa023bf9c
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/JobProvidersDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/PaymentDto.class b/out/production/classes/com/example/demo/dto/PaymentDto.class
new file mode 100644
index 000000000..15b76d6f8
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/PaymentDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/ResponseDto.class b/out/production/classes/com/example/demo/dto/ResponseDto.class
new file mode 100644
index 000000000..95aa745eb
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/ResponseDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/ReviewDto.class b/out/production/classes/com/example/demo/dto/ReviewDto.class
new file mode 100644
index 000000000..61b08141d
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/ReviewDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/SignInDto.class b/out/production/classes/com/example/demo/dto/SignInDto.class
new file mode 100644
index 000000000..9361df2a3
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/SignInDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/SignInResponseDto.class b/out/production/classes/com/example/demo/dto/SignInResponseDto.class
new file mode 100644
index 000000000..2b3b4cb8d
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/SignInResponseDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/SignUpDto.class b/out/production/classes/com/example/demo/dto/SignUpDto.class
new file mode 100644
index 000000000..7f51c089d
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/SignUpDto.class differ
diff --git a/out/production/classes/com/example/demo/dto/UserUpdateDto.class b/out/production/classes/com/example/demo/dto/UserUpdateDto.class
new file mode 100644
index 000000000..45efe11c8
Binary files /dev/null and b/out/production/classes/com/example/demo/dto/UserUpdateDto.class differ
diff --git a/out/production/classes/com/example/demo/entity/AnnouncementEntity.class b/out/production/classes/com/example/demo/entity/AnnouncementEntity.class
new file mode 100644
index 000000000..d632617f6
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/AnnouncementEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/BlacklistEntity$BlacklistEntityBuilder.class b/out/production/classes/com/example/demo/entity/BlacklistEntity$BlacklistEntityBuilder.class
new file mode 100644
index 000000000..13b7aa76d
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/BlacklistEntity$BlacklistEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/BlacklistEntity.class b/out/production/classes/com/example/demo/entity/BlacklistEntity.class
new file mode 100644
index 000000000..e09751452
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/BlacklistEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/CommentEntity$CommentEntityBuilder.class b/out/production/classes/com/example/demo/entity/CommentEntity$CommentEntityBuilder.class
new file mode 100644
index 000000000..50bd263bf
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/CommentEntity$CommentEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/CommentEntity.class b/out/production/classes/com/example/demo/entity/CommentEntity.class
new file mode 100644
index 000000000..481fa3a4a
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/CommentEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/CourseEntity$CourseEntityBuilder.class b/out/production/classes/com/example/demo/entity/CourseEntity$CourseEntityBuilder.class
new file mode 100644
index 000000000..4246b61ea
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/CourseEntity$CourseEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/CourseEntity.class b/out/production/classes/com/example/demo/entity/CourseEntity.class
new file mode 100644
index 000000000..db65c143c
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/CourseEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/FriendshipEntity$FriendshipEntityBuilder.class b/out/production/classes/com/example/demo/entity/FriendshipEntity$FriendshipEntityBuilder.class
new file mode 100644
index 000000000..0451c9976
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/FriendshipEntity$FriendshipEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/FriendshipEntity.class b/out/production/classes/com/example/demo/entity/FriendshipEntity.class
new file mode 100644
index 000000000..99af4895c
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/FriendshipEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/MatchingEntity$MatchingEntityBuilder.class b/out/production/classes/com/example/demo/entity/MatchingEntity$MatchingEntityBuilder.class
new file mode 100644
index 000000000..7f44a9094
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/MatchingEntity$MatchingEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/MatchingEntity.class b/out/production/classes/com/example/demo/entity/MatchingEntity.class
new file mode 100644
index 000000000..ea5f74edb
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/MatchingEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/PaymentEntity$PaymentEntityBuilder.class b/out/production/classes/com/example/demo/entity/PaymentEntity$PaymentEntityBuilder.class
new file mode 100644
index 000000000..61fe9a8fa
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/PaymentEntity$PaymentEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/PaymentEntity.class b/out/production/classes/com/example/demo/entity/PaymentEntity.class
new file mode 100644
index 000000000..747f9de60
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/PaymentEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/ProviderEntity$ProviderEntityBuilder.class b/out/production/classes/com/example/demo/entity/ProviderEntity$ProviderEntityBuilder.class
new file mode 100644
index 000000000..153bd0fe7
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/ProviderEntity$ProviderEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/ProviderEntity.class b/out/production/classes/com/example/demo/entity/ProviderEntity.class
new file mode 100644
index 000000000..ad3677a5c
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/ProviderEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/RecruitmentEntity.class b/out/production/classes/com/example/demo/entity/RecruitmentEntity.class
new file mode 100644
index 000000000..f247ec741
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/RecruitmentEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/ReviewEntity$ReviewEntityBuilder.class b/out/production/classes/com/example/demo/entity/ReviewEntity$ReviewEntityBuilder.class
new file mode 100644
index 000000000..b2999e6f7
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/ReviewEntity$ReviewEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/ReviewEntity.class b/out/production/classes/com/example/demo/entity/ReviewEntity.class
new file mode 100644
index 000000000..c1e8b017a
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/ReviewEntity.class differ
diff --git a/out/production/classes/com/example/demo/entity/UserEntity$UserEntityBuilder.class b/out/production/classes/com/example/demo/entity/UserEntity$UserEntityBuilder.class
new file mode 100644
index 000000000..46da6989c
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/UserEntity$UserEntityBuilder.class differ
diff --git a/out/production/classes/com/example/demo/entity/UserEntity.class b/out/production/classes/com/example/demo/entity/UserEntity.class
new file mode 100644
index 000000000..9e5738689
Binary files /dev/null and b/out/production/classes/com/example/demo/entity/UserEntity.class differ
diff --git a/out/production/classes/com/example/demo/env/SecurityEnvironment.class b/out/production/classes/com/example/demo/env/SecurityEnvironment.class
new file mode 100644
index 000000000..07afc263e
Binary files /dev/null and b/out/production/classes/com/example/demo/env/SecurityEnvironment.class differ
diff --git a/build/classes/java/main/com/example/demo/filter/JwtAuthenticationFilter.class b/out/production/classes/com/example/demo/filter/JwtAuthenticationFilter.class
similarity index 75%
rename from build/classes/java/main/com/example/demo/filter/JwtAuthenticationFilter.class
rename to out/production/classes/com/example/demo/filter/JwtAuthenticationFilter.class
index 01d409c85..d6904236a 100644
Binary files a/build/classes/java/main/com/example/demo/filter/JwtAuthenticationFilter.class and b/out/production/classes/com/example/demo/filter/JwtAuthenticationFilter.class differ
diff --git a/build/classes/java/main/com/example/demo/repository/AnnouncementRepository.class b/out/production/classes/com/example/demo/repository/AnnouncementRepository.class
similarity index 100%
rename from build/classes/java/main/com/example/demo/repository/AnnouncementRepository.class
rename to out/production/classes/com/example/demo/repository/AnnouncementRepository.class
diff --git a/out/production/classes/com/example/demo/repository/BlacklistRepository.class b/out/production/classes/com/example/demo/repository/BlacklistRepository.class
new file mode 100644
index 000000000..f4adaa767
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/BlacklistRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/CommentRepository.class b/out/production/classes/com/example/demo/repository/CommentRepository.class
new file mode 100644
index 000000000..d7804489d
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/CommentRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/CourseRepository.class b/out/production/classes/com/example/demo/repository/CourseRepository.class
new file mode 100644
index 000000000..bdea753ce
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/CourseRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/FriendshipRepository.class b/out/production/classes/com/example/demo/repository/FriendshipRepository.class
new file mode 100644
index 000000000..9f272687c
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/FriendshipRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/MatchingRepository.class b/out/production/classes/com/example/demo/repository/MatchingRepository.class
new file mode 100644
index 000000000..1fd17d089
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/MatchingRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/PaymentRepository.class b/out/production/classes/com/example/demo/repository/PaymentRepository.class
new file mode 100644
index 000000000..be14679be
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/PaymentRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/ProvidersRepository.class b/out/production/classes/com/example/demo/repository/ProvidersRepository.class
new file mode 100644
index 000000000..0c3515613
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/ProvidersRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/RecruitmentRepository.class b/out/production/classes/com/example/demo/repository/RecruitmentRepository.class
new file mode 100644
index 000000000..71dff674a
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/RecruitmentRepository.class differ
diff --git a/build/classes/java/main/com/example/demo/repository/ReviewRepository.class b/out/production/classes/com/example/demo/repository/ReviewRepository.class
similarity index 79%
rename from build/classes/java/main/com/example/demo/repository/ReviewRepository.class
rename to out/production/classes/com/example/demo/repository/ReviewRepository.class
index 4fea5921d..d083b6075 100644
Binary files a/build/classes/java/main/com/example/demo/repository/ReviewRepository.class and b/out/production/classes/com/example/demo/repository/ReviewRepository.class differ
diff --git a/out/production/classes/com/example/demo/repository/UserRepository.class b/out/production/classes/com/example/demo/repository/UserRepository.class
new file mode 100644
index 000000000..3911d0155
Binary files /dev/null and b/out/production/classes/com/example/demo/repository/UserRepository.class differ
diff --git a/out/production/classes/com/example/demo/request/ApplicationNotificationRequest.class b/out/production/classes/com/example/demo/request/ApplicationNotificationRequest.class
new file mode 100644
index 000000000..9fb8f3d8e
Binary files /dev/null and b/out/production/classes/com/example/demo/request/ApplicationNotificationRequest.class differ
diff --git a/out/production/classes/com/example/demo/request/DeleteBlacklistRequestBody.class b/out/production/classes/com/example/demo/request/DeleteBlacklistRequestBody.class
new file mode 100644
index 000000000..d988a7f60
Binary files /dev/null and b/out/production/classes/com/example/demo/request/DeleteBlacklistRequestBody.class differ
diff --git a/out/production/classes/com/example/demo/request/RequestFriendshipRequestBody.class b/out/production/classes/com/example/demo/request/RequestFriendshipRequestBody.class
new file mode 100644
index 000000000..47d0a7f51
Binary files /dev/null and b/out/production/classes/com/example/demo/request/RequestFriendshipRequestBody.class differ
diff --git a/out/production/classes/com/example/demo/request/RequestRecruitmentRequestBody.class b/out/production/classes/com/example/demo/request/RequestRecruitmentRequestBody.class
new file mode 100644
index 000000000..aaa596981
Binary files /dev/null and b/out/production/classes/com/example/demo/request/RequestRecruitmentRequestBody.class differ
diff --git a/out/production/classes/com/example/demo/request/VerificationRequestBody.class b/out/production/classes/com/example/demo/request/VerificationRequestBody.class
new file mode 100644
index 000000000..59aab3e98
Binary files /dev/null and b/out/production/classes/com/example/demo/request/VerificationRequestBody.class differ
diff --git a/out/production/classes/com/example/demo/response/ResponseBody$ResponseBodyBuilder.class b/out/production/classes/com/example/demo/response/ResponseBody$ResponseBodyBuilder.class
new file mode 100644
index 000000000..1ec7a8125
Binary files /dev/null and b/out/production/classes/com/example/demo/response/ResponseBody$ResponseBodyBuilder.class differ
diff --git a/out/production/classes/com/example/demo/response/ResponseBody.class b/out/production/classes/com/example/demo/response/ResponseBody.class
new file mode 100644
index 000000000..f8649cbe9
Binary files /dev/null and b/out/production/classes/com/example/demo/response/ResponseBody.class differ
diff --git a/out/production/classes/com/example/demo/response/ResponseBuilder.class b/out/production/classes/com/example/demo/response/ResponseBuilder.class
new file mode 100644
index 000000000..df53c6e8b
Binary files /dev/null and b/out/production/classes/com/example/demo/response/ResponseBuilder.class differ
diff --git a/out/production/classes/com/example/demo/security/TokenProvider.class b/out/production/classes/com/example/demo/security/TokenProvider.class
new file mode 100644
index 000000000..6faa6ded4
Binary files /dev/null and b/out/production/classes/com/example/demo/security/TokenProvider.class differ
diff --git a/out/production/classes/com/example/demo/service/AnnouncementService.class b/out/production/classes/com/example/demo/service/AnnouncementService.class
new file mode 100644
index 000000000..652ab0bbe
Binary files /dev/null and b/out/production/classes/com/example/demo/service/AnnouncementService.class differ
diff --git a/out/production/classes/com/example/demo/service/BlacklistService.class b/out/production/classes/com/example/demo/service/BlacklistService.class
new file mode 100644
index 000000000..065841ec2
Binary files /dev/null and b/out/production/classes/com/example/demo/service/BlacklistService.class differ
diff --git a/out/production/classes/com/example/demo/service/CommentService.class b/out/production/classes/com/example/demo/service/CommentService.class
new file mode 100644
index 000000000..9b29ce433
Binary files /dev/null and b/out/production/classes/com/example/demo/service/CommentService.class differ
diff --git a/out/production/classes/com/example/demo/service/CourseService.class b/out/production/classes/com/example/demo/service/CourseService.class
new file mode 100644
index 000000000..3690402ff
Binary files /dev/null and b/out/production/classes/com/example/demo/service/CourseService.class differ
diff --git a/out/production/classes/com/example/demo/service/FriendshipService.class b/out/production/classes/com/example/demo/service/FriendshipService.class
new file mode 100644
index 000000000..d17b5b8ec
Binary files /dev/null and b/out/production/classes/com/example/demo/service/FriendshipService.class differ
diff --git a/build/classes/java/main/com/example/demo/service/ImageService.class b/out/production/classes/com/example/demo/service/ImageService.class
similarity index 66%
rename from build/classes/java/main/com/example/demo/service/ImageService.class
rename to out/production/classes/com/example/demo/service/ImageService.class
index 9c2fdc9c0..8ec4bee6b 100644
Binary files a/build/classes/java/main/com/example/demo/service/ImageService.class and b/out/production/classes/com/example/demo/service/ImageService.class differ
diff --git a/out/production/classes/com/example/demo/service/MatchingService.class b/out/production/classes/com/example/demo/service/MatchingService.class
new file mode 100644
index 000000000..eb9c7b5c2
Binary files /dev/null and b/out/production/classes/com/example/demo/service/MatchingService.class differ
diff --git a/out/production/classes/com/example/demo/service/PaymentService.class b/out/production/classes/com/example/demo/service/PaymentService.class
new file mode 100644
index 000000000..5ec475a83
Binary files /dev/null and b/out/production/classes/com/example/demo/service/PaymentService.class differ
diff --git a/out/production/classes/com/example/demo/service/ProvidersService.class b/out/production/classes/com/example/demo/service/ProvidersService.class
new file mode 100644
index 000000000..19a75dc6c
Binary files /dev/null and b/out/production/classes/com/example/demo/service/ProvidersService.class differ
diff --git a/out/production/classes/com/example/demo/service/RecruitmentService.class b/out/production/classes/com/example/demo/service/RecruitmentService.class
new file mode 100644
index 000000000..faafdf09b
Binary files /dev/null and b/out/production/classes/com/example/demo/service/RecruitmentService.class differ
diff --git a/out/production/classes/com/example/demo/service/ReviewService.class b/out/production/classes/com/example/demo/service/ReviewService.class
new file mode 100644
index 000000000..11784d4dc
Binary files /dev/null and b/out/production/classes/com/example/demo/service/ReviewService.class differ
diff --git a/out/production/classes/com/example/demo/service/ServiceResult$ServiceResultBuilder.class b/out/production/classes/com/example/demo/service/ServiceResult$ServiceResultBuilder.class
new file mode 100644
index 000000000..63821a3d3
Binary files /dev/null and b/out/production/classes/com/example/demo/service/ServiceResult$ServiceResultBuilder.class differ
diff --git a/out/production/classes/com/example/demo/service/ServiceResult.class b/out/production/classes/com/example/demo/service/ServiceResult.class
new file mode 100644
index 000000000..df029748c
Binary files /dev/null and b/out/production/classes/com/example/demo/service/ServiceResult.class differ
diff --git a/out/production/classes/com/example/demo/service/SmsRequest.class b/out/production/classes/com/example/demo/service/SmsRequest.class
new file mode 100644
index 000000000..413a765ec
Binary files /dev/null and b/out/production/classes/com/example/demo/service/SmsRequest.class differ
diff --git a/out/production/classes/com/example/demo/service/SmsService.class b/out/production/classes/com/example/demo/service/SmsService.class
new file mode 100644
index 000000000..e1a9560f0
Binary files /dev/null and b/out/production/classes/com/example/demo/service/SmsService.class differ
diff --git a/out/production/classes/com/example/demo/service/UserService.class b/out/production/classes/com/example/demo/service/UserService.class
new file mode 100644
index 000000000..d7bcc3bf7
Binary files /dev/null and b/out/production/classes/com/example/demo/service/UserService.class differ
diff --git a/out/production/classes/com/example/demo/view/ViewController.class b/out/production/classes/com/example/demo/view/ViewController.class
new file mode 100644
index 000000000..dacc13815
Binary files /dev/null and b/out/production/classes/com/example/demo/view/ViewController.class differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..fc11ad6d5
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "HanSoYeon",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {}
+}
diff --git a/settings.gradle b/settings.gradle
index 1df6e4b35..321c747f9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-rootProject.name = 'HanSoYeon'
+rootProject.name = 'HansoYeon'
diff --git a/src/.DS_Store b/src/.DS_Store
deleted file mode 100644
index 47bfe08c1..000000000
Binary files a/src/.DS_Store and /dev/null differ
diff --git a/src/main/.DS_Store b/src/main/.DS_Store
deleted file mode 100644
index 7334c738a..000000000
Binary files a/src/main/.DS_Store and /dev/null differ
diff --git a/src/main/java/com/example/demo/config/WebSecurityConfig.java b/src/main/java/com/example/demo/config/WebSecurityConfig.java
index fc3bf349f..bfe3295ed 100644
--- a/src/main/java/com/example/demo/config/WebSecurityConfig.java
+++ b/src/main/java/com/example/demo/config/WebSecurityConfig.java
@@ -39,7 +39,14 @@ protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Except
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// "/", "/api/auth/**" 모듈에 대해서는 모두 허용 (인증을 하지 않고 사용 가능 하게 함)
.authorizeRequests()
- .antMatchers("/", "/api/auth/**", "/api/createAnnouncement","/api/announcements/**", "api/reviews" , "/api/reviews/**", "/api/recruits/**", "/api/uploadProfileImage", "/api/cos/**", "/api/sms/**", "/api/schedulers","/api/recruitments" ).permitAll()
+// .antMatchers(
+// "/", "/api/auth/**", "/api/createAnnouncement",
+// "/api/announcements/**", "api/reviews" ,
+// "/api/reviews/**", "/api/recruits/**",
+// "/api/uploadProfileImage", "/api/cos/**",
+// "/api/sms/**", "/api/schedulers","/api/recruitments",
+// "/api/createRecruitment","/api/recruitments/**","/dev/**", "/view/**").permitAll()
+ .antMatchers("/**").permitAll()
// 나머지 Request에 대해서는 모두 인증된 사용자만 사용 가능하게 함
.anyRequest().authenticated();
diff --git a/src/main/java/com/example/demo/controller/AuthController.java b/src/main/java/com/example/demo/controller/AuthController.java
index d9c0d271b..76b9b7e8d 100644
--- a/src/main/java/com/example/demo/controller/AuthController.java
+++ b/src/main/java/com/example/demo/controller/AuthController.java
@@ -1,15 +1,15 @@
package com.example.demo.controller;
import com.example.demo.dto.*;
-import com.example.demo.entity.ProvidersEntity;
-import com.example.demo.entity.UsersEntity;
+import com.example.demo.entity.ProviderEntity;
+import com.example.demo.entity.UserEntity;
import com.example.demo.security.TokenProvider;
import com.example.demo.service.ProvidersService;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
-import com.example.demo.service.UsersService;
+import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
@@ -24,7 +24,7 @@
public class AuthController {
@Autowired
- private UsersService usersService;
+ private UserService userService;
@Autowired
private ProvidersService providersService;
@@ -40,7 +40,7 @@ public class AuthController {
@PostMapping("/signUp")
public ResponseDto> signUp(@RequestBody SignUpDto signUpDto){
try {
- usersService.registerUser(signUpDto);
+ userService.registerUser(signUpDto);
return ResponseDto.setSuccess("회원가입 성공", null);
} catch (Exception e) {
// 예외 처리
@@ -92,7 +92,7 @@ public ResponseEntity> handleGoogleAuthCode(@RequestBody Map p
@PostMapping("/signIn")
public ResponseDto signIn(@RequestBody SignInDto requestBody){
- ResponseDto result = usersService.signIn(requestBody);
+ ResponseDto result = userService.signIn(requestBody);
return result;
}
@@ -105,7 +105,7 @@ public ResponseDto companySignIn(@RequestBody CompanyS
@PostMapping("/signIn/kakao")
public ResponseDto kakaoSignIn(@RequestBody Map requestBody){
String userEmail = requestBody.get("userEmail");
- return usersService.signInWithKakaoEmail(userEmail);
+ return userService.signInWithKakaoEmail(userEmail);
}
@GetMapping("/currentUser")
@@ -116,7 +116,7 @@ public ResponseEntity> getCurrentUser(@RequestHeader("Authorization") String t
// TokenProvider를 사용하여 토큰에서 이메일을 추출합니다.
String userId = tokenProvider.getIdFromToken(jwtToken);
- UsersEntity user = usersService.getUserById(userId);
+ UserEntity user = userService.getUserById(userId);
return ResponseEntity.ok(user);
}
@@ -128,7 +128,7 @@ public ResponseEntity> getCurrentCompany(@RequestHeader("Authorization") Strin
// TokenProvider를 사용하여 토큰에서 이메일을 추출합니다.
String providerId = tokenProvider.getIdFromToken(jwtToken);
- ProvidersEntity user = providersService.getUserById(providerId);
+ ProviderEntity user = providersService.getUserById(providerId);
return ResponseEntity.ok(user);
}
@@ -152,7 +152,7 @@ public ResponseEntity> verifyPassword(@RequestBody Map request
String userId = tokenProvider.getIdFromToken(jwtToken); // 토큰에서 이메일 주소 추출
String userPassword = request.get("userPassword");
- boolean isValid = usersService.verifyUserPassword(userId, userPassword);
+ boolean isValid = userService.verifyUserPassword(userId, userPassword);
Map response = new HashMap<>();
response.put("isValid", isValid);
@@ -194,7 +194,7 @@ public ResponseEntity> verifyToken(@RequestHeader("Authorization") String toke
// 토큰 검증 로직
if (tokenProvider.validate(token) != null) {
String id = tokenProvider.getIdFromToken(token);
- UsersEntity user = usersService.getUserById(id);
+ UserEntity user = userService.getUserById(id);
return ResponseEntity.ok().body(user);
} else {
@@ -211,7 +211,7 @@ public ResponseEntity> updateUserInfo(@RequestBody UserUpdateDto userUpdateDto
String token = tokenHeader.split(" ")[1];
String userId = tokenProvider.getIdFromToken(token);
- return usersService.updateUserInfo(userId, userUpdateDto);
+ return userService.updateUserInfo(userId, userUpdateDto);
}
@PostMapping("/updateCompanyInfo")
@@ -225,7 +225,7 @@ public ResponseEntity> updateCompanyInfo(@RequestBody CompanyUpdateDto company
@GetMapping("/allCompanies")
public ResponseEntity> getAllCompanies() {
- List companies = providersService.getAllCompanies();
+ List companies = providersService.getAllCompanies();
return ResponseEntity.ok(companies);
}
@@ -248,13 +248,39 @@ public ResponseEntity> revokeProviderApproval(@RequestBody Map
@GetMapping("/allUsers")
public ResponseEntity> getAllMembers() {
- List companies = usersService.getAllUsers();
+ List companies = userService.getAllUsers();
return ResponseEntity.ok(companies);
}
@DeleteMapping("/deleteUser/{userId}")
public ResponseEntity> deleteUser(@PathVariable String userId) {
- return usersService.deleteUser(userId);
+ return userService.deleteUser(userId);
+ }
+
+ @GetMapping("/users/search")
+ public ResponseEntity> searchUsers(@RequestParam String search) {
+ List users = userService.searchUsers(search);
+ return ResponseEntity.ok(users);
+ }
+
+ @GetMapping("/provider/{providerId}")
+ public ResponseEntity> getProviderById(@PathVariable String providerId) {
+ ProviderEntity provider = providersService.getUserById(providerId);
+ if (provider != null) {
+ return ResponseEntity.ok(provider);
+ } else {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Provider not found");
+ }
+ }
+
+ @GetMapping("/user/{userId}")
+ public ResponseEntity> getUserById(@PathVariable String userId) {
+ UserEntity user = userService.getUserById(userId);
+ if (user != null) {
+ return ResponseEntity.ok(user);
+ } else {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Provider not found");
+ }
}
}
diff --git a/src/main/java/com/example/demo/controller/BlacklistController.java b/src/main/java/com/example/demo/controller/BlacklistController.java
new file mode 100644
index 000000000..7bae6407d
--- /dev/null
+++ b/src/main/java/com/example/demo/controller/BlacklistController.java
@@ -0,0 +1,131 @@
+package com.example.demo.controller;
+
+import com.example.demo.request.DeleteBlacklistRequestBody;
+import com.example.demo.response.ResponseBuilder;
+import com.example.demo.service.BlacklistService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@CrossOrigin(origins = "http://localhost:3000")
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+@SuppressWarnings("all")
+public class BlacklistController {
+
+ private final BlacklistService blacklistService;
+
+ /**
+ * [GET] /api/blacklists
+ * @author WoodyK
+ * @apiNote 모든 블랙리스트 조회
+ */
+ @GetMapping("/api/blacklists")
+ public ResponseEntity> getAllBlacklists() {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.getAllBlacklists())
+ .build();
+ }
+
+ /**
+ * [POST] /api/blacklists?providerId={providerId}&userId={userId}
+ * @author WoodyK
+ * @apiNote 블랙리스트 추가
+ * @param providerId 블랙을 수행하는 주체의 회원 식별자(기업 회원)
+ * @param userId 블랙 당한 대상의 회원 식별자
+ */
+ @PostMapping("/api/blacklists")
+ public ResponseEntity> addBlacklist(@RequestParam("providerId") String providerId,
+ @RequestParam("userId") String userId){
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.addBlacklist(providerId, userId))
+ .build();
+ }
+
+ /**
+ * [DELETE] /api/blacklists/{blacklistId}
+ * @author WoodyK
+ * @apiNote 블랙리스트 식별자로 특정 블랙리스트 항목 삭제
+ * @param blacklistId 블랙리스트 식별자
+ */
+ @DeleteMapping("/api/blacklists/{blacklistId}")
+ public ResponseEntity> deleteBlacklistById(@PathVariable("blacklistId") int blacklistId) {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.deleteBlacklistById(blacklistId))
+ .build();
+ }
+
+ /**
+ * [DELETE] /api/blacklists
+ * @author WoodyK
+ * @apiNote 기업 회원 식별자와 블랙 당한 사용자 식별자로 특정 블랙리스트 항목 삭제
+ * @param reqBody.providerId 블랙을 수행하는 주체의 회원 식별자(기업 회원)
+ * @param reqBody.userId 블랙 당한 대상의 회원 식별자
+ * @see DeleteBlacklistRequestBody
+ */
+ @DeleteMapping("/api/blacklists")
+ public ResponseEntity> deleteBlacklistByProviderIdAndUserId(
+ @RequestBody DeleteBlacklistRequestBody reqBody) {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.deleteBlacklistByProviderIdAndUserId(reqBody.providerId, reqBody.userId))
+ .build();
+ }
+
+ /**
+ * [GET] /api/blacklists/{blacklistId}
+ * @author WoodyK
+ * @apiNote 블랙리스트 식별자로 특정 블랙리스트 조회
+ * @param blacklistId 블랙리스트 식별자
+ */
+ @GetMapping("/api/blacklists/{blacklistId}")
+ public ResponseEntity> getBlacklistByBlacklistId(@PathVariable("blacklistId") int blacklistId) {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.getBlacklistByBlacklistId(blacklistId))
+ .build();
+ }
+
+ /**
+ * [GET] /api/blacklists/users?providerId={providerId}
+ * @author WoodyK
+ * @apiNote 기업 회원 식별자로 기업의 블랙리스트 리스트를 조회
+ * @param providerId 자신이 등록한 블랙리스트 목록을 조회하고자 하는 기업 회원의 식별자
+ * @see com.example.demo.service.BlacklistService#getBlacklistUserListByProviderId(String)
+ */
+ @GetMapping("/api/blacklists/users")
+ public ResponseEntity> getBlacklistsByProviderId(@RequestParam("providerId") String providerId) {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.getBlacklistUserListByProviderId(providerId))
+ .build();
+ }
+
+ /**
+ * [GET] /api/blacklists/isUserInBlacklist?userId={userId}
+ * @author WoodyK
+ * @apiNote 특정 사용자가 블랙리스트에 한번이라도 등록되었는지 조회
+ * @param userId 블랙리스트에 등록된 적이 있는지 조회하고자 하는 사용자의 식별자
+ * @return 블랙리스트에 등록된 적이 있으면 true, 없으면 false
+ */
+ @GetMapping("/api/blacklists/isUserInBlacklist")
+ public ResponseEntity> isUserInBlacklist(@RequestParam("userId") String userId) {
+
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.isUserInBlacklist(userId))
+ .build();
+ }
+
+ @GetMapping("/api/blacklists/user/{userId}")
+ public ResponseEntity> getBlacklistsByUserId(@PathVariable("userId") String userId) {
+ return new ResponseBuilder()
+ .serviceResult(blacklistService.getBlacklistsByUserId(userId))
+ .build();
+ }
+}
+
diff --git a/src/main/java/com/example/demo/controller/CommentController.java b/src/main/java/com/example/demo/controller/CommentController.java
new file mode 100644
index 000000000..244030619
--- /dev/null
+++ b/src/main/java/com/example/demo/controller/CommentController.java
@@ -0,0 +1,41 @@
+package com.example.demo.controller;
+
+import com.example.demo.dto.CommentDto;
+import com.example.demo.entity.CommentEntity;
+import com.example.demo.service.CommentService;
+import org.modelmapper.ModelMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api")
+public class CommentController {
+
+ @Autowired
+ private CommentService commentService;
+
+ @Autowired
+ private ModelMapper modelMapper;
+
+ @GetMapping("/comments/{reviewId}")
+ public ResponseEntity> getComments(@PathVariable int reviewId) {
+ List comments = commentService.findCommentsByReviewId(reviewId);
+ List commentDtos = comments.stream()
+ .map(comment -> modelMapper.map(comment, CommentDto.class))
+ .collect(Collectors.toList());
+ return ResponseEntity.ok(commentDtos);
+ }
+
+ @PostMapping("/comments/{reviewId}")
+ public ResponseEntity addComment(@PathVariable int reviewId, @RequestBody CommentDto commentDto) {
+ CommentEntity comment = modelMapper.map(commentDto, CommentEntity.class);
+ comment.setReviewId(reviewId);
+ CommentEntity savedComment = commentService.save(comment);
+ CommentDto savedCommentDto = modelMapper.map(savedComment, CommentDto.class);
+ return ResponseEntity.ok(savedCommentDto);
+ }
+}
diff --git a/src/main/java/com/example/demo/controller/FriendshipController.java b/src/main/java/com/example/demo/controller/FriendshipController.java
new file mode 100644
index 000000000..705fbbc14
--- /dev/null
+++ b/src/main/java/com/example/demo/controller/FriendshipController.java
@@ -0,0 +1,145 @@
+package com.example.demo.controller;
+
+import com.example.demo.request.RequestFriendshipRequestBody;
+import com.example.demo.response.ResponseBuilder;
+import com.example.demo.service.FriendshipService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@CrossOrigin(origins = "http://localhost:3000")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api")
+public class FriendshipController {
+
+ private final FriendshipService friendshipService;
+
+ /**
+ * [GET] /api/friendships
+ * @apiNote 모든 친구 관계를 조회한다.
+ * @author WoodyK
+ * @since JDK 11
+ *
+ * @return
+ */
+ @GetMapping("/friendships")
+ public ResponseEntity> getFriends() {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.getAllFriendships())
+ .build();
+ }
+
+ /**
+ * [POST] /api/friendships
+ * @apiNote 친구 요청을 보낸다.
+ * @author WoodyK
+ * @since JDK 11
+ * @param reqBody `reqBody.userId`: 친구 요청을 보내는 사용자의 회원 아이디(친구 요청을 보내는 주체)
+ * `reqBody.friendId`: 친구 요청을 보낼 대상의 회원 아이디
+ * @return 친구 추가 결과에 따른 ResponseEntitiy
+ */
+ @PostMapping("/friendships")
+ public ResponseEntity> addFriend(@RequestBody RequestFriendshipRequestBody reqBody) {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.requestFriendship(reqBody.getUserId(), reqBody.getFriendId()))
+ .build();
+ }
+
+ /**
+ * [PUT] /api/friendships
+ * @author WoodyK
+ * @param reqBody
+ * `reqBody.userId`: 친구 요청을 수락하는 사용자의 회원 아이디(친구 요청을 수락하는 주체)
+ * `reqBody.friendId`: 친구 요청을 수락할 대상의 회원 아이디
+ * @apiNote 친구 요청을 수락한다.
+ */
+ @PutMapping("/friendships")
+ public ResponseEntity> acceptFriend(@RequestBody RequestFriendshipRequestBody reqBody) {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.acceptFriendshipRequest(reqBody.getUserId(), reqBody.getFriendId()))
+ .build();
+ }
+
+ /**
+ * [DELETE] /api/friendships?userId={userId}&friendId={friendId}
+ * @author WoodyK
+ * @apiNote 친구 관계를 삭제한다
+ * @param userId 친구를 삭제하려는 사용자의 회원 아이디
+ * @param friendId 삭제하려는 친구의 회원 아이디
+ * @return 친구 삭제 결과에 따른 ResponseEntity
+ */
+ @DeleteMapping("/friendships")
+ public ResponseEntity> deleteFriend(@RequestParam("userId") String userId,
+ @RequestParam("friendId") String friendId) {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.deleteFriendshipByUserIdAndFriendId(userId, friendId))
+ .build();
+ }
+
+
+ /**
+ * [GET] /api/friendships?userId={userId}
+ * @author WoodyK
+ * @apiNote 친구 목록 조회
+ * @param userId 친구 목록을 조회하려는 사용자의 회원 아이디
+ * @return 친구 목록 조회 결과에 따른 ResponseEntity
+ */
+ @GetMapping("/friendships/friends")
+ public ResponseEntity> getFriends(@RequestParam("userId") String userId) {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.getFriendList(userId))
+ .build();
+ }
+
+ /**
+ * [GET] /api/friendships/isFriend?userId={userId}&targetId={targetId}
+ * @apiNote 친구 여부 확인
+ * @author WoodyK
+ * @param userId 친구관계를 확인하려는 사용자의 회원 아이디
+ * @param targetId 확인하고 싶은 대상의 회원 아이디
+ */
+ @GetMapping("/friendships/isFriend")
+ public ResponseEntity> isFriend(@RequestParam("userId") String userId,
+ @RequestParam("targetId") String targetId) {
+
+ return new ResponseBuilder()
+ .serviceResult(friendshipService.isFriend(userId, targetId))
+ .build();
+ }
+
+ /**
+ * 사용자가 보낸 친구 요청 목록 조회
+ * @param userId 요청을 보낸 사용자의 회원 아이디
+ * @return 보낸 친구 요청 목록에 따른 ResponseEntity
+ */
+ @GetMapping("/friends/sentRequests/{userId}")
+ public ResponseEntity> getSentFriendRequests(@PathVariable("userId") String userId) {
+ var sr = friendshipService.getSentFriendRequests(userId);
+
+ return sr.isSuccess() ?
+ ResponseEntity.ok(sr.data) :
+ ResponseEntity.badRequest().body(sr.message);
+ }
+
+ /**
+ * 사용자가 받은 친구 요청 목록 조회
+ * @param userId 요청을 받은 사용자의 회원 아이디
+ * @return 받은 친구 요청 목록에 따른 ResponseEntity
+ */
+ @GetMapping("/friends/receivedRequests/{userId}")
+ public ResponseEntity> getReceivedFriendRequests(@PathVariable("userId") String userId) {
+ var sr = friendshipService.getReceivedFriendRequests(userId);
+
+ return sr.isSuccess() ?
+ ResponseEntity.ok(sr.data) :
+ ResponseEntity.badRequest().body(sr.message);
+ }
+
+
+}
diff --git a/src/main/java/com/example/demo/controller/ImageController.java b/src/main/java/com/example/demo/controller/ImageController.java
index c4580fbe4..a998d7b14 100644
--- a/src/main/java/com/example/demo/controller/ImageController.java
+++ b/src/main/java/com/example/demo/controller/ImageController.java
@@ -9,6 +9,9 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.util.ArrayList;
+import java.util.List;
+
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:3000")
@@ -34,6 +37,37 @@ public ResponseEntity> uploadProfileImage(@RequestParam("profileImage") Multip
}
return ResponseEntity.badRequest().build();
}
+
+ @PostMapping("/uploadProfileImages")
+ public ResponseEntity> uploadProfileImages(@RequestParam("profileImages") MultipartFile[] files) {
+ try {
+ if (files == null || files.length == 0) {
+ return ResponseEntity.badRequest().body("Files cannot be empty");
+ }
+
+ List imageUrls = new ArrayList<>();
+ for (MultipartFile file : files) {
+ if (!file.isEmpty()) {
+ String imageUrl = imageService.uploadImage(file);
+ if (imageUrl != null) {
+ imageUrls.add(imageUrl);
+ } else {
+ System.out.println("Failed to upload image: " + file.getOriginalFilename());
+ }
+ }
+ }
+
+ if (!imageUrls.isEmpty()) {
+ System.out.println("Image upload successful. URLs: " + imageUrls);
+ return ResponseEntity.ok(new MultipleImageResponse(imageUrls));
+ } else {
+ System.out.println("No valid images uploaded.");
+ }
+ } catch (Exception e) {
+ logger.error("Error while processing image upload", e);
+ }
+ return ResponseEntity.badRequest().build();
+ }
}
@@ -50,3 +84,16 @@ public void setImageUrl(String imageUrl) {
}
}
+@Getter
+class MultipleImageResponse {
+ private List imageUrls;
+
+ public MultipleImageResponse(List imageUrls) {
+ this.imageUrls = imageUrls;
+ }
+
+ public void setImageUrls(List imageUrls) {
+ this.imageUrls = imageUrls;
+ }
+}
+
diff --git a/src/main/java/com/example/demo/controller/MatchingController.java b/src/main/java/com/example/demo/controller/MatchingController.java
new file mode 100644
index 000000000..1d46117de
--- /dev/null
+++ b/src/main/java/com/example/demo/controller/MatchingController.java
@@ -0,0 +1,170 @@
+package com.example.demo.controller;
+
+import com.example.demo.request.RequestRecruitmentRequestBody;
+import com.example.demo.response.ResponseBuilder;
+import com.example.demo.service.MatchingService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@CrossOrigin(origins = "http://localhost:3000")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api")
+@SuppressWarnings("all")
+public class MatchingController {
+
+ private final MatchingService matchingService;
+
+
+ /**
+ * [GET] /api/matchings
+ * @author WoodyK
+ * @apiNote 모든 매칭 조회
+ * @return 모든 매칭 목록
+ */
+ @GetMapping("/matchings")
+ public ResponseEntity> getAllMatchings() {
+ return new ResponseBuilder()
+ .serviceResult(matchingService.getAllMatchings())
+ .build();
+ }
+
+ /**
+ * [POST] /api/matchings
+ * @author WoodyK
+ * @apiNote 매칭 생성, 모집 공고 지원
+ * @param reqBody
+ * `reqBody.recruitmentId`: 지원하려는 모집 공고 식별자
+ * `reqBody.userId`: 지원자의 회원 식별자
+ * @see RequestRecruitmentRequestBody
+ */
+ @PostMapping("/matchings")
+ public ResponseEntity> requestMatching(@RequestBody RequestRecruitmentRequestBody reqBody){
+ return new ResponseBuilder()
+ .serviceResult(matchingService.requestRecruitment(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+ /**
+ * [DELETE] /api/matchings
+ * @author WoodyK
+ * @apiNote 매칭 제거, 모집 공고 지원 취소
+ * @param reqBody
+ * `reqBody.recruitmentId`: 지원하려는 모집 공고 식별자
+ `reqBody.userId`: 지원자의 회원 식별자
+ * @see RequestRecruitmentRequestBody
+ */
+
+ @DeleteMapping("/matchings")
+ public ResponseEntity> cancelMatching(@RequestBody RequestRecruitmentRequestBody reqBody){
+
+ return new ResponseBuilder()
+ .serviceResult(matchingService.cancelRecruitment(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+ /**
+ * [PUT] /api/matchings
+ * @autor WoodyK
+ * @param reqBody
+ * `reqBody.recruitmentId`: 지원하려는 모집 공고 식별자
+ `reqBody.userId`: 지원자의 회원 식별자
+ * @see RequestRecruitmentRequestBody
+ */
+ @PutMapping("/matchings")
+ public ResponseEntity> acceptMatching(@RequestBody RequestRecruitmentRequestBody reqBody){
+
+ return new ResponseBuilder()
+ .serviceResult(matchingService.acceptRecruitment(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+ /**
+ * [GET] /api/matchings/isAccepted?recruitmentId={recruitmentId}&userId={userId}
+ * @author WoodyK
+ * @param recruitmentId 지원하려는 모집 공고 식별자
+ * @param userId 지원자의 회원 식별자
+ * @return 성공적으로 수행 시, 매칭 수락 여부를 반환
+ */
+
+ @GetMapping("/matchings/isAccepted")
+ public ResponseEntity> isAccepted(@RequestParam("recruitmentId") int recruitmentId,
+ @RequestParam("userId") String userId){
+
+ return new ResponseBuilder()
+ .serviceResult(matchingService.isMatchingAccepted(recruitmentId, userId))
+ .build();
+ }
+
+ /**
+ * [GET] /api/matchings/recruitments/recruitmentId={userId}&status={status}
+ * @author WoodyK
+ * @apiNote 공고별 매칭 목록을 조회한다. 매칭 상태를 지정하지 않으면 모든 매칭 목록을 조회하고, 매칭 상태를 지정하면 해당 공고의 매칭 중에서
+ * 대응 되는 상태의 매칭 목록을 조회한다.
+ * @param recruitmentId 공고 식별자
+ * @param status 매칭 상태 (Optional)
+ * @return 공고별 매칭 목록
+ */
+ @GetMapping("/matchings/recruitments/{recruitmentId}")
+ public ResponseEntity> getMatchingsByRecruitmentId(@PathVariable("recruitmentId") int recruitmentId,
+ @RequestParam(value = "status", required = false) String status) {
+ return new ResponseBuilder()
+ .serviceResult(matchingService.getMatchingsByRecruitmentId(recruitmentId, status))
+ .build();
+ }
+
+ @GetMapping("/matchings/user/{userId}")
+ public ResponseEntity> getMatchingsByUserId(@PathVariable("userId") String userId) {
+ return new ResponseBuilder()
+ .serviceResult(matchingService.getMatchingsByUserId(userId))
+ .build();
+ }
+
+ @PutMapping("/matchings/cancelApproval")
+ public ResponseEntity> cancelApproval(@RequestBody RequestRecruitmentRequestBody reqBody) {
+ return new ResponseBuilder()
+ .serviceResult(matchingService.cancelApproval(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+ @DeleteMapping("/matchings/byRecruitment/{recruitmentId}")
+ public ResponseEntity> deleteAllMatchingsByRecruitmentId(@PathVariable int recruitmentId) {
+ return new ResponseBuilder()
+ .serviceResult(matchingService.deleteAllMatchingsByRecruitmentId(recruitmentId))
+ .build();
+ }
+
+ /**
+ * [PUT] /api/matchings/completed
+ * @autor YangjiwooGN
+ * @param reqBody
+ * `reqBody.recruitmentId`: 지원하려는 모집 공고 식별자
+ `reqBody.userId`: 지원자의 회원 식별자
+ * @see RequestRecruitmentRequestBody
+ */
+ @PutMapping("/matchings/completed")
+ public ResponseEntity> completeMatching(@RequestBody RequestRecruitmentRequestBody reqBody){
+
+ return new ResponseBuilder()
+ .serviceResult(matchingService.completeRecruitment(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+ /**
+ * [PUT] /api/matchings/completedCancel
+ * @autor YangjiwooGN
+ * @param reqBody
+ * `reqBody.recruitmentId`: 지원하려는 모집 공고 식별자
+ `reqBody.userId`: 지원자의 회원 식별자
+ * @see RequestRecruitmentRequestBody
+ */
+ @PutMapping("/matchings/completedCancel")
+ public ResponseEntity> completeCancelMatching(@RequestBody RequestRecruitmentRequestBody reqBody){
+
+ return new ResponseBuilder()
+ .serviceResult(matchingService.completeCancelRecruitment(reqBody.getRecruitmentId(), reqBody.getUserId()))
+ .build();
+ }
+
+}
diff --git a/src/main/java/com/example/demo/controller/PayController.java b/src/main/java/com/example/demo/controller/PayController.java
new file mode 100644
index 000000000..e4653708b
--- /dev/null
+++ b/src/main/java/com/example/demo/controller/PayController.java
@@ -0,0 +1,39 @@
+package com.example.demo.controller;
+
+import com.example.demo.dto.PaymentDto;
+import com.example.demo.service.PaymentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/payment")
+public class PayController {
+
+ @Autowired
+ private PaymentService paymentService;
+
+ @PostMapping("/saveClass")
+ public void savePayment(@RequestBody PaymentDto paymentDto) {
+ System.out.println("Received payment data:");
+ System.out.println("Email: " + paymentDto.getEmail());
+ System.out.println("Company: " + paymentDto.getCompany());
+ System.out.println("Amount: " + paymentDto.getAmount());
+ System.out.println("Merchant UID: " + paymentDto.getMerchant_uid());
+ System.out.println("Apply Num: " + paymentDto.getApply_num());
+ System.out.println("Paid At: " + paymentDto.getPaid_at());
+ System.out.println("Point: " + paymentDto.getPoints());
+ paymentService.savePayment(paymentDto);
+ }
+
+ @GetMapping("/responseClass")
+ public List getPayment() {
+ return paymentService.getAllPaymentDtos();
+ }
+ // 특정 기업의 결제 내역 조회
+ @GetMapping("/company/{email}")
+ public List getPaymentsByCompany(@PathVariable String email) {
+ return paymentService.getPaymentsByCompany(email);
+ }
+}
diff --git a/src/main/java/com/example/demo/controller/RecruitmentController.java b/src/main/java/com/example/demo/controller/RecruitmentController.java
index 491599b03..3fe24fc53 100644
--- a/src/main/java/com/example/demo/controller/RecruitmentController.java
+++ b/src/main/java/com/example/demo/controller/RecruitmentController.java
@@ -30,7 +30,6 @@ public RecruitmentController(RecruitmentService recruitmentService) {
//글 작성
@PostMapping("/createRecruitment")
public ResponseEntity createJobProviders(@RequestBody JobProvidersDto jobProvidersDto) {
- System.out.printf(jobProvidersDto.toString());
log.info("Received request to create a new job provider.");
try {
JobProvidersDto createdJobProviders = recruitmentService.createJobProviders(jobProvidersDto);
@@ -39,7 +38,6 @@ public ResponseEntity createJobProviders(@RequestBody JobProvid
} catch (ValidationException e) {
logger.error("Validation error while creating announcement", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
-
} catch (Exception e) {
logger.error("Error creating announcement", e);
e.printStackTrace();
@@ -49,14 +47,15 @@ public ResponseEntity createJobProviders(@RequestBody JobProvid
//글 목록 불러오기
@GetMapping("/recruitments")
- public List getAllJobProviders() {
+ public List getAllJobProviders()
+ {
return recruitmentService.getAlljobProviders();
}
+ //상세글
//상세글
@GetMapping("/recruitments/{Id}")
public ResponseEntity getJobProviderById(@PathVariable int Id) {
- System.out.println(Id);
try {
JobProvidersDto jobProviders = recruitmentService.getProvidersById(Id);
@@ -70,4 +69,28 @@ public ResponseEntity getJobProviderById(@PathVariable int Id)
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
+
+ @GetMapping("/recruitments/byProvider/{provider}")
+ public ResponseEntity> getAnnouncementsByProvider(@PathVariable String provider) {
+ try {
+ List announcements = recruitmentService.getJobProvidersAnnouncements(provider);
+ return new ResponseEntity<>(announcements, HttpStatus.OK);
+ } catch (Exception e) {
+ logger.error("Error getting announcements by provider", e);
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ @DeleteMapping("/recruitments/{recruitmentId}")
+ public ResponseEntity> deleteRecruitment(@PathVariable int recruitmentId) {
+ try {
+ recruitmentService.deleteRecruitmentById(recruitmentId);
+ return new ResponseEntity<>(HttpStatus.OK);
+ } catch (Exception e) {
+ logger.error("Error occurred while deleting recruitment with id " + recruitmentId, e);
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/controller/ReviewController.java b/src/main/java/com/example/demo/controller/ReviewController.java
index 8395f1e8c..55d77f437 100644
--- a/src/main/java/com/example/demo/controller/ReviewController.java
+++ b/src/main/java/com/example/demo/controller/ReviewController.java
@@ -2,14 +2,20 @@
import com.example.demo.dto.ReviewDto;
import com.example.demo.entity.ReviewEntity;
+import com.example.demo.service.ImageService;
import com.example.demo.service.ReviewService;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
-@CrossOrigin(origins = "http://localhost:3000/") // React 앱이 실행 중인 주소
+@CrossOrigin(origins = "http://localhost:3000") // React 앱이 실행 중인 주소
@RestController
@RequestMapping("/api")
public class ReviewController {
@@ -17,52 +23,98 @@ public class ReviewController {
@Autowired
private ReviewService reviewService;
+ @Autowired
+ private ImageService imageService;
+
@PostMapping("/reviews")
public ResponseEntity addReview(@RequestBody ReviewEntity review) {
- if (review.getReviewContent() == null || review.getReviewContent().isEmpty()) {
- return ResponseEntity.badRequest().body(null);
- }
- ReviewEntity newReview = reviewService.addReview(review);
- return ResponseEntity.ok(newReview);
+ return ResponseEntity.ok(reviewService.saveReview(review));
}
@GetMapping("/reviews")
- public ResponseEntity> getAllReviews() {
+ public ResponseEntity> getAllReviews() {
return ResponseEntity.ok(reviewService.getAllReviews());
}
// 특정 리뷰 조회
@GetMapping("/reviews/{reviewId}")
- public ResponseEntity getReviewById(@PathVariable Long reviewId) {
- ReviewDto review = reviewService.getReviewById(reviewId);
- if (review != null) {
- return ResponseEntity.ok(review);
- } else {
- return ResponseEntity.notFound().build();
- }
+ public ResponseEntity getReviewById(@PathVariable int reviewId) {
+ return reviewService.getReviewById(reviewId)
+ .map(review -> ResponseEntity.ok(review))
+ .orElseGet(() -> ResponseEntity.notFound().build());
}
// 리뷰 수정
@PutMapping("/reviews/{reviewId}")
- public ResponseEntity updateReview(@PathVariable Long reviewId, @RequestBody ReviewDto reviewDto) {
- ReviewDto updatedReview = reviewService.updateReview(reviewId, reviewDto);
- if (updatedReview != null) {
- return ResponseEntity.ok(updatedReview);
- } else {
- return ResponseEntity.notFound().build();
+ public ResponseEntity updateReview(@PathVariable int reviewId,
+ @RequestPart("reviewTitle") String reviewTitle,
+ @RequestPart("reviewContent") String reviewContent,
+ @RequestPart(value = "reviewImage", required = false) MultipartFile reviewImage) {
+ ReviewEntity existingReview = reviewService.getReviewById(reviewId)
+ .orElseThrow(() -> new RuntimeException("리뷰 없음"));
+
+ try {
+ // 이미지가 요청에 포함되어 있다면 업로드하고 URL을 가져온다.
+ if (reviewImage != null && !reviewImage.isEmpty()) {
+ String imageUrl = uploadImage(reviewImage);
+ existingReview.setReviewImage(imageUrl);
+ }
+
+ // 기존 게시글 데이터를 업데이트한다.
+ existingReview.setReviewTitle(reviewTitle);
+ existingReview.setReviewContent(reviewContent);
+
+ return ResponseEntity.ok(reviewService.saveReview(existingReview));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
+ private String uploadImage(MultipartFile file) throws IOException { // uploadImage 메서드 추가
+ return imageService.uploadImage(file);
+ }
+
// 리뷰 삭제
@DeleteMapping("/reviews/{reviewId}")
- public ResponseEntity deleteReview(@PathVariable Long reviewId) {
- boolean isDeleted = reviewService.deleteReview(reviewId);
- if (isDeleted) {
- return ResponseEntity.ok().build();
- } else {
- return ResponseEntity.notFound().build();
+ public ResponseEntity deleteReview(@PathVariable int reviewId) {
+ reviewService.deleteReviewById(reviewId);
+ return ResponseEntity.noContent().build();
+ }
+
+ @PostMapping("/createReview")
+ public ResponseEntity |