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 */}
+
+
+ {/* 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 */}
+
+
+ {/* 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 (
-
-
-
-
);
}
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