Skip to content

๐Ÿ‘‘[ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ํ’€์Šคํƒ ๋ฐ๋ธŒ์ฝ”์Šค 7๊ธฐ] 2์ฐจ ํ”„๋กœ์ ํŠธ ์šฐ์ˆ˜์ž‘_๋‚ด์ผํŒ€_๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ๊ธฐ๋ฐ˜ ํ•ด๋น— ํŠธ๋ž˜์ปค

Notifications You must be signed in to change notification settings

Monixc/Levelyn-FE

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐ŸŽฎ Levelyn - ๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ๊ธฐ๋ฐ˜ ํ•ด๋น— ํŠธ๋ž˜์ปค

Levelyn์€ RPG ์š”์†Œ๋ฅผ ๊ฒฐํ•ฉํ•œ ํ•ด๋น—ํŠธ๋ž˜์ปค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ํ•  ์ผ์„ ์™„๋ฃŒํ•˜๋ฉฐ ์บ๋ฆญํ„ฐ๋ฅผ ์„ฑ์žฅ์‹œํ‚ค๊ณ , ํ—ฅ์‚ฌ๊ณค ๋งต์„ ํƒํ—˜ํ•˜๋ฉฐ, ๋ชฌ์Šคํ„ฐ์™€ ์ „ํˆฌ๋ฅผ ๋ฒŒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

https://levelyn.p-e.kr/

๐Ÿ‘ฅ ํŒ€ ๋ฉค๋ฒ„

ํ”„๋กœํ•„ ์—ญํ•  ์ด๋ฆ„ ๋‹ด๋‹น ์˜์—ญ
Frontend ํ™ฉ๋‹ค๊ฒฝ - Github actions + S3 ์ž๋™ ๋ฐฐํฌ
- ์นด์นด์˜ค OAuth 2.0 ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„, ๋ผ์šฐํŠธ ๊ฐ€๋“œ ๋ฐ ์ธ์ฆ ์ƒํƒœ ๊ด€๋ฆฌ
- appwrite storage๋ฅผ ํ™œ์šฉํ•œ ๋™์  ์ด๋ฏธ์ง€ ์„œ๋น™
- ๋ฉ”์ธ, ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ๊ตฌํ˜„
- ์‹œ๋“œ ๊ธฐ๋ฐ˜ ๋žœ๋ค ํ—ฅ์‚ฌ๊ณค ํƒ€์ผ๋งต ์‹œ์Šคํ…œ ๊ตฌํ˜„
- SSE ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ์ „ํˆฌ ์‹œ์Šคํ…œ ๊ตฌํ˜„
- Recharts ๊ธฐ๋ฐ˜ ์ฃผ๊ฐ„ ํ†ต๊ณ„ ์ฐจํŠธ ๊ตฌํ˜„
- ๋ฐœํ‘œ
Frontend ์ •๊ธฐ์˜ - Husky ์„ธํŒ…, ESLint+Prettier ์ฝ”๋“œ ํ’ˆ์งˆ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๊ตฌ์ฃฝ
- ๋””์ž์ธ ํ† ํฐ ๊ธฐ๋ฐ˜ ํ…Œ๋งˆ ์‹œ์Šคํ…œ ๋ฐ ์ „์—ญ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ
- ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์‹œ์Šคํ…œ ๋ฐ ๊ณต์šฉ ์•„์ดํ…œ/์Šคํ‚ฌ ์Šฌ๋กฏ ๊ตฌํ˜„
- ํ•  ์ผ ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ ๊ตฌํ˜„
- ์ธ๋ฒคํ† ๋ฆฌ(์Šคํ‚ฌ, ์•„์ดํ…œ) ํ™”๋ฉด ๋””์ž์ธ ๋ฐ ๊ตฌํ˜„
- ์Šคํ”Œ๋ž˜์‹œ ํ™”๋ฉด ๋ฐ PWA ๋ฉ”๋‹ˆํŽ˜์ŠคํŠธ ๊ตฌ์„ฑ
- React-Query ๊ธฐ๋ฐ˜ ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ
- ๋ฐœํ‘œ ์ž๋ฃŒ ์ œ์ž‘
Backend ์›ํ˜ธ์„ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ

