Proovyλ μ΄κ³΅κ³ λνμμ μν νΌμ€λ AI νν° μλΉμ€λ‘,
μ νν νμ΄ κ²μ¦ Β· μμ μΉνμ μΈ μ λ ₯ Β· νμ΅ λ£¨ν μ 곡μ λͺ©νλ‘ ν©λλ€.
λ³Έ μ μ₯μλ Proovy μλΉμ€μ Frontend(Web) μ½λλ² μ΄μ€μ΄λ©°,
ν λ¨μ νμ μ μ μ λ‘ ν λͺ νν 컨벀μ κ³Ό ꡬ쑰λ₯Ό λ°λ¦ λλ€.
| κ°λ―Όκ²½ | μ΄λ€μ | μ΄μΉμ€ | μ§νꡬ |
|---|---|---|---|
| κ°λ―Όκ²½ | μ΄λ€μ | μ΄μΉμ€ | μ§νꡬ |
- React ^19.2.0
- TypeScript ~5.9.3
Proovyλ μ±ν , μμ μ λ ₯, PDF λ·°μ΄, μ€μκ° μλν° λ± UI 볡μ‘λκ° λ§€μ° λμ νλ©΄λ€λ‘ ꡬμ±λμ΄ μμ΅λλ€.
- μ»΄ν¬λνΈ λ¨μ μ€κ³λ‘ μ±ν , λ·°μ΄, μΊλ²μ€, μν¬μ€νμ΄μ€λ₯Ό λͺ νν μ± μ λ¨μλ‘ λΆλ¦¬ κ°λ₯ν©λλ€.
- μν λ³νκ° λΉλ²ν λνν UIμ μ€μκ° μ€νΈλ¦¬λ°(SSE) κΈ°λ°μ λ©μμ§ μ λ°μ΄νΈμ μ΅μ νλμ΄ μμ΅λλ€.
Proovyμ λλ©μΈ λͺ¨λΈμ μ±ν λ©μμ§, μμ λ©νλ°μ΄ν°, API μλ΅ λ±μΌλ‘ κ΅¬μ‘°κ° λ³΅μ‘ν©λλ€.
- νμ μμ μ±μΌλ‘ λ°νμ μλ¬λ₯Ό μ¬μ μ λ°©μ§νκ³ , ν νμ μ νμ μ΄ μ체 λ¬Έμ μν μνν©λλ€.
- IDE μλμμ± μ§μμΌλ‘ κ°λ° μμ°μ±μ΄ ν₯μλ©λλ€.
- TailwindCSS v4
- λμμΈ ν ν°μ μ νΈλ¦¬ν° ν΄λμ€λ‘ κ΄λ¦¬νμ¬ UI μΌκ΄μ± μ μ§κ° μ©μ΄ν©λλ€.
- λΉ λ₯Έ UI νλ‘ν νμ΄νμ΄ κ°λ₯νμ¬ MVP κ°λ° μλλ₯Ό ν¬κ² ν₯μμν΅λλ€.
- Prettier νλ¬κ·ΈμΈκ³Ό ν¨κ» μ¬μ©νμ¬ μ€νμΌ μ½λ μ λ ¬ μλνκ° κ°λ₯ν©λλ€.
- pnpm
- Prettier
- ESLint
- λͺ¨λ Έλ ν¬ λ° λκ·λͺ¨ μμ‘΄μ± νκ²½μμ λμ€ν¬ μ¬μ©λκ³Ό μ€μΉ μλκ° ν¨μ¨μ μ λλ€.
- lockfile κΈ°λ°μΌλ‘ νμ κ° μμ ν λμΌν μμ‘΄μ± νκ²½μ 보μ₯ν©λλ€.
- μ½λ μ€νμΌ λ Όμμ μ κ±°νκ³ , 리뷰 ν¬μΈνΈλ₯Ό λ‘μ§μλ§ μ§μ€νκΈ° μν¨μ λλ€.
- μ»€λ° μ ν¬λ§·ν μ κ°μ νμ¬ μ½λ νμ§μ μΌμ μμ€ μ΄μμΌλ‘ μ μ§ν©λλ€.
- @tanstack/react-query: μλ² μν κ΄λ¦¬ λ° μΊμ±
- zustand: ν΄λΌμ΄μΈνΈ μν κ΄λ¦¬ (κ°κ²°νκ³ κ°λ²Όμ΄ ꡬ쑰)
- mathlive: μν μμ μ λ ₯ μ»΄ν¬λνΈ
- katex: LaTeX κΈ°λ° μμ λ λλ§
- remark-math, rehype-katex: markdown λ΄ μμ μ§μ
- pdfjs-dist: PDF λ λλ§ λ° λ·°μ΄
- react-markdown: markdown λ§ν¬μ λ λλ§
- tldraw: νμ΄νΈλ³΄λ λ° λλ‘μ κΈ°λ₯
- axios: HTTP ν΄λΌμ΄μΈνΈ
- msw: Mock Service Worker (κ°λ° νκ²½μμ API λͺ¨νΉ)
- lucide-react: μμ΄μ½ λΌμ΄λΈλ¬λ¦¬
- clsx, tailwind-merge: CSS ν΄λμ€ μ νΈλ¦¬ν°
- react-router-dom: ν΄λΌμ΄μΈνΈ λΌμ°ν
# κ°λ° μλ² μ€ν (<http://localhost:5173>)
pnpm run dev
# νλ‘λμ
λΉλ
pnpm run build
# ESLint κ²μ¬
pnpm run lint
# Prettier μ½λ ν¬λ§€ν
pnpm run format
# λΉλλ κ²°κ³Ό 미리보기
pnpm run previewVite μ€μ μμ @ aliasκ° src/ λλ ν 리λ₯Ό κ°λ¦¬ν€λλ‘ μ€μ λμ΄ μμ΅λλ€.
// β
μ’μ: @ alias μ¬μ©
import { useAuth } from "@/features/auth/hooks/useAuth";
import { ChatHeader } from "@/features/chat/components/ChatHeader";
import { cn } from "@/shared/lib/utils";
// β λμ μ: μλ κ²½λ‘
import { useAuth } from "../../../features/auth/hooks/useAuth";# μ μ₯μ ν΄λ‘
git clone <https://github.com/Team-Proovy/Proovy-front.git>
cd Proovy-front
# μμ‘΄μ± μ€μΉ (pnpm νμ)
pnpm install
# pnpmμ΄ μλ€λ©΄ μ€μΉ
npm install -g pnpm# .env νμΌ μμ±
cp .env.example .env # (λλ μλμΌλ‘ .env μμ±)
# .env νμΌμ λ€μ λ΄μ© μΆκ°
echo "VITE_API_BASE_URL=http://localhost:3000" >> .env
echo "VITE_ENV=development" >> .envκΆμ₯ νμ₯νλ‘κ·Έλ¨:
- ESLint (
dbaeumer.vscode-eslint) - Prettier - Code formatter (
esbenp.prettier-vscode) - TypeScript Vue Plugin (Volar) (
Vue.volar) - TypeScript μ§μ - Tailwind CSS IntelliSense (
bradlc.vscode-tailwindcss) - Tailwind μλμμ±
μλ ν¬λ§€ν
μ΄ μ μ₯ μ νμ±νλ©λλ€ (.vscode/settings.json μ€μ λ¨):
formatOnSave: true- κΈ°λ³Έ ν¬λ§€ν°: Prettier
- ESLint μλ μμ νμ±ν
# κ°λ° μλ² μμ
pnpm run dev
# λΈλΌμ°μ μμ <http://localhost:5173> μ μ# νμ
κ²μ¬
pnpm run build
# λ¦°νΈ κ²μ¬
pnpm run lint
# μ½λ ν¬λ§€ν
pnpm run formatsrc/
βββ app/ # μ± μ μ μ€μ
β βββ providers/ # μ μ Provider (React Query, MSW)
β βββ router/ # λΌμ°ν° μ€μ
β βββ styles/ # μ μ μ€νμΌ
β
βββ features/ # ν΅μ¬ λΉμ¦λμ€ κΈ°λ₯
β βββ auth/ # λ‘κ·ΈμΈ / μΈμ¦
β βββ chat/ # μ±ν
/ SSE
β βββ editor/ # μΊλ²μ€ / μμ μ
λ ₯ (tldraw, mathlive)
β βββ assets/ # μμ° μ
λ‘λ / νμΌ κ΄λ¦¬
β βββ notes/ # λ
ΈνΈ κ΄λ¦¬
β βββ search/ # κ²μ κΈ°λ₯
β βββ settings/ # μ€μ
β βββ sidebar/ # μ¬μ΄λλ°
β βββ storage/ # μ€ν λ¦¬μ§ κ΄λ¦¬
β βββ subscription/ # ꡬλ
λ° κ²°μ
β
βββ shared/ # μ μ κ³΅ν΅ λͺ¨λ
β βββ api/ # API ν΄λΌμ΄μΈνΈ (axios)
β βββ assets/ # μ΄λ―Έμ§ λ± μ μ μμ°
β βββ components/ # κ³΅μ© μ»΄ν¬λνΈ
β βββ hooks/ # κ³΅μ© ν
β βββ layout/ # λ μ΄μμ μ»΄ν¬λνΈ
β βββ lib/ # μ νΈλ¦¬ν° ν¨μ
β βββ utils/ # ν¬νΌ ν¨μ
β
βββ pages/ # νΉμ κΈ°λ₯μ΄ ν¬ν¨λμ§ μλ νμ΄μ§
β βββ HomePage.tsx
β βββ LandingPage.tsx
β
βββ mocks/ # Mock Service Worker (MSW)
β βββ browser.ts
β βββ handlers.ts
β βββ handlers/
β
βββ main.tsx # μ± μ§μ
μ main: μ€μ λ°°ν¬μ©dev: κ°λ° ν΅ν© λΈλμΉfeat/*: κΈ°λ₯ κ°λ° λΈλμΉ- κΈ°λ₯ ꡬνμ© λΈλμΉ
- λ°λμ
devμμ λΆκΈ°νκ³devλ‘ λ¨Έμ§
[Feature]: μλ‘μ΄ κΈ°λ₯ μΆκ°[Bug]: λ²κ·Έ λ°μ[Fix]: λ²κ·Έ μμ [Refactor]: μ½λ ꡬ쑰 κ°μ[Documentation]: λ¬Έμ μμ±/μμ [Chore]: νλ‘μ νΈ μ€μ λ° κΈ°ν μμ[Enhancement]: κΈ°λ₯ κ°μ μμ²- Issue λ°ν
- μ΄μ νλ(λΈλμΉ νλ)μμ νλμ κΈ°λ₯λ§ κ°λ°
- Issue ν
νλ¦Ώ (
.github/ISSUE_TEMPLATE/feature-request.md) μ¬μ© - μ΄μ μ λͺ© νμ:
[μ΄μ μ’ λ₯(λλ¬Έμλ‘ μμ)] μ΄μ_μ λͺ©- μ:
[Feat] example API ꡬν - μ:
[Fix] dev λΈλμΉ μΆ©λ ν΄κ²°
- μ:
- Assignees / Labels μ€μ
- λ‘컬 μ΅μ ν
git fetchgit pull
- Issue λ°ν
- κΈ°λ³Έ κ·μΉ:
feat/μ΄μλ²νΈ-κΈ°λ₯λͺ - μ€μ μ¬μ© ν¨ν΄(μ΄μ λΌλ²¨ λ°μ):
issue_label/issue_number-detail- μ:
feat/12-init-project - μ:
fix/3-add-login - μ:
refactor/22-cart-page
- μ:
- μ»€λ° μ μ ν리ν°μ΄ μ μ©νκΈ° β
pnpm run format.prettierc
- μ»€λ° μμ± μ Bodyμ μ»€λ° κ΄λ ¨ μ€λͺ μ μμΈνκ² μ κΈ°
- μ»€λ° λ©μμ§:
μ΄μμ’ λ₯: βμ»€λ° κ΄λ ¨ μ€λͺ β (#μ΄μλ²νΈ)
μμ)
feat: λ‘κ·ΈμΈ κ΅¬ν (#9)
fix: μΉ΄λ νμ΄μ§ μμ (#10)
refactor: μμ΄μ½ 리ν©ν λ§ (#13)- PR μ λͺ© κ·μΉ: νμ (#μ΄μλ²νΈ): prλ΄μ©
μμ)
Feat(#9): λ‘κ·ΈμΈ κ΅¬ν
Fix(#10): μΉ΄λ νμ΄μ§ μμ
Refactor(#13): μμ΄μ½ 리ν©ν λ§- PRμ λμΆ© μ°μ§ λ§κ³ νμμ΄ λ¦¬λ·°νκΈ° μ½λλ‘ μμκ³ μμΈνκ² μμ±νκΈ°
- ν μ€νΈ κ²°κ³Όλ₯Ό μ€ν¬λ¦°μ·μΌλ‘ PRμ ν¬ν¨νκΈ° (λ¨Έμ§ μ΄μ μ κ°μΈμ μΌλ‘ ν μ€νΈλ₯Ό κΌ νκΈ°)
- Pull Request μμ±
- PR ν
νλ¦Ώ μ¬μ© β
.github/PULL_REQUEST_TEMPLATE.md
- PR ν
νλ¦Ώ μ¬μ© β
devλΈλμΉλ‘μ λ¨Έμ§λ 2λͺ μ΄μμ Approveκ° νμνλ€.
1οΈβ£ νμΌ μμ± κ·μΉ (File Naming)
| νμΌ μ’ λ₯ | κ·μΉ (Case) | μμ (Example) | λΉκ³ |
|---|---|---|---|
| νμ΄μ§ & μ»΄ν¬λνΈ | PascalCase | LoginPage.tsx |
|
SidebarItem.tsx |
λλ¬Έμλ‘ μμ (.tsx) |
||
| ν (Hook) | camelCase | useAuth.ts |
|
| κ·Έ μΈ λͺ¨λ νμΌ | snake_case | auth_api.ts |
date_utils.ts
global_style.css | μλ¬Έμ + μΈλλ° (_) |
2οΈβ£ μ½λ μμ± κ·μΉ (Variable & Function)
| μ’ λ₯ | κ·μΉ (Case) | μμ (Example) |
|---|---|---|
| λ³μ (Variable) | camelCase | const userInfo = ... |
const isLoggedIn = true |
||
| ν¨μ (Function) | camelCase | const getUserData = () => {} |
function handleClick() {} |
||
| μ»΄ν¬λνΈ ν¨μ | PascalCase | export default function LoginPage() {} |
π λ³μλͺ λ€μ΄λ° κ·μΉ (Naming Convention)
| λ°μ΄ν° μ’ λ₯ (Type) | κ·μΉ (Rule) | μ’μ μμ (Good β ) | λμ μμ / λΉκ³ (Bad β) |
|---|---|---|---|
| λ°°μ΄ (Array) | 볡μν (-s) |
||
λλ List μ λ―Έμ¬ |
const users = [] |
||
const mediaList = [] |
const user = [] (λ¨μν κΈμ§) |
||
const medias (μ΄μν 볡μν μ§μ) |
|||
| κ°μ²΄ / λ¨μΌ κ° | λ¨μν | const currentUser = {...} |
|
const selectedId = 1 |
const users = {...} (κ°μ²΄μΈλ° 볡μν κΈμ§) |
||
| λΆλ¦¬μΈ (Boolean) | μν μ λμ¬ | ||
(is, has, should) |
const isLoading = true |
||
const hasError = false |
const loading = true (λμ¬ μμ) |
||
const check = false (λͺ¨νΈν¨) |
|||
| ν¨μ (Function) | λμ¬ + λͺ μ¬ | getUserData() |
|
handleClick() |
userData() (λͺ
μ¬λ§ μ¬μ© κΈμ§) |
||
process() (λ무 ν¬κ΄μ μ) |
- Issue μμ±: μμ λ΄μ©μ λͺ νν κΈ°μ νκ³ Assignee/Label μ€μ
- λΈλμΉ μμ±:
devλΈλμΉ μ΅μ ν νfeat/μ΄μλ²νΈ-κΈ°λ₯λͺλΈλμΉ μμ± - μ½λ 컨벀μ μ€μ: νμΌλͺ , λ³μλͺ , ν¨μλͺ μ΄ λͺ¨λ κ°μ΄λ λ°λ¦
- νμ μ μ: λͺ¨λ API μλ΅, Propsμ TypeScript νμ μ μ μλ£
- ν¬λ§€ν
: κ°λ° μ€μλ μμ£Ό
pnpm run formatμ€ν - λ¦°νΈ: λ¦°νΈ μλ¬ μλ μν μ μ§ (
pnpm run lint) - νμ
체ν¬: νμ
μλ¬ μμ (
pnpm run buildμ±κ³΅) - λ‘λ©/μλ¬ μν: λΉλκΈ° μμ
μ
isLoading,errorμν νμ
- ν¬λ§€ν
:
pnpm run formatμ€ν - λ¦°νΈ:
pnpm run lintκ²μ¬ ν΅κ³Ό - λΉλ:
pnpm run buildμ±κ³΅ - κΈ°λ₯ ν μ€νΈ: ν΄λΉ κΈ°λ₯μ΄ μλλλ‘ μλνλμ§ νμΈ
- μ€ν¬λ¦°μ·: μ£Όμ UI λ³κ²½μ¬ν μ€ν¬λ¦°μ· μ€λΉ
- PR μ λͺ©:
νμ (#μ΄μλ²νΈ): μ€λͺνμ μ€μ - 체ν¬λ¦¬μ€νΈ: PR ν νλ¦Ώμ λͺ¨λ νλͺ© 체ν¬
- 리뷰 μμ²: μ΅μ 2λͺ μ΄μμ λ¦¬λ·°μ΄ μ§μ
- Approve ν보: 2λͺ μ΄μ Approve ν λ¨Έμ§
κ°λ° μ€ λ°μν λ¬Έμ μ ν΄κ²° λ°©λ²μ μλ λ§ν¬λ₯Ό μ°Έκ³ νμΈμ:
μ£Όμ λ΄μ©:
- νκ²½ μ€μ μ€λ₯
- μμ‘΄μ± μ€μΉ λ¬Έμ
- κ°λ° μλ² μ€ν μ€λ₯
- νμ /λ¦°νΈ μ€λ₯ ν΄κ²°
- API μ°λ κ΄λ ¨ λ¬Έμ