LeeYoonSam / duolingo-clone

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Duo Lingo Clone

πŸ‘¨β€πŸ’» Source Code & More: https://www.codewithantonio.com/projects/duolingo-clone GitHub: https://github.com/AntonioErdeljac/next14-duolingo-clone

라이브러리

ν•™μŠ΅ μš”μ•½

  • 이 11μ‹œκ°„μ§œλ¦¬ νŠœν† λ¦¬μ–Όμ—μ„œλŠ” λ“€μ˜€λ§κ³ μ™€ μœ μ‚¬ν•œ λ‚˜λ§Œμ˜ μ–Έμ–΄ ν•™μŠ΅ SaaSλ₯Ό λ§Œλ“œλŠ” 방법을 λ°°μ›λ‹ˆλ‹€.
  • μ‚¬μš©μžλŠ” μ–Έμ–΄ μ½”μŠ€λ₯Ό 선택할 수 있으며 μ•„λ¦„λ‹€μš΄ λ””μžμΈ, 캐릭터, μ˜€λ””μ˜€ 및 μ‹œκ° νš¨κ³Όκ°€ ν¬ν•¨λœ κ°€μ΄λ“œ λ ˆμŠ¨μ„ 받을 수 μžˆμŠ΅λ‹ˆλ‹€.
  • Next.js 14, Drizzle ORM, PostgreSQL, μ„œλ²„ μ•‘μ…˜, Stripe, ShadcnUI, Tailwind 등을 배우게 λ©λ‹ˆλ‹€.

Key Features

  • 🌐 Next.js 14 & server actions
  • πŸ—£ AI Voices using Elevenlabs AI
  • 🎨 Beautiful component system using Shadcn UI
  • 🎭 Amazing characters thanks to KenneyNL
  • πŸ” Auth using Clerk
  • πŸ”Š Sound effects
  • ❀️ Hearts system
  • 🌟 Points / XP system
  • πŸ’” No hearts left popup
  • πŸšͺ Exit confirmation popup
  • πŸ”„ Practice old lessons to regain hearts
  • πŸ† Leaderboard
  • πŸ—Ί Quests milestones
  • πŸ› Shop system to exchange points with hearts
  • πŸ’³ Pro tier for unlimited hearts using Stripe
  • 🏠 Landing page
  • πŸ“Š Admin dashboard React Admin
  • 🌧 ORM using DrizzleORM
  • πŸ’Ύ PostgresDB using NeonDB
  • πŸš€ Deployment on Vercel
  • πŸ“± Mobile responsiveness

Intro & Demo

Project Setup

  • shadcn/ui μ„€μ •
npx shadcn-ui@latest init
βœ” Which style would you like to use? β€Ί Default
βœ” Which color would you like to use as base color? β€Ί Slate
βœ” Would you like to use CSS variables for colors? … no / yes
  • tailwind extention μ„€μΉ˜
  • shadcn-ui button μΆ”κ°€
    • npx shadcn-ui@latest add button

Buttons library

  • app/buttons/page.tsx 생성
    • Variants 별 λ²„νŠΌ ν™”λ©΄
  • components/ui/button.tsx μˆ˜μ •
    • λ²„νŠΌ variants, sizse 전체 μ»€μŠ€ν„°λ§ˆμ΄μ§•

Marketing Skeleton

  • app/page.tsx -> app/(marketing)/page.tsx 폴더 이동
  • app/(marketing)/layout.tsx 생성
    • 곡톡 λ ˆμ΄μ•„μ›ƒ 생성 (헀더, ν‘Έν„°)
  • app/(marketing)/header.tsx 생성
    • 헀더 정보
  • app/(marketing)/footer.tsx 생성
    • ν‘Έν„° 정보

Authentication

  • Clerk μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ 생성 및 μ„€μ •
  • npm install @clerk/nextjs
  • .env 생성
    • clerk key μΆ”κ°€
  • middleware.ts 생성
    • publicRoutes μΆ”κ°€
  • app/layout.tsx μˆ˜μ •
    • ClerkProvider μΆ”κ°€
  • svg 이미지 μΆ”κ°€
    • public/hero.svg
    • public/mascot.svg
  • app/(marketing)/header.tsx μˆ˜μ •
    • 둜고 μΆ”κ°€
    • 둜그인 λ²„νŠΌ μΆ”κ°€
  • app/(marketing)/page.tsx μˆ˜μ •
    • 둜그인 / 미둜그인 μƒνƒœμ— 따라 UI λΆ„κΈ° 처리

Footer

  • public κ΅­κΈ° 이미지 μΆ”κ°€
  • app/(marketing)/footer.tsx μˆ˜μ •
    • ꡭ가별 κ΅­κΈ° μΆ”κ°€