โœจ ์ฃผ์š” ๊ธฐ๋Šฅ

ํ™”๋ฉด ์ด๋ฆ„ ์„ค๋ช… ์Šคํฌ๋ฆฐ์ƒท
๐Ÿ  ํ™ˆ ํ™”๋ฉด ์‹œ๋“œ ๊ธฐ๋ฐ˜ ๋žœ๋ค ํ—ฅ์‚ฌ๊ณค ํด๋Ÿฌ์Šคํ„ฐ์™€ ์‹ค์‹œ๊ฐ„ ์บ๋ฆญํ„ฐ ์ƒํƒœ๋ฅผ ํ†ตํ•œ ๊ฒŒ์ž„ํ™”๋œ ํ•  ์ผ ๊ด€๋ฆฌ

โ€ข ํ—ฅ์‚ฌ๊ณค ํƒ€์ผ๋งต: Axial ์ขŒํ‘œ๊ณ„ ๊ธฐ๋ฐ˜ ํ—ฅ์‚ฌ๊ณค ํด๋Ÿฌ์Šคํ„ฐ ์‹œ์Šคํ…œ์œผ๋กœ ํ•  ์ผ ์™„๋ฃŒ ํ˜„ํ™ฉ ์‹œ๊ฐํ™”
โ€ข ์‹œ๋“œ ๊ธฐ๋ฐ˜ ๋งต ์ƒ์„ฑ: 8๊ฐœ ํ—ฅ์‚ฌ๊ณค๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์‹œ๋“œ๋กœ ๋‹ค์–‘ํ•œ ํŒจํ„ด ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ธ๊ฒŒ์ด์ง€๋จผํŠธ ์ง€์†์„ฑ ํ™•๋ณด
โ€ข ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ๋™๊ธฐํ™”: React Query๋ฅผ ํ™œ์šฉํ•œ ์บ๋ฆญํ„ฐ ๋ ˆ๋ฒจ/๊ฒฝํ—˜์น˜ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
โ€ข ๋“œ๋กœ์–ด ๊ธฐ๋ฐ˜ Todo UI: sessionStorage๋ฅผ ํ†ตํ•œ ์ƒํƒœ ์ง€์†์„ฑ๊ณผ ์ง๊ด€์ ์ธ ์Šฌ๋ผ์ด๋“œ ์ธํ„ฐํŽ˜์ด์Šค
ํ™ˆ ํ™”๋ฉด
โš”๏ธ ์‹ค์‹œ๊ฐ„ ์ „ํˆฌ ์‹œ์Šคํ…œ SSE(Server-Sent Events)๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ๋ชฌ์Šคํ„ฐ ๋ฐฐํ‹€๊ณผ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ

โ€ข SSE ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ : ์ „ํˆฌ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ ๋ชฐ์ž…๊ฐ ์žˆ๋Š” ์‹ค์‹œ๊ฐ„ ๋ฐฐํ‹€ ๊ตฌํ˜„
โ€ข ๋น„๋™๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์‹œ์Šคํ…œ: ์Šคํ‚ฌ ์ดํŽ™ํŠธ, ํ”ผ๊ฒฉ ๋ชจ์…˜, HP ์—…๋ฐ์ดํŠธ ํƒ€์ด๋ฐ ์ตœ์ ํ™”
โ€ข ์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋”ฉ: ์ „ํˆฌ ์‹œ์ž‘ ์ „ ๋ชจ๋“  ๋ฆฌ์†Œ์Šค ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜์—ฌ ๋Š๊น€ ์—†๋Š” UX ์ œ๊ณต
์ „ํˆฌ ํ™”๋ฉด
๐ŸŽ’ ์ธ๋ฒคํ† ๋ฆฌ ์‹œ์Šคํ…œ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ๊ณผ ์‹ค์‹œ๊ฐ„ ๋Šฅ๋ ฅ์น˜ ๋ฐ˜์˜์„ ํ†ตํ•œ ์ง๊ด€์  ์žฅ๋น„ ๊ด€๋ฆฌ

