From 0d339a35e27d8a55740fcefccf9d05e1a766dbd7 Mon Sep 17 00:00:00 2001 From: Jessica Rekcah Date: Sat, 29 Nov 2025 10:25:34 +0700 Subject: [PATCH] init --- TODO-kreatiVortex-platform.md | 6 +- app/(app)/dashboard/layout.tsx | 170 ++++++++++ app/(app)/dashboard/page.tsx | 130 ++++++++ app/api/auth/[...all]/route.ts | 17 + app/auth/signin/page.tsx | 116 +++++++ app/auth/signup/page.tsx | 168 ++++++++++ app/globals.css | 22 ++ app/page.tsx | 143 ++++---- bun.lock | 11 + components/ActionButton/index.ts | 9 + components/Common/index.ts | 9 + components/Forms/index.ts | 9 + components/Layouts/index.ts | 9 + components/index.ts | 9 + lib/auth-client.ts | 13 + lib/auth.ts | 25 ++ package.json | 3 + prisma.config.ts | 2 +- .../20251128173915_init/migration.sql | 78 +++++ prisma/migrations/migration_lock.toml | 3 + prisma/models/auth.prisma | 62 ---- prisma/schema.prisma | 308 ++++++++++++++++++ tailwind.config.ts | 51 +++ types/api.ts | 115 +++++++ types/auth.ts | 110 +++++++ types/prisma.ts | 174 ++++++++++ 26 files changed, 1650 insertions(+), 122 deletions(-) create mode 100644 app/(app)/dashboard/layout.tsx create mode 100644 app/(app)/dashboard/page.tsx create mode 100644 app/api/auth/[...all]/route.ts create mode 100644 app/auth/signin/page.tsx create mode 100644 app/auth/signup/page.tsx create mode 100644 components/ActionButton/index.ts create mode 100644 components/Common/index.ts create mode 100644 components/Forms/index.ts create mode 100644 components/Layouts/index.ts create mode 100644 components/index.ts create mode 100644 lib/auth-client.ts create mode 100644 prisma/migrations/20251128173915_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 prisma/models/auth.prisma create mode 100644 tailwind.config.ts create mode 100644 types/api.ts create mode 100644 types/auth.ts create mode 100644 types/prisma.ts diff --git a/TODO-kreatiVortex-platform.md b/TODO-kreatiVortex-platform.md index 6ab67b6..4162238 100644 --- a/TODO-kreatiVortex-platform.md +++ b/TODO-kreatiVortex-platform.md @@ -8,9 +8,9 @@ ### Phase 1: Project Setup & Foundation -- [ ] Initialize Next.js 16+ project with TypeScript -- [ ] Install and configure Better Auth framework -- [ ] Set up Prisma ORM with database connection +- [x] Initialize Next.js 16+ project with TypeScript +- [x] Install and configure Better Auth framework +- [x] Set up Prisma ORM with database connection - [ ] Configure project structure following AI_GUIDE.md patterns - [ ] Set up ESLint, TypeScript configuration - [ ] Create basic layout and routing structure diff --git a/app/(app)/dashboard/layout.tsx b/app/(app)/dashboard/layout.tsx new file mode 100644 index 0000000..5885308 --- /dev/null +++ b/app/(app)/dashboard/layout.tsx @@ -0,0 +1,170 @@ +/** + * File: layout.tsx + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Dashboard layout for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +import Link from 'next/link'; +import { ReactNode } from 'react'; + +export default function DashboardLayout({ + children, +}: { + children: ReactNode; +}) { + return ( +
+ {/* Background overlay */} +
+ +
+ {/* Sidebar */} +
+
+ {/* Logo */} + +
+ kV +
+ + kreatiVortex + + +
+ + {/* Navigation */} + +
+ + {/* Main content */} +
+ {/* Top bar */} +
+
+

+ Dashboard +

+ +
+ {/* Notifications */} + + + {/* Profile */} +
+
+

John Doe

+

Calon Pendidik