Main Layout

  • app/(main)/layout.tsx 생성
    • 메인 폴더 λ ˆμ΄μ•„μ›ƒ μΆ”κ°€
  • app/(main)/learn/page.tsx 생성
    • learn νŽ˜μ΄μ§€ μΆ”κ°€
  • components/sidebar.tsx 생성
    • sidebarλŠ” μž¬μ‚¬μš© κ°€λŠ₯ν•˜λ„λ‘ λ§Œλ“€κΈ° μœ„ν•΄ componenets 에 λ§Œλ“¦
  • components/mobile-header.tsx 생성
    • lg μ΄ν•˜μ—μ„œλŠ” λͺ¨λ°”일 헀더가 보이도둝 ꡐ체
  • components/mobile-sidebar.tsx 생성
    • λͺ¨λ°”일 ν—€λ”μ—μ„œ 보여쀄 mobile 용 μ‚¬μ΄λ“œλ°”
    • Sheet 둜 κ΅¬ν˜„ν•΄μ„œ sidebar λ₯Ό μž¬ν™œμš©ν•΄μ„œ ν‘œν˜„

dependencies

  • npx shadcn-ui@latest add sheet
    • MobileHeader μ—μ„œ μ‚¬μš©ν•  sheet

Sidebar

  • public μ‚¬μ΄λ“œλ°” 이미지 μΆ”κ°€
  • components/sidebar-item.tsx 생성
    • μ‚¬μ΄λ“œλ°” 메뉴 μ•„μ΄ν…œ μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
    • μ•„μ΄μ½˜, 라벨, 링크 λ°μ΄ν„°λ‘œ 메뉴 λ§Œλ“€κΈ°
  • components/sidebar.tsx μˆ˜μ •
    • μ‚¬μ΄λ“œλ°” μ•„μ΄ν…œ μ»΄ν¬λ„ŒνŠΈ 적용

Learn Page Wrappers

  • app/(main)/layout.tsx μˆ˜μ •
    • λ ˆλ“œ λ°°κ²½ 제거 및 css μˆ˜μ •
  • app/(main)/learn/page.tsx μˆ˜μ •
    • StickyWrapper μΆ”κ°€
    • FeedWrapper μΆ”κ°€
  • components/sticky-wrapper.tsx 생성
    • StickyWrapper μ»΄ν¬λ„ŒνŠΈ
  • components/feed-wrapper.tsx 생성
    • FeedWrapper μ»΄ν¬λ„ŒνŠΈ
  • app/(main)/learn/header.tsx 생성
    • 헀더 μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
    • λ’€λ‘œκ°€κΈ° λ²„νŠΌ, 타이틀 μΆ”κ°€
  • components/user-progress.tsx 생성
    • 였λ₯Έμͺ½μ— κ³ μ •λœ μœ μ € 진행상황 μ»΄ν¬λ„ŒνŠΈ
  • 이미지 μΆ”κ°€
    • public/heart.svg
    • public/points.svg

Drizzle & Neon

  • 둜컬 mysql λ‘œλŠ” μ–΄λ–»κ²Œ μ‚¬μš©ν•˜λŠ”μ§€ κ²€ν† !
  • λ“œλ¦¬μ¦ μŠ€νŠœλ””μ˜€λŠ” λ“œλ¦¬μ¦ ꡬ성 νŒŒμΌμ„ 가져와 λ°μ΄ν„°λ² μ΄μŠ€μ— μ—°κ²°ν•˜κ³  κΈ°μ‘΄ λ“œλ¦¬μ¦ SQL μŠ€ν‚€λ§ˆλ₯Ό 기반으둜 λͺ¨λ“  것을 탐색, μΆ”κ°€, μ‚­μ œ 및 μ—…λ°μ΄νŠΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λͺ…μ‹œμ  null 및 빈 λ¬Έμžμ—΄ κ°’, λΆ€μšΈ, 숫자 및 큰 μ •μˆ˜, json 객체 및 json 배열을 μ§€μ›ν•©λ‹ˆλ‹€.

DrizzleKit Setting

  • Dependencies
    • npm i drizzle-orm @neondatabase/serverless
    • npm i -D drizzle-kit
  • package.json
    • script μž‘μ„±
    • npm run db:studio
    • npm run db:push
  • db/drizzle.ts 생성
    • λ””λΉ„ μ—°κ²° 정보
  • db/schema.ts 생성
    • μŠ€ν‚€λ§ˆ 정보
  • npm i dotenv
    • DotenvλŠ” .env νŒŒμΌμ—μ„œ process.env둜 ν™˜κ²½ λ³€μˆ˜λ₯Ό λ‘œλ“œν•˜λŠ” 제둜 쒅속성 λͺ¨λ“ˆμž…λ‹ˆλ‹€.
    • μ½”λ“œμ™€ λ³„λ„λ‘œ ν™˜κ²½μ— ꡬ성을 μ €μž₯ν•˜λŠ” 것은 12μš”μ†Œ μ•± 방법둠을 기반으둜 ν•©λ‹ˆλ‹€.
  • npm run db:push
  • npm i -D pg
  • npm run db:studio