โ€ข ํ„ฐ์น˜ ๊ธฐ๋ฐ˜ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ: ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋œ ํ„ฐ์น˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง
โ€ข ํƒ€์ž… ๊ธฐ๋ฐ˜ ์žฅ๋น„ ๋ถ„๋ฅ˜: ๋ฌด๊ธฐ(1), ํŒ”์ฐŒ(2), ๋ชฉ๊ฑธ์ด(3), ๋ฐ˜์ง€(4), ๊ท€๊ฑธ์ด(5) ํƒ€์ž…๋ณ„ ์Šฌ๋กฏ ๊ด€๋ฆฌ
โ€ข ์‹ค์‹œ๊ฐ„ ๋Šฅ๋ ฅ์น˜ ๋ฐ˜์˜: ์žฅ๋น„ ๋ณ€๊ฒฝ ์‹œ ์ฆ‰์‹œ ์บ๋ฆญํ„ฐ ์Šคํƒฏ ์—…๋ฐ์ดํŠธ
โ€ข ์Šคํ‚ฌ ๋งคํŠธ๋ฆญ์Šค ๊ด€๋ฆฌ: ์‚ฌ์šฉ์ž ๋ณด์œ  ์Šคํ‚ฌ๊ณผ ์žฅ์ฐฉ ์Šคํ‚ฌ ๋ถ„๋ฆฌ ๊ด€๋ฆฌ
์ธ๋ฒคํ† ๋ฆฌ ํ™”๋ฉด
๐Ÿ“Š ํ”„๋กœํ•„ & ํ†ต๊ณ„ Recharts ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”์™€ ์ข…ํ•ฉ์ ์ธ ์บ๋ฆญํ„ฐ ์ •๋ณด ๋Œ€์‹œ๋ณด๋“œ

โ€ข ์ฃผ๊ฐ„ ํ†ต๊ณ„ ์ฐจํŠธ: localStorage ๊ธฐ๋ฐ˜ ์ผ์ผ ์™„๋ฃŒ ํ†ต๊ณ„๋ฅผ Recharts๋กœ ์‹œ๊ฐํ™”
โ€ข ์‹ค์‹œ๊ฐ„ ์บ๋ฆญํ„ฐ ์ •๋ณด: API ์—ฐ๋™์„ ํ†ตํ•œ ๋ ˆ๋ฒจ, ๊ฒฝํ—˜์น˜, ๋Šฅ๋ ฅ์น˜ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ
โ€ข ์žฅ์ฐฉ ์•„์ดํ…œ ํ˜„ํ™ฉ: ํ˜„์žฌ ์ฐฉ์šฉ ์ค‘์ธ ๋ชจ๋“  ์žฅ๋น„์˜ ์‹œ๊ฐ์  ๋ฐฐ์น˜
โ€ข ํšจ๊ณผ ์‹œ์Šคํ…œ: ์žฅ์ฐฉ ์•„์ดํ…œ์˜ ๋Šฅ๋ ฅ์น˜ ์ฆ๊ฐ€ ํšจ๊ณผ ์ข…ํ•ฉ ํ‘œ์‹œ
ํ”„๋กœํ•„ ํ™”๋ฉด
๐Ÿ” ์ธ์ฆ ์‹œ์Šคํ…œ ์นด์นด์˜ค OAuth์™€ JWT ํ† ํฐ ๊ด€๋ฆฌ๋ฅผ ํ†ตํ•œ ๋ณด์•ˆ์„ฑ ์žˆ๋Š” ์†Œ์…œ ๋กœ๊ทธ์ธ

โ€ข ์นด์นด์˜ค OAuth 2.0: REST API๋ฅผ ํ†ตํ•œ ์•ˆ์ „ํ•œ ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„
โ€ข ๋ผ์šฐํŠธ ๊ฐ€๋“œ: ์ธ์ฆ ์ƒํƒœ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด์™€ ์ž๋™ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
๋กœ๊ทธ์ธ ํ™”๋ฉด์ธ์ฆ ํ™”๋ฉด

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