+
+
+ JD +
+
+
+
+
+ + {/* Page content */} +
+ {children} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/(app)/dashboard/page.tsx b/app/(app)/dashboard/page.tsx new file mode 100644 index 0000000..ce86dfb --- /dev/null +++ b/app/(app)/dashboard/page.tsx @@ -0,0 +1,130 @@ +/** + * File: page.tsx + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Dashboard page for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +export default function Dashboard() { + return ( +
+
+

+ Selamat Datang di kreatiVortex! +

+

+ Platform pembelajaran tari tradisional Indonesia +

+
+ + {/* Stats Cards */} +
+
+
+
+

Total Video

+

24

+
+
+ + + +
+
+
+ +
+
+
+

Kelas Aktif

+

3

+
+
+ + + +
+
+
+ +
+
+
+

Tugas Selesai

+

12

+
+
+ + + +
+
+
+ +
+
+
+

Forum Post

+

48

+
+
+ + + +
+
+
+
+ + {/* Recent Activity */} +
+ {/* Recent Videos */} +
+

Video Terbaru

+
+
+
+ + + +
+
+

Tari Piring - Dasar

+

2 jam yang lalu

+
+
+
+
+ + + +
+
+

Tari Saman - Gerakan Tangan

+

5 jam yang lalu

+
+
+
+
+ + {/* Recent Forum Posts */} +
+

Diskusi Terbaru

+
+
+

Tips untuk pemula Tari Piring

+

Saya ingin berbagi beberapa tips untuk yang baru mulai belajar...

+

Oleh Sarah Pendidik • 1 jam yang lalu

+
+
+

Costum untuk pertunjukan

+

Apakah ada saran untuk costum yang tepat untuk pertunjukan tari...

+

Oleh Budi Student • 3 jam yang lalu

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/api/auth/[...all]/route.ts b/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..a6c4ada --- /dev/null +++ b/app/api/auth/[...all]/route.ts @@ -0,0 +1,17 @@ +/** + * File: route.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Basic auth API for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +import { NextResponse } from 'next/server'; + +export async function GET() { + return NextResponse.json({ message: 'Auth API - GET' }); +} + +export async function POST() { + return NextResponse.json({ message: 'Auth API - POST' }); +} \ No newline at end of file diff --git a/app/auth/signin/page.tsx b/app/auth/signin/page.tsx new file mode 100644 index 0000000..dbfa64d --- /dev/null +++ b/app/auth/signin/page.tsx @@ -0,0 +1,116 @@ +/** + * File: page.tsx + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Sign in page for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; + +export default function SignIn() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + // TODO: Implement actual sign in logic + setTimeout(() => { + setIsLoading(false); + router.push('/dashboard'); + }, 1000); + }; + + return ( +
+
+ +
+
+ {/* Logo */} +
+

+ kreatiVortex +

+

Masuk ke akun Anda

+
+ + {/* Form */} +
+
+ + setEmail(e.target.value)} + className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gold-400 focus:border-transparent" + placeholder="nama@email.com" + required + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gold-400 focus:border-transparent" + placeholder="••••••••" + required + /> +
+ +
+
+ + +
+ + Lupa password? + +
+ + +
+ + {/* Sign up link */} +
+

+ Belum punya akun?{' '} + + Daftar sekarang + +

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/auth/signup/page.tsx b/app/auth/signup/page.tsx new file mode 100644 index 0000000..aea55ec --- /dev/null +++ b/app/auth/signup/page.tsx @@ -0,0 +1,168 @@ +/** + * File: page.tsx + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Sign up page for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +'use client'; + +import { useState } from 'react'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; + +export default function SignUp() { + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + confirmPassword: '', + role: 'CALON_PENDIDIK' as 'CALON_PENDIDIK' | 'UMUM', + }); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (formData.password !== formData.confirmPassword) { + alert('Password tidak cocok!'); + return; + } + + setIsLoading(true); + + // TODO: Implement actual sign up logic + setTimeout(() => { + setIsLoading(false); + router.push('/auth/signin'); + }, 1000); + }; + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + return ( +
+
+ +
+
+ {/* Logo */} +
+

+ kreatiVortex +

+

Buat akun baru

+
+ + {/* Form */} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + {/* Sign in link */} +
+