MySQL 간단 λͺ…λ Ήμ–΄

mysql> status;
  • mysql μ—°κ²° μƒνƒœ 정보
mysql> show databases;
  • Database 리슀트
mysql> CREATE DATABASE somedatabase;
  • λ°μ΄ν„°λ² μ΄μŠ€ 생성
mysql> select database();
  • ν˜„μž¬ μ‚¬μš©μ€‘μΈ 데이터 베이슀 쑰회
mysql> use somedatabase;
Database changed

Courses Page

  • db/queries.ts 생성
    • getCourses - μ½”μŠ€ 데이터 κ°€μ Έμ˜€κΈ°
  • app/(main)/courses/page.tsx 생성
    • /courses νŽ˜μ΄μ§€ 생성
    • μ–Έμ–΄ μ½”μŠ€ 선택
  • app/(main)/courses/list.tsx 생성
    • μ½”μŠ€ 선택 리슀트 μ»΄ν¬λ„ŒνŠΈ
  • app/(main)/courses/card.tsx 생성
    • μ½”μŠ€ 선택 μ•„μ΄ν…œ μΉ΄λ“œ μ»΄ν¬λ„ŒνŠΈ
    • κ΅­κΈ°, μ–Έμ–΄ 이름 ν‘œμ‹œ

User Progress

  • db/schema.ts μˆ˜μ •
    • userProgress ν…Œμ΄λΈ” μΆ”κ°€
    • courses, userProgress κ°„ relation μ—°κ²°
  • db/queries.ts μˆ˜μ •
    • μœ μ €μ— ν•΄λ‹Ήν•˜λŠ” μ½”μŠ€ 진행 상황 κ°€μ Έμ˜€κΈ°
    • μ½”μŠ€ 정보 κ°€μ Έμ˜€κΈ°
  • app/(main)/courses/page.tsx μˆ˜μ •
    • UserProgress κ°€μ Έμ™€μ„œ μ½”μŠ€ ν‘œμ‹œ
  • app/(main)/learn/page.tsx μˆ˜μ •
    • UserProgress 정보λ₯Ό κ°€μ Έμ™€μ„œ 진행쀑인 μ½”μŠ€κ°€ μ—†μœΌλ©΄ λ¦¬λ””λ ‰νŠΈ 처리
  • loading.tsx 생성
    • Page root 에 loading 을 μΆ”κ°€ν•˜λ©΄ μžλ™μœΌλ‘œ ν‘œμ‹œλ¨
  • actions/user-progress.ts 생성
    • User 진행 상황을 λ°μ΄ν„°λ² μ΄μŠ€μ— μ—…λ°μ΄νŠΈ ν•  μ•‘μ…˜
  • app/(main)/courses/list.tsx μˆ˜μ •
    • μ½”μŠ€ ν΄λ¦­μ‹œ μ½”μŠ€ 정보 μ €μž₯ 및 Learn νŽ˜μ΄μ§€ λ¦¬λ””λ ‰νŠΈ
    • μΉ΄λ“œ 클릭 μ—°κ²°
  • app/layout.tsx μˆ˜μ •
    • Body μ•ˆμ— Toaster μΆ”κ°€

dependencies

  • npx shadcn-ui@latest add sonner
    • ν† μŠ€ν„°

Note

  • upsert: UPDATE + INSERT ν•©μ„±μ–΄

Seed Script

  • scripts/seed.ts 생성
    • λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” 및 μ–Έμ–΄ ν•™μŠ΅ 정보 기초 생성
  • package.json μˆ˜μ •
    • seed.ts μ‹€ν–‰ 슀크립트 μΆ”κ°€
    • tsx 라이브러리 μ‚¬μš©
  • app/(main)/learn/page.tsx μˆ˜μ •
    • μ„ νƒλœ μ–Έμ–΄ μ½”μŠ€λ‘œ 헀더 타이틀 λ³€κ²½

dependencies

  • npm i -D tsx
    • νƒ€μž… 슀크립트 μ‹€ν–‰

Notes

  • tsx λŒ€μ•ˆμœΌλ‘œ bun μ‚¬μš© κ°€λŠ₯
    • Bun으둜 JavaScript 및 TypeScript ν”„λ‘œμ νŠΈλ₯Ό 개발, ν…ŒμŠ€νŠΈ, μ‹€ν–‰, λ²ˆλ“€λ§
    • Bun은 λ²ˆλ“€λŸ¬, ν…ŒμŠ€νŠΈ 런처, Node.js ν˜Έν™˜ νŒ¨ν‚€μ§€ κ΄€λ¦¬μžκ°€ ν¬ν•¨λœ 속도λ₯Ό μœ„ν•΄ μ„€κ³„λœ μ˜¬μΈμ› JavaScript λŸ°νƒ€μž„ 및 νˆ΄ν‚·

