├── .eslintrc.json ├── .env ├── logo.png ├── src ├── app │ ├── favicon.ico │ ├── fonts │ │ ├── GeistVF.woff │ │ └── GeistMonoVF.woff │ ├── leaderboard │ │ └── page.jsx │ ├── users │ │ └── [id] │ │ │ └── page.jsx │ ├── api │ │ ├── generate-course-plan │ │ │ └── route.js │ │ ├── topics │ │ │ └── [id] │ │ │ │ └── route.js │ │ ├── leaderboard │ │ │ └── route.js │ │ ├── users │ │ │ └── [id] │ │ │ │ ├── route.js │ │ │ │ └── courses │ │ │ │ └── route.js │ │ ├── update-exp │ │ │ └── route.js │ │ ├── background-tasks │ │ │ └── route.js │ │ ├── login │ │ │ └── route.js │ │ ├── quizzes │ │ │ └── [id] │ │ │ │ └── route.js │ │ ├── generate-quiz │ │ │ └── route.js │ │ ├── register │ │ │ └── route.js │ │ ├── courses │ │ │ ├── [id] │ │ │ │ └── route.js │ │ │ └── route.js │ │ └── enroll │ │ │ └── route.js │ ├── layout.js │ ├── settings │ │ └── page.js │ ├── page.js │ ├── globals.css │ └── courses │ │ └── [id] │ │ └── page.js ├── lib │ ├── .auth.js.swp │ ├── utils.js │ ├── loadBalancer.js │ ├── backgroundService.js │ └── gemini.js ├── components │ ├── ThemeProvider.jsx │ ├── ui │ │ ├── label.jsx │ │ ├── textarea.jsx │ │ ├── input.jsx │ │ ├── badge.jsx │ │ ├── switch.jsx │ │ ├── radio-group.jsx │ │ ├── card.jsx │ │ ├── accordion.jsx │ │ ├── button.jsx │ │ ├── table.jsx │ │ ├── dialog.jsx │ │ └── select.jsx │ ├── ErrorBoundary.js │ ├── ThemeToggle.jsx │ ├── OnboardingInfo.jsx │ ├── CourseView.jsx │ ├── Navigation.jsx │ ├── CourseTopicsModal.jsx │ ├── CourseCard.jsx │ ├── Quiz.jsx │ ├── Leaderboard.jsx │ ├── Onboarding.jsx │ ├── UserProfile.jsx │ ├── InterestingCourses.jsx │ ├── CourseDetails.jsx │ ├── MyCourses.jsx │ └── CreateCourse.jsx └── middleware │ └── auth.js ├── jsconfig.json ├── prisma ├── migrations │ ├── 20241019144804_add_streak_days_to_user │ │ └── migration.sql │ ├── migration_lock.toml │ ├── 20241019171036_add_student_count │ │ └── migration.sql │ ├── 20241019144531_add_timestamps_to_course │ │ └── migration.sql │ ├── 20241019142330_quiz │ │ └── migration.sql │ └── 20241019130510_remove_email_field │ │ └── migration.sql └── schema.prisma ├── postcss.config.mjs ├── next.config.js ├── components.json ├── .gitignore ├── vercel.json ├── LICENSE.md ├── scripts ├── clearDatabase.js └── seed.js ├── package.json ├── tailwind.config.js └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | GEMINI_API_KEYS= 2 | DATABASE_URL="" 3 | JWT_SECRET=your_secret_key_here -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad0n1m/decentrathon2-0/HEAD/logo.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad0n1m/decentrathon2-0/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/lib/.auth.js.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad0n1m/decentrathon2-0/HEAD/src/lib/.auth.js.swp -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad0n1m/decentrathon2-0/HEAD/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad0n1m/decentrathon2-0/HEAD/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /prisma/migrations/20241019144804_add_streak_days_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "User" ADD COLUMN "streakDays" INTEGER NOT NULL DEFAULT 0; 3 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/components/ThemeProvider.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ThemeProvider as NextThemesProvider } from 'next-themes' 4 | 5 | export function ThemeProvider({ children, ...props }) { 6 | return {children} 7 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | env: { 4 | GEMINI_API_KEY: process.env.GEMINI_API_KEY, 5 | }, 6 | images: { 7 | domains: ['via.placeholder.com'], 8 | }, 9 | }; 10 | 11 | module.exports = nextConfig; -------------------------------------------------------------------------------- /src/app/leaderboard/page.jsx: -------------------------------------------------------------------------------- 1 | import { Leaderboard } from '@/components/Leaderboard'; 2 | 3 | export default function LeaderboardPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/users/[id]/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { UserProfile } from '@/components/UserProfile'; 4 | import { useParams } from 'next/navigation'; 5 | 6 | export default function UserProfilePage() { 7 | const { id } = useParams(); 8 | return ; 9 | } -------------------------------------------------------------------------------- /prisma/migrations/20241019171036_add_student_count/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Course" ADD COLUMN "studentCount" INTEGER NOT NULL DEFAULT 0; 3 | 4 | -- AlterTable 5 | ALTER TABLE "Quiz" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 7 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": [ 3 | { 4 | "src": "next.config.js", 5 | "use": "@vercel/next" 6 | } 7 | ], 8 | "build": { 9 | "env": { 10 | "NEXT_TELEMETRY_DISABLED": "1" 11 | } 12 | }, 13 | "buildCommand": "npx prisma generate && npm run build", 14 | "outputDirectory": ".", 15 | "devCommand": "npm run dev", 16 | "installCommand": "npm install", 17 | "rewrites": [ 18 | { 19 | "source": "/(.*)", 20 | "destination": "/index.html" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/app/api/generate-course-plan/route.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { generateCourseContent } from '@/lib/gemini'; 3 | 4 | export async function POST(request) { 5 | try { 6 | const { topic, level } = await request.json(); 7 | 8 | const coursePlan = await generateCourseContent(topic, level); 9 | 10 | return NextResponse.json(coursePlan); 11 | } catch (error) { 12 | console.error('Error generating course plan:', error); 13 | return NextResponse.json({ error: 'Failed to generate course plan' }, { status: 500 }); 14 | } 15 | } -------------------------------------------------------------------------------- /src/components/ui/label.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva } from "class-variance-authority"; 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef(({ className, ...props }, ref) => ( 14 | 15 | )) 16 | Label.displayName = LabelPrimitive.Root.displayName 17 | 18 | export { Label } 19 | -------------------------------------------------------------------------------- /src/middleware/auth.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import jwt from 'jsonwebtoken'; 3 | 4 | export function auth(handler) { 5 | return async (request) => { 6 | const token = request.headers.get('Authorization')?.split(' ')[1]; 7 | 8 | if (!token) { 9 | return NextResponse.json({ error: 'No token provided' }, { status: 401 }); 10 | } 11 | 12 | try { 13 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 14 | request.user = decoded; 15 | return handler(request); 16 | } catch (error) { 17 | return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/api/topics/[id]/route.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | const prisma = new PrismaClient(); 5 | 6 | export async function PUT(request, { params }) { 7 | try { 8 | const { content, status } = await request.json(); 9 | const updatedTopic = await prisma.topic.update({ 10 | where: { id: params.id }, 11 | data: { content, status }, 12 | }); 13 | return NextResponse.json(updatedTopic); 14 | } catch (error) { 15 | console.error('Error updating topic:', error); 16 | return NextResponse.json({ error: 'Failed to update topic' }, { status: 500 }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class ErrorBoundary extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { hasError: false, error: null }; 7 | } 8 | 9 | static getDerivedStateFromError(error) { 10 | return { hasError: true, error }; 11 | } 12 | 13 | componentDidCatch(error, errorInfo) { 14 | console.error("Uncaught error:", error, errorInfo); 15 | } 16 | 17 | render() { 18 | if (this.state.hasError) { 19 | return

Something went wrong: {this.state.error.message}

; 20 | } 21 | 22 | return this.props.children; 23 | } 24 | } 25 | 26 | export default ErrorBoundary; 27 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export function getContrastColor(bgColor) { 9 | // Преобразуем цвет в RGB 10 | let color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor; 11 | let r = parseInt(color.substring(0, 2), 16); 12 | let g = parseInt(color.substring(2, 4), 16); 13 | let b = parseInt(color.substring(4, 6), 16); 14 | 15 | // Вычисляем яркость 16 | let yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; 17 | 18 | // Возвращаем черный или белый в зависимости от яркости фона 19 | return (yiq >= 128) ? 'black' : 'white'; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/ui/textarea.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef(({ className, ...props }, ref) => { 6 | return ( 7 | (