diff --git a/src/App.jsx b/src/App.jsx
index 41ccabc..42a5d4c 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,15 +1,41 @@
-import { EmptyView, Header } from '@/components';
+import { createContext, useState } from 'react';
+
+import { EmptyView, Header, ListView } from '@/components';
+import useModalContext from '@/components/Context/formProvider';
import styles from './App.module.scss';
+export const modalContext = createContext();
+
function App() {
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useModalContext();
+
+ const users = localStorage.getItem('goormUsers')
+ ? JSON.parse(localStorage.getItem('goormUsers'))
+ : [];
+
return (
-
-
-
-
-
-
+
+
+
+
+ {users.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+
);
}
diff --git a/src/components/Context/formProvider.jsx b/src/components/Context/formProvider.jsx
new file mode 100644
index 0000000..6c64d6e
--- /dev/null
+++ b/src/components/Context/formProvider.jsx
@@ -0,0 +1,74 @@
+import { createContext, useEffect, useState } from 'react';
+
+const intialState = {
+ // 1번
+ name: '이름',
+ phone: '01012345678',
+ email: 'test@email.com',
+ permissions: {
+ privacyApproval: false,
+ marketingApproval: false,
+ marketingMediaApproval: {
+ email: false,
+ sms: false,
+ },
+
+ // 2번
+
+ swMajored: true,
+ groomUsed: true,
+ groomService: {
+ edu: false,
+ level: false,
+ devth: false,
+ ide: false,
+ exp: false,
+ },
+ groomWhy: '',
+
+ // 3번
+
+ expect: -1 /* 0번 ~ 3번 중 택 1 선택 */,
+
+ // 4번
+
+ freeMessage: '파이팅~~',
+ },
+};
+
+const useModalContext = () => {
+ const [form, setFormState] = useState(intialState);
+
+ const [modalIndex, setModalIndex] = useState(0);
+
+ const updateForm = (key, value) => {
+ setFormState((prev) => {
+ return {
+ ...prev,
+ [key]: value,
+ };
+ });
+
+ console.log('update Form', form);
+ };
+
+ const updateModalIndex = (value) => {
+ setModalIndex(value);
+ };
+
+ const resetForm = () => {
+ setFormState(intialState);
+ };
+
+ const api = {
+ form,
+ updateForm,
+ modalIndex,
+ updateModalIndex,
+ resetForm,
+ };
+
+ return api;
+};
+
+export default useModalContext;
diff --git a/src/components/EmptyView/EmptyView.jsx b/src/components/EmptyView/EmptyView.jsx
index fa09372..8784aed 100644
--- a/src/components/EmptyView/EmptyView.jsx
+++ b/src/components/EmptyView/EmptyView.jsx
@@ -1,3 +1,4 @@
+import { useContext } from 'react';
import cn from 'classnames';
import { Card } from '@/components';
diff --git a/src/components/EmptyView/EmptyView.module.scss b/src/components/EmptyView/EmptyView.module.scss
index 9f33e54..07bb50d 100644
--- a/src/components/EmptyView/EmptyView.module.scss
+++ b/src/components/EmptyView/EmptyView.module.scss
@@ -1,4 +1,6 @@
.emptyView {
+ display: flex;
+ flex-direction: column;
padding-top: 4.5rem /* 72px -> 4.5rem */;
padding-bottom: 4.5rem /* 72px -> 4.5rem */;
gap: .5rem /* 8px -> .5rem */;
diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx
index cb03179..8c82706 100644
--- a/src/components/Header/Header.jsx
+++ b/src/components/Header/Header.jsx
@@ -1,18 +1,46 @@
+import React, { useContext, useState } from 'react';
import cn from 'classnames';
import { Button, Typography } from '@goorm-dev/gds-challenge';
+import useModalContext from '../Context/formProvider';
+import CustomModal from '../Modal';
+
import styles from './Header.module.scss';
const Header = () => {
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useModalContext();
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const toggle = () => {
+ setModalOpen((prevModalOpen) => {
+ return !prevModalOpen;
+ });
+ };
+
+ const handleCloseModal = () => {
+ toggle();
+ resetForm();
+ };
+
+ const handleModalOpen = () => {
+ updateModalIndex(0);
+ toggle();
+ };
+
return (
);
};
diff --git a/src/components/ListView/ListView.jsx b/src/components/ListView/ListView.jsx
new file mode 100644
index 0000000..6739807
--- /dev/null
+++ b/src/components/ListView/ListView.jsx
@@ -0,0 +1,24 @@
+import UserDetails from '../UserDetail/UserDetails';
+import { Typography } from '@goorm-dev/gds-challenge';
+
+import { Card } from '@/components';
+import React from 'react';
+
+const ListView = ({ users }) => {
+
+ return (
+ <>
+
+ 응답한 참여자{" "}
+ {users.length}
+
+ {users.map((user, idx) => (
+
+
+
+ ))}
+ >
+ )
+};
+
+export default ListView;
\ No newline at end of file
diff --git a/src/components/Modal/components/Modal1.jsx b/src/components/Modal/components/Modal1.jsx
new file mode 100644
index 0000000..decb049
--- /dev/null
+++ b/src/components/Modal/components/Modal1.jsx
@@ -0,0 +1,267 @@
+import React, { useContext, useEffect, useState } from 'react';
+
+import { modalContext } from '@/App';
+
+import { Button, Form, Input, Label, Modal } from '@goorm-dev/gds-challenge';
+
+import useModalContext from '../../Context/formProvider';
+
+const Modal1 = ({ onClose, headerName }) => {
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useContext(modalContext);
+
+ // name
+ const [name, setName] = useState('');
+
+ // phone
+ const [phone, setPhone] = useState('');
+
+ // email
+ const [email, setEmail] = useState('');
+
+ // permissions
+ const [allChecked, setAllChecked] = useState(true);
+ const [privacyChecked, setPrivacyChecked] = useState(true);
+ const [marketingChecked, setMarketingChecked] = useState(true);
+ const [adChecked, setAdChecked] = useState(true);
+ const [emailChecked, setEmailChecked] = useState(true);
+ const [snsChecked, setSnsChecked] = useState(true);
+
+ const handleNameChange = (e) => {
+ const input = e.target.value;
+ setName(input);
+ console.log(name);
+ updateForm('name', input);
+ };
+
+ const handlePhoneChange = (e) => {
+ const input = e.target.value;
+ setPhone(input);
+ console.log(phone);
+ };
+
+ const handleEmailChange = (e) => {
+ const input = e.target.value;
+ setEmail(input);
+ console.log(email);
+ };
+
+ useEffect(() => {
+ setName(name);
+ setPhone(phone);
+ setEmail(email);
+ }, [name, phone, email]);
+
+ // 전체 동의
+ const handleAllCheckboxChange = () => {
+ const newState = !allChecked;
+ setAllChecked(newState);
+ setPrivacyChecked(newState);
+ setMarketingChecked(newState);
+ setAdChecked(newState);
+ setEmailChecked(newState);
+ setSnsChecked(newState);
+ };
+
+ // 개인정보처리방침
+ const handleprivacyCheckedChange = () => {
+ const newState = !privacyChecked;
+ setAllChecked(false);
+ setPrivacyChecked(newState);
+ };
+
+ // 마케팅 목적의 개인 정보 수집 및 이용
+ const handlemarketingCheckedChange = () => {
+ const newState = !marketingChecked;
+ setAllChecked(false);
+ setMarketingChecked(newState);
+ };
+
+ // 광고성 정보 수신
+ const handleadCheckedChange = () => {
+ const newState = !adChecked;
+ setAllChecked(false);
+ setAdChecked(newState);
+ setEmailChecked(newState);
+ setSnsChecked(newState);
+ };
+
+ // email 정보 수신
+ const handleEmailChecked = () => {
+ const newState = !emailChecked;
+ setAllChecked(false);
+ setAdChecked(newState);
+ setEmailChecked(newState);
+ };
+
+ // SNS 정보 수신
+ const handleSnsChecked = () => {
+ const newState = !snsChecked;
+ setAllChecked(false);
+ setAdChecked(newState);
+ setSnsChecked(newState);
+ };
+ // 유효성 검사 함수
+ const isEmailValid = (value) => {
+ return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value);
+ };
+
+ const isPhoneValid = (value) => {
+ return /^\d{10,11}$/.test(value);
+ };
+
+ const isNextButtonEnabled = () => {
+ return !(
+ name === '' ||
+ phone === '' ||
+ email === '' ||
+ !privacyChecked ||
+ (phone.length > 0 && !/^\d{10,11}$/.test(phone)) ||
+ (email.length > 0 &&
+ !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email))
+ );
+ };
+
+ const handleFormSubmit = () => {};
+
+ return (
+ <>
+ {headerName}
+
+
+ {/* title & sub title */}
+
+
참여자 정보를 입력해주세요.
+
+ 오프라인 팀 챌린지 참여자의 정보를 수집하려고 해요.
+
+
+
+
+
+
0 && !isPhoneValid(phone)}
+ />
+ {phone.length > 0 && isPhoneValid(phone) ? (
+
+ 양식에 맞게 입력되었습니다.
+
+ ) : (
+ phone.length > 0 &&
+ !isPhoneValid(phone) && (
+
+ 양식에 맞게 입력해주세요.
+
+ )
+ )}
+
+
0 && !isEmailValid(email)}
+ />
+ {email.length > 0 && isEmailValid(email) ? (
+
+ 양식에 맞게 입력되었습니다.
+
+ ) : (
+ email.length > 0 &&
+ !isEmailValid(email) && (
+
+ 양식에 맞게 입력해주세요.
+
+ )
+ )}
+
+
+
+
+
+ ※ 광고성 정보 수신에 동의하시면 챌린지에 꾸준히 참여하실 수
+ 있도록 리마인드 알림을 보내드려요.
+
+
+
+
+
+ >
+ );
+};
+
+export default Modal1;
diff --git a/src/components/Modal/components/Modal3.jsx b/src/components/Modal/components/Modal3.jsx
new file mode 100644
index 0000000..1885bb6
--- /dev/null
+++ b/src/components/Modal/components/Modal3.jsx
@@ -0,0 +1,122 @@
+// GDS 컴포넌트
+// alias (src/* === @/*)
+// import { Card } from '@/components';
+import { useState } from 'react';
+
+import useModalContext from '@/components/Context/formProvider';
+
+import {
+ Button,
+ CarouselIndicators,
+ Modal,
+ Typography,
+} from '@goorm-dev/gds-challenge';
+// GDS 아이콘
+import { ChevronDoubleLeftIcon } from '@goorm-dev/gds-icons';
+
+const Modal3 = ({ isOpen, toggle }) => {
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useModalContext();
+
+ const [expect, setExpect] = useState();
+
+ const handleBtnClick = (e) => {
+ const num = e.target.value;
+ setExpect(num);
+ };
+
+ const moveIndex = (index) => {
+ updateForm('expect', expect);
+ updateModalIndex(index);
+ };
+ return (
+
+
+
+ 오프라인 팀 챌린지에 가장 기대하는 점은 무엇인가요?
+
+
+ 더 좋은 챌린지가 될 수 있도록 데이터를 수집하려고 해요.
+
+
+
+
+
+
+
+
+
+ {
+ return moveIndex(index);
+ }}
+ activeIndex={modalIndex}
+ />
+
+
+
+
+
+
+
+ );
+};
+
+export default Modal3;
diff --git a/src/components/Modal/components/Modal4.jsx b/src/components/Modal/components/Modal4.jsx
new file mode 100644
index 0000000..abf3b98
--- /dev/null
+++ b/src/components/Modal/components/Modal4.jsx
@@ -0,0 +1,88 @@
+import React, { useContext, useState } from 'react';
+
+import {
+ Button,
+ CarouselIndicators,
+ Modal,
+ TextArea,
+ Typography,
+} from '@goorm-dev/gds-challenge';
+
+import useModalContext from '../../Context/formProvider';
+
+const Modal4 = ({ onClose, headerName }) => {
+ // const { form, setForm, modal, setModal } = useContext(FormContext);
+
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useModalContext();
+
+ const [freeMessage, setFreeMessage] = useState(form.freeMessage);
+ const handleFormSubmit = () => {
+ console.log(form);
+ localStorage.setItem('goormUsers', JSON.stringify(form));
+ onClose();
+ };
+
+ const moveIndex = (index) => {
+ updateForm('freeMessage', freeMessage);
+ updateModalIndex(index);
+ };
+
+ return (
+ <>
+
+
+ {headerName}
+
+
+ 더 좋은 챌린지가 될 수 있도록 데이터를 수집하려고 해요.
+
+
+
+
+
+
+ {
+ return moveIndex(index);
+ }}
+ activeIndex={modalIndex}
+ />
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Modal4;
diff --git a/src/components/Modal/index.jsx b/src/components/Modal/index.jsx
new file mode 100644
index 0000000..5b3fe6b
--- /dev/null
+++ b/src/components/Modal/index.jsx
@@ -0,0 +1,58 @@
+import React, { useContext, useState } from 'react';
+import cn from 'classnames';
+
+import { Modal } from '@goorm-dev/gds-challenge';
+
+import useModalContext from '../Context/formProvider';
+
+import Modal1 from './components/Modal1';
+import Modal4 from './components/Modal4';
+
+const CustomModal = ({ isOpen, onClose }) => {
+ const { form, updateForm, modalIndex, updateModalIndex, resetForm } =
+ useModalContext();
+ const curIndex = modalIndex;
+
+ const renderComponent = () => {
+ switch (curIndex) {
+ case 0:
+ return (
+
+ );
+ case 1:
+ return (
+
+ );
+ // 추가 케이스
+ case 2:
+ return (
+
+ );
+
+ case 3:
+ return (
+
+ );
+ default:
+ return '에러';
+ }
+ };
+
+ return {renderComponent()};
+};
+
+export default CustomModal;
diff --git a/src/components/UserDetail/UserDetail.module.scss b/src/components/UserDetail/UserDetail.module.scss
new file mode 100644
index 0000000..dec6337
--- /dev/null
+++ b/src/components/UserDetail/UserDetail.module.scss
@@ -0,0 +1,3 @@
+.selectBtn {
+
+}
\ No newline at end of file
diff --git a/src/components/UserDetail/UserDetails.jsx b/src/components/UserDetail/UserDetails.jsx
new file mode 100644
index 0000000..702e6c2
--- /dev/null
+++ b/src/components/UserDetail/UserDetails.jsx
@@ -0,0 +1,52 @@
+import { Button, Collapse, Typography } from '@goorm-dev/gds-challenge';
+import { ChevronDownIcon, ChevronUpIcon } from '@goorm-dev/gds-icons';
+import { Card } from '@/components';
+import cn from 'classnames';
+import styles from './UserDetail.module.scss';
+import { useState } from 'react';
+import { userDetailTitle, userDetailQuestion } from '../../constants/UserDeatilList';
+import { userDetailAnswer } from '../../constants/UserDeatilList';
+
+const UserDetails = ({ user, idx, total }) => {
+ const [toggle, setToggle] = useState(false);
+ const titles = Object.keys(userDetailTitle);
+
+ console.log(user);
+
+ return (
+ <>
+ setToggle(!toggle)} className={cn(styles.selectBtn)}>
+ 참여자 {idx}. {user.name}
+
+
+
+ {titles.map((title) => (
+ ['name', 'email', 'phone'].includes(title)
+ ? (
+
+ {user[title]}
+
+ )
+ :(
+
+ {userDetailQuestion[title]}
+ A. {userDetailAnswer(title, user)}
+
+ )
+ ))}
+
+ >
+ )
+};
+
+export default UserDetails;
\ No newline at end of file
diff --git a/src/components/index.js b/src/components/index.js
index ad78ab9..80004b7 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,3 +1,4 @@
export { default as Card } from './Card/Card';
export { default as EmptyView } from './EmptyView/EmptyView';
export { default as Header } from './Header/Header';
+export { default as ListView } from './ListView/ListView';
\ No newline at end of file
diff --git a/src/constants/UserDeatilList.js b/src/constants/UserDeatilList.js
new file mode 100644
index 0000000..75ba031
--- /dev/null
+++ b/src/constants/UserDeatilList.js
@@ -0,0 +1,61 @@
+export const userDetailTitle = {
+ name: '이름',
+ phone: '전화번호',
+ email: '이메일',
+ swMajored: '질문 1',
+ groomUsed: '질문 2',
+ groomService: '질문 2-1',
+ groomWhy: '질문 2-2',
+ expect: '질문 3',
+ freeMessage: '질문 4',
+};
+
+export const userDetailQuestion = {
+ swMajored: 'Q. SW 관련 학과를 전공하셨나요? (객관식)',
+ groomUsed: 'Q. 구름 서비스를 사용해보신 적 있나요? (객관식)',
+ groomService: 'Q. 사용 경험이 있는 서비스를 선택해주세요. (객관식, 복수 선택 가능)',
+ groomWhy: 'Q. 해당 서비스를 사용하게 된 이유는 무엇인가요? (주관식)',
+ expect: 'Q. 오프라인 팀 챌린지에 가장 기대하는 점은 무엇인가요? (객관식)',
+ freeMessage: 'Q. 구름톤 챌린지에 전달하고 싶은 말을 적어주세요. (주관식)',
+};
+
+export const userDetailAnswer = (answer, userInfo) => {
+ console.log('answer', answer);
+ console.log('userInfo', userInfo);
+
+ if (['swMajored', 'groomUsed'].includes(answer)) {
+ return answer ? '예' : '아니요';
+ }
+
+ if (answer === 'groomService') {
+ return Object
+ .keys(goormServices)
+ .filter((key) => {
+ console.log(key);
+ return userInfo.groomService[key]
+ })
+ .map((key) => goormServices[key])
+ .join(', ');
+ }
+
+ if (answer === 'expect') {
+ return expectChallenge[userInfo.expect];
+ }
+
+ return userInfo[answer];
+};
+
+const goormServices = {
+ edu: '구름EDU',
+ level: '구름LEVEL',
+ devth: '구름DEVTH',
+ ide: '구름IDE',
+ exp: '구름EXP',
+};
+
+const expectChallenge = [
+ '정해진 시간 내에 오프라인 팀 챌린지 과제를 수행, 1번 수행',
+ '정해진 시간 내에 오프라인 팀 챌린지 과제를 수행, 2번 수행',
+ '정해진 시간 내에 오프라인 팀 챌린지 과제를 수행, 3번 수행',
+ '정해진 시간 내에 오프라인 팀 챌린지 과제를 수행, 4번 수행'
+];
\ No newline at end of file