Schema

  • db/schema.ts μˆ˜μ •
    • table, relations 생성
      • units
      • lessons
      • challenges
      • challenge_options
      • challenge_progress

Units

  • scripts/seed.ts μˆ˜μ •
    • unit, lessons, challenge, challengeOptions 초기 λ°μ΄ν„°λ² μ΄μŠ€ μΆ”κ°€
  • db/queries.ts μˆ˜μ •
    • getUnits μž‘μ„±
  • app/(main)/learn/page.tsx μˆ˜μ •
    • unit 정보 Json 으둜 μž„μ‹œ ν‘œμ‹œ

Lesson Button

  • db/queries.ts μˆ˜μ •
    • getUnit - userId 비ꡐ μΆ”κ°€
  • app/(main)/learn/page.tsx μˆ˜μ •
    • Unit μΆ”κ°€
  • app/(main)/learn/unit.tsx 생성
    • Unit μ»΄ν¬λ„ŒνŠΈ
  • app/(main)/learn/unit-banner.tsx 생성
    • μ½”μŠ€ κ²½λ‘œμ—μ„œ λ³΄μ—¬μ§€λŠ” 레슨 μœ λ‹› λ°°λ„ˆ
  • app/(main)/learn/lesson-button.tsx 생성
    • μ½”μŠ€ κ²½λ‘œμ—μ„œ λ³΄μ—¬μ§€λŠ” 레슨 λ²„νŠΌ
    • react-circular-progressbar λ₯Ό μ‚¬μš©ν•΄μ„œ λ²„νŠΌμ˜ 진행상황 ν‘œμ‹œ

dependencies

  • npm i react-circular-progressbar
    • SVG둜 μ œμž‘λ˜κ³  κ΄‘λ²”μœ„ν•˜κ²Œ μ‚¬μš©μž μ •μ˜ν•  수 μžˆλŠ” μ›ν˜• 진행λ₯  ν‘œμ‹œμ€„ μ»΄ν¬λ„ŒνŠΈμž…λ‹ˆλ‹€.

Course Progress

  • db/queries.ts μˆ˜μ •
    • getCourseProgress: 진행쀑인 μ½”μŠ€ κ°€μ Έμ˜€κΈ°
    • getLesson: 레슨 정보 κ°€μ Έμ˜€κΈ°
    • getLessonPercentage: 레슨 진행λ₯  κ°€μ Έμ˜€κΈ°
  • app/(main)/learn/page.tsx μˆ˜μ •
    • activeLesson λ””λΉ„ 데이터 μΆ”κ°€
    • activeLessonPercentage λ””λΉ„ 데이터 μΆ”κ°€

Lesson Header

  • db/queries.ts μˆ˜μ •
    • normalizedData μ±Œλ¦°μ§€κ°€ μ—†μ„λ•Œ μ˜ˆμ™Έ 처리 μΆ”κ°€
  • app/lesson/layout.tsx 생성
    • lesson κΈ°λ³Έ λ ˆμ΄μ•„μ›ƒ
  • app/lesson/page.tsx 생성
    • 레슨 νŽ˜μ΄μ§€
  • app/lesson/quiz.tsx 생성
    • ν€΄μ¦ˆ μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
  • app/lesson/header.tsx 생성
    • 헀더 μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
    • shadcn-ui progress μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€

dependencies

  • npx shadcn-ui@latest add progress

Exit Modal

  • dialog, zustand λ””νŽœλ˜μ‹œ μΆ”κ°€
  • store/use-exit-modal.ts 생성
    • exit modal μƒνƒœκ΄€λ¦¬
  • app/layout.tsx μˆ˜μ •
    • μ „μ—­ ExitModal μΆ”κ°€
  • app/lesson/header.tsx μˆ˜μ •
    • X λ²„νŠΌ 클릭 μ—°κ²°
  • components/modals/exit-modal.tsx μΆ”κ°€
    • μ’…λ£Œ νŒμ—… μΆ”κ°€

dependencies

  • npx shadcn-ui@latest add dialog
  • npm i zustand
    • μž‘κ³  λΉ λ₯΄λ©° ν™•μž₯ κ°€λŠ₯ν•œ λ² μ–΄λ³Έ μƒνƒœ 관리 μ†”λ£¨μ…˜μž…λ‹ˆλ‹€.
    • ZustandλŠ” 훅을 기반으둜 ν•˜λŠ” νŽΈμ•ˆν•œ APIλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€
    • μ’€λΉ„ μžμ‹ 문제, React λ™μ‹œμ„±, ν˜Όν•© λ Œλ”λŸ¬ κ°„μ˜ μ»¨ν…μŠ€νŠΈ 손싀과 같은 일반적인 함정을 처리