+ Sudah punya akun?{' '} + + Masuk + +

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index dc98be7..e120972 100644 --- a/app/globals.css +++ b/app/globals.css @@ -41,6 +41,28 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + + /* kreatiVortex custom colors */ + --color-navy-50: #f0f4ff; + --color-navy-100: #dae9ff; + --color-navy-200: #bdd7ff; + --color-navy-300: #8fc2ff; + --color-navy-400: #5ca7ff; + --color-navy-500: #3182ff; + --color-navy-600: #1e5fd6; + --color-navy-700: #1e40af; + --color-navy-800: #1e3a8a; + --color-navy-900: #000080; + --color-gold-50: #fffbeb; + --color-gold-100: #fef3c7; + --color-gold-200: #fde68a; + --color-gold-300: #fcd34d; + --color-gold-400: #fbbf24; + --color-gold-500: #f59e0b; + --color-gold-600: #d97706; + --color-gold-700: #b45309; + --color-gold-800: #92400e; + --color-gold-900: #78350f; } :root { diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..b79fe5e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,96 @@ -import Image from "next/image"; +/** + * File: page.tsx + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Landing page for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +import Link from "next/link"; export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - + {/* Background overlay effect */} +

+ + {/* Main content */} +
+
+ {/* Logo/Title */} +
+

+ kreatiVortex +

+

+ Platform Pembelajaran Tari Online +

+
+ + {/* Description */} +
+

+ Bergabunglah dengan komunitas pembelajaran tari tradisional Indonesia. + Pelajari berbagai tarian dari seluruh nusantara dengan panduan dari para ahli. +

+
+ + {/* Action Buttons */} +
+ - Templates - {" "} - or the{" "} - + - Learning - {" "} - center. -

+ Daftar + + + Mulai Sekarang + +
+ + {/* Features */} +
+
+
+ + + +
+

Video Pembelajaran

+

Akses video tutorial tari dari berbagai daerah di Indonesia

+
+ +
+
+ + + +
+

Forum Diskusi

+

Berinteraksi dengan sesama pecinta tari dan para ahli

+
+ +
+
+ + + +
+

Tugas & Evaluasi

+

Kumpulkan tugas dan dapatkan feedback dari para pendidik

