diff --git a/package-lock.json b/package-lock.json
index a1e590ee6..53262aad3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,8 +11,10 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "classnames": "^2.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
@@ -3241,6 +3243,14 @@
}
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
+ "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -5954,6 +5964,11 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
"integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ=="
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/clean-css": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
@@ -14671,6 +14686,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
+ "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
+ "dependencies": {
+ "@remix-run/router": "1.16.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
+ "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
+ "dependencies": {
+ "@remix-run/router": "1.16.1",
+ "react-router": "6.23.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
diff --git a/package.json b/package.json
index 7ff0d6b58..48a05b3ce 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,10 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "classnames": "^2.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-router-dom": "^6.23.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index a11777cc4..000000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/images/home/bottom-banner-image.png b/public/images/home/bottom-banner-image.png
new file mode 100644
index 000000000..4a5f85b28
Binary files /dev/null and b/public/images/home/bottom-banner-image.png differ
diff --git a/public/images/home/feature-search-img.png b/public/images/home/feature-search-img.png
new file mode 100644
index 000000000..31e20b979
Binary files /dev/null and b/public/images/home/feature-search-img.png differ
diff --git a/public/images/home/feature1-image.png b/public/images/home/feature1-image.png
new file mode 100644
index 000000000..4684b9a72
Binary files /dev/null and b/public/images/home/feature1-image.png differ
diff --git a/public/images/home/feature3-image.png b/public/images/home/feature3-image.png
new file mode 100644
index 000000000..5b8084a77
Binary files /dev/null and b/public/images/home/feature3-image.png differ
diff --git a/public/images/home/hero-image.png b/public/images/home/hero-image.png
new file mode 100644
index 000000000..d28fb6522
Binary files /dev/null and b/public/images/home/hero-image.png differ
diff --git a/public/images/icons/arrow_left.svg b/public/images/icons/arrow_left.svg
new file mode 100644
index 000000000..2a9de23a6
--- /dev/null
+++ b/public/images/icons/arrow_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/icons/arrow_right.svg b/public/images/icons/arrow_right.svg
new file mode 100644
index 000000000..daa483c3e
--- /dev/null
+++ b/public/images/icons/arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/icons/eye-invisible.svg b/public/images/icons/eye-invisible.svg
new file mode 100644
index 000000000..92252b05d
--- /dev/null
+++ b/public/images/icons/eye-invisible.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/images/icons/eye-visible.svg b/public/images/icons/eye-visible.svg
new file mode 100644
index 000000000..35a75305e
--- /dev/null
+++ b/public/images/icons/eye-visible.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/icons/ic_heart.svg b/public/images/icons/ic_heart.svg
new file mode 100644
index 000000000..cad016c13
--- /dev/null
+++ b/public/images/icons/ic_heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/icons/ic_search.svg b/public/images/icons/ic_search.svg
new file mode 100644
index 000000000..52241e6d8
--- /dev/null
+++ b/public/images/icons/ic_search.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/icons/ic_sort.svg b/public/images/icons/ic_sort.svg
new file mode 100644
index 000000000..657b44f93
--- /dev/null
+++ b/public/images/icons/ic_sort.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/logo/favicon.ico b/public/images/logo/favicon.ico
new file mode 100644
index 000000000..9fecc692d
Binary files /dev/null and b/public/images/logo/favicon.ico differ
diff --git a/public/images/logo/logo.svg b/public/images/logo/logo.svg
new file mode 100644
index 000000000..d497acbfe
--- /dev/null
+++ b/public/images/logo/logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/public/images/logo/panda-market-logo.png b/public/images/logo/panda-market-logo.png
new file mode 100644
index 000000000..a1dc1c6a1
Binary files /dev/null and b/public/images/logo/panda-market-logo.png differ
diff --git a/public/images/market/img_default.png b/public/images/market/img_default.png
new file mode 100644
index 000000000..9a1bd6c32
Binary files /dev/null and b/public/images/market/img_default.png differ
diff --git a/public/images/social/facebook-logo.svg b/public/images/social/facebook-logo.svg
new file mode 100644
index 000000000..8491c2f83
--- /dev/null
+++ b/public/images/social/facebook-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/social/google-logo.png b/public/images/social/google-logo.png
new file mode 100644
index 000000000..199f3d628
Binary files /dev/null and b/public/images/social/google-logo.png differ
diff --git a/public/images/social/instagram-logo.svg b/public/images/social/instagram-logo.svg
new file mode 100644
index 000000000..c83306f84
--- /dev/null
+++ b/public/images/social/instagram-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/social/kakao-logo.png b/public/images/social/kakao-logo.png
new file mode 100644
index 000000000..bfadc1d35
Binary files /dev/null and b/public/images/social/kakao-logo.png differ
diff --git a/public/images/social/twitter-logo.svg b/public/images/social/twitter-logo.svg
new file mode 100644
index 000000000..14a6069a1
--- /dev/null
+++ b/public/images/social/twitter-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/social/youtube-logo.svg b/public/images/social/youtube-logo.svg
new file mode 100644
index 000000000..5fcc0ff34
--- /dev/null
+++ b/public/images/social/youtube-logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/index.html b/public/index.html
index aa069f27c..7ccb20524 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,43 +1,32 @@
-
+
-
-
-
-
-
-
-
-
-
- React App
+
+
+
+
+
+
+
+
+ 판다마켓
+
+
+
-
diff --git a/public/logo192.png b/public/logo192.png
deleted file mode 100644
index fc44b0a37..000000000
Binary files a/public/logo192.png and /dev/null differ
diff --git a/public/logo512.png b/public/logo512.png
deleted file mode 100644
index a4e47a654..000000000
Binary files a/public/logo512.png and /dev/null differ
diff --git a/public/signin/index.html b/public/signin/index.html
new file mode 100644
index 000000000..d7ec21315
--- /dev/null
+++ b/public/signin/index.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+ 판다마켓 - 로그인
+
+
+
+
+
+
+
+
+
+
+
간편 로그인하기
+
+
+
+
+
+
+
+
+
+
diff --git a/public/signin/signin.js b/public/signin/signin.js
new file mode 100644
index 000000000..798f27915
--- /dev/null
+++ b/public/signin/signin.js
@@ -0,0 +1,107 @@
+document.addEventListener('DOMContentLoaded', () => {
+ let isValidEmail = false;
+ let isValidPassword = false;
+
+ const inputEmail = document.getElementById('email');
+ const inputPassword = document.getElementById('password');
+ const btnTogglePasswordVisibleList = document.querySelectorAll(
+ '.btn-password-visible'
+ );
+ const btnSignin = document.getElementById('btn-signin');
+ const messageErrorEmail = document.getElementById('message-error-email');
+ const messageErrorPassword = document.getElementById(
+ 'message-error-password'
+ );
+
+ const validateEmail = () => {
+ const emailValue = inputEmail.value.trim();
+ if (!emailValue) {
+ showError(inputEmail, messageErrorEmail, '이메일을 입력해주세요.');
+ isValidEmail = false;
+ } else if (!checkEmailRegex(emailValue)) {
+ showError(inputEmail, messageErrorEmail, '잘못된 이메일 형식입니다.');
+ isValidEmail = false;
+ } else {
+ clearError(inputEmail, messageErrorEmail);
+ isValidEmail = true;
+ }
+ updateBtnSignin();
+ };
+
+ const validatePassword = () => {
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordValue) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 입력해주세요.'
+ );
+ isValidPassword = false;
+ } else if (passwordValue.length < 8) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 8자 이상 입력해주세요'
+ );
+ isValidPassword = false;
+ } else {
+ clearError(inputPassword, messageErrorPassword);
+ isValidPassword = true;
+ }
+ updateBtnSignin();
+ };
+
+ const togglePasswordVisible = (e) => {
+ e.preventDefault();
+ const visibleBtn = e.currentTarget;
+ const targetInput = visibleBtn.parentElement.querySelector('input');
+ const visibleImg = visibleBtn.querySelector('.img-password-visible');
+ if (targetInput.type === 'password') {
+ targetInput.type = 'text';
+ visibleImg.src = '/images/icons/eye-visible.svg';
+ } else {
+ targetInput.type = 'password';
+ visibleImg.src = '/images/icons/eye-invisible.svg';
+ }
+ };
+
+ const updateBtnSignin = () => {
+ if (isValidPassword && isValidEmail) {
+ btnSignin.disabled = false;
+ } else {
+ btnSignin.disabled = true;
+ }
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (btnSignin.disabled) return;
+ window.location.href = '/items';
+ };
+
+ const checkEmailRegex = (email) => {
+ const emailRegex = new RegExp('[a-z0-9]+@[a-z]+\\.[a-z]{2,3}');
+ return emailRegex.test(email);
+ };
+
+ const showError = (input, errorMessageElement, message) => {
+ input.classList.add('error');
+ errorMessageElement.textContent = message;
+ errorMessageElement.style.display = 'block';
+ };
+
+ const clearError = (input, errorMessageElement) => {
+ input.classList.remove('error');
+ errorMessageElement.textContent = '';
+ errorMessageElement.style.display = 'none';
+ };
+
+ inputEmail.addEventListener('focusout', validateEmail);
+ inputPassword.addEventListener('focusout', validatePassword);
+ inputEmail.addEventListener('input', validateEmail);
+ inputPassword.addEventListener('input', validatePassword);
+ btnSignin.addEventListener('click', handleSubmit);
+ btnTogglePasswordVisibleList.forEach((button) => {
+ button.addEventListener('click', togglePasswordVisible);
+ });
+});
diff --git a/public/signup/index.html b/public/signup/index.html
new file mode 100644
index 000000000..f387c5259
--- /dev/null
+++ b/public/signup/index.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+ 판다마켓 - 회원가입
+
+
+
+
+
+
+
+
+
+
+
간편 로그인하기
+
+
+
+
+
+
+
+
+
+
diff --git a/public/signup/signup.js b/public/signup/signup.js
new file mode 100644
index 000000000..c75081ed0
--- /dev/null
+++ b/public/signup/signup.js
@@ -0,0 +1,173 @@
+document.addEventListener('DOMContentLoaded', () => {
+ let isValidEmail = false;
+ let isValidNickname = false;
+ let isValidPassword = false;
+ let isValidPasswordConfirmation = false;
+ let isPasswordVisible = false;
+
+ const inputEmail = document.getElementById('email');
+ const inputNickname = document.getElementById('nickname');
+ const inputPassword = document.getElementById('password');
+ const inputPasswordConfirmation = document.getElementById(
+ 'password-confirmation'
+ );
+ const btnTogglePasswordVisibleList = document.querySelectorAll(
+ '.btn-password-visible'
+ );
+ const btnSignup = document.getElementById('btn-signup');
+ const messageErrorEmail = document.getElementById('message-error-email');
+ const messageErrorNickname = document.getElementById(
+ 'message-error-nickname'
+ );
+ const messageErrorPassword = document.getElementById(
+ 'message-error-password'
+ );
+ const messageErrorPasswordConfirmation = document.getElementById(
+ 'message-error-password-confirmation'
+ );
+
+ const validateEmail = () => {
+ const emailValue = inputEmail.value.trim();
+ if (!emailValue) {
+ showError(inputEmail, messageErrorEmail, '이메일을 입력해주세요.');
+ isValidEmail = false;
+ } else if (!checkEmailRegex(emailValue)) {
+ showError(inputEmail, messageErrorEmail, '잘못된 이메일 형식입니다.');
+ isValidEmail = false;
+ } else {
+ clearError(inputEmail, messageErrorEmail);
+ isValidEmail = true;
+ }
+ updatebtnSignup();
+ };
+
+ const validateNickname = () => {
+ const nicknameValue = inputNickname.value.trim();
+ if (!nicknameValue) {
+ showError(inputNickname, messageErrorNickname, '닉네임을 입력해주세요.');
+ isValidNickname = false;
+ } else {
+ clearError(inputNickname, messageErrorNickname);
+ isValidNickname = true;
+ }
+ updatebtnSignup();
+ };
+
+ const validatePassword = () => {
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordValue) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 입력해주세요.'
+ );
+ isValidPassword = false;
+ } else if (passwordValue.length < 8) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 8자 이상 입력해주세요'
+ );
+ isValidPassword = false;
+ } else {
+ clearError(inputPassword, messageErrorPassword);
+ isValidPassword = true;
+ }
+ validatePasswordConfirmation();
+ updatebtnSignup();
+ };
+
+ const validatePasswordConfirmation = () => {
+ const passwordConfirmationValue = inputPasswordConfirmation.value.trim();
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordConfirmationValue) {
+ showError(
+ inputPasswordConfirmation,
+ messageErrorPasswordConfirmation,
+ '비밀번호를 입력해주세요.'
+ );
+ } else if (passwordConfirmationValue !== passwordValue) {
+ showError(
+ inputPasswordConfirmation,
+ messageErrorPasswordConfirmation,
+ '비밀번호가 일치하지 않습니다.'
+ );
+ isValidPasswordConfirmation = false;
+ } else {
+ clearError(inputPasswordConfirmation, messageErrorPasswordConfirmation);
+ isValidPasswordConfirmation = true;
+ }
+ updatebtnSignup();
+ };
+
+ const togglePasswordVisible = (e) => {
+ e.preventDefault();
+ const visibleBtn = e.currentTarget;
+ const targetInput = visibleBtn.parentElement.querySelector('input');
+ const visibleImg = visibleBtn.querySelector('.img-password-visible');
+ if (targetInput.type === 'password') {
+ targetInput.type = 'text';
+ visibleImg.src = '/images/icons/eye-visible.svg';
+ visibleImg.alt = '비밀번호 표시';
+ } else {
+ targetInput.type = 'password';
+ visibleImg.src = '/images/icons/eye-invisible.svg';
+ visibleImg.alt = '비밀번호 숨김';
+ }
+ };
+
+ const updatebtnSignup = () => {
+ if (
+ isValidPassword &&
+ isValidEmail &&
+ isValidNickname &&
+ isValidPasswordConfirmation
+ ) {
+ btnSignup.disabled = false;
+ } else {
+ btnSignup.disabled = true;
+ }
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (btnSignup.disabled) return;
+ window.location.href = '/signin';
+ };
+
+ const checkEmailRegex = (email) => {
+ const emailRegex = new RegExp('[a-z0-9]+@[a-z]+\\.[a-z]{2,3}');
+ return emailRegex.test(email);
+ };
+
+ const showError = (input, errorMessageElement, message) => {
+ input.classList.add('error');
+ errorMessageElement.textContent = message;
+ errorMessageElement.style.display = 'block';
+ };
+
+ const clearError = (input, errorMessageElement) => {
+ input.classList.remove('error');
+ errorMessageElement.textContent = '';
+ errorMessageElement.style.display = 'none';
+ };
+
+ inputEmail.addEventListener('focusout', validateEmail);
+ inputEmail.addEventListener('input', validateEmail);
+ inputNickname.addEventListener('focusout', validateNickname);
+ inputNickname.addEventListener('input', validateNickname);
+ inputPassword.addEventListener('focusout', validatePassword);
+ inputPassword.addEventListener('input', validatePassword);
+ inputPasswordConfirmation.addEventListener(
+ 'focusout',
+ validatePasswordConfirmation
+ );
+ inputPasswordConfirmation.addEventListener(
+ 'input',
+ validatePasswordConfirmation
+ );
+ btnSignup.addEventListener('click', handleSubmit);
+ btnTogglePasswordVisibleList.forEach((button) => {
+ button.addEventListener('click', togglePasswordVisible);
+ });
+});
diff --git a/public/styles/global.css b/public/styles/global.css
new file mode 100644
index 000000000..8bb0384c5
--- /dev/null
+++ b/public/styles/global.css
@@ -0,0 +1,177 @@
+:root {
+ --gray-900: #1b1d1f;
+ --gray-800: #26282b;
+ --gray-600: #454c53;
+ --gray-500: #72787f;
+ --gray-400: #9ea4a8;
+ --gray-200: #e5e7eb;
+ --gray-100: #e8ebed;
+ --gray-50: #f7f7f8;
+
+ --blue: #3692ff;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ color: #374151;
+ word-break: keep-all;
+ font-family: 'Pretendard', sans-serif;
+}
+
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 70px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #dfdfdf;
+}
+
+main {
+ margin-top: 70px;
+}
+
+footer {
+ background-color: #111827;
+ padding: 32px;
+ font-size: 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 60px;
+}
+
+#copyright {
+ order: 3;
+ flex-basis: 100%;
+ color: #9ca3af;
+}
+
+#footer-menu {
+ display: flex;
+ gap: 30px;
+ color: var(--gray-200);
+}
+
+#social-media {
+ display: flex;
+ gap: 12px;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+img {
+ vertical-align: bottom;
+}
+
+.wrapper {
+ max-width: 1200px;
+ margin: 0 auto;
+ width: 100%;
+ padding: 0 16px;
+}
+
+button {
+ background: none;
+ border: none;
+ outline: none;
+ box-shadow: none;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.button {
+ background-color: var(--blue);
+ color: #ffffff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.button:hover {
+ background-color: #1967d6;
+}
+
+.button:focus {
+ background-color: #1251aa;
+}
+
+.button:disabled {
+ background-color: #9ca3af;
+ cursor: default;
+ pointer-events: none;
+}
+
+.pill-button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 999px;
+ padding: 14.5px 33.5px;
+}
+
+.full-width {
+ width: 100%;
+}
+
+.break-on-desktop {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ header {
+ padding: 0 24px;
+ }
+
+ .wrapper {
+ padding: 0 24px;
+ }
+
+ .pill-button {
+ font-size: 20px;
+ font-weight: 700;
+ padding: 16px 126px;
+ }
+
+ footer {
+ padding: 32px 104px 108px 104px;
+ }
+
+ #copyright {
+ flex-basis: auto;
+ order: 0;
+ }
+}
+
+@media (min-width: 1280px) {
+ header {
+ padding: 0 200px;
+ }
+
+ .break-on-desktop {
+ display: inline;
+ }
+
+ footer {
+ padding: 32px 200px 108px 200px;
+ }
+}
diff --git a/public/styles/sign.css b/public/styles/sign.css
new file mode 100644
index 000000000..430ffcabb
--- /dev/null
+++ b/public/styles/sign.css
@@ -0,0 +1,138 @@
+.container-sign {
+ max-width: 400px;
+ margin: 0 auto;
+ padding: 0 16px;
+}
+
+.wrapper-btn-logo {
+ margin: 0 auto;
+ display: block;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ width: 198px;
+}
+
+.btn-home-logo {
+ width: 100%;
+}
+
+.input-item {
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+}
+
+.message-error {
+ display: none;
+ color: #f74747;
+ font-weight: 600;
+ font-size: 15px;
+ margin-top: 8px;
+ padding-left: 16px;
+}
+
+label {
+ display: block;
+ font-weight: 700;
+ font-size: 14px;
+ margin-bottom: 8px;
+}
+
+input {
+ padding: 16px 24px;
+ background-color: #f3f4f6;
+ border: none;
+ border-radius: 12px;
+ font-size: 16px;
+ line-height: 24px;
+ width: 100%;
+}
+
+input::placeholder {
+ color: #9ca3af;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+input:focus {
+ outline-color: var(--blue);
+}
+
+input.error {
+ border: 1px solid #f74747;
+}
+
+.wrapper-input {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.img-password-toggle {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.btn-password-visible {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.container-sns-signin {
+ background-color: #e6f2ff;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 23px;
+ margin: 24px 0;
+}
+
+.container-sns-signin h3 {
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+.container-btn-sns-signin {
+ display: flex;
+ gap: 16px;
+}
+
+.container-btn-sign {
+ font-weight: 500;
+ font-size: 15px;
+ text-align: center;
+}
+
+.container-btn-sign a {
+ color: #3182f6;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+@media (min-width: 768px) {
+ .container-sign {
+ max-width: 640px;
+ }
+
+ .wrapper-btn-logo {
+ width: 396px;
+ margin-top: 48px;
+ margin-bottom: 40px;
+ }
+
+ label {
+ font-size: 18px;
+ margin-bottom: 16px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .wrapper-btn-logo {
+ margin-top: 60px;
+ margin-bottom: 40px;
+ }
+}
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index 74b5e0534..000000000
--- a/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/App.js b/src/App.js
deleted file mode 100644
index 378457572..000000000
--- a/src/App.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import logo from './logo.svg';
-import './App.css';
-
-function App() {
- return (
-
- );
-}
-
-export default App;
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 000000000..8cd05c49f
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,29 @@
+import { BrowserRouter, Route, Routes } from 'react-router-dom';
+import Home from './pages/home/Home';
+// import Signin from './pages/signin/Signin';
+// import Signup from './pages/signup/Signup';
+import Faq from './pages/faq/Faq';
+import Privacy from './pages/privacy/Privacy';
+import Items from './pages/items/Items';
+import AddItems from './pages/addItems/AddItems';
+import NavigationBar from './components/navigationBar/NavigationBar';
+import './styles/global.css';
+
+function App() {
+ return (
+
+
+
+ } />
+ {/* } />
+ } /> */}
+ } />
+ } />
+ } />
+ } />
+
+
+ );
+}
+
+export default App;
diff --git a/src/App.test.js b/src/App.test.js
deleted file mode 100644
index 1f03afeec..000000000
--- a/src/App.test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- render();
- const linkElement = screen.getByText(/learn react/i);
- expect(linkElement).toBeInTheDocument();
-});
diff --git a/src/assets/images/home/bottom-banner-image.png b/src/assets/images/home/bottom-banner-image.png
new file mode 100644
index 000000000..4a5f85b28
Binary files /dev/null and b/src/assets/images/home/bottom-banner-image.png differ
diff --git a/src/assets/images/home/feature-search-img.png b/src/assets/images/home/feature-search-img.png
new file mode 100644
index 000000000..31e20b979
Binary files /dev/null and b/src/assets/images/home/feature-search-img.png differ
diff --git a/src/assets/images/home/feature1-image.png b/src/assets/images/home/feature1-image.png
new file mode 100644
index 000000000..4684b9a72
Binary files /dev/null and b/src/assets/images/home/feature1-image.png differ
diff --git a/src/assets/images/home/feature3-image.png b/src/assets/images/home/feature3-image.png
new file mode 100644
index 000000000..5b8084a77
Binary files /dev/null and b/src/assets/images/home/feature3-image.png differ
diff --git a/src/assets/images/home/hero-image.png b/src/assets/images/home/hero-image.png
new file mode 100644
index 000000000..d28fb6522
Binary files /dev/null and b/src/assets/images/home/hero-image.png differ
diff --git a/src/assets/images/icons/arrow_left.svg b/src/assets/images/icons/arrow_left.svg
new file mode 100644
index 000000000..2a9de23a6
--- /dev/null
+++ b/src/assets/images/icons/arrow_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/arrow_right.svg b/src/assets/images/icons/arrow_right.svg
new file mode 100644
index 000000000..daa483c3e
--- /dev/null
+++ b/src/assets/images/icons/arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/eye-invisible.svg b/src/assets/images/icons/eye-invisible.svg
new file mode 100644
index 000000000..92252b05d
--- /dev/null
+++ b/src/assets/images/icons/eye-invisible.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/assets/images/icons/eye-visible.svg b/src/assets/images/icons/eye-visible.svg
new file mode 100644
index 000000000..35a75305e
--- /dev/null
+++ b/src/assets/images/icons/eye-visible.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_heart.svg b/src/assets/images/icons/ic_heart.svg
new file mode 100644
index 000000000..cad016c13
--- /dev/null
+++ b/src/assets/images/icons/ic_heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_search.svg b/src/assets/images/icons/ic_search.svg
new file mode 100644
index 000000000..52241e6d8
--- /dev/null
+++ b/src/assets/images/icons/ic_search.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/icons/ic_sort.svg b/src/assets/images/icons/ic_sort.svg
new file mode 100644
index 000000000..657b44f93
--- /dev/null
+++ b/src/assets/images/icons/ic_sort.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/images/logo/favicon.ico b/src/assets/images/logo/favicon.ico
new file mode 100644
index 000000000..9fecc692d
Binary files /dev/null and b/src/assets/images/logo/favicon.ico differ
diff --git a/src/assets/images/logo/logo.svg b/src/assets/images/logo/logo.svg
new file mode 100644
index 000000000..d497acbfe
--- /dev/null
+++ b/src/assets/images/logo/logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/assets/images/logo/panda-market-logo.png b/src/assets/images/logo/panda-market-logo.png
new file mode 100644
index 000000000..a1dc1c6a1
Binary files /dev/null and b/src/assets/images/logo/panda-market-logo.png differ
diff --git a/src/assets/images/market/img_default.png b/src/assets/images/market/img_default.png
new file mode 100644
index 000000000..9a1bd6c32
Binary files /dev/null and b/src/assets/images/market/img_default.png differ
diff --git a/src/assets/images/social/facebook-logo.svg b/src/assets/images/social/facebook-logo.svg
new file mode 100644
index 000000000..8491c2f83
--- /dev/null
+++ b/src/assets/images/social/facebook-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/social/google-logo.png b/src/assets/images/social/google-logo.png
new file mode 100644
index 000000000..199f3d628
Binary files /dev/null and b/src/assets/images/social/google-logo.png differ
diff --git a/src/assets/images/social/instagram-logo.svg b/src/assets/images/social/instagram-logo.svg
new file mode 100644
index 000000000..c83306f84
--- /dev/null
+++ b/src/assets/images/social/instagram-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/social/kakao-logo.png b/src/assets/images/social/kakao-logo.png
new file mode 100644
index 000000000..bfadc1d35
Binary files /dev/null and b/src/assets/images/social/kakao-logo.png differ
diff --git a/src/assets/images/social/twitter-logo.svg b/src/assets/images/social/twitter-logo.svg
new file mode 100644
index 000000000..14a6069a1
--- /dev/null
+++ b/src/assets/images/social/twitter-logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/images/social/youtube-logo.svg b/src/assets/images/social/youtube-logo.svg
new file mode 100644
index 000000000..5fcc0ff34
--- /dev/null
+++ b/src/assets/images/social/youtube-logo.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/components/items/BestItems/BestItems.css b/src/components/items/BestItems/BestItems.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/items/BestItems/BestItems.jsx b/src/components/items/BestItems/BestItems.jsx
new file mode 100644
index 000000000..5c1b75bd3
--- /dev/null
+++ b/src/components/items/BestItems/BestItems.jsx
@@ -0,0 +1,59 @@
+import './BestItems.css';
+import { useEffect, useState } from 'react';
+import Item from '../Item/Item';
+import { getProducts } from '../../../pages/api/Items';
+
+const getPageSize = () => {
+ const width = window.innerWidth;
+ if (width < 768) {
+ return 1;
+ } else if (width < 1280) {
+ return 2;
+ } else {
+ return 4;
+ }
+};
+
+function BestItems() {
+ // 상품 목록
+ const [itemList, setItemList] = useState([]);
+ // 쿼리
+ const [order, setOrder] = useState('favorite');
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(4);
+ const [keyword, setKeyword] = useState('');
+
+ const fetchItemList = async ({ order, page, pageSize, keyword }) => {
+ let products = await getProducts({ order, page, pageSize, keyword });
+ setItemList(products.list);
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ setPageSize(getPageSize());
+ };
+
+ window.addEventListener('resize', handleResize);
+ fetchItemList({ order, page, pageSize, keyword });
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [order, page, pageSize, keyword]);
+
+ return (
+ <>
+
+
베스트 상품
+
+
+ {itemList?.map((item) => (
+
+ ))}
+
+
+ >
+ );
+}
+
+export default BestItems;
diff --git a/src/components/items/Item/Item.css b/src/components/items/Item/Item.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/items/Item/Item.jsx b/src/components/items/Item/Item.jsx
new file mode 100644
index 000000000..a60035754
--- /dev/null
+++ b/src/components/items/Item/Item.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { ReactComponent as IconHeart } from '../../../assets/images/icons/ic_heart.svg';
+import ImgDefault from '../../../assets/images/market/img_default.png';
+
+function Item({ item }) {
+ const handleErrorImage = (e) => {
+ e.target.src = ImgDefault;
+ };
+
+ return (
+
+
+
+
{item.name}
+
{item.price.toLocaleString()}원
+
+
+ {item.favoriteCount}
+
+
+
+ );
+}
+
+export default Item;
diff --git a/src/components/items/SailItems/SailItems.css b/src/components/items/SailItems/SailItems.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/items/SailItems/SailItems.jsx b/src/components/items/SailItems/SailItems.jsx
new file mode 100644
index 000000000..4582f9b6e
--- /dev/null
+++ b/src/components/items/SailItems/SailItems.jsx
@@ -0,0 +1,120 @@
+import './SailItems.css';
+import { ReactComponent as IconSearch } from '../../../assets/images/icons/ic_search.svg';
+import { ReactComponent as IconSort } from '../../../assets/images/icons/ic_sort.svg';
+import { useEffect, useState } from 'react';
+import { getProducts } from '../../../pages/api/Items';
+import Item from '../Item/Item';
+
+const getPageSize = () => {
+ const width = window.innerWidth;
+ if (width < 768) {
+ return 4;
+ } else if (width < 1280) {
+ return 6;
+ } else {
+ return 10;
+ }
+};
+
+function SailItems() {
+ // 상품
+ const [itemList, setItemList] = useState([]);
+ // 쿼리
+ const [order, setOrder] = useState('recent');
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [keyword, setKeyword] = useState('');
+ // 검색
+ const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+
+ const handleClickDropdown = () => {
+ setIsDropdownVisible(!isDropdownVisible);
+ };
+ const handleClickDropdownItem = (order) => {
+ setOrder(order);
+ setIsDropdownVisible(!isDropdownVisible);
+ };
+ const handleKeyupSearchInput = (event) => {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ setKeyword(event.target.value);
+ }
+ };
+
+ const fetchItemList = async ({ order, page, pageSize, keyword }) => {
+ let products = await getProducts({ order, page, pageSize, keyword });
+ setItemList(products.list);
+ };
+
+ useEffect(() => {
+ const handleResize = () => {
+ setPageSize(getPageSize());
+ };
+ window.addEventListener('resize', handleResize);
+ fetchItemList({ order, page, pageSize, keyword });
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [order, page, pageSize, keyword]);
+
+ return (
+ <>
+
+ {/* 판매 중인 상품 헤더 */}
+
+
+
+
+ 상품 등록하기
+
+
+
+
+
+
+
+ {isDropdownVisible && (
+
+
handleClickDropdownItem('recent')}
+ >
+ 최신순
+
+
handleClickDropdownItem('favorite')}
+ >
+ 좋아요순
+
+
+ )}
+
+
+
+ {/* 판매 중인 상품 목록 */}
+
+ {itemList?.map((item) => (
+
+ ))}
+
+
+ >
+ );
+}
+
+export default SailItems;
diff --git a/src/components/navigationBar/NavigationBar.css b/src/components/navigationBar/NavigationBar.css
new file mode 100644
index 000000000..d4f2845ca
--- /dev/null
+++ b/src/components/navigationBar/NavigationBar.css
@@ -0,0 +1,55 @@
+.navbar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 70px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #dfdfdf;
+}
+
+.navbarLeft {
+ display: flex;
+ align-items: center;
+}
+
+.navbarHomeLogo {
+ width: 153px;
+}
+
+.navbarMenu {
+ margin: 0 15px;
+ text-decoration: none;
+}
+
+.navbarMenu ol {
+ list-style-type: none;
+ padding-left: 0;
+}
+
+.navbarMenuItem {
+ display: inline-block;
+ margin: 10px;
+ font-size: 18px;
+ font-weight: bold;
+}
+
+.navbarMenuItem.active {
+ color: var(--blue);
+}
+
+@media (min-width: 768px) {
+ .navbar {
+ padding: 0 24px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .navbar {
+ padding: 0 200px;
+ }
+}
diff --git a/src/components/navigationBar/NavigationBar.jsx b/src/components/navigationBar/NavigationBar.jsx
new file mode 100644
index 000000000..ccbd95479
--- /dev/null
+++ b/src/components/navigationBar/NavigationBar.jsx
@@ -0,0 +1,38 @@
+import { useEffect, useState } from 'react';
+import imgPandaMarketLogo from '../../assets/images/logo/panda-market-logo.png';
+import './NavigationBar.css';
+
+function NavigationBar() {
+ return (
+
+ );
+}
+
+export default NavigationBar;
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index ec2585e8c..000000000
--- a/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/src/index.js b/src/index.js
index d563c0fb1..593edf121 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,8 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
-import './index.css';
import App from './App';
-import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
@@ -10,8 +8,3 @@ root.render(
);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/src/logo.svg b/src/logo.svg
deleted file mode 100644
index 9dfc1c058..000000000
--- a/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/pages/addItems/AddItems.jsx b/src/pages/addItems/AddItems.jsx
new file mode 100644
index 000000000..d055b09b3
--- /dev/null
+++ b/src/pages/addItems/AddItems.jsx
@@ -0,0 +1,9 @@
+function AddItems() {
+ return (
+ <>
+ 임시 상품등록 페이지
+ >
+ );
+}
+
+export default AddItems;
diff --git a/src/pages/api/Items.js b/src/pages/api/Items.js
new file mode 100644
index 000000000..cf7355a3d
--- /dev/null
+++ b/src/pages/api/Items.js
@@ -0,0 +1,41 @@
+/**
+ * 상품 목록 조회
+ * @param {string} order 정렬 기준: recent, favorite
+ * @param {number} page 페이지 번호
+ * @param {number} pageSize 페이지 당 상품 수
+ * @param {string} keyword 검색 키워드
+ * @returns json. ex) {
+ "list": [
+ {
+ "id": 22,
+ "name": "ee",
+ "description": "eeeeeeeeeeeeeeee",
+ "price": 4,
+ "tags": [
+ "44"
+ ],
+ "images": [
+ "https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Sprint_Mission/user/210/1718529146905/arrow_left-icon.svg"
+ ],
+ "ownerId": 210,
+ "favoriteCount": 0,
+ "createdAt": "2024-06-16T09:12:27.057Z",
+ "updatedAt": "2024-06-16T09:12:27.057Z"
+ }, ... }
+ */
+export async function getProducts({
+ order, // 정렬 기준: recent, favorite
+ page, // 페이지 번호
+ pageSize, // 페이지 당 상품 수
+ keyword, // 검색 키워드
+}) {
+ const query = `orderBy=${order}&page=${page}&pageSize=${pageSize}&keyword=${keyword}`;
+ const response = await fetch(
+ `https://panda-market-api.vercel.app/products?${query}`
+ );
+ if (!response.ok) {
+ throw new Error('데이터를 불러오는데 실패했습니다');
+ }
+ const body = await response.json();
+ return body;
+}
diff --git a/src/pages/faq/Faq.jsx b/src/pages/faq/Faq.jsx
new file mode 100644
index 000000000..2318b6eb1
--- /dev/null
+++ b/src/pages/faq/Faq.jsx
@@ -0,0 +1,9 @@
+function Faq() {
+ return (
+ <>
+ 임시 FAQ 페이지
+ >
+ );
+}
+
+export default Faq;
diff --git a/src/pages/home/Home.css b/src/pages/home/Home.css
new file mode 100644
index 000000000..0914d215e
--- /dev/null
+++ b/src/pages/home/Home.css
@@ -0,0 +1,172 @@
+.banner {
+ background-color: #cfe5ff;
+ height: 60vh;
+ text-align: center;
+ background-repeat: no-repeat;
+ background-position: bottom;
+ background-size: 130%;
+}
+
+#hero {
+ background-image: url('../../assets/images/home/hero-image.png');
+}
+
+.banner h1 {
+ font-weight: 700;
+ font-size: 32px;
+ line-height: 44.8px;
+ padding-top: 48px;
+ padding-bottom: 18px;
+}
+
+#bottom-banner {
+ background-image: url('../../assets/images/home/bottom-banner-image.png');
+}
+
+/* #login-link-button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 14.5px 43px;
+} */
+
+#features {
+ padding-top: 51px;
+}
+
+.feature {
+ margin-bottom: 64px;
+}
+
+.feature img {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.feature:nth-child(2) {
+ text-align: right;
+}
+
+.feature-content {
+ flex: 1;
+}
+
+.feature-content h2 {
+ color: var(--blue);
+ font-size: 16px;
+ line-height: 22.4px;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.feature-content h1 {
+ font-weight: 700;
+ font-size: 24px;
+ line-height: 33.6px;
+}
+
+.feature-content h1 br {
+ display: none;
+}
+
+.feature-description {
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 19.2px;
+ letter-spacing: 0.08em;
+ margin-top: 20px;
+}
+
+@media (min-width: 768px) {
+ .banner {
+ height: 90vh;
+ background-size: 120%;
+ }
+
+ .banner h1 {
+ font-size: 40px;
+ line-height: 56px;
+ padding-top: 84px;
+ padding-bottom: 24px;
+ }
+
+ #hero h1 br {
+ display: none;
+ }
+
+ #features {
+ padding-top: 24px;
+ padding-bottom: 16px;
+ }
+
+ .feature-content h2 {
+ font-size: 18px;
+ line-height: 25.2px;
+ margin-bottom: 12px;
+ }
+
+ .feature-content h1 {
+ font-size: 32px;
+ line-height: 44.8px;
+ }
+
+ .feature-description {
+ font-size: 18px;
+ line-height: 21.6px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .banner {
+ text-align: left;
+ height: 540px;
+ display: flex;
+ align-items: center;
+ background-position: 80% bottom;
+ background-size: 55%;
+ }
+
+ .banner h1 {
+ padding-top: 0;
+ padding-bottom: 32px;
+ }
+
+ #hero h1 br {
+ display: inline;
+ }
+
+ #features {
+ padding: 138px 0;
+ }
+
+ .feature {
+ margin-bottom: 138px;
+ display: flex;
+ align-items: center;
+ gap: 5%;
+ }
+
+ .feature:nth-child(2) {
+ flex-direction: row-reverse;
+ }
+
+ .feature img {
+ width: 50%;
+ margin-bottom: 0;
+ }
+
+ .feature-content h1 {
+ font-size: 40px;
+ line-height: 56px;
+ }
+
+ .feature-content h1 br {
+ display: block;
+ }
+
+ .feature-description {
+ font-size: 24px;
+ line-height: 28.8px;
+ margin-top: 24px;
+ }
+}
diff --git a/src/pages/home/Home.jsx b/src/pages/home/Home.jsx
new file mode 100644
index 000000000..1dee4e3d6
--- /dev/null
+++ b/src/pages/home/Home.jsx
@@ -0,0 +1,140 @@
+import './Home.css';
+import imgPandaMarketLogo from '../../assets/images/logo/panda-market-logo.png';
+import imgFeature1 from '../../assets/images/home/feature1-image.png';
+import imgFeatureSearch from '../../assets/images/home/feature-search-img.png';
+import imgFeature3 from '../../assets/images/home/feature3-image.png';
+import imgFacebookLogo from '../../assets/images/social/facebook-logo.svg';
+import imgTwitterLogo from '../../assets/images/social/twitter-logo.svg';
+import imgYoutubeLogo from '../../assets/images/social/youtube-logo.svg';
+import imgInstagramLogo from '../../assets/images/social/instagram-logo.svg';
+
+function Home() {
+ return (
+ <>
+ {/* */}
+
+
+
+
+
+
+
Hot item
+
+ 인기 상품을
+
+ 확인해 보세요
+
+
+ 가장 HOT한 중고거래 물품을
+
+ 판다마켓에서 확인해 보세요
+
+
+
+
+
+
+
Search
+
+ 구매를 원하는
+
+ 상품을 검색하세요
+
+
+ 구매하고 싶은 물품은 검색해서
+
+ 쉽게 찾아보세요
+
+
+
+
+
+
+
Register
+
+ 판매를 원하는
+
+ 상품을 등록하세요
+
+
+ 어떤 물건이든 판매하고 싶은 상품을
+
+ 쉽게 등록하세요
+
+
+
+
+
+
+
+ 믿을 수 있는
+
+ 판다마켓 중고거래
+
+
+
+
+
+ >
+ );
+}
+
+export default Home;
diff --git a/src/pages/items/Items.css b/src/pages/items/Items.css
new file mode 100644
index 000000000..1ff75f2ee
--- /dev/null
+++ b/src/pages/items/Items.css
@@ -0,0 +1,174 @@
+/* Items */
+.container-items {
+ max-width: 1200px;
+ margin: 94px auto;
+ padding: 0 16px;
+}
+
+/* Best Items */
+.container-best-items {
+ padding: 0 0 40px;
+}
+
+.title-best-items {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.list-best-items {
+}
+
+/* Sail Items */
+.container-sail-items {
+}
+
+.header-sail-items {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 0 24px;
+}
+
+.header-sail-items-left {
+}
+
+.title-sail-items {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.header-sail-items-right {
+ display: flex;
+ justify-content: space-between;
+}
+
+.btn-add-item {
+}
+
+.wrapper-input-search-item {
+ display: flex;
+ background-color: var(--gray-100);
+ border-radius: 12px;
+ font-size: 32px;
+ min-width: 250px;
+ margin: 0 0 0 8px;
+ padding: 9px 16px;
+ align-items: center;
+}
+
+.input-search-item {
+ border: none;
+ flex: 1;
+ background-color: inherit;
+ margin: 0 0 0 4px;
+}
+
+.input-search-item::placeholder {
+ color: var(--gray-400);
+ font-size: 16px;
+}
+
+.wrapper-sort-item {
+ position: relative;
+}
+
+.btn-sort-item {
+ border: 1px solid var(--gray-200);
+ border-radius: 12px;
+ margin: 0 0 0 8px;
+ padding: 12px 40px;
+ display: flex;
+}
+
+.dropdown-sort-item {
+ position: absolute;
+ background-color: white;
+ margin: 5px 0 0 8px;
+ border-radius: 12px;
+ border: 1px solid var(--gray-200);
+ z-index: 99;
+}
+
+.item-dropdown {
+ padding: 12px 44px;
+ border-bottom: 1px solid var(--gray-200);
+ font-size: 16px;
+ color: #1f2937;
+ cursor: pointer;
+}
+
+.list-sail-items {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+}
+
+/* Item */
+.wrapper-item {
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.item-img {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ border-radius: 16px;
+ overflow: hidden;
+ aspect-ratio: 1;
+ margin: 0 0 16px;
+}
+
+.wrapper-item-info {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ line-height: 1.5;
+}
+
+.item-name {
+ font-size: 16px;
+ font-weight: 400;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.item-price {
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.item-favorite-count {
+ display: flex;
+ align-items: center;
+ color: var(--gray-heart);
+}
+
+@media (min-width: 768px) {
+ /* Best Items */
+ .container-best-items {
+ }
+
+ .list-best-items {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ /* Sail Items */
+ .list-sail-items {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media (min-width: 1280px) {
+ /* Best Items */
+ .list-best-items {
+ grid-template-columns: repeat(4, 1fr);
+ }
+
+ /* Sail Items */
+ .list-sail-items {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ }
+}
diff --git a/src/pages/items/Items.jsx b/src/pages/items/Items.jsx
new file mode 100644
index 000000000..0e5615f4e
--- /dev/null
+++ b/src/pages/items/Items.jsx
@@ -0,0 +1,18 @@
+import './Items.css';
+import BestItems from '../../components/items/BestItems/BestItems';
+import SailItems from '../../components/items/SailItems/SailItems';
+
+function Items() {
+ return (
+ <>
+
+ {/* 베스트 상품 */}
+
+ {/* 판매 중인 상품 */}
+
+
+ >
+ );
+}
+
+export default Items;
diff --git a/src/pages/privacy/Privacy.jsx b/src/pages/privacy/Privacy.jsx
new file mode 100644
index 000000000..c2576ccb3
--- /dev/null
+++ b/src/pages/privacy/Privacy.jsx
@@ -0,0 +1,9 @@
+function Privacy() {
+ return (
+ <>
+ 임시 이용약관 페이지
+ >
+ );
+}
+
+export default Privacy;
diff --git a/src/pages/signin/Signin.jsx b/src/pages/signin/Signin.jsx
new file mode 100644
index 000000000..fa668fc25
--- /dev/null
+++ b/src/pages/signin/Signin.jsx
@@ -0,0 +1,79 @@
+import './sign.css';
+import './signinScript';
+import imgPandaLogo from '../../assets/images/logo/logo.svg';
+import iconEyeInvisible from '../../assets/images/icons/eye-invisible.svg';
+import imgGoogleLogo from '../../assets/images/social/google-logo.png';
+import imgKakaoLogo from '../../assets/images/social/kakao-logo.png';
+
+function Signin() {
+ return (
+ <>
+
+ >
+ );
+}
+
+export default Signin;
diff --git a/src/pages/signin/sign.css b/src/pages/signin/sign.css
new file mode 100644
index 000000000..430ffcabb
--- /dev/null
+++ b/src/pages/signin/sign.css
@@ -0,0 +1,138 @@
+.container-sign {
+ max-width: 400px;
+ margin: 0 auto;
+ padding: 0 16px;
+}
+
+.wrapper-btn-logo {
+ margin: 0 auto;
+ display: block;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ width: 198px;
+}
+
+.btn-home-logo {
+ width: 100%;
+}
+
+.input-item {
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+}
+
+.message-error {
+ display: none;
+ color: #f74747;
+ font-weight: 600;
+ font-size: 15px;
+ margin-top: 8px;
+ padding-left: 16px;
+}
+
+label {
+ display: block;
+ font-weight: 700;
+ font-size: 14px;
+ margin-bottom: 8px;
+}
+
+input {
+ padding: 16px 24px;
+ background-color: #f3f4f6;
+ border: none;
+ border-radius: 12px;
+ font-size: 16px;
+ line-height: 24px;
+ width: 100%;
+}
+
+input::placeholder {
+ color: #9ca3af;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+input:focus {
+ outline-color: var(--blue);
+}
+
+input.error {
+ border: 1px solid #f74747;
+}
+
+.wrapper-input {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.img-password-toggle {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.btn-password-visible {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.container-sns-signin {
+ background-color: #e6f2ff;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 23px;
+ margin: 24px 0;
+}
+
+.container-sns-signin h3 {
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+.container-btn-sns-signin {
+ display: flex;
+ gap: 16px;
+}
+
+.container-btn-sign {
+ font-weight: 500;
+ font-size: 15px;
+ text-align: center;
+}
+
+.container-btn-sign a {
+ color: #3182f6;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+@media (min-width: 768px) {
+ .container-sign {
+ max-width: 640px;
+ }
+
+ .wrapper-btn-logo {
+ width: 396px;
+ margin-top: 48px;
+ margin-bottom: 40px;
+ }
+
+ label {
+ font-size: 18px;
+ margin-bottom: 16px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .wrapper-btn-logo {
+ margin-top: 60px;
+ margin-bottom: 40px;
+ }
+}
diff --git a/src/pages/signin/signinScript.js b/src/pages/signin/signinScript.js
new file mode 100644
index 000000000..798f27915
--- /dev/null
+++ b/src/pages/signin/signinScript.js
@@ -0,0 +1,107 @@
+document.addEventListener('DOMContentLoaded', () => {
+ let isValidEmail = false;
+ let isValidPassword = false;
+
+ const inputEmail = document.getElementById('email');
+ const inputPassword = document.getElementById('password');
+ const btnTogglePasswordVisibleList = document.querySelectorAll(
+ '.btn-password-visible'
+ );
+ const btnSignin = document.getElementById('btn-signin');
+ const messageErrorEmail = document.getElementById('message-error-email');
+ const messageErrorPassword = document.getElementById(
+ 'message-error-password'
+ );
+
+ const validateEmail = () => {
+ const emailValue = inputEmail.value.trim();
+ if (!emailValue) {
+ showError(inputEmail, messageErrorEmail, '이메일을 입력해주세요.');
+ isValidEmail = false;
+ } else if (!checkEmailRegex(emailValue)) {
+ showError(inputEmail, messageErrorEmail, '잘못된 이메일 형식입니다.');
+ isValidEmail = false;
+ } else {
+ clearError(inputEmail, messageErrorEmail);
+ isValidEmail = true;
+ }
+ updateBtnSignin();
+ };
+
+ const validatePassword = () => {
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordValue) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 입력해주세요.'
+ );
+ isValidPassword = false;
+ } else if (passwordValue.length < 8) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 8자 이상 입력해주세요'
+ );
+ isValidPassword = false;
+ } else {
+ clearError(inputPassword, messageErrorPassword);
+ isValidPassword = true;
+ }
+ updateBtnSignin();
+ };
+
+ const togglePasswordVisible = (e) => {
+ e.preventDefault();
+ const visibleBtn = e.currentTarget;
+ const targetInput = visibleBtn.parentElement.querySelector('input');
+ const visibleImg = visibleBtn.querySelector('.img-password-visible');
+ if (targetInput.type === 'password') {
+ targetInput.type = 'text';
+ visibleImg.src = '/images/icons/eye-visible.svg';
+ } else {
+ targetInput.type = 'password';
+ visibleImg.src = '/images/icons/eye-invisible.svg';
+ }
+ };
+
+ const updateBtnSignin = () => {
+ if (isValidPassword && isValidEmail) {
+ btnSignin.disabled = false;
+ } else {
+ btnSignin.disabled = true;
+ }
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (btnSignin.disabled) return;
+ window.location.href = '/items';
+ };
+
+ const checkEmailRegex = (email) => {
+ const emailRegex = new RegExp('[a-z0-9]+@[a-z]+\\.[a-z]{2,3}');
+ return emailRegex.test(email);
+ };
+
+ const showError = (input, errorMessageElement, message) => {
+ input.classList.add('error');
+ errorMessageElement.textContent = message;
+ errorMessageElement.style.display = 'block';
+ };
+
+ const clearError = (input, errorMessageElement) => {
+ input.classList.remove('error');
+ errorMessageElement.textContent = '';
+ errorMessageElement.style.display = 'none';
+ };
+
+ inputEmail.addEventListener('focusout', validateEmail);
+ inputPassword.addEventListener('focusout', validatePassword);
+ inputEmail.addEventListener('input', validateEmail);
+ inputPassword.addEventListener('input', validatePassword);
+ btnSignin.addEventListener('click', handleSubmit);
+ btnTogglePasswordVisibleList.forEach((button) => {
+ button.addEventListener('click', togglePasswordVisible);
+ });
+});
diff --git a/src/pages/signup/Signup.jsx b/src/pages/signup/Signup.jsx
new file mode 100644
index 000000000..4bd690f05
--- /dev/null
+++ b/src/pages/signup/Signup.jsx
@@ -0,0 +1,126 @@
+import './sign.css';
+import './signupScript';
+
+function Signup() {
+ return (
+ <>
+
+ >
+ );
+}
+
+export default Signup;
diff --git a/src/pages/signup/sign.css b/src/pages/signup/sign.css
new file mode 100644
index 000000000..430ffcabb
--- /dev/null
+++ b/src/pages/signup/sign.css
@@ -0,0 +1,138 @@
+.container-sign {
+ max-width: 400px;
+ margin: 0 auto;
+ padding: 0 16px;
+}
+
+.wrapper-btn-logo {
+ margin: 0 auto;
+ display: block;
+ margin-top: 24px;
+ margin-bottom: 24px;
+ width: 198px;
+}
+
+.btn-home-logo {
+ width: 100%;
+}
+
+.input-item {
+ margin-bottom: 24px;
+ display: flex;
+ flex-direction: column;
+}
+
+.message-error {
+ display: none;
+ color: #f74747;
+ font-weight: 600;
+ font-size: 15px;
+ margin-top: 8px;
+ padding-left: 16px;
+}
+
+label {
+ display: block;
+ font-weight: 700;
+ font-size: 14px;
+ margin-bottom: 8px;
+}
+
+input {
+ padding: 16px 24px;
+ background-color: #f3f4f6;
+ border: none;
+ border-radius: 12px;
+ font-size: 16px;
+ line-height: 24px;
+ width: 100%;
+}
+
+input::placeholder {
+ color: #9ca3af;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+input:focus {
+ outline-color: var(--blue);
+}
+
+input.error {
+ border: 1px solid #f74747;
+}
+
+.wrapper-input {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.img-password-toggle {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.btn-password-visible {
+ position: absolute;
+ right: 24px;
+ cursor: pointer;
+}
+
+.container-sns-signin {
+ background-color: #e6f2ff;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 23px;
+ margin: 24px 0;
+}
+
+.container-sns-signin h3 {
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+.container-btn-sns-signin {
+ display: flex;
+ gap: 16px;
+}
+
+.container-btn-sign {
+ font-weight: 500;
+ font-size: 15px;
+ text-align: center;
+}
+
+.container-btn-sign a {
+ color: #3182f6;
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+@media (min-width: 768px) {
+ .container-sign {
+ max-width: 640px;
+ }
+
+ .wrapper-btn-logo {
+ width: 396px;
+ margin-top: 48px;
+ margin-bottom: 40px;
+ }
+
+ label {
+ font-size: 18px;
+ margin-bottom: 16px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .wrapper-btn-logo {
+ margin-top: 60px;
+ margin-bottom: 40px;
+ }
+}
diff --git a/src/pages/signup/signupScript.js b/src/pages/signup/signupScript.js
new file mode 100644
index 000000000..3ee9a6822
--- /dev/null
+++ b/src/pages/signup/signupScript.js
@@ -0,0 +1,172 @@
+document.addEventListener('DOMContentLoaded', () => {
+ let isValidEmail = false;
+ let isValidNickname = false;
+ let isValidPassword = false;
+ let isValidPasswordConfirmation = false;
+
+ const inputEmail = document.getElementById('email');
+ const inputNickname = document.getElementById('nickname');
+ const inputPassword = document.getElementById('password');
+ const inputPasswordConfirmation = document.getElementById(
+ 'password-confirmation'
+ );
+ const btnTogglePasswordVisibleList = document.querySelectorAll(
+ '.btn-password-visible'
+ );
+ const btnSignup = document.getElementById('btn-signup');
+ const messageErrorEmail = document.getElementById('message-error-email');
+ const messageErrorNickname = document.getElementById(
+ 'message-error-nickname'
+ );
+ const messageErrorPassword = document.getElementById(
+ 'message-error-password'
+ );
+ const messageErrorPasswordConfirmation = document.getElementById(
+ 'message-error-password-confirmation'
+ );
+
+ const validateEmail = () => {
+ const emailValue = inputEmail.value.trim();
+ if (!emailValue) {
+ showError(inputEmail, messageErrorEmail, '이메일을 입력해주세요.');
+ isValidEmail = false;
+ } else if (!checkEmailRegex(emailValue)) {
+ showError(inputEmail, messageErrorEmail, '잘못된 이메일 형식입니다.');
+ isValidEmail = false;
+ } else {
+ clearError(inputEmail, messageErrorEmail);
+ isValidEmail = true;
+ }
+ updatebtnSignup();
+ };
+
+ const validateNickname = () => {
+ const nicknameValue = inputNickname.value.trim();
+ if (!nicknameValue) {
+ showError(inputNickname, messageErrorNickname, '닉네임을 입력해주세요.');
+ isValidNickname = false;
+ } else {
+ clearError(inputNickname, messageErrorNickname);
+ isValidNickname = true;
+ }
+ updatebtnSignup();
+ };
+
+ const validatePassword = () => {
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordValue) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 입력해주세요.'
+ );
+ isValidPassword = false;
+ } else if (passwordValue.length < 8) {
+ showError(
+ inputPassword,
+ messageErrorPassword,
+ '비밀번호를 8자 이상 입력해주세요'
+ );
+ isValidPassword = false;
+ } else {
+ clearError(inputPassword, messageErrorPassword);
+ isValidPassword = true;
+ }
+ validatePasswordConfirmation();
+ updatebtnSignup();
+ };
+
+ const validatePasswordConfirmation = () => {
+ const passwordConfirmationValue = inputPasswordConfirmation.value.trim();
+ const passwordValue = inputPassword.value.trim();
+ if (!passwordConfirmationValue) {
+ showError(
+ inputPasswordConfirmation,
+ messageErrorPasswordConfirmation,
+ '비밀번호를 입력해주세요.'
+ );
+ } else if (passwordConfirmationValue !== passwordValue) {
+ showError(
+ inputPasswordConfirmation,
+ messageErrorPasswordConfirmation,
+ '비밀번호가 일치하지 않습니다.'
+ );
+ isValidPasswordConfirmation = false;
+ } else {
+ clearError(inputPasswordConfirmation, messageErrorPasswordConfirmation);
+ isValidPasswordConfirmation = true;
+ }
+ updatebtnSignup();
+ };
+
+ const togglePasswordVisible = (e) => {
+ e.preventDefault();
+ const visibleBtn = e.currentTarget;
+ const targetInput = visibleBtn.parentElement.querySelector('input');
+ const visibleImg = visibleBtn.querySelector('.img-password-visible');
+ if (targetInput.type === 'password') {
+ targetInput.type = 'text';
+ visibleImg.src = '/images/icons/eye-visible.svg';
+ visibleImg.alt = '비밀번호 표시';
+ } else {
+ targetInput.type = 'password';
+ visibleImg.src = '/images/icons/eye-invisible.svg';
+ visibleImg.alt = '비밀번호 숨김';
+ }
+ };
+
+ const updatebtnSignup = () => {
+ if (
+ isValidPassword &&
+ isValidEmail &&
+ isValidNickname &&
+ isValidPasswordConfirmation
+ ) {
+ btnSignup.disabled = false;
+ } else {
+ btnSignup.disabled = true;
+ }
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ if (btnSignup.disabled) return;
+ window.location.href = '/signin';
+ };
+
+ const checkEmailRegex = (email) => {
+ const emailRegex = new RegExp('[a-z0-9]+@[a-z]+\\.[a-z]{2,3}');
+ return emailRegex.test(email);
+ };
+
+ const showError = (input, errorMessageElement, message) => {
+ input.classList.add('error');
+ errorMessageElement.textContent = message;
+ errorMessageElement.style.display = 'block';
+ };
+
+ const clearError = (input, errorMessageElement) => {
+ input.classList.remove('error');
+ errorMessageElement.textContent = '';
+ errorMessageElement.style.display = 'none';
+ };
+
+ inputEmail.addEventListener('focusout', validateEmail);
+ inputEmail.addEventListener('input', validateEmail);
+ inputNickname.addEventListener('focusout', validateNickname);
+ inputNickname.addEventListener('input', validateNickname);
+ inputPassword.addEventListener('focusout', validatePassword);
+ inputPassword.addEventListener('input', validatePassword);
+ inputPasswordConfirmation.addEventListener(
+ 'focusout',
+ validatePasswordConfirmation
+ );
+ inputPasswordConfirmation.addEventListener(
+ 'input',
+ validatePasswordConfirmation
+ );
+ btnSignup.addEventListener('click', handleSubmit);
+ btnTogglePasswordVisibleList.forEach((button) => {
+ button.addEventListener('click', togglePasswordVisible);
+ });
+});
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
deleted file mode 100644
index 5253d3ad9..000000000
--- a/src/reportWebVitals.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const reportWebVitals = onPerfEntry => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/src/setupTests.js b/src/setupTests.js
deleted file mode 100644
index 8f2609b7b..000000000
--- a/src/setupTests.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/src/styles/global.css b/src/styles/global.css
new file mode 100644
index 000000000..5369bca56
--- /dev/null
+++ b/src/styles/global.css
@@ -0,0 +1,183 @@
+:root {
+ --gray-900: #1b1d1f;
+ --gray-800: #26282b;
+ --gray-600: #454c53;
+ --gray-500: #72787f;
+ --gray-400: #9ea4a8;
+ --gray-200: #e5e7eb;
+ --gray-100: #e8ebed;
+ --gray-50: #f7f7f8;
+ --gray-heart: #4b5563;
+
+ --blue: #3692ff;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ color: #374151;
+ word-break: keep-all;
+ font-family: 'Pretendard', sans-serif;
+}
+
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 70px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #dfdfdf;
+}
+
+main {
+ margin-top: 70px;
+}
+
+footer {
+ background-color: #111827;
+ padding: 32px;
+ font-size: 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 60px;
+}
+
+#copyright {
+ order: 3;
+ flex-basis: 100%;
+ color: #9ca3af;
+}
+
+#footer-menu {
+ display: flex;
+ gap: 30px;
+ color: var(--gray-200);
+}
+
+#social-media {
+ display: flex;
+ gap: 12px;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+img {
+ vertical-align: bottom;
+}
+
+.wrapper {
+ max-width: 1200px;
+ margin: 0 auto;
+ width: 100%;
+ padding: 0 16px;
+}
+
+button {
+ background: none;
+ border: none;
+ outline: none;
+ box-shadow: none;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+.button {
+ background-color: var(--blue);
+ color: #ffffff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ /* 20240621 수정 */
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 12px 23px;
+}
+
+.button:hover {
+ background-color: #1967d6;
+}
+
+.button:focus {
+ background-color: #1251aa;
+}
+
+.button:disabled {
+ background-color: #9ca3af;
+ cursor: default;
+ pointer-events: none;
+}
+
+.pill-button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 999px;
+ padding: 14.5px 33.5px;
+}
+
+.full-width {
+ width: 100%;
+}
+
+.break-on-desktop {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ header {
+ padding: 0 24px;
+ }
+
+ .wrapper {
+ padding: 0 24px;
+ }
+
+ .pill-button {
+ font-size: 20px;
+ font-weight: 700;
+ padding: 16px 126px;
+ }
+
+ footer {
+ padding: 32px 104px 108px 104px;
+ }
+
+ #copyright {
+ flex-basis: auto;
+ order: 0;
+ }
+}
+
+@media (min-width: 1280px) {
+ header {
+ padding: 0 200px;
+ }
+
+ .break-on-desktop {
+ display: inline;
+ }
+
+ footer {
+ padding: 32px 200px 108px 200px;
+ }
+}
diff --git a/src/utils/items.js b/src/utils/items.js
new file mode 100644
index 000000000..e819b567b
--- /dev/null
+++ b/src/utils/items.js
@@ -0,0 +1,10 @@
+// export const getPageSize = () => {
+// const width = window.innerWidth;
+// if (width < 768) {
+// return 1;
+// } else if (width < 1280) {
+// return 2;
+// } else {
+// return 4;
+// }
+// };