Challenge Cards

  • app/lesson/quiz.tsx μˆ˜μ •
  • app/lesson/question-bubble.tsx 생성
    • 질문 버블 μ»΄ν¬λ„ŒνŠΈ
  • app/lesson/challenge.tsx 생성
    • 질문 μ»΄ν¬λ„ŒνŠΈ
  • app/lesson/card.tsx 생성
    • μ •λ‹΅ μΉ΄λ“œ μ»΄ν¬λ„ŒνŠΈ
    • correct, wrong 에 따라 μΉ΄λ“œ μƒνƒœ λ³€κ²½

Challenge Footer

  • Elevenlabs AI 둜 μ†Œλ¦¬ λ§Œλ“€κΈ°
  • app/lesson/card.tsx μˆ˜μ •
    • 보기 ν΄λ¦­μ‹œ μŒμ„± μž¬μƒ μΆ”κ°€
    • react-use 의 useAudio μ‚¬μš©ν•˜κΈ°
  • app/lesson/quiz.tsx μˆ˜μ •
    • Footer μΆ”κ°€
  • app/lesson/footer.tsx 생성
    • Footer μ»΄ν¬λ„ŒνŠΈ
    • status 에 따라 μƒνƒœ λ³€κ²½

public μŒμ„± 파일 μΆ”κ°€

  • es_man.mp3
  • es_woman.mp3
  • es_robot.mp3

dependencies

  • npm i react-use

Challenge Actions

  • app/lesson/quiz.tsx μˆ˜μ •
    • footer check λ²„νŠΌ μ˜΅μ…˜ μ„ νƒμ‹œ 둜직 μΆ”κ°€
      • μ„±κ³΅μ‹œ λ‹€μŒ 문제 진행
      • μ‹€νŒ¨μ‹œ ν•˜νŠΈ κ°μ†Œ 및 λ‹€μ‹œμ‹œλ„ 처리
  • actions/challenge-progress.ts 생성
    • 문제 μ •λ‹΅ μ‹œ 진행 상황 μ—…λ°μ΄νŠΈ
  • scripts/seed.ts μˆ˜μ •
    • 문제 μΆ”κ°€
  • actions/user-progress.ts μˆ˜μ •
    • 디비에 ν•˜νŠΈ κ°μ†Œμ‹œν‚€λŠ” 둜직 μΆ”κ°€

Challenge Finish Screen

  • μ •λ‹΅, μ˜€λ‹΅, μ™„λ£Œ μŒμ„± 파일 μΆ”κ°€
  • scripts/seed.ts μˆ˜μ •
    • κΈ°λ³Έ μ±Œλ¦°μ§€ μΆ”κ°€
  • app/lesson/quiz.tsx μˆ˜μ •
    • μ±Œλ¦°μ§€ μ™„λ£Œ ν‘œμ‹œ
    • 보기 μ„ νƒμ‹œ μ˜€λ””μ˜€ μž¬μƒ
    • μ±Œλ¦°μ§€ μ™„λ£Œ μ˜€λ””μ˜€ μžλ™μž¬μƒ
    • confetti 효과 μΆ”κ°€
  • app/lesson/result-card.tsx 생성
    • μ±Œλ¦°μ§€ μ™„λ£Œ κ²°κ³Ό μΉ΄λ“œ μ»΄ν¬λ„ŒνŠΈ

λ¦¬μ†ŒμŠ€ μΆ”κ°€

  • correct.wav
  • incorrect.wav
  • finish.mp3
  • finish.svg

dependencies

  • npm i react-confetti
    • 쒅이 꽃가루 μ• λ‹ˆλ©”μ΄μ…˜

Challenge Practice

  • store/use-hearts-modal.ts 생성
    • ν•˜νŠΈλͺ¨λ‹¬μ—μ„œ μ‚¬μš©ν•  store
  • components/modals/hearts-modal.tsx 생성
    • ν•˜νŠΈλͺ¨λ‹¬ μ»΄ν¬λ„ŒνŠΈ
  • app/layout.tsx μˆ˜μ •
    • HeartsModal μΆ”κ°€
  • app/lesson/quiz.tsx μˆ˜μ •
    • μ •λ‹΅/μ˜€λ‹΅ μ„ νƒμ‹œ Heart κ°€ μ—†μœΌλ©΄ λͺ¨λ‹¬ νŒμ—… ν‘œμ‹œ

λ¦¬μ†ŒμŠ€ μΆ”κ°€

  • mascot_bad.svg

