Learn Chinese the way you learn touch‑typing. Read an English prompt, hear the audio, type the pinyin — get instant, gamified feedback.
中文文档 · [Report a bug](GitHub Repository/issues) · [Request a feature](GitHub Repository/issues)
Langora is an interactive practice app for Chinese learners, inspired by TypingClub's "type‑to‑progress" model. Each question shows an English prompt and auto‑plays its audio; the learner types the matching pinyin, and the app validates the answer, plays sound effects, and records progress in real time.
Lessons are grouped by theme — Greetings, Casual Conversation, Ordering Food, and more — and the content is monetized through a monthly subscription, a lifetime plan, and single‑lesson purchases.
🔗 Live demo: [https://langora.joy-codex.com] · 📦 Repository: [GitHub Repository](https://github.com/KapiYue/Langora)
![]() |
![]() |
| Course Store — browse lessons by topic and access tier | Dashboard — streaks, accuracy, and recent lessons |
![]() |
|
| Simple, transparent pricing — Free, Monthly Pro, and Lifetime | |
- Type‑to‑progress drills — English prompt + auto‑played audio; answer in pinyin with lenient matching (case, tone numbers, and extra spaces are ignored), and multiple accepted answers per question.
- Immersive feedback — keystroke, correct, and error sound effects, plus a confetti animation on lesson completion.
- Keyboard shortcuts —
Ctrl + Preplays the audio,Entersubmits an answer or advances to the next question. - Progress visualization — per‑item progress bar, question counter, and live accuracy tracking.
- Email/password sign‑up, login, forgot‑password, and reset flows powered by Supabase Auth.
- Cookie‑based SSR sessions (
@supabase/ssr), refreshed centrally through Next.js Middleware.
- Learning overview: lessons completed, total words mastered, current streak, weekly practice time, and average accuracy.
- Recent lessons, a course store with per‑lesson progress, a profile page, and account settings (password change).
- Lessons, lesson items, and progress are stored in Postgres and can be imported via a seed script.
- Access rules: the free lesson (
greetings_l1) is open to everyone; Pro and Lifetime members unlock every lesson; single‑lesson purchases unlock just that lesson.
- Three tiers: Free, Monthly Pro ($10/mo), Lifetime ($99 one‑time) — plus standalone single‑lesson purchases.
- Webhook requests are verified with an HMAC‑SHA256 signature check (
crypto.timingSafeEqual) before any data is written. - Grace period on cancellation — a canceled subscription stays active until the end of the current billing cycle; a refund revokes access immediately.
- Upgrade protection — purchasing Lifetime access automatically cancels an active Monthly Pro subscription to prevent double billing.
- Duplicate‑purchase guard — blocks redundant checkouts when the user already owns an equal or higher tier.
| Area | Technology |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack), React 19 |
| Language | TypeScript 5 |
| UI | Tailwind CSS 3, shadcn/ui (Radix UI), Framer Motion, lucide-react |
| Auth | Supabase Auth (@supabase/ssr, cookie/SSR) |
| Database | Supabase Postgres + Drizzle ORM (postgres-js driver) |
| Migrations | drizzle-kit |
| Payments | Creem (Checkout + Webhook) |
| Misc | use-sound, react-confetti, next-themes |
flowchart LR
U[Browser] -->|SSR / Client| N[Next.js App Router]
N -->|Middleware session refresh| SB[(Supabase Auth)]
N -->|Drizzle ORM| PG[(Supabase Postgres)]
N -->|Create checkout| CR[Creem API]
CR -->|Webhook event + signature| N
N -->|Write subscriptions / purchases / transactions| PG
| Table | Purpose |
|---|---|
lessons |
Lessons (title, description, tag, order, cover image) |
lesson_items |
Lesson items (English prompt, Chinese, pinyin, accepted answers, audio) |
user_profiles |
Profile and aggregate stats (lessons completed, words mastered, streak) |
user_lesson_progress |
Lesson‑level progress (completed items, accuracy, time spent) |
user_item_progress |
Item‑level progress (attempts, correct attempts) |
user_daily_stats |
Daily learning statistics |
user_subscriptions |
Pro / Lifetime subscriptions |
user_course_purchases |
Single‑lesson purchases |
payment_transactions |
Payment transaction ledger |
app/
page.tsx # Landing page (Hero / Features / Pricing / ...)
auth/ # Login / sign-up / password recovery
dashboard/ # Overview / courses / membership / billing / profile / settings
lesson/[lessonId]/ # Lesson player (with access control)
api/
lesson-progress/ # Save learning progress
payment/ # checkout / webhook / status / cancel
components/ # Landing page, dashboard, lesson, and shared UI components
lib/
supabase/ # client / server / middleware
db/ # schema / queries / seed / connection
creem.ts # Payment config and helpers
membership/plans.ts # Plan definitions
drizzle/ # Migration files
docs/ # API references, lesson seed data, screenshots
- Node.js ≥ 20 and pnpm
- A Supabase project (Postgres + Auth)
- A Creem account (the test environment is fine)
pnpm installCopy .env.example to .env.local and fill in the values:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY=
DATABASE_URL=
# Site
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Creem
CREEM_API_KEY=
CREEM_WEBHOOK_SECRET=
NEXT_PUBLIC_CREEM_URL=https://test-api.creem.io
CREEM_LIFETIME_PRODUCT_ID=
CREEM_SUBSCRIPTION_PRODUCT_ID=
CREEM_SINGLE_COURSE_PRODUCT_ID=pnpm db:push # sync the schema (or db:migrate to apply migrations)
pnpm db:seed # import lessons from docs/lessons.jsonpnpm devOpen http://localhost:3000.
| Command | Description |
|---|---|
pnpm dev |
Start the dev server (Turbopack) |
pnpm build / pnpm start |
Build / run in production |
pnpm lint |
Run ESLint |
pnpm db:generate |
Generate a new migration |
pnpm db:migrate / pnpm db:push |
Apply migrations / push schema directly |
pnpm db:studio |
Open Drizzle Studio |
pnpm db:seed |
Seed lesson data from docs/lessons.json |
- Checkout sessions pass
userId,paymentType, andlessonIdthroughmetadata, which the webhook uses to post ledger entries accurately. - The webhook path (
/api/payment/webhook) is excluded from the Middleware matcher so the session‑refresh logic never interferes with it, and it verifies its own signature independently. - Switch between test and production by changing
NEXT_PUBLIC_CREEM_URLand the three*_PRODUCT_IDenvironment variables.
Recommended stack: Vercel + Supabase.
- Create a Supabase project, then run migrations and seed the lesson data.
- Set the environment variables above in your Vercel project (
NEXT_PUBLIC_SITE_URLshould point to your live domain). - Point the Creem webhook to
https://<your-domain>/api/payment/webhook.
- More lesson themes and difficulty tiers
- Mistake review / spaced repetition
- Leaderboards and achievements
- Mobile UX polish
- Full UI internationalization (i18n)
Contributions, issues, and feature requests are welcome. Please read CONTRIBUTING.md before opening a pull request, and note that this project follows a Code of Conduct.
If you discover a security vulnerability, please follow the responsible‑disclosure process described in SECURITY.md instead of opening a public issue.
Released under the MIT License.