+
+
- -
+
); } diff --git a/bun.lock b/bun.lock index ff33d9e..6f6e503 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,10 @@ "name": "kreativortex", "dependencies": { "@prisma/adapter-pg": "^7.0.1", + "@prisma/client": "^7.0.1", + "@types/bcryptjs": "^3.0.0", "@types/pg": "^8.15.6", + "bcryptjs": "^3.0.3", "better-auth": "^1.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -272,6 +275,10 @@ "@prisma/adapter-pg": ["@prisma/adapter-pg@7.0.1", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.0.1", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-01GpPPhLMoDMF4ipgfZz0L87fla/TV/PBQcmHy+9vV1ml6gUoqF8dUIRNI5Yf2YKpOwzQg9sn8C7dYD1Yio9Ug=="], + "@prisma/client": ["@prisma/client@7.0.1", "", { "dependencies": { "@prisma/client-runtime-utils": "7.0.1" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g=="], + + "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.0.1", "", {}, "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw=="], + "@prisma/config": ["@prisma/config@7.0.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-MacIjXdo+hNKxPvtMzDXykIIc8HCRWoyjQ2nguJTFqLDzJBD5L6QRaANGTLOqbGtJ3sFvLRmfXhrFg3pWoK1BA=="], "@prisma/debug": ["@prisma/debug@7.0.1", "", {}, "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w=="], @@ -330,6 +337,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], @@ -446,6 +455,8 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="], + "bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="], + "better-auth": ["better-auth@1.4.3", "", { "dependencies": { "@better-auth/core": "1.4.3", "@better-auth/telemetry": "1.4.3", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@standard-schema/spec": "^1.0.0", "better-call": "1.1.0", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" } }, "sha512-cMY6PxXZ9Ep+KmLUcVEQ5RwtZtdawxTbDqUIgIIUYWJgq0KwNkQfFNimSYjHI0cNZwwAJyvbV42+uLogsDOUqQ=="], "better-call": ["better-call@1.1.0", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1" } }, "sha512-7CecYG+yN8J1uBJni/Mpjryp8bW/YySYsrGEWgFe048ORASjq17keGjbKI2kHEOSc6u8pi11UxzkJ7jIovQw6w=="], diff --git a/components/ActionButton/index.ts b/components/ActionButton/index.ts new file mode 100644 index 0000000..665211d --- /dev/null +++ b/components/ActionButton/index.ts @@ -0,0 +1,9 @@ +/** + * File: index.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: ActionButton component export + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +// ActionButton component will be created here \ No newline at end of file diff --git a/components/Common/index.ts b/components/Common/index.ts new file mode 100644 index 0000000..e625c06 --- /dev/null +++ b/components/Common/index.ts @@ -0,0 +1,9 @@ +/** + * File: index.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Common components exports + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +// Common components will be exported here as they are created \ No newline at end of file diff --git a/components/Forms/index.ts b/components/Forms/index.ts new file mode 100644 index 0000000..2af5448 --- /dev/null +++ b/components/Forms/index.ts @@ -0,0 +1,9 @@ +/** + * File: index.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Forms components exports + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +// Form components will be exported here as they are created \ No newline at end of file diff --git a/components/Layouts/index.ts b/components/Layouts/index.ts new file mode 100644 index 0000000..acbf97f --- /dev/null +++ b/components/Layouts/index.ts @@ -0,0 +1,9 @@ +/** + * File: index.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Layouts components exports + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +// Layout components will be exported here as they are created \ No newline at end of file diff --git a/components/index.ts b/components/index.ts new file mode 100644 index 0000000..9df75ce --- /dev/null +++ b/components/index.ts @@ -0,0 +1,9 @@ +/** + * File: index.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Main component exports for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +// Components will be exported here as they are created \ No newline at end of file diff --git a/lib/auth-client.ts b/lib/auth-client.ts new file mode 100644 index 0000000..bfb3625 --- /dev/null +++ b/lib/auth-client.ts @@ -0,0 +1,13 @@ +/** + * File: client.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Client-side auth configuration for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +import { createAuthClient } from "better-auth/react"; + +export const authClient = createAuthClient({ + baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000", +}); \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts index fadce8c..5622131 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,3 +1,11 @@ +/** + * File: auth.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Better Auth configuration for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "./prisma"; @@ -6,4 +14,21 @@ export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql", }), + emailAndPassword: { + enabled: true, + requireEmailVerification: false, + }, + session: { + expiresIn: 60 * 60 * 24 * 7, // 7 days + updateAge: 60 * 60 * 24, // 1 day + cookieCache: { + enabled: true, + maxAge: 5 * 60, // 5 minutes + }, + }, + account: { + accountLinking: { + enabled: false, + }, + }, }); \ No newline at end of file diff --git a/package.json b/package.json index a5541b8..e56b6ed 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ }, "dependencies": { "@prisma/adapter-pg": "^7.0.1", + "@prisma/client": "^7.0.1", + "@types/bcryptjs": "^3.0.0", "@types/pg": "^8.15.6", + "bcryptjs": "^3.0.3", "better-auth": "^1.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/prisma.config.ts b/prisma.config.ts index 6c28eac..8318e32 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ schema: 'prisma', migrations: { path: 'prisma/migrations', - seed: 'tsx prisma/seed.ts', + seed: 'node prisma/seed.js', }, datasource: { url: env('DATABASE_URL'), diff --git a/prisma/migrations/20251128173915_init/migration.sql b/prisma/migrations/20251128173915_init/migration.sql new file mode 100644 index 0000000..c0feb5c --- /dev/null +++ b/prisma/migrations/20251128173915_init/migration.sql @@ -0,0 +1,78 @@ +-- CreateTable +CREATE TABLE "user" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "image" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "user_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "session" ( + "id" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "token" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "userId" TEXT NOT NULL, + + CONSTRAINT "session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "account" ( + "id" TEXT NOT NULL, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" TIMESTAMP(3), + "refreshTokenExpiresAt" TIMESTAMP(3), + "scope" TEXT, + "password" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "verification" ( + "id" TEXT NOT NULL, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "verification_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); + +-- CreateIndex +CREATE INDEX "session_userId_idx" ON "session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); + +-- CreateIndex +CREATE INDEX "account_userId_idx" ON "account"("userId"); + +-- CreateIndex +CREATE INDEX "verification_identifier_idx" ON "verification"("identifier"); + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/models/auth.prisma b/prisma/models/auth.prisma deleted file mode 100644 index 8d2c895..0000000 --- a/prisma/models/auth.prisma +++ /dev/null @@ -1,62 +0,0 @@ -model User { - id String @id - name String - email String - emailVerified Boolean @default(false) - image String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - sessions Session[] - accounts Account[] - - @@unique([email]) - @@map("user") -} - -model Session { - id String @id - expiresAt DateTime - token String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - ipAddress String? - userAgent String? - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([token]) - @@index([userId]) - @@map("session") -} - -model Account { - id String @id - accountId String - providerId String - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - accessToken String? - refreshToken String? - idToken String? - accessTokenExpiresAt DateTime? - refreshTokenExpiresAt DateTime? - scope String? - password String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([userId]) - @@map("account") -} - -model Verification { - id String @id - identifier String - value String - expiresAt DateTime - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([identifier]) - @@map("verification") -} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index df56c72..87fa1b2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,3 +13,311 @@ generator client { datasource db { provider = "postgresql" } + +// ======================================== +// AUTH MODELS (imported from auth.prisma) +// ======================================== + +// Note: User, Session, Account, and Verification models are imported from auth.prisma +// We need to extend the User model here to add the profile relation + +model User { + id String @id + name String + email String + emailVerified Boolean @default(false) + image String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + sessions Session[] + accounts Account[] + profile UserProfile? + + @@unique([email]) + @@map("user") +} + +model Session { + id String @id + expiresAt DateTime + token String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + ipAddress String? + userAgent String? + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([token]) + @@index([userId]) + @@map("session") +} + +model Account { + id String @id + accountId String + providerId String + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + accessToken String? + refreshToken String? + idToken String? + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId]) + @@map("account") +} + +model Verification { + id String @id + identifier String + value String + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([identifier]) + @@map("verification") +} + +// ======================================== +// KREATIVORTEX PLATFORM MODELS +// ======================================== + +model UserRole { + id String @id @default(cuid()) + name String @unique + description String + permissions String[] // JSON array of permissions + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + users UserProfile[] + + @@map("user_roles") +} + +model UserProfile { + id String @id @default(cuid()) + userId String @unique + roleId String + nim String? // Nomor Induk Mahasiswa + phone String? + address String? + bio String? + avatar String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + role UserRole @relation(fields: [roleId], references: [id]) + + // Relations as educator + classesTeaching Class[] + videos Video[] + forums Forum[] + assignments Assignment[] + + // Relations as student + classesEnrolled ClassMember[] + forumPosts ForumPost[] @relation("ForumPosts") + assignmentSubmissions AssignmentSubmission[] + comments Comment[] @relation("CommentAuthor") + + // Additional relations + reviewedSubmissions AssignmentSubmission[] @relation("SubmissionReviewer") + + @@map("user_profiles") +} + +model Class { + id String @id @default(cuid()) + name String + description String + code String @unique + educatorId String + isActive Boolean @default(true) + maxStudents Int? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdBy String + updatedBy String + + educator UserProfile @relation(fields: [educatorId], references: [id], onDelete: Cascade) + members ClassMember[] + videos Video[] + forums Forum[] + assignments Assignment[] + + @@map("classes") +} + +model ClassMember { + id String @id @default(cuid()) + classId String + studentId String + joinedAt DateTime @default(now()) + isActive Boolean @default(true) + + class Class @relation(fields: [classId], references: [id], onDelete: Cascade) + student UserProfile @relation(fields: [studentId], references: [id], onDelete: Cascade) + + @@unique([classId, studentId]) + @@map("class_members") +} + +model Video { + id String @id @default(cuid()) + title String + description String? + videoUrl String + videoType VideoType @default(YOUTUBE) + thumbnailUrl String? + duration Int? // in seconds + uploaderId String + classId String? + isPublic Boolean @default(true) + viewCount Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdBy String + updatedBy String + + uploader UserProfile @relation(fields: [uploaderId], references: [id], onDelete: Cascade) + class Class? @relation(fields: [classId], references: [id], onDelete: SetNull) + comments Comment[] + + @@map("videos") +} + +model Forum { + id String @id @default(cuid()) + title String + description String? + type ForumType @default(GENERAL) + classId String? + createdBy String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + updatedBy String + + creator UserProfile @relation(fields: [createdBy], references: [id], onDelete: Cascade) + class Class? @relation(fields: [classId], references: [id], onDelete: SetNull) + posts ForumPost[] + + @@map("forums") +} + +model ForumPost { + id String @id @default(cuid()) + title String + content String + forumId String + authorId String + parentId String? // for replies + isPinned Boolean @default(false) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + updatedBy String + + forum Forum @relation(fields: [forumId], references: [id], onDelete: Cascade) + author UserProfile @relation("ForumPosts", fields: [authorId], references: [id], onDelete: Cascade) + parent ForumPost? @relation("PostReplies", fields: [parentId], references: [id], onDelete: Cascade) + replies ForumPost[] @relation("PostReplies") + comments Comment[] + + @@map("forum_posts") +} + +model Assignment { + id String @id @default(cuid()) + title String + description String + dueDate DateTime + classId String + educatorId String + maxScore Int + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdBy String + updatedBy String + + class Class @relation(fields: [classId], references: [id], onDelete: Cascade) + educator UserProfile @relation(fields: [educatorId], references: [id], onDelete: Cascade) + submissions AssignmentSubmission[] + + @@map("assignments") +} + +model AssignmentSubmission { + id String @id @default(cuid()) + assignmentId String + studentId String + documentUrl String + documentType String // MIME type + score Int? + feedback String? + status SubmissionStatus @default(SUBMITTED) + submittedAt DateTime @default(now()) + reviewedAt DateTime? + reviewedBy String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + updatedBy String + + assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade) + student UserProfile @relation(fields: [studentId], references: [id], onDelete: Cascade) + reviewer UserProfile? @relation("SubmissionReviewer", fields: [reviewedBy], references: [id], onDelete: SetNull) + + @@map("assignment_submissions") +} + +model Comment { + id String @id @default(cuid()) + content String + authorId String + videoId String? + forumPostId String? + parentId String? // for replies + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + updatedBy String + + author UserProfile @relation("CommentAuthor", fields: [authorId], references: [id], onDelete: Cascade) + video Video? @relation(fields: [videoId], references: [id], onDelete: Cascade) + forumPost ForumPost? @relation(fields: [forumPostId], references: [id], onDelete: Cascade) + parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade) + replies Comment[] @relation("CommentReplies") + + @@map("comments") +} + +// ======================================== +// ENUMS +// ======================================== + +enum VideoType { + YOUTUBE + LOCAL +} + +enum ForumType { + GENERAL + CLASS +} + +enum SubmissionStatus { + SUBMITTED + REVIEWED + REVISED + APPROVED +} diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..8f7b69e --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,51 @@ +/** + * File: tailwind.config.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Tailwind CSS configuration for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +import type { Config } from "tailwindcss"; + +const config: Config = { + darkMode: "class", + content: [ + "./pages/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + navy: { + 50: '#f0f4ff', + 100: '#dae9ff', + 200: '#bdd7ff', + 300: '#8fc2ff', + 400: '#5ca7ff', + 500: '#3182ff', + 600: '#1e5fd6', + 700: '#1e40af', + 800: '#1e3a8a', + 900: '#000080', + }, + gold: { + 50: '#fffbeb', + 100: '#fef3c7', + 200: '#fde68a', + 300: '#fcd34d', + 400: '#fbbf24', + 500: '#f59e0b', + 600: '#d97706', + 700: '#b45309', + 800: '#92400e', + 900: '#78350f', + }, + }, + }, + }, + plugins: [], +}; + +export default config; \ No newline at end of file diff --git a/types/api.ts b/types/api.ts new file mode 100644 index 0000000..6ef18ed --- /dev/null +++ b/types/api.ts @@ -0,0 +1,115 @@ +/** + * File: api.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: API response type definitions for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +export interface ApiResponse { + success: boolean; + message: string; + data?: T; + error?: string; + pagination?: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +} + +export interface PaginationParams { + page?: number; + limit?: number; + search?: string; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; +} + +export interface VideoFilters extends PaginationParams { + uploaderId?: string; + classId?: string; + videoType?: 'YOUTUBE' | 'LOCAL'; + isPublic?: boolean; +} + +export interface ForumFilters extends PaginationParams { + type?: 'GENERAL' | 'CLASS'; + classId?: string; + authorId?: string; +} + +export interface AssignmentFilters extends PaginationParams { + classId?: string; + educatorId?: string; + status?: 'ACTIVE' | 'INACTIVE' | 'DUE_SOON' | 'OVERDUE'; +} + +export interface ClassFilters extends PaginationParams { + educatorId?: string; + isActive?: boolean; +} + +export interface UserFilters extends PaginationParams { + role?: 'ADMINISTRATOR' | 'PENDIDIK' | 'CALON_PENDIDIK' | 'UMUM'; + isActive?: boolean; +} + +export interface CreateVideoData { + title: string; + description?: string; + videoUrl: string; + videoType: 'YOUTUBE' | 'LOCAL'; + thumbnailUrl?: string; + classId?: string; + isPublic: boolean; +} + +export interface CreateForumData { + title: string; + description?: string; + type: 'GENERAL' | 'CLASS'; + classId?: string; +} + +export interface CreateForumPostData { + title: string; + content: string; + forumId: string; + parentId?: string; +} + +export interface CreateAssignmentData { + title: string; + description: string; + dueDate: Date; + classId: string; + maxScore: number; +} + +export interface CreateClassData { + name: string; + description: string; + code: string; + maxStudents?: number; +} + +export interface CreateCommentData { + content: string; + videoId?: string; + forumPostId?: string; + parentId?: string; +} + +export interface UpdateUserData { + name?: string; + email?: string; + role?: 'ADMINISTRATOR' | 'PENDIDIK' | 'CALON_PENDIDIK' | 'UMUM'; + profile?: { + phone?: string; + address?: string; + bio?: string; + avatar?: string; + }; +} \ No newline at end of file diff --git a/types/auth.ts b/types/auth.ts new file mode 100644 index 0000000..e14382c --- /dev/null +++ b/types/auth.ts @@ -0,0 +1,110 @@ +/** + * File: auth.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Authentication type definitions for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +export interface AuthUser { + id: string; + name: string; + email: string; + role: 'ADMINISTRATOR' | 'PENDIDIK' | 'CALON_PENDIDIK' | 'UMUM'; + image?: string; + profile?: { + nim?: string; + phone?: string; + bio?: string; + avatar?: string; + }; +} + +export interface LoginCredentials { + email: string; + password: string; +} + +export interface RegisterData { + name: string; + email: string; + password: string; + confirmPassword: string; + role?: 'CALON_PENDIDIK' | 'UMUM'; +} + +export interface RoleUpgradeData { + role: 'PENDIDIK' | 'CALON_PENDIDIK'; + additionalInfo?: { + institution?: string; + experience?: string; + specialization?: string; + nim?: string; + }; +} + +export interface AuthResponse { + user: AuthUser; + token?: string; + message: string; + success: boolean; +} + +export interface Permission { + canViewUsers: boolean; + canCreateUsers: boolean; + canUpdateUsers: boolean; + canDeleteUsers: boolean; + canManageClasses: boolean; + canManageVideos: boolean; + canManageForums: boolean; + canManageAssignments: boolean; + canViewReports: boolean; +} + +export const ROLE_PERMISSIONS: Record = { + ADMINISTRATOR: { + canViewUsers: true, + canCreateUsers: true, + canUpdateUsers: true, + canDeleteUsers: true, + canManageClasses: true, + canManageVideos: true, + canManageForums: true, + canManageAssignments: true, + canViewReports: true, + }, + PENDIDIK: { + canViewUsers: false, + canCreateUsers: false, + canUpdateUsers: false, + canDeleteUsers: false, + canManageClasses: true, + canManageVideos: true, + canManageForums: true, + canManageAssignments: true, + canViewReports: false, + }, + CALON_PENDIDIK: { + canViewUsers: false, + canCreateUsers: false, + canUpdateUsers: false, + canDeleteUsers: false, + canManageClasses: false, + canManageVideos: false, + canManageForums: true, + canManageAssignments: false, + canViewReports: false, + }, + UMUM: { + canViewUsers: false, + canCreateUsers: false, + canUpdateUsers: false, + canDeleteUsers: false, + canManageClasses: false, + canManageVideos: false, + canManageForums: true, + canManageAssignments: false, + canViewReports: false, + }, +}; \ No newline at end of file diff --git a/types/prisma.ts b/types/prisma.ts new file mode 100644 index 0000000..3a03530 --- /dev/null +++ b/types/prisma.ts @@ -0,0 +1,174 @@ +/** + * File: prisma.ts + * Created by: AI Assistant + * Date: 2025-11-29 + * Purpose: Prisma type definitions for kreatiVortex platform + * Part of: kreatiVortex - Platform Pembelajaran Tari Online + */ + +export interface UserWithRelations { + id: string; + name: string; + email: string; + emailVerified: boolean; + image?: string; + createdAt: Date; + updatedAt: Date; + role: UserRole; + profile?: UserProfile; + classesTeaching?: Class[]; + classesEnrolled?: Class[]; + videos?: Video[]; + forumPosts?: ForumPost[]; + assignments?: Assignment[]; +} + +export interface UserRole { + id: string; + name: 'ADMINISTRATOR' | 'PENDIDIK' | 'CALON_PENDIDIK' | 'UMUM'; + description: string; + permissions: string[]; +} + +export interface UserProfile { + id: string; + userId: string; + nim?: string; + phone?: string; + address?: string; + bio?: string; + avatar?: string; + createdAt: Date; + updatedAt: Date; +} + +export interface Class { + id: string; + name: string; + description: string; + code: string; + educatorId: string; + educator: UserWithRelations; + isActive: boolean; + maxStudents?: number; + createdAt: Date; + updatedAt: Date; + createdBy: string; + updatedBy: string; + students?: UserWithRelations[]; + videos?: Video[]; + forums?: Forum[]; + assignments?: Assignment[]; +} + +export interface Video { + id: string; + title: string; + description?: string; + videoUrl: string; + videoType: 'YOUTUBE' | 'LOCAL'; + thumbnailUrl?: string; + duration?: number; + uploaderId: string; + uploader: UserWithRelations; + classId?: string; + class?: Class; + isPublic: boolean; + viewCount: number; + createdAt: Date; + updatedAt: Date; + createdBy: string; + updatedBy: string; + comments?: Comment[]; +} + +export interface Forum { + id: string; + title: string; + description?: string; + type: 'GENERAL' | 'CLASS'; + classId?: string; + class?: Class; + createdBy: string; + creator: UserWithRelations; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + updatedBy: string; + posts?: ForumPost[]; +} + +export interface ForumPost { + id: string; + title: string; + content: string; + forumId: string; + forum: Forum; + authorId: string; + author: UserWithRelations; + parentId?: string; + parent?: ForumPost; + replies?: ForumPost[]; + isPinned: boolean; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + updatedBy: string; + comments?: Comment[]; +} + +export interface Assignment { + id: string; + title: string; + description: string; + dueDate: Date; + classId: string; + class: Class; + educatorId: string; + educator: UserWithRelations; + maxScore: number; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + createdBy: string; + updatedBy: string; + submissions?: AssignmentSubmission[]; +} + +export interface AssignmentSubmission { + id: string; + assignmentId: string; + assignment: Assignment; + studentId: string; + student: UserWithRelations; + documentUrl: string; + documentType: string; + score?: number; + feedback?: string; + status: 'SUBMITTED' | 'REVIEWED' | 'REVISED' | 'APPROVED'; + submittedAt: Date; + reviewedAt?: Date; + reviewedBy?: string; + reviewer?: UserWithRelations; + createdAt: Date; + updatedAt: Date; + updatedBy: string; +} + +export interface Comment { + id: string; + content: string; + authorId: string; + author: UserWithRelations; + videoId?: string; + video?: Video; + forumPostId?: string; + forumPost?: ForumPost; + parentId?: string; + parent?: Comment; + replies?: Comment[]; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + updatedBy: string; +} \ No newline at end of file