Shop

  • app/(main)/shop/page.tsx 생성
    • 상점 νŽ˜μ΄μ§€
  • app/(main)/shop/items.tsx 생성
    • μƒμ μ˜ μ•„μ΄ν…œμ„ ν‘œμ‹œν•˜λŠ” μ»΄ν¬λ„ŒνŠΈ
  • actions/user-progress.ts μˆ˜μ •
    • refillHearts μΆ”κ°€
      • point 차감 ν›„ ν•˜νŠΈ ν’€ 리필

Stripe

  • db/schema.ts μˆ˜μ •
    • userSubscription ν…Œμ΄λΈ” 생성
  • stripe μ„€μΉ˜
  • lib/stripe.ts 생성
    • Stripe 객체 생성
      • API 버전, Stripe secret ν‚€ μΆ”κ°€
  • .env μˆ˜μ •
    • Stripe λŒ€μ‹œλ³΄λ“œ account 생성
    • STRIPE_API_KEY μΆ”κ°€
  • λ¦¬μ†ŒμŠ€ μΆ”κ°€
    • public/unlimited.svg
  • actions/user-subscription.ts 생성
    • ꡬ독 정보 및 callback url 지정
    • stripe μ„Έμ…˜ 생성
      • 결제 데이터
      • 메타 데이터
  • db/queries.ts μˆ˜μ •
    • ν˜„μž¬ ꡬ독쀑인지 정보 λ°˜ν™˜
  • lib/utils.ts μˆ˜μ •
    • μ ˆλŒ€ 경둜 생성 μœ ν‹Έ μΆ”κ°€
  • app/(main)/shop/page.tsx μˆ˜μ •
    • ν”„λ‘œ 버전 μ‚¬μš© μœ λ¬΄μ— 따라 μ»΄ν¬λ„ŒνŠΈ μƒνƒœ λ³€κ²½
  • app/(main)/shop/items.tsx μˆ˜μ •
    • onUpgrade ν”„λ‘œ 버전 μ „ν™˜
  • Stripe Webhooks μ„€μ •
    • app/api/webhooks/stripe/route.ts 생성
      • stripe κ²°μ œκ°€ 200으둜 μ„±κ³΅ν•˜λ©΄ DB userSubscription ν…Œμ΄λΈ”μ— ꡬ독 정보 및 만료 일자 μΆ”κ°€
      • checkout, invoice 에 따라 insert, update 둜 처리
    • .env - STRIPE_WEBHOOK_SECRET μΆ”κ°€
    • middleware.ts μˆ˜μ •
      export default authMiddleware({
        publicRoutes: ["/", "/api/webhooks/stripe"],
      });
      • publicRoutes webhooks api μΆ”κ°€

Stripe Webhooks μ„€μ •

  1. stripe 둜그인
stripe login

Your pairing code is: lively-merit-rosy-serene
This pairing code verifies your authentication with Stripe.
Press Enter to open the browser or visit https://dashboard.stripe.com/stripecli/confirm_auth?t=some_key
  • 링크둜 이동
    • Stripe CLIκ°€ 계정 정보에 μ•‘μ„ΈμŠ€ν•  수 μžˆλ„λ‘ ν—ˆμš©ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? Allow access 클릭
    • Access granted - CLI 둜 λŒμ•„κ°€κΈ°
  1. stripe listen forward 경둜 μ„€μ •
stripe listen --forward-to localhost:3000/api/webhooks/stripe

A newer version of the Stripe CLI is available, please update to: v1.19.4
> Ready! You are using Stripe API Version [2024-04-10]. Your webhook signing secret is your_secret (^C to quit)
  • forward-to 경둜λ₯Ό μ‹€μ œ webhooks μ²˜λ¦¬ν• κ³³μœΌλ‘œ λ³€κ²½
  • your_secret 뢀뢄이 μ‚¬μš©ν•  secret key
  • stripe 이벀트 트리거 이후 -> http status code 200 확인
  1. stripe CLI둜 이벀트 트리거
stripe trigger payment_intent.succeeded
  1. Settings > Billing > Customer portal - ν…ŒμŠ€νŠΈ 링크 ν™œμ„±ν™” Launch customer portal with a link
  • Activate test link
  • ν™œμ„±ν™” ν•˜λ©΄ 이미 결제 ν›„ 결제 λ²„νŠΌ ν΄λ¦­μ‹œ 고객 결제 정보 ν¬ν„Έλ‘œ 이동

μ˜ˆμ „μ— 이미 CLI λ₯Ό μ‚¬μš©ν•΄μ„œ μ˜€λž˜λ˜μ–΄μ„œ ν‚€ 만료

  • Stripe - Error codes api_key_expired
  • The API key provided has expired. Obtain your current API keys from the Dashboard and update your integration to use them.
  • Restricted keys λ₯Ό μƒˆλ‘œ Roll key ν•΄μ„œ μž¬μƒμ„±
  • stripe login λΆ€ν„° λ‹€μ‹œ CLI λͺ…λ Ήμ–΄ μ‹€ν–‰ ν•˜λ©΄ μ™„λ£Œ

