TypeScript / Эцсийн төсөл

Эцсийн төсөл

Баяр хүргэе! TypeScript-ийн суурь, interface, generic, utility type, React хэрэглэлт, design pattern, тест бичих хүртэл маш олон зүйл сурлаа. Эдгээр мэдлэгийг нэгтгэн бодит ажиллагаатай програм бүтээх цаг боллоо.

Эцсийн төсөл нь хичээлийн дэвшил хянах систем юм — яг энэ платформ дотор ашиглагдаж буй функциональтай төстэй.

Төслийн тодорхойлолт

Дараах шаардлагуудыг хангасан TypeScript + Next.js програм бүтээнэ:

Функциональ шаардлагууд:

  • Хэрэглэгч бүртгүүлж, нэвтэрч болно (Supabase Auth)
  • Хичээл үзэж "Дуусгах" товч дарахад дэвшил хадгалагдана
  • XP цуглуулж, түвшин ахина
  • Профайл хуудаст нийт дэвшил харагдана

Техникийн шаардлагууд:

  • TypeScript strict mode — any ашиглахгүй
  • Бүх өгөгдлийн бүтцэд interface тодорхойлсон
  • Server Component болон Client Component зөв ялгасан
  • Монгол хэл дээр бүх UI текст

Төслийн бүтэц

код
typescript-эцсийн-төсөл/
├── app/
│   ├── layout.tsx
│   ├── page.tsx                    ← нүүр хуудас
│   ├── (auth)/
│   │   ├── login/page.tsx
│   │   └── register/page.tsx
│   ├── courses/
│   │   └── [courseSlug]/
│   │       └── [lessonSlug]/
│   │           └── page.tsx
│   ├── profile/
│   │   └── page.tsx
│   └── api/
│       └── progress/
│           └── route.ts
├── components/
│   ├── lesson/
│   │   ├── LessonLayout.tsx
│   │   └── ProgressButton.tsx
│   └── ui/
│       └── XPBar.tsx
├── lib/
│   └── supabase/
│       ├── client.ts
│       └── server.ts
└── types/
    ├── lesson.ts
    └── user.ts

Үндсэн төрлүүд тодорхойлох

typescript
// types/lesson.ts
export interface LessonMeta {
  slug: string;
  title: string;
  order: number;
}

export interface Course {
  slug: string;
  title: string;
  description: string;
  language: "javascript" | "typescript" | "python";
  color: "green" | "purple" | "amber";
  isFree: boolean;
  lessons: LessonMeta[];
}

export interface LessonДэвшил {
  user_id: string;
  course_slug: string;
  lesson_slug: string;
  completed_at: string;
}

// types/user.ts
export interface UserProfile {
  id: string;
  username: string | null;
  xp: number;
  streak: number;
  last_active: string | null;
  created_at: string;
}

export interface XPТүвшин {
  түвшин: number;
  нэр: string;
  шаардагдахXP: number;
  дараагийнXP: number;
}

// XP-ээс түвшин тооцоолох helper
export function xpТүвшинТооцох(xp: number): XPТүвшин {
  const түвшингүүд: Omit<XPТүвшин, "түвшин">[] = [
    { нэр: "Анхан шатны", шаардагдахXP: 0,   дараагийнXP: 100  },
    { нэр: "Суралцагч",   шаардагдахXP: 100,  дараагийнXP: 300  },
    { нэр: "Хөгжүүлэгч", шаардагдахXP: 300,  дараагийнXP: 600  },
    { нэр: "Мэргэжилтэн", шаардагдахXP: 600, дараагийнXP: 1000 },
    { нэр: "Мастер",      шаардагдахXP: 1000, дараагийнXP: Infinity },
  ];

  const индекс = түвшингүүд.findLastIndex((т) => xp >= т.шаардагдахXP);
  return { түвшин: индекс + 1, ...түвшингүүд[индекс] };
}

Progress API route

typescript
// app/api/progress/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";

interface ДэвшилийнОруулга {
  lessonSlug: string;
  courseSlug: string;
  passed: true;
}