stacks

Frontend

My Skills

Deployment

My Skills

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

Levelyn-FE/
โ”œโ”€โ”€ public/                     # ์ •์  ํŒŒ์ผ
โ”‚   โ”œโ”€โ”€ fonts/                 # ํฐํŠธ ํŒŒ์ผ (Pretendard)
โ”‚   โ”œโ”€โ”€ icons/                 # ์•ฑ ์•„์ด์ฝ˜
โ”‚   โ””โ”€โ”€ manifest.json          # PWA ๋งค๋‹ˆํŽ˜์ŠคํŠธ
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ assets/                # ์ด๋ฏธ์ง€ ๋ฐ ์ •์  ์ž์›
โ”‚   โ”‚   โ”œโ”€โ”€ avatar.png         # ์บ๋ฆญํ„ฐ ์•„๋ฐ”ํƒ€
โ”‚   โ”‚   โ”œโ”€โ”€ background.png     # ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ home.png          # ํ™ˆ ๋ฐฐ๊ฒฝ
โ”‚   โ”‚   โ”œโ”€โ”€ logo.png          # ๋กœ๊ณ 
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ components/            # ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ common/           # ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ BarChart.tsx   # ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Button.tsx     # ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ CheckBox.tsx   # ์ฒดํฌ๋ฐ•์Šค
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Drawer.tsx     # ๋“œ๋กœ์–ด UI
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Dropdown.tsx   # ๋“œ๋กญ๋‹ค์šด
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Header.tsx     # ํ—ค๋”
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ItemBox.tsx    # ์•„์ดํ…œ ๋ฐ•์Šค
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Modal/         # ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๋“ค
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ CombatModalContent.tsx  # ์ „ํˆฌ ๋ชจ๋‹ฌ
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ EventModalContent.tsx   # ์ด๋ฒคํŠธ ๋ชจ๋‹ฌ
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ index.tsx              # ๊ธฐ๋ณธ ๋ชจ๋‹ฌ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ProgressBar.tsx # ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ TextField.tsx   # ํ…์ŠคํŠธ ํ•„๋“œ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ tilemap.tsx     # ํ—ฅ์‚ฌ๊ณค ํƒ€์ผ๋งต
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ TodoItem.tsx    # ํ•  ์ผ ์•„์ดํ…œ
โ”‚   โ”‚   โ”‚
โ”‚   โ”‚   โ””โ”€โ”€ layout/            # ๋ ˆ์ด์•„์›ƒ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚       โ””โ”€โ”€ BottomNavigation/  # ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ contexts/              # React Context
โ”‚   โ”‚   โ”œโ”€โ”€ AuthContext.tsx    # ์ธ์ฆ ์ƒํƒœ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ””โ”€โ”€ NotificationContext.tsx  # ์•Œ๋ฆผ ๋ฐ ์ „ํˆฌ ์ƒํƒœ
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ hooks/                 # ์ปค์Šคํ…€ ํ›…
โ”‚   โ”‚   โ”œโ”€โ”€ useDragAndDrop.ts  # ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๋กœ์ง
โ”‚   โ”‚   โ”œโ”€โ”€ useHome.ts         # ํ™ˆ ํ™”๋ฉด ๋กœ์ง
โ”‚   โ”‚   โ””โ”€โ”€ useInventory.ts    # ์ธ๋ฒคํ† ๋ฆฌ ๋กœ์ง
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ pages/                 # ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ home/             # ํ™ˆ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ login/            # ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ Inventory/        # ์ธ๋ฒคํ† ๋ฆฌ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ Profile/          # ํ”„๋กœํ•„ ํŽ˜์ด์ง€
โ”‚   โ”‚   โ”œโ”€โ”€ CreateTodo/       # ํ•  ์ผ ์ƒ์„ฑ
โ”‚   โ”‚   โ”œโ”€โ”€ EditTodo/         # ํ•  ์ผ ์ˆ˜์ •
โ”‚   โ”‚   โ”œโ”€โ”€ KakaoCallback/    # ์นด์นด์˜ค ๋กœ๊ทธ์ธ ์ฝœ๋ฐฑ
โ”‚   โ”‚   โ””โ”€โ”€ ErrorPage/        # ์—๋Ÿฌ ํŽ˜์ด์ง€
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ services/             # API ์„œ๋น„์Šค ๋ ˆ์ด์–ด
โ”‚   โ”‚   โ”œโ”€โ”€ api.ts           # ๊ธฐ๋ณธ API ์„ค์ •
โ”‚   โ”‚   โ”œโ”€โ”€ appwrite.ts      # Appwrite ํด๋ผ์ด์–ธํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ auth.ts          # ์ธ์ฆ ๊ด€๋ จ API
โ”‚   โ”‚   โ”œโ”€โ”€ goal.ts          # ๋ชฉํ‘œ ๊ด€๋ จ API
โ”‚   โ”‚   โ”œโ”€โ”€ inventory.ts     # ์ธ๋ฒคํ† ๋ฆฌ API
โ”‚   โ”‚   โ”œโ”€โ”€ myPage.ts        # ๋งˆ์ดํŽ˜์ด์ง€ API
โ”‚   โ”‚   โ”œโ”€โ”€ sse.ts           # SSE ์—ฐ๊ฒฐ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ””โ”€โ”€ todo.ts          # ํ•  ์ผ ๊ด€๋ จ API
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ stories/             # Storybook ์Šคํ† ๋ฆฌ
โ”‚   โ”‚   โ”œโ”€โ”€ button.stories.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ Modal.stories.tsx
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ styles/              # ์Šคํƒ€์ผ ๊ด€๋ จ
โ”‚   โ”‚   โ”œโ”€โ”€ GlobalStyles.tsx  # ์ „์—ญ ์Šคํƒ€์ผ
โ”‚   โ”‚   โ”œโ”€โ”€ theme.ts         # ํ…Œ๋งˆ ์„ค์ •
โ”‚   โ”‚   โ”œโ”€โ”€ emotion.d.ts     # Emotion ํƒ€์ž… ์ •์˜
โ”‚   โ”‚   โ””โ”€โ”€ tokens/          # ๋””์ž์ธ ํ† ํฐ
โ”‚   โ”‚       โ”œโ”€โ”€ colors.ts    # ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ
โ”‚   โ”‚       โ””โ”€โ”€ textStyles.ts # ํ…์ŠคํŠธ ์Šคํƒ€์ผ
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ types/               # TypeScript ํƒ€์ž… ์ •์˜
โ”‚   โ”‚   โ”œโ”€โ”€ battle.types.ts   # ์ „ํˆฌ ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ”‚   โ”œโ”€โ”€ goal.types.ts     # ๋ชฉํ‘œ ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ”‚   โ”œโ”€โ”€ inventory.types.ts # ์ธ๋ฒคํ† ๋ฆฌ ํƒ€์ž…
โ”‚   โ”‚   โ”œโ”€โ”€ myPage.types.ts   # ๋งˆ์ดํŽ˜์ด์ง€ ํƒ€์ž…
โ”‚   โ”‚   โ””โ”€โ”€ todo.types.ts     # ํ•  ์ผ ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ utils/               # ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜
โ”‚   โ”‚   โ”œโ”€โ”€ hexagon.ts       # ํ—ฅ์‚ฌ๊ณค ๊ด€๋ จ ๊ณ„์‚ฐ
โ”‚   โ”‚   โ”œโ”€โ”€ localStorage.ts  # ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ””โ”€โ”€ longPressHandler.ts # ๋กฑํ”„๋ ˆ์Šค ํ•ธ๋“ค๋Ÿฌ
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ App.tsx              # ๋ฉ”์ธ ์•ฑ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ main.tsx            # ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ
โ”‚   โ””โ”€โ”€ vite-env.d.ts       # Vite ํ™˜๊ฒฝ ํƒ€์ž…
โ”‚
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ””โ”€โ”€ deploy.yml       # GitHub Actions ๋ฐฐํฌ ์›Œํฌํ”Œ๋กœ์šฐ
โ”‚
โ”œโ”€โ”€ package.json             # ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ
โ”œโ”€โ”€ vite.config.ts          # Vite ์„ค์ •
โ”œโ”€โ”€ tsconfig.json           # TypeScript ์„ค์ •
โ”œโ”€โ”€ jest.config.js          # Jest ํ…Œ์ŠคํŠธ ์„ค์ •
โ”œโ”€โ”€ eslint.config.js        # ESLint ์„ค์ •
โ””โ”€โ”€ README.md               # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ

๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ

ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ

  • Node.js 18.0.0 ์ด์ƒ
  • npm

์„ค์น˜ ๋ฐ ์‹คํ–‰

  1. ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ํด๋ก 
git clone https://github.com/prgrms-fullstack-devcourse/Levelyn-FE.git
cd levelyn-fe
  1. ์˜์กด์„ฑ ์„ค์น˜
npm install
  1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • .env ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋ณ€์ˆ˜๋“ค์„ ์„ค์ •ํ•˜์„ธ์š”:
VITE_KAKAO_REST_API_KEY=your_kakao_api_key
VITE_KAKAO_REDIRECT_URI=your_redirect_uri
VITE_APPWRITE_PROJECT_ID=your_appwrite_project_id
VITE_APPWRITE_ENDPOINT=your_appwrite_endpoint
VITE_APPWRITE_IMAGES_BUCKET_ID=your_bucket_id
  1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
npm run dev
  1. ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธ http://localhost:5173์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽฏ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ƒ์„ธ

๐Ÿ—บ๏ธ ํ—ฅ์‚ฌ๊ณค ํƒ€์ผ๋งต ์‹œ์Šคํ…œ

๊ฒŒ์ž„์  ๋„๋ฉ”์ธ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•œ UX ๊ณ ๋ ค ์„ค๊ณ„