Drizzle 버전 λ³€κ²½μœΌλ‘œ μΈν•œ config μ„€μ • 방법 λ³€κ²½

as-is

import "dotenv/config";
import type { Config } from "drizzle-kit";

export default {
  schema: "./db/schema.ts",
  out: "./drizzle",
  driver: "pg",
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
} satisfies Config;

to-be

import { defineConfig } from 'drizzle-kit'
export default defineConfig({
  dialect: "postgresql",
  schema: "./db/schema.ts",
  out: "./drizzle",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  }
})

Note

μ›Ήν›… listen 을 μ‹€ν–‰ν•˜κ³  결제 ν…ŒμŠ€νŠΈλ₯Ό ν•΄μ•Ό 웹훅이 μ •μƒμ μœΌλ‘œ μž‘λ™!

  • stripe listen --forward-to localhost:3000/api/webhooks/stripe

dependencies

  • npm i stripe

Details

  • app/(main)/learn/page.tsx μˆ˜μ •
    • 상단 진행 상황 헀더 userSubscription μƒνƒœ 반영
  • app/lesson/page.tsx μˆ˜μ •
    • ν€΄μ¦ˆ μ»΄ν¬λ„ŒνŠΈμ— userSubscription μƒνƒœ 전달
  • app/lesson/quiz.tsx μˆ˜μ •
    • Props userSubscription type λ³€κ²½
  • app/lesson/header.tsx μˆ˜μ •
    • infinite μ•„μ΄μ½˜ shirink-0 적용
  • actions/user-progress.ts μˆ˜μ •
    • 리필 카운트 μƒμˆ˜ 이동
    • μ˜ˆμ™Έμ²˜λ¦¬ ν™œμ„±ν™”
  • db/queries.ts μˆ˜μ •
    • getUnits μ •λ ¬ 적용
  • constants.ts μˆ˜μ •
    • 곡톡 μƒμˆ˜ 관리
  • app/(main)/leaderboard/page.tsx 생성
    • λ¦¬λ”λ³΄λ“œ νŽ˜μ΄μ§€ μΆ”κ°€
    • Separator, Avatar - shadcnui μΆ”κ°€
    • λ¦¬λ”λ³΄λ“œ λž­ν‚Ή λ·° μΆ”κ°€
  • db/queries.ts μˆ˜μ •
    • μƒμœ„ 점수 10λͺ…을 κ°€μ Έμ˜€λŠ” getTopTenUsers μΆ”κ°€
  • app/(main)/quests/page.tsx 생성
    • ν€˜μŠ€νŠΈ νŽ˜μ΄μ§€ μΆ”κ°€
    • ν€˜μŠ€νŠΈ ν•­λͺ© μΆ”κ°€

Propmo / Qeust

  • components/promo.tsx 생성
    • ν”„λ‘œλͺ¨μ…˜ μ»΄ν¬λ„ŒνŠΈ
  • components/quests.tsx 생성
    • ν€˜μŠ€νŠΈ μ»΄ν¬λ„ŒνŠΈ
  • μ‚¬μ΄λ“œλ°” 메뉴에 ν”„λ‘œλͺ¨μ…˜/ν€˜μŠ€νŠΈ μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€
    • app/(main)/learn/page.tsx
    • app/(main)/shop/page.tsx
    • app/(main)/leaderboard/page.tsx
    • app/(main)/quests/page.tsx

Note

Flex Shrink

  • flex ν•­λͺ© μΆ•μ†Œ 방식을 μ œμ–΄ν•˜κΈ° μœ„ν•œ μœ ν‹Έλ¦¬ν‹°
  • flex ν•­λͺ©μ΄ μΆ•μ†Œλ˜μ§€ μ•Šλ„λ‘ ν•˜λ €λ©΄ shrink-0 을 μ‚¬μš©

dependencies

  • npx shadcn-ui@latest add avatar
  • npx shadcn-ui@latest add separator

Admin

Admin - ReactAdmin Base

  • dependencies μΆ”κ°€
    • npm i react-admin ra-data-simple-rest
  • app/admin/page.tsx 생성
    • Admin Page
  • app/admin/app.tsx 생성
    • React Admin Page
    • Courses λ¦¬μ†ŒμŠ€ μΆ”κ°€
  • app/api/courses/route.ts 생성
    • GET λ©”μ†Œλ“œ 생성
    • Admin Resource 둜 μ‚¬μš© ν•  Courses λ₯Ό λ””λΉ„μ—μ„œ 쑰회 ν›„ 제곡
  • next.config.mjs μˆ˜μ •
    • source / headers μΆ”κ°€

