diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..f7944b2d
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,30 @@
+## ๐ฅ ๊ฒฐ๊ณผ
+
+
+
+- ๋ฐฐํฌํ ํ์ด์ง ์ ๊ทผ ๊ฒฝ๋ก(GitHub Pages):
+- ์คํฌ๋ฆฐ ๋ฆฌ๋ ํ๋ฉด ๋
นํ ์์ (before / after)
+
+## โ
๊ฐ์ ์์
๋ชฉ๋ก
+
+
+
+**1 ์ปดํฌ๋ํธ ์ ๊ทผ์ฑ ๊ฐ์ - ์ด๋ฏธ์ง ์บ๋ก์
**
+
+- [ ] ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์บ๋ก์
์ ์ ์ฒด ์์ดํ
์๋ฅผ ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
+- [ ] ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์ด๋ฏธ์ง ์บ๋ก์
๋ด ๊ฐ ์์ดํ
์ ๋ณด๋ฅผ ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
+ - [ ] ์ฌํ์ง, ์ข์ ์ ํ, ๊ฐ๊ฒฉ ์ ๋ณด๋ฅผ ํ๋ฒ์ ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
+ - [ ] ์ด์ /๋ค์ ์์ดํ
์ผ๋ก ์ด๋ํ๊ณ ํ์ฌ ๋ณด์ด๋ ์์ดํ
์ ์ ๋ณด๋ฅผ ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
+- [ ] ๊ฐ ์์ดํ
์ ์ ํํ๋ฉด ๊ฐ๊ฐ์ ๋ง๋ ๋งํฌ๋ก ์ด๋ํ ์ ์์ด์ผ ํฉ๋๋ค.
+
+**2 ํ์ด์ง ์ ๊ทผ์ฑ ๊ฐ์ **
+
+- [ ] ํ์ด์ง๋ฅผ ํ๋์ ๋ฌธ์๋ก ์ฝ์ ์ ์์ด์ผ ํฉ๋๋ค.
+ - [ ] ํ์ด์ง์ ์ ์ ํ ์ ๋ชฉ(title)์ ์ ๊ณตํ์ธ์. ์ ๋ชฉ์ ํ์ด์ง์ ์ฃผ์ ๋ด์ฉ์ ๊ฐ๊ฒฐํ๊ฒ ์ค๋ช
ํด์ผ ํฉ๋๋ค.
+ - [ ] ํ์ด์ง์ ์ฃผ์ ์์ญ์ ์๋งจํฑ ํ๊ทธ๋ฅผ ์ฌ์ฉํด ๋ช
ํํ ๊ตฌ๋ถํด ์ฃผ์ธ์
+ - [ ] ํค๋ฉ์ ๋
ผ๋ฆฌ์ ์ธ ์์๋ก ์ฌ์ฉํด ํ์ด์ง ๊ตฌ์กฐ๋ฅผ ๋ช
ํํ ํด์ฃผ์ธ์
+- [ ] ํค๋ณด๋ ์ฌ์ฉ์๋ฅผ ์ํด ํ์ด์ง ์ต์๋จ์ '๋ณธ๋ฌธ์ผ๋ก ๋ฐ๋ก๊ฐ๊ธฐ' ๋งํฌ๋ฅผ ์ ๊ณตํด ๋ฐ๋ณต๋๋ ๋ฉ๋ด๋ฅผ ๊ฑด๋๋ธ ์ ์๊ฒ ํด์ฃผ์ธ์
+
+## ๐ง ๋๋ง์ ์ ๊ทผ์ฑ ์ฒดํฌ๋ฆฌ์คํธ
+
+
diff --git a/.gitignore b/.gitignore
index 4d29575d..a547bf36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,24 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
+# Logs
+logs
+*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..f26ae07d
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "trailingComma": "none",
+ "tabWidth": 2,
+ "singleQuote": true,
+ "bracketSpacing": true,
+ "printWidth": 100
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 13ee2b04..45fe25f6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,10 @@
{
- "nuxt.isNuxtApp": false
-}
\ No newline at end of file
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
+}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 2a91af05..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2021 woowacourse
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
index 4b8c274f..6ca73d28 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-
-
+
+
+
+
A11Y AIRLINE
+
+ A11Y AIRLINE์ ๊ณ ๊ฐ ์ฌ๋ฌ๋ถ์ ์์ ํ๊ณ ์พ์ ํ ์ฌํ์ ์ํด ์ต์ ์ ๋คํ๊ณ ์์ต๋๋ค.
+
+
+
+
+
+
+
+
์ง๊ธ ๋ ๋๊ธฐ ์ข์ ์ฌํ
+
+
+
+
+ {/* ์ถ๊ฐ CHALLENGE: ๋ชจ๋ฌ ํฌ์ปค์ค ํธ๋ฉ */}
+ {/*
*/}
);
}
diff --git a/src/Typography.css b/src/Typography.css
new file mode 100644
index 00000000..2231d279
--- /dev/null
+++ b/src/Typography.css
@@ -0,0 +1,34 @@
+.heading-1-text {
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: normal;
+}
+
+.heading-2-text {
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: normal;
+}
+
+.heading-3-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: normal;
+}
+
+.body-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 150%;
+}
+
+.button-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 100%;
+}
diff --git a/src/assets/booking-bg.png b/src/assets/booking-bg.png
new file mode 100644
index 00000000..92dcaf35
Binary files /dev/null and b/src/assets/booking-bg.png differ
diff --git a/src/assets/chevron-left.svg b/src/assets/chevron-left.svg
new file mode 100644
index 00000000..73ef3e28
--- /dev/null
+++ b/src/assets/chevron-left.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/chevron-right.svg b/src/assets/chevron-right.svg
new file mode 100644
index 00000000..7d4ea7be
--- /dev/null
+++ b/src/assets/chevron-right.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/close.svg b/src/assets/close.svg
new file mode 100644
index 00000000..c784d0c6
--- /dev/null
+++ b/src/assets/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/help.svg b/src/assets/help.svg
new file mode 100644
index 00000000..b7016a6d
--- /dev/null
+++ b/src/assets/help.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/minus.svg b/src/assets/minus.svg
new file mode 100644
index 00000000..f6105fc4
--- /dev/null
+++ b/src/assets/minus.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/plus.svg b/src/assets/plus.svg
new file mode 100644
index 00000000..87f15c1f
--- /dev/null
+++ b/src/assets/plus.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/travel-item-01.png b/src/assets/travel-item-01.png
new file mode 100644
index 00000000..d6ad14a2
Binary files /dev/null and b/src/assets/travel-item-01.png differ
diff --git a/src/assets/travel-item-02.png b/src/assets/travel-item-02.png
new file mode 100644
index 00000000..be18975e
Binary files /dev/null and b/src/assets/travel-item-02.png differ
diff --git a/src/assets/travel-item-03.png b/src/assets/travel-item-03.png
new file mode 100644
index 00000000..22a5a345
Binary files /dev/null and b/src/assets/travel-item-03.png differ
diff --git a/src/components/FlightBooking.module.css b/src/components/FlightBooking.module.css
new file mode 100644
index 00000000..904232ea
--- /dev/null
+++ b/src/components/FlightBooking.module.css
@@ -0,0 +1,95 @@
+.flightBooking {
+ background-color: white;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.passengerLabel {
+ display: flex;
+ align-items: center;
+}
+
+.helpIconWrapper {
+ position: relative;
+ margin-left: 5px;
+ cursor: pointer;
+}
+
+.helpIcon {
+ width: 16px;
+ height: 16px;
+}
+
+.tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #333;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.tooltip::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #333 transparent transparent transparent;
+}
+
+.passengerCount {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.passengerCount span {
+ font-weight: 700;
+}
+
+.counter {
+ display: flex;
+ align-items: center;
+}
+
+.counter button {
+ width: 30px;
+ height: 30px;
+ border-radius: 16px;
+ border: 1px solid #c0c0c0;
+ background-color: #fff;
+ font-size: 18px;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.counter span {
+ font-size: 18px;
+ text-align: center;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: normal;
+ padding: 0 16px;
+}
+
+.searchButton {
+ width: 100%;
+ padding: 10px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
diff --git a/src/components/FlightBooking.tsx b/src/components/FlightBooking.tsx
new file mode 100644
index 00000000..535594e4
--- /dev/null
+++ b/src/components/FlightBooking.tsx
@@ -0,0 +1,72 @@
+import { useCallback, useState } from 'react';
+
+import helpIcon from '../assets/help.svg';
+import plus from '../assets/plus.svg';
+import minus from '../assets/minus.svg';
+
+import styles from './FlightBooking.module.css';
+
+const MIN_PASSENGERS = 1;
+const MAX_PASSENGERS = 3;
+
+const FlightBooking = () => {
+ const [adultCount, setAdultCount] = useState(MIN_PASSENGERS);
+ const [statusMessage, setStatusMessage] = useState('');
+ const [showTooltip, setShowTooltip] = useState(false);
+
+ const incrementCount = useCallback(() => {
+ if (adultCount === MAX_PASSENGERS) {
+ setStatusMessage('์ต๋ ์น๊ฐ ์์ ๋๋ฌํ์ต๋๋ค');
+ return;
+ }
+
+ setAdultCount((prev) => Math.min(MAX_PASSENGERS, prev + 1));
+ setStatusMessage('');
+ }, [adultCount]);
+
+ const decrementCount = useCallback(() => {
+ if (adultCount === MIN_PASSENGERS) {
+ setStatusMessage('์ต์ 1๋ช
์ ์น๊ฐ์ด ํ์ํฉ๋๋ค');
+ return;
+ }
+
+ setAdultCount((prev) => Math.max(MIN_PASSENGERS, prev - 1));
+ setStatusMessage('');
+ }, [adultCount]);
+
+ return (
+
+
ํญ๊ณต๊ถ ์๋งค
+
+
+
์ฑ์ธ
+
setShowTooltip(true)}
+ onMouseLeave={() => setShowTooltip(false)}
+ >
+

+ {showTooltip &&
์ต๋ 3๋ช
๊น์ง ์์ฝํ ์ ์์ต๋๋ค
}
+
+
+
+
+
{adultCount}
+
+
+
+ {statusMessage && (
+
+ {statusMessage}
+
+ )}
+
+
+ );
+};
+
+export default FlightBooking;
diff --git a/src/components/Navigation.module.css b/src/components/Navigation.module.css
new file mode 100644
index 00000000..85fb9e87
--- /dev/null
+++ b/src/components/Navigation.module.css
@@ -0,0 +1,110 @@
+.mainNav {
+ background-color: #f8f8f8;
+ padding: 10px 0;
+}
+
+.navList {
+ list-style-type: none;
+ padding: 10px 16px;
+ margin: 0;
+}
+
+.navItem {
+ display: inline-block;
+ position: relative;
+ margin-right: 20px;
+}
+
+.navItem a {
+ text-decoration: none;
+ color: #333;
+ font-size: 16px;
+ padding: 5px 10px;
+ display: block;
+}
+
+.navItem a:hover,
+.navItem a:focus {
+ background-color: #e0e0e0;
+ border-radius: 4px;
+}
+
+.navItem .navList {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+}
+
+.navItem:hover > .navList,
+.navItem:focus-within > .navList {
+ display: block;
+}
+
+.navItem .navList .navItem {
+ width: 200px;
+ display: block;
+ margin-right: 0;
+}
+
+.navItem .navList .navItem a {
+ padding: 10px;
+}
+
+.navItem .navList .navItem a:hover,
+.navItem .navList .navItem a:focus {
+ background-color: #f0f0f0;
+}
+
+.navToggle {
+ display: none;
+ font-size: 16px;
+ padding: 10px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+@media (max-width: 768px) {
+ .navToggle {
+ display: block;
+ }
+
+ .mainNav {
+ display: none;
+ }
+
+ .mainNavActive {
+ display: block;
+ }
+
+ .navItem {
+ display: block;
+ margin-right: 0;
+ margin-bottom: 10px;
+ }
+
+ .navItem .navList {
+ position: static;
+ display: none;
+ width: auto;
+ border: none;
+ box-shadow: none;
+ margin-left: 20px;
+ }
+
+ .navItem:hover > .navList,
+ .navItem:focus-within > .navList {
+ display: block;
+ }
+
+ .navItem .navList .navItem {
+ width: auto;
+ }
+}
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
new file mode 100644
index 00000000..80a3f092
--- /dev/null
+++ b/src/components/Navigation.tsx
@@ -0,0 +1,70 @@
+import { useState } from 'react';
+import styles from './Navigation.module.css';
+
+interface NavItem {
+ title: string;
+ link: string;
+ subItems?: NavItem[];
+}
+
+const navItems: NavItem[] = [
+ {
+ title: '์๋น์ค',
+ link: '#',
+ subItems: [
+ {
+ title: '๊ธฐ๋ด ์๋น์ค',
+ link: 'https://www.koreanair.com/contents/plan-your-travel/in-flight-experience'
+ },
+ { title: '์ํ๋ฌผ', link: 'https://www.koreanair.com/contents/plan-your-travel/baggage' },
+ {
+ title: '๋ผ์ด์ง',
+ link: 'https://www.koreanair.com/contents/plan-your-travel/at-the-airport/lounge'
+ }
+ ]
+ },
+ {
+ title: '์ฌํ ์ ๋ณด',
+ link: '#',
+ subItems: [
+ {
+ title: '์ฌํ์ ๋ณดํ',
+ link: 'https://www.koreanair.com/contents/booking/book-and-manage/partner-service'
+ },
+ { title: '์ฒดํฌ์ธ', link: 'https://www.koreanair.com/contents/plan-your-travel/check-in' }
+ ]
+ },
+ { title: '๊ณ ๊ฐ ์ง์', link: '#' }
+];
+
+const Navigation = () => {
+ const [isNavOpen, setIsNavOpen] = useState(false);
+
+ const toggleNav = () => {
+ setIsNavOpen((prev) => !prev);
+ };
+
+ const renderNavItems = (items: NavItem[]) => (
+
+ {items.map((item, index) => (
+ -
+ {item.title}
+ {item.subItems && renderNavItems(item.subItems)}
+
+ ))}
+
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default Navigation;
diff --git a/src/components/PromotionModal.module.css b/src/components/PromotionModal.module.css
new file mode 100644
index 00000000..d43ddbf2
--- /dev/null
+++ b/src/components/PromotionModal.module.css
@@ -0,0 +1,60 @@
+.modal {
+ display: block;
+}
+
+.modalBackdrop {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 100%;
+ max-width: 480px;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+}
+
+.modalContainer {
+ position: fixed;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: white;
+ padding: 20px;
+ width: 100%;
+ max-width: 480px;
+ border-radius: 8px 8px 0 0;
+ z-index: 1001;
+}
+
+.modalContent {
+ position: relative;
+}
+
+.modalTitle {
+ margin: 16px 0;
+}
+
+.modalDescription {
+ margin-bottom: 24px;
+}
+
+.modalActionButton {
+ display: block;
+ width: 100%;
+ padding: 12px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.modalCloseButton {
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
diff --git a/src/components/PromotionModal.tsx b/src/components/PromotionModal.tsx
new file mode 100644
index 00000000..0b1fe3cf
--- /dev/null
+++ b/src/components/PromotionModal.tsx
@@ -0,0 +1,38 @@
+import { useState } from 'react';
+
+import close from '../assets/close.svg';
+
+import styles from './PromotionModal.module.css';
+
+const PromotionModal = () => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const closeModal = () => {
+ setIsOpen(false);
+ };
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
์ฌํํ ๋ A11Y AIRLINE ์ฑ
+
+ ์ฒดํฌ์ธ, ํ์น๊ถ ์ ์ฅ, ์ํ๋ฌผ ์๋ฆผ๊น์ง
+
- ์ฑ์ผ๋ก ๋์ฑ ํธํ๊ฒ ์ฌํํ์ธ์!
+
+
+
+
+
+
+ );
+};
+
+export default PromotionModal;
diff --git a/src/components/SpinButton.css b/src/components/SpinButton.css
deleted file mode 100644
index e8540b90..00000000
--- a/src/components/SpinButton.css
+++ /dev/null
@@ -1,67 +0,0 @@
-.spinButtonContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 10px;
-}
-
-.spinButton {
- width: 40px;
- height: 40px;
- border: 1px solid #ddd;
- background-color: rgba(0, 0, 0, 0);
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- margin: 4px 12px;
- cursor: pointer;
- border-radius: 100%;
-}
-
-.spinButton:hover {
- background-color: #f3f3f3;
-}
-
-.spinButtonInput {
- padding: 10px;
- font-size: 16px;
- border-radius: 5px;
- text-align: center;
-}
-
-.helpIcon {
- width: 6px;
- height: 6px;
- display: flex;
- justify-content: center;
- align-items: center;
- bottom: 4px;
- margin-left: 10px;
- padding: 10px;
- background-color: rgba(0, 0, 0, 0);
- border-radius: 100%;
- border: 1px solid #ddd;
- cursor: help;
- position: relative;
-}
-
-.tooltip {
- position: absolute;
- bottom: 100%;
- left: 50%;
- transform: translateX(-50%);
- margin-bottom: 10px;
- padding: 10px;
- background-color: #000;
- color: #fff;
- border-radius: 5px;
- white-space: nowrap;
-}
-
-.spinButtonLabel {
- display: flex;
- font-weight: bold;
- padding: 0 16px;
- margin-bottom: 8px;
-}
diff --git a/src/components/SpinButton.tsx b/src/components/SpinButton.tsx
deleted file mode 100644
index 92915f97..00000000
--- a/src/components/SpinButton.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { useState, MouseEvent } from "react";
-import "./SpinButton.css";
-
-const SpinButton: React.FC = () => {
- const [count, setCount] = useState
(0);
- const [isTooltipVisible, setIsTooltipVisible] = useState(false);
-
- const increment = () => {
- setCount((prevCount) => prevCount + 1);
- };
-
- const decrement = () => {
- setCount((prevCount) => prevCount - 1);
- };
-
- const toggleTooltip = (event: MouseEvent) => {
- setIsTooltipVisible(!isTooltipVisible);
- };
-
- return (
-
-
-
์น๊ฐ ์ ํ
-
-
-
- ?
- {isTooltipVisible && (
- ์ต๋ ์ธ์์๋ 3๋ช
๊น์ง ๊ฐ๋ฅํฉ๋๋ค
- )}
-
-
-
-
-
-
-
- );
-};
-
-export default SpinButton;
diff --git a/src/components/TravelSection.module.css b/src/components/TravelSection.module.css
new file mode 100644
index 00000000..445a9fef
--- /dev/null
+++ b/src/components/TravelSection.module.css
@@ -0,0 +1,88 @@
+.travelSection {
+ position: relative;
+ padding-bottom: 32px;
+}
+
+.carousel {
+ position: relative;
+ overflow: hidden;
+ padding: 0 24px;
+}
+
+.card {
+ position: relative;
+ border-radius: 4px;
+ border: 1px solid rgba(217, 217, 217, 0.5);
+ overflow: hidden;
+ display: none;
+ width: 100%;
+ height: 246px;
+}
+
+.cardActive {
+ display: block;
+}
+
+.cardImage {
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: cover;
+ object-position: center;
+}
+
+.cardContent {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 26%, rgba(255, 255, 255, 0) 100%);
+ padding: 24px 16px;
+}
+
+.cardTitle {
+ margin-bottom: 24px;
+}
+
+.cardType,
+.cardPrice {
+ font-size: 1rem;
+ font-style: normal;
+ font-weight: 500;
+ line-height: normal;
+ padding-bottom: 8px;
+}
+
+.navButton {
+ position: absolute;
+ top: 25%;
+ background-color: rgb(44, 44, 44);
+ color: black;
+ border: none;
+ width: 32px;
+ height: 64px;
+ font-size: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 10;
+}
+
+.navButtonPrev {
+ left: 0;
+ border-radius: 0 40px 40px 0;
+ padding-left: 15px;
+}
+
+.navButtonNext {
+ right: 0;
+ border-radius: 40px 0 0 40px;
+ padding-right: 15px;
+}
+
+.navButtonIcon {
+ width: 24px;
+ height: 24px;
+}
diff --git a/src/components/TravelSection.tsx b/src/components/TravelSection.tsx
new file mode 100644
index 00000000..ed563a9f
--- /dev/null
+++ b/src/components/TravelSection.tsx
@@ -0,0 +1,92 @@
+import { useState } from 'react';
+
+import travelItem01 from '../assets/travel-item-01.png';
+import travelItem02 from '../assets/travel-item-02.png';
+import travelItem03 from '../assets/travel-item-03.png';
+import chevronLeft from '../assets/chevron-left.svg';
+import chevronRight from '../assets/chevron-right.svg';
+
+import styles from './TravelSection.module.css';
+
+interface TravelOption {
+ departure: string;
+ destination: string;
+ type: string;
+ price: number;
+ image: string;
+ link: string;
+}
+
+const travelOptions: TravelOption[] = [
+ {
+ departure: '์์ธ/์ธ์ฒ',
+ destination: '๋๋ฐ์ด',
+ type: '์ผ๋ฐ์ ์๋ณต',
+ price: 1121600,
+ image: travelItem01,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasList?TOURTYP=KALPAK&PKGBRA=KP&PKGARE=E5®NB1=%uC720%uB7FD®NB2=%uC911%uB3D9®TOP=1'
+ },
+ {
+ departure: '์์ธ/์ธ์ฒ',
+ destination: '๋ฐ๋ฅด์
๋ก๋',
+ type: '์ผ๋ฐ์ ์๋ณต',
+ price: 1515200,
+ image: travelItem02,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasView?pkgpnh=KP44129'
+ },
+ {
+ departure: '์์ธ/์ธ์ฒ',
+ destination: '๋ก๋ง',
+ type: '์ผ๋ฐ์ ์๋ณต',
+ price: 1415800,
+ image: travelItem03,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasView?pkgpnh=KP41216'
+ }
+];
+
+const TravelSection = () => {
+ const [currentIndex, setCurrentIndex] = useState(0);
+
+ const nextTravel = () => {
+ setCurrentIndex((prevIndex) => (prevIndex + 1) % travelOptions.length);
+ };
+
+ const prevTravel = () => {
+ setCurrentIndex((prevIndex) => (prevIndex - 1 + travelOptions.length) % travelOptions.length);
+ };
+
+ const handleCardClick = (link: string) => {
+ window.open(link, '_blank', 'noopener,noreferrer');
+ };
+
+ return (
+
+
+
+ {travelOptions.map((option, index) => (
+
handleCardClick(option.link)}
+ >
+

+
+
+ {option.departure} - {option.destination}
+
+
{option.type}
+
KRW {option.price.toLocaleString()}
+
+
+ ))}
+
+
+
+ );
+};
+
+export default TravelSection;
diff --git a/src/image.d.ts b/src/image.d.ts
new file mode 100644
index 00000000..cdb2b1a9
--- /dev/null
+++ b/src/image.d.ts
@@ -0,0 +1,4 @@
+declare module '*.svg' {
+ const content: string;
+ export default content;
+}
diff --git a/src/images/airplane.png b/src/images/airplane.png
deleted file mode 100644
index f1a06f23..00000000
Binary files a/src/images/airplane.png and /dev/null differ
diff --git a/src/images/carousel_mobile_sample.png b/src/images/carousel_mobile_sample.png
deleted file mode 100644
index 2353660e..00000000
Binary files a/src/images/carousel_mobile_sample.png and /dev/null differ
diff --git a/src/images/navigation_sample.png b/src/images/navigation_sample.png
deleted file mode 100644
index 03227a7e..00000000
Binary files a/src/images/navigation_sample.png and /dev/null differ
diff --git a/src/images/spin_button_sample.png b/src/images/spin_button_sample.png
deleted file mode 100644
index fd916fbc..00000000
Binary files a/src/images/spin_button_sample.png and /dev/null differ
diff --git a/src/index.css b/src/index.css
index ec2585e8..8710a346 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,13 +1,119 @@
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ box-sizing: border-box;
+}
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol,
+ul {
+ list-style: none;
+}
+button {
+ color: #000;
+}
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;
+ padding: 0;
+ font-family: Arial, sans-serif;
+ background-color: #f5f5f5;
}
diff --git a/src/index.tsx b/src/index.tsx
deleted file mode 100644
index cfd77849..00000000
--- a/src/index.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import "./index.css";
-import App from "./App";
-
-const root = ReactDOM.createRoot(
- document.getElementById("root") as HTMLElement
-);
-root.render(
-
-
-
-);
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..c50b7b63
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,14 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+
+import './Typography.css';
+import './Accessibility.css';
+import './index.css';
+
+import App from './App.tsx';
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5f..00000000
--- a/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 00000000..f0a23505
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
index a273b0cf..d32ff682 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,26 +1,4 @@
{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..0d3d7144
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..43789d75
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ base: '/a11y-airline/',
+ server: {
+ host: '0.0.0.0'
+ }
+});