์‚ฌ์šฉ์ž ์ธ๊ฒŒ์ด์ง€๋จผํŠธ ์ง€์†์„ฑ๊ณผ ํƒํ—˜ ๋™๊ธฐ ๋ถ€์—ฌ๋ฅผ ์œ„ํ•ด ์‹œ๋“œ ๊ธฐ๋ฐ˜ ๋žœ๋ค ํ—ฅ์‚ฌ๊ณค ํด๋Ÿฌ์Šคํ„ฐ ์‹œ์Šคํ…œ์„ ๊ณ ์•ˆํ–ˆ์Šต๋‹ˆ๋‹ค.

// ์‹œ๋“œ ๊ธฐ๋ฐ˜ ๋žœ๋ค ํ•จ์ˆ˜๋กœ ์ผ๊ด€์„ฑ ์žˆ๋Š” ํŒจํ„ด ์ƒ์„ฑ
export const seededRandom = (seed: number, min = 0, max = 1): number => {
  const x = Math.sin(seed) * 10000;
  const random = x - Math.floor(x);
  return min + random * (max - min);
};

// Axial ์ขŒํ‘œ๊ณ„ ๊ธฐ๋ฐ˜ ํ—ฅ์‚ฌ๊ณค ๋ฐฐ์น˜
export const axialToPixel = (q: number, r: number, size: number): [number, number] => {
  const x = size * (1.5 * q);
  const y = size * ((Math.sqrt(3) / 2) * q + Math.sqrt(3) * r);
  return [x, y];
};