Admin - Manage admin site access

  • lib/admin.ts 생성
    • adminIds 배열에 μ–΄λ“œλ―Ό κ΄€λ¦¬μž userId λ₯Ό λ„£κ³  λΉ„κ΅ν•΄μ„œ admin 유무 νŒλ‹¨
  • app/admin/page.tsx μˆ˜μ •
    • admin 이 μ•„λ‹ˆλ©΄ redirect
  • app/api/courses/route.ts μˆ˜μ •
    • admin 이 μ•„λ‹ˆλ©΄ 401 μ—λŸ¬ λ°˜ν™˜

Admin Course - List, Create, Edit, Delete

  • app/admin/course/list.tsx μˆ˜μ •

    • μ»€μŠ€ν…€ 리슀트 μΆ”κ°€
    • ReactAdmin μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©
  • app/admin/app.tsx μˆ˜μ •

    • list, create, edit(delete) μΆ”κ°€
  • app/api/courses/route.ts μˆ˜μ •

    • POST λ©”μ„œλ“œ μΆ”κ°€
  • app/admin/course/create.tsx 생성

    • λ””λΉ„ 데이터 생성
    • POST 둜 데이터 생성
    • ReactAdmin μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©
  • scripts/reset.ts μΆ”κ°€

    • λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” 슀크립트
  • app/admin/course/edit.tsx 생성

    • λ””λΉ„ 데이터 μˆ˜μ •, μ‚­μ œ μΆ”κ°€
    • ReactAdmin μ»΄ν¬λ„ŒνŠΈ μ‚¬μš©
  • app/api/courses/[courseId]/route.ts 생성

    • Edit 에 ν•„μš”ν•œ API 생성
    • GET, PUT, DELETE 둜 μˆ˜μ • 지원

    Admin Units - List, Create, Edit, Delete

    • app/admin/unit 생성
      • unit κ΄€λ ¨ admin νŽ˜μ΄μ§€ 생성
      • create, edit, list 생성
    • app/api/units 생성
      • unit κ΄€λ ¨ API 생성

Admin Lessons - List, Create, Edit, Delete

  • app/admin/lesson 생성
    • lesson κ΄€λ ¨ admin νŽ˜μ΄μ§€ 생성
    • create, edit, list 생성
  • app/api/lessons 생성
    • lesson κ΄€λ ¨ API 생성

Admin Challenges - List, Create, Edit, Delete

  • app/admin/challenge 생성
    • challenge κ΄€λ ¨ admin νŽ˜μ΄μ§€ 생성
    • create, edit, list 생성
  • app/api/challenges 생성
    • challenge κ΄€λ ¨ API 생성

Admin Challenge Options - List, Create, Edit, Delete

  • app/admin/challengeOption 생성
    • challengeOption κ΄€λ ¨ admin νŽ˜μ΄μ§€ 생성
    • create, edit, list 생성
  • app/api/challengeOptions 생성
    • challengeOption κ΄€λ ¨ API 생성

ES6, React 및 머티리얼 λ””μžμΈμ„ μ‚¬μš©ν•˜μ—¬ REST/GraphQL APIλ₯Ό 기반으둜 λΈŒλΌμš°μ €μ—μ„œ μ‹€ν–‰λ˜λŠ” 데이터 기반 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ κ΅¬μΆ•ν•˜κΈ° μœ„ν•œ ν”„λ‘ νŠΈμ—”λ“œ ν”„λ ˆμž„μ›Œν¬μž…λ‹ˆλ‹€. μ΄μ „μ—λŠ” admin-on-rest둜 λͺ…λͺ…λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ˜€ν”ˆ μ†ŒμŠ€μ΄λ©° marmelabμ—μ„œ μœ μ§€ κ΄€λ¦¬ν•©λ‹ˆλ‹€.

  • npm i react-admin

REST/GraphQL μ„œλΉ„μŠ€ μœ„μ— κ΄€λ¦¬μž μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ κ΅¬μΆ•ν•˜κΈ° μœ„ν•œ ν”„λ‘ νŠΈμ—”λ“œ ν”„λ ˆμž„μ›Œν¬μΈ react-admin을 μœ„ν•œ κ°„λ‹¨ν•œ REST 데이터 κ³΅κΈ‰μžμž…λ‹ˆλ‹€.

  • npm i ra-data-simple-rest

Note

  • next/dynamic 으둜 client side rendering λ§Œλ“€κΈ°

Deployment

  • scripts/prod.ts 생성
  • package.json μˆ˜μ •
    • prod 용 seed μΆ”κ°€

About


Languages

Language:TypeScript 98.3%Language:CSS 1.2%Language:JavaScript 0.5%