diff --git a/src/App.jsx b/src/App.jsx index d509b19..c1fcba1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,6 +14,8 @@ import ProjectRecruitment from './components/project-recruitment'; import ProjectCollaboration from './components/project-collaboration'; import ProjectPromotion from './components/project-promotion'; import Community from './components/community/Top10-rank'; +import ContactForm from './components/contact/contactForm'; +import TeamMemberSearch from './components/contact/member-registration'; const router = createBrowserRouter([ @@ -86,7 +88,10 @@ const router = createBrowserRouter([ }, { path: 'community', + //element: + //element: element: + }, { //마이페이지 경로 path: 'mypage', diff --git a/src/components/contact/contactForm.jsx b/src/components/contact/contactForm.jsx new file mode 100644 index 0000000..df8dc2a --- /dev/null +++ b/src/components/contact/contactForm.jsx @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; + +import { + MainContainer, + Title, + Description, + ContactsContainer, + InputContainer, + InputWrapper, + InputGroup, + Input, + RegisterButton, + RegisteredContact, + ContactInfo, + DeleteButton +} from "../../styled-components/contact/styled-contactForm"; + +const ContactForm = () => { + const [contacts, setContacts] = useState([]); + const [currentContact, setCurrentContact] = useState({ + method: '', + link: '', + isRegistered: false + }); + + const handleRegister = () => { + if (currentContact.method && currentContact.link) { + setContacts([...contacts, { ...currentContact, isRegistered: true }]); + setCurrentContact({ method: '', link: '', isRegistered: false }); + } + }; + + const handleDelete = (index) => { + const newContacts = contacts.filter((_, i) => i !== index); + setContacts(newContacts); + }; + + return ( + + 연락 방법 + 이메일, 오픈채팅방, 인스타그램 등 연락 방법을 입력해주세요 + + + {contacts.length < 10 && ( + + + + setCurrentContact({ + ...currentContact, + method: e.target.value + })} + className="method" + /> + + + setCurrentContact({ + ...currentContact, + link: e.target.value + })} + className="link" + /> + + + 등록하기 + + )} + + {contacts.map((contact, index) => ( + + + + + + handleDelete(index)}> + + + + + + + ))} + + + ); +}; + + +export default ContactForm; diff --git a/src/components/contact/member-registration.jsx b/src/components/contact/member-registration.jsx new file mode 100644 index 0000000..44325c2 --- /dev/null +++ b/src/components/contact/member-registration.jsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; + +import { + Container, + Title, + Description, + MainSection, + SearchSection, + SearchInputWrapper, + SearchInput, + SearchButton, + TagsSection, + TagGroup, + TagLabel, + TagsWrapper, + LeaderContainer, + MemberContainer, + LeaderTag, + MemberTag, + DeleteButton, + SearchResultsList, + SearchResultItem, + ProfileImage, + ResultNickname + } from '../../styled-components/contact/styled-member-registration'; + +export const TeamMemberSearch = () => { + // 임시 데이터 + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [teamMembers, setTeamMembers] = useState([ + { id: 1, nickname: '도도', isLeader: false }, + { id: 2, nickname: '이노', isLeader: false } + ]); + + // 임시 사용자 데이터베이스 + const userDatabase = [ + { id: 3, nickname: '에이치', profileImage: '프로필이미지URL' }, + { id: 4, nickname: '에이호', profileImage: '프로필이미지URL' }, + { id: 5, nickname: '에이든', profileImage: '프로필이미지URL' }, + ]; + + const handleSearch = (e) => { + setSearchQuery(e.target.value); + if (e.target.value.trim()) { + const results = userDatabase.filter(user => + user.nickname.toLowerCase().includes(e.target.value.toLowerCase()) + ); + setSearchResults(results); + } else { + setSearchResults([]); + } + }; + + const handleAddMember = (member) => { + if (!teamMembers.find(m => m.id === member.id)) { + setTeamMembers([...teamMembers, { ...member, isLeader: false }]); + } + setSearchResults([]); + setSearchQuery(''); + }; + + const handleRemoveMember = (memberId) => { + setTeamMembers(teamMembers.filter(member => member.id !== memberId)); + }; + + return ( + + 함께 한 팀원 + 프로젝트를 함께하고 있는 팀원이 있다면 추가해주세요. + + + + + + + + + + + + + + {searchResults.length > 0 && ( + + {searchResults.map(result => ( + handleAddMember(result)}> + + {result.nickname} + + ))} + + )} + + + + + 리더 + 노브 + + + + 팀원 + + {teamMembers.map(member => ( + + {member.nickname} + handleRemoveMember(member.id)}> + + + + + + + ))} + + + + + + ); +}; + + +export default TeamMemberSearch; diff --git a/src/components/contact/permission-registration.jsx b/src/components/contact/permission-registration.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/partnerd-search.jsx b/src/components/partnerd-search.jsx index ba72107..46e2d10 100644 --- a/src/components/partnerd-search.jsx +++ b/src/components/partnerd-search.jsx @@ -76,7 +76,21 @@ const PartnerSearch = () => { key="prev" onClick={() => setCurrentPage(prev => prev === 1 ? totalPages : prev - 1)} > - + + + + ); @@ -111,7 +125,21 @@ const PartnerSearch = () => { key="next" onClick={() => setCurrentPage(prev => prev === totalPages ? 1 : prev + 1)} > - + + + + ); diff --git a/src/components/project-collaboration.jsx b/src/components/project-collaboration.jsx index 04eaadc..f7baa83 100644 --- a/src/components/project-collaboration.jsx +++ b/src/components/project-collaboration.jsx @@ -73,7 +73,21 @@ const ProjectCollaboration = () => { key="prev" onClick={() => setCurrentPage(prev => prev === 1 ? totalPages : prev - 1)} > - + + + + ); @@ -106,7 +120,21 @@ const ProjectCollaboration = () => { key="next" onClick={() => setCurrentPage(prev => prev === totalPages ? 1 : prev + 1)} > - + + + + ); diff --git a/src/components/project-promotion.jsx b/src/components/project-promotion.jsx index c629803..c50010e 100644 --- a/src/components/project-promotion.jsx +++ b/src/components/project-promotion.jsx @@ -95,7 +95,21 @@ const ProjectPromotion = () => { key="prev" onClick={() => setCurrentPage(prev => prev === 1 ? totalPages : prev - 1)} > - + + + + ); @@ -142,7 +156,21 @@ const ProjectPromotion = () => { key="next" onClick={() => setCurrentPage(prev => prev === totalPages ? 1 : prev + 1)} > - + + + + ); diff --git a/src/components/project-recruitment.jsx b/src/components/project-recruitment.jsx index 625ef1a..7993944 100644 --- a/src/components/project-recruitment.jsx +++ b/src/components/project-recruitment.jsx @@ -103,7 +103,21 @@ const ProjectRecruitment = () => { } }} > - + + + + ); @@ -154,7 +168,21 @@ const ProjectRecruitment = () => { } }} > - + + + + ); diff --git a/src/styled-components/contact/styled-contactForm.jsx b/src/styled-components/contact/styled-contactForm.jsx new file mode 100644 index 0000000..8f81d6d --- /dev/null +++ b/src/styled-components/contact/styled-contactForm.jsx @@ -0,0 +1,159 @@ +import styled from "styled-components"; + +export const MainContainer = styled.div` + background: #FFFFFF; + padding: 24px; + border-radius: 8px; + width: 1180px; + height: 886px; + flex-shrink: 0; +`; + +export const Title = styled.h2` + color: #212121; + font-family: Pretendard; + font-size: 32px; + font-style: normal; + font-weight: 700; + line-height: normal; + letter-spacing: -0.64px; + margin-bottom: 8px; +`; + +export const Description = styled.p` + color: #707070; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.32px; + margin-bottom: 20px; +`; + +export const ContactsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + width: 1028px; + height: 229px; + flex-shrink: 0; +`; + +export const InputContainer = styled.div` + background: #F3F3F3; + padding: 16px; + border-radius: 8px; + display: flex; + gap: 12px; + align-items: flex-start; +`; + +export const InputWrapper = styled.div` + display: grid; + grid-template-columns: 210px 632px; + gap: 16px; + flex: 1; +`; + +export const InputGroup = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +export const Input = styled.input` + padding: 22px 28px; + border: 2px solid #E1E1E1; + border-radius: 4px; + font-size: 14px; + color: #212121; + background: #FFFFFF; + box-sizing: border-box; + display: flex; + align-items: center; + flex-shrink: 0; + + &.method { + width: 210px; + height: 64px; + } + + &.link { + width: 632px; + height: 64px; + } + + &:focus { + border-color: #C2C2C2; + outline: none; + } + + &::placeholder { + color: #E1E1E1; + } + + &:disabled { + background: #FFFFFF; + border-color: #E1E1E1; + color: #212121; + } +`; + +export const RegisterButton = styled.button` + display: inline-flex; + padding: 20px 24px; + align-items: center; + gap: 8px; + border-radius: 8px; + border: 1px solid #0D29B7; + background: #FFF; + color: #0D29B7; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.4px; + cursor: pointer; + + &:hover { + background: #F8F9FF; + } +`; + +export const RegisteredContact = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + background: #F3F3F3; + padding: 16px; + border-radius: 8px; +`; + +export const ContactInfo = styled.div` + display: grid; + grid-template-columns: 210px 632px; + gap: 16px; + flex: 1; +`; + +export const DeleteButton = styled.button` + width: 40px; + height: 40px; + flex-shrink: 0; + background: none; + border: none; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + margin-right: 82px; + + &:hover { + svg path { + stroke: #666666; + } + } +`; \ No newline at end of file diff --git a/src/styled-components/contact/styled-member-registration.jsx b/src/styled-components/contact/styled-member-registration.jsx new file mode 100644 index 0000000..5299e56 --- /dev/null +++ b/src/styled-components/contact/styled-member-registration.jsx @@ -0,0 +1,191 @@ +import styled from "styled-components"; + +export const Container = styled.div` + width: 863px; +`; + +export const Title = styled.h2` + color: #212121; + font-family: Pretendard; + font-size: 32px; + font-weight: 700; + letter-spacing: -0.64px; + margin-bottom: 16px; +`; + +export const Description = styled.p` + color: #707070; + font-family: Pretendard; + font-size: 24px; + font-weight: 500; + letter-spacing: -0.48px; + margin-bottom: 32px; +`; + +export const MainSection = styled.div` + display: flex; + gap: 120px; +`; + +export const SearchSection = styled.div` + position: relative; + width: 500px; +`; + +export const SearchInputWrapper = styled.div` + position: relative; + width: 500px; +`; + +export const SearchInput = styled.input` + width: 500px; + height: 52px; + padding: 14px 24px; + border-radius: 4px; + border: 1px solid #E0E0E0; + background: #FFF; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); + color: #212121; + font-family: Pretendard; + font-size: 16px; + font-weight: 500; + + &::placeholder { + color: #C2C2C2; + } +`; + +export const SearchButton = styled.button` + position: absolute; + margin-left: 500px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + padding: 0; + width: 20px; + height: 20px; +`; + +export const TagsSection = styled.div` + display: flex; + flex-direction: column; + gap: 24px; +`; + +export const TagGroup = styled.div` + display: flex; + align-items: center; +`; + +export const TagLabel = styled.span` + color: #212121; + font-family: Pretendard; + font-size: 20px; + font-weight: 700; + letter-spacing: -0.4px; + white-space: nowrap; + position: sticky; + top: 0; +`; + +export const TagsWrapper = styled.div` + display: grid; + grid-template-columns: repeat(3, auto); + gap: 8px; + width: fit-content; + align-items: flex-start; +`; + +export const LeaderContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 16px; +`; + +export const MemberContainer = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 16px; +`; + +export const LeaderTag = styled.div` + display: flex; + padding: 4px 20px; + align-items: center; + border-radius: 100px; + border: 1px solid #0D29B7; + background: #FFF; + color: #0D29B7; + font-family: Pretendard; + font-size: 20px; + font-weight: 600; + letter-spacing: -0.4px; +`; + +export const MemberTag = styled.div` + display: inline-flex; + padding: 4px 10px 4px 20px; + align-items: center; + gap: 10px; + border-radius: 100px; + border: 1px solid #0D29B7; + background: #EAF1FF; + color: #0D29B7; + font-family: Pretendard; + font-size: 20px; + font-weight: 600; + letter-spacing: -0.4px; + white-space: nowrap; +`; + +export const DeleteButton = styled.button` + background: none; + border: none; + padding: 0; + cursor: pointer; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; +`; + +export const SearchResultsList = styled.div` + position: absolute; + top: 100%; + left: 0; + width: 100%; + background: #FFFFFF; + border-radius: 4px; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; +`; + +export const SearchResultItem = styled.div` + display: flex; + align-items: center; + padding: 16px; + cursor: pointer; + + &:hover { + background: #F3F3F3; + } +`; + +export const ProfileImage = styled.img` + width: 50px; + height: 50px; + border-radius: 50%; + margin-right: 12px; +`; + +export const ResultNickname = styled.span` + color: #212121; + font-family: Pretendard; + font-size: 16px; + font-weight: 600; +`; \ No newline at end of file diff --git a/src/styled-components/styled-common.jsx b/src/styled-components/styled-common.jsx index 0777cf6..096982d 100644 --- a/src/styled-components/styled-common.jsx +++ b/src/styled-components/styled-common.jsx @@ -19,60 +19,18 @@ export const ArrowButton = styled.button` justify-content: center; `; -export const ArrowIcon = styled.span` +export const ArrowIcon = styled.svg` position: relative; display: inline-block; width: 24px; height: 24px; &.right { - &::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - width: 16px; - height: 2px; - background: #4B48DF; - transform: translateY(-50%); - } - - &::after { - content: ''; - position: absolute; - left: 10px; - top: 50%; - width: 8px; - height: 8px; - border-top: 2px solid #4B48DF; - border-right: 2px solid #4B48DF; - transform: translateY(-50%) rotate(45deg); - } + transform: rotate(180deg); } - &.left { - &::before { - content: ''; - position: absolute; - right: 0; - top: 50%; - width: 16px; - height: 2px; - background: #4B48DF; - transform: translateY(-50%); - } - - &::after { - content: ''; - position: absolute; - right: 10px; - top: 50%; - width: 8px; - height: 8px; - border-top: 2px solid #4B48DF; - border-left: 2px solid #4B48DF; - transform: translateY(-50%) rotate(-45deg); - } + path { + stroke: #4B48DF; } `;