ํ•ต์‹ฌ ๊ตฌํ˜„ ํฌ์ธํŠธ:

  • 8๊ฐœ ํ—ฅ์‚ฌ๊ณค ์™„๋ฃŒ ์‹œ ์ƒˆ๋กœ์šด ๋งต ์ƒ์„ฑ: Math.floor(totalCompleted / MAX_HEXAGONS) + 1 ์‹œ๋“œ ๊ณ„์‚ฐ
  • ์ธ์ ‘ ํ—ฅ์‚ฌ๊ณค ํƒ์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜: 6๋ฐฉํ–ฅ ์ด์›ƒ ์ขŒํ‘œ๋ฅผ ํ†ตํ•œ ์ž์—ฐ์Šค๋Ÿฌ์šด ํด๋Ÿฌ์Šคํ„ฐ ํ™•์žฅ
  • Axial ์ขŒํ‘œ๊ณ„ ํ™œ์šฉ: ํ—ฅ์‚ฌ๊ณค ๊ทธ๋ฆฌ๋“œ์˜ ์ˆ˜ํ•™์  ์ •ํ™•์„ฑ๊ณผ ํšจ์œจ์ ์ธ ๊ณ„์‚ฐ
  • ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ์ตœ์ ํ™”: useMemo๋ฅผ ํ†ตํ•œ ์‹œ๋“œ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ์žฌ๊ณ„์‚ฐ

โšก ์‹ค์‹œ๊ฐ„ ์ „ํˆฌ ์‹œ์Šคํ…œ (SSE)

๊ฒŒ์ž„์  ๋ชฐ์ž…๊ฐ์„ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ์•„ํ‚คํ…์ฒ˜

๊ธฐ์กด HTTP ํด๋ง์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ณ  ์‹ค์‹œ๊ฐ„ ์ „ํˆฌ์˜ ๊ธด๋ฐ•๊ฐ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด SSE(Server-Sent Events)๋ฅผ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.

// SSE ์—ฐ๊ฒฐ ๊ด€๋ฆฌ ๋ฐ ๋ฉ€ํ‹ฐํ”Œ๋ ‰์‹ฑ
export const connectSSE = (endpoint: string, eventHandlers: { [event: string]: (data: any) => void }) => {
  const url = `${API_BASE_URL}${endpoint}?token=${token}`;
  const eventSource = new EventSource(url, { withCredentials: true });

  // ๋™์  ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
  Object.entries(eventHandlers).forEach(([event, handler]) => {
    eventSource.addEventListener(event, (e) => {
      handler(JSON.parse(e.data));
    });
  });

  sseConnections[endpoint] = eventSource; // ์—ฐ๊ฒฐ ์ƒํƒœ ๊ด€๋ฆฌ
};

// ์‹ค์‹œ๊ฐ„ ์ „ํˆฌ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
const handleBattleStream = (data: BattleStreamData) => {
  if (data.damage > 0) {
    // ์Šคํ‚ฌ ์ดํŽ™ํŠธ ๋™๊ธฐํ™”
    const key = data.skillId === -1 ? 'basic-attack' : `skill-${data.skillId}`;
    setSkillEffectUrl(preloadedUrls[key]);
    setShowSkill(true);
    setTimeout(() => setShowSkill(false), 500); // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ด๋ฐ
  }

  // HP ์ฆ‰์‹œ ๋ฐ˜์˜์œผ๋กœ ์‹ค์‹œ๊ฐ„์„ฑ ๋ณด์žฅ
  setBattleState((prev) => ({
    ...prev,
    monster: { ...prev.monster, hp: data.mobHp },
  }));
};

ํ•ต์‹ฌ ๊ตฌํ˜„ ํฌ์ธํŠธ:

  • ๋‹จ๋ฐฉํ–ฅ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ : ์„œ๋ฒ„โ†’ํด๋ผ์ด์–ธํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ์œผ๋กœ ์ง€์—ฐ ์‹œ๊ฐ„ ์ตœ์†Œํ™”
  • ์—ฐ๊ฒฐ ํ’€ ๊ด€๋ฆฌ: ์—”๋“œํฌ์ธํŠธ๋ณ„ SSE ์—ฐ๊ฒฐ ์ƒํƒœ ์ถ”์  ๋ฐ ์ค‘๋ณต ๋ฐฉ์ง€
  • ์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋”ฉ: ์ „ํˆฌ ์‹œ์ž‘ ์ „ ๋ชจ๋“  ์Šคํ‚ฌ ์ดํŽ™ํŠธ ๋ฆฌ์†Œ์Šค ์‚ฌ์ „ ๋กœ๋”ฉ
  • ๋น„๋™๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜: ๋ฐ๋ฏธ์ง€ ๊ณ„์‚ฐ๊ณผ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ์˜ ๋ถ„๋ฆฌ๋œ ํƒ€์ด๋ฐ ์ œ์–ด