export async function POST(хүсэлт: NextRequest) {
  const supabase = await createClient();

  // Нэвтэрсэн эсэхийг шалгана
  const {
    data: { user },
  } = await supabase.auth.getUser();

  if (!user) {
    return NextResponse.json(
      { error: "Нэвтрэх шаардлагатай" },
      { status: 401 }
    );
  }

  const биет: ДэвшилийнОруулга = await хүсэлт.json();

  if (!биет.lessonSlug || !биет.courseSlug) {
    return NextResponse.json(
      { error: "lessonSlug болон courseSlug шаардлагатай" },
      { status: 400 }
    );
  }

  // Дэвшил хадгалах — давхардвал шинэчлэнэ (upsert)
  const { error: дэвшилийнАлдаа } = await supabase
    .from("lesson_progress")
    .upsert({
      user_id: user.id,
      course_slug: биет.courseSlug,
      lesson_slug: биет.lessonSlug,
    });

  if (дэвшилийнАлдаа) {
    return NextResponse.json({ error: "Хадгалахад алдаа гарлаа" }, { status: 500 });
  }

  // XP нэмэх
  await supabase.rpc("increment_xp", { user_id: user.id, amount: 10 });

  return NextResponse.json({ success: true });
}

ProgressButton client component

tsx
// components/lesson/ProgressButton.tsx
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";

interface ProgressButtonProps {
  хичээлийнСлуг: string;
  courseSlug: string;
  дараагийнХичээл: string | null;
}

export default function ProgressButton({
  хичээлийнСлуг,
  courseSlug,
  дараагийнХичээл,
}: ProgressButtonProps) {
  const [стан, setСтан] = useState<"бэлэн" | "ачааллаж" | "дууссан">("бэлэн");
  const router = useRouter();

  const дуусгах = async () => {
    setСтан("ачааллаж");

    try {
      const хариу = await fetch("/api/progress", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          lessonSlug: хичээлийнСлуг,
          courseSlug,
          passed: true,
        }),
      });

      if (хариу.status === 401) {
        router.push("/login");
        return;
      }

      if (!хариу.ok) throw new Error("Хадгалахад алдаа гарлаа");

      setСтан("дууссан");

      // 1 секундын дараа дараагийн хичээл рүү очно
      setTimeout(() => {
        if (дараагийнХичээл) {
          router.push(дараагийнХичээл);
        }
      }, 1000);
    } catch {
      setСтан("бэлэн");
    }
  };

  if (стан === "дууссан") {
    return (
      <button
        disabled
        className="bg-[#052e16] text-[#4ade80] border border-[#166534] px-6 py-3 rounded-lg"
      >
        ✓ Хичээл дууссан — +10 XP
      </button>
    );
  }

  return (
    <button
      onClick={дуусгах}
      disabled={стан === "ачааллаж"}
      className="bg-[#4c1d95] text-[#c4b5fd] px-6 py-3 rounded-lg hover:bg-[#5b21b6] disabled:opacity-50"
    >
      {стан === "ачааллаж" ? "Хадгалж байна..." : "Дуусгах"}
    </button>
  );
}

Чи яг энд хүрлээ

TypeScript-ийн эхнээс — string, number, boolean — эхлээд decorator, design pattern, тест бичих хүртэл бүхнийг үзлээ. Чи одоо:

  • Бодит TypeScript + Next.js 15 програм бүтээж чадна
  • Алдааг ажиллуулахаас өмнө илрүүлэх мэдлэгтэй
  • SOLID зарчим, design pattern мэддэг
  • React component-ийг зөв бүтэцтэй бичиж чадна
  • Тест бичиж, кодоо баталгаажуулж чадна

Дараагийн алхам: Энэ эцсийн төслөө бүрэн хэрэгжүүлж, GitHub-д байршуул. Ажлын байрны ярилцлагад portfolio болгон харуулж болно.

Дараагийн хичээлд:

Энэ нь TypeScript курсын сүүлийн хичээл юм. Та Python эсвэл JavaScript курс үргэлжлүүлж, шинэ мэдлэг нэмэх боломжтой. Аялалд чинь амжилт хүсье! 🚀