๐ŸŽ’ ํ„ฐ์น˜ ์ตœ์ ํ™” ์ธ๋ฒคํ† ๋ฆฌ ์‹œ์Šคํ…œ

๋ชจ๋ฐ”์ผ ํผ์ŠคํŠธ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์ธํ„ฐํŽ˜์ด์Šค

export function useDragAndDrop<T>(onDrop: (item: T) => void) {
  const [draggedItem, setDraggedItem] = useState<T | null>(null);
  const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null);

  const onTouchStart = (item: T) => (e: React.TouchEvent) => {
    const touch = e.touches[0];
    setDraggedItem(item);
    setDragPosition({ x: touch.clientX, y: touch.clientY });
  };

  const onTouchEnd = (_e: React.TouchEvent) => {
    if (draggedItem && dropZoneRef.current) {
      const rect = dropZoneRef.current.getBoundingClientRect();
      const touch = _e.changedTouches[0];

      // ๋“œ๋กญ ์กด ์ถฉ๋Œ ๊ฐ์ง€
      if (
        touch.clientX >= rect.left &&
        touch.clientX <= rect.right &&
        touch.clientY >= rect.top &&
        touch.clientY <= rect.bottom
      ) {
        onDrop(draggedItem);
      }
    }
  };
}

ํ•ต์‹ฌ ๊ตฌํ˜„ ํฌ์ธํŠธ:

  • ํ„ฐ์น˜ ์ด๋ฒคํŠธ ์šฐ์„  ์„ค๊ณ„: ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์˜ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๋„ค์ดํ‹ฐ๋ธŒ ์ง€์›
  • ์‹ค์‹œ๊ฐ„ ์œ„์น˜ ์ถ”์ : clientX/Y ์ขŒํ‘œ๋ฅผ ํ†ตํ•œ ์ •ํ™•ํ•œ ๋“œ๋ž˜๊ทธ ์œ„์น˜ ๊ณ„์‚ฐ
  • ์ถฉ๋Œ ๊ฐ์ง€ ์•Œ๊ณ ๋ฆฌ์ฆ˜: getBoundingClientRect()๋ฅผ ํ™œ์šฉํ•œ ๋“œ๋กญ ์กด ์˜์—ญ ํŒ์ •
  • ์žฅ๋น„ ํƒ€์ž… ์ œ์•ฝ: ID ๊ธฐ๋ฐ˜ ์Šฌ๋กฏ ๋งค์นญ์œผ๋กœ ์ž˜๋ชป๋œ ์žฅ์ฐฉ ๋ฐฉ์ง€

๐Ÿ”ง ๋ฐฐํฌ

ํ”„๋กœ์ ํŠธ๋Š” GitHub Actions๋ฅผ ํ†ตํ•ด AWS S3์— ์ž๋™ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค.

  1. main ๋ธŒ๋žœ์น˜์— ์ฝ”๋“œ ํ‘ธ์‹œ
  2. GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ ์ž๋™ ์‹คํ–‰
  3. ๋นŒ๋“œ ๋ฐ S3 ๋ฐฐํฌ ์™„๋ฃŒ

Levelyn์œผ๋กœ ํ•  ์ผ ๊ด€๋ฆฌ๋ฅผ ๊ฒŒ์ž„์ฒ˜๋Ÿผ ์ฆ๊ฒ๊ฒŒ! ๐ŸŽฎโœจ

About

๐Ÿ‘‘[ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ํ’€์Šคํƒ ๋ฐ๋ธŒ์ฝ”์Šค 7๊ธฐ] 2์ฐจ ํ”„๋กœ์ ํŠธ ์šฐ์ˆ˜์ž‘_๋‚ด์ผํŒ€_๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ๊ธฐ๋ฐ˜ ํ•ด๋น— ํŠธ๋ž˜์ปค

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 98.7%
  • Other 1.3%