init
This commit is contained in:
parent
22f85d731c
commit
0d339a35e2
@ -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
|
||||
|
||||
170
app/(app)/dashboard/layout.tsx
Normal file
170
app/(app)/dashboard/layout.tsx
Normal file
@ -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 (
|
||||
<div className="min-h-screen bg-gradient-to-br from-navy-900 via-navy-800 to-gray-900">
|
||||
{/* Background overlay */}
|
||||
<div className="absolute inset-0 bg-black/20"></div>
|
||||
|
||||
<div className="relative z-10 flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 bg-navy-900/50 backdrop-blur-sm border-r border-white/10">
|
||||
<div className="p-6">
|
||||
{/* Logo */}
|
||||
<Link href="/dashboard" className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-gold-400 rounded-lg flex items-center justify-center">
|
||||
<span className="text-navy-900 font-bold text-sm">kV</span>
|
||||
</div>
|
||||
<span className="text-white font-bold text-xl">
|
||||
kreati<span className="text-gold-400">Vortex</span>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="px-4 pb-6">
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 00-1-1v-4a1 1 0 011-1h2m4 0a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<span>Beranda</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/teori"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
<span>Teori</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/praktik"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>Praktik</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/template-makalah"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span>Template Makalah</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Additional sections */}
|
||||
<div className="mt-8">
|
||||
<h3 className="px-4 text-xs font-semibold text-gray-400 uppercase tracking-wider mb-4">
|
||||
Komunitas
|
||||
</h3>
|
||||
<ul className="space-y-2">
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/videos"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>Video Saya</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/forum"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z" />
|
||||
</svg>
|
||||
<span>Forum</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/dashboard/assignments"
|
||||
className="flex items-center space-x-3 px-4 py-3 text-gray-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2v2a2 2 0 01-2 2M9 5a2 2 0 012-2v2a2 2 0 01-2 2m-3 7h8m-8 4h8m-8 4h8" />
|
||||
</svg>
|
||||
<span>Tugas</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex-1 overflow-auto">
|
||||
{/* Top bar */}
|
||||
<header className="bg-navy-800/50 backdrop-blur-sm border-b border-white/10 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-white">
|
||||
Dashboard
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Notifications */}
|
||||
<button className="relative p-2 text-gray-300 hover:text-white transition-colors">
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 10v6.159c0 .538.214 1.055.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span className="absolute top-1 right-1 w-2 h-2 bg-gold-400 rounded-full"></span>
|
||||
</button>
|
||||
|
||||
{/* Profile */}
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="text-right">
|
||||
<p className="text-sm font-medium text-white">John Doe</p>
|
||||
<p className="text-xs text-gray-400">Calon Pendidik</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 bg-gold-400 rounded-full flex items-center justify-center">
|
||||
<span className="text-navy-900 font-semibold">JD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="p-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
app/(app)/dashboard/page.tsx
Normal file
130
app/(app)/dashboard/page.tsx
Normal file
@ -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 (
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<h2 className="text-2xl font-bold text-white mb-2">
|
||||
Selamat Datang di kreatiVortex!
|
||||
</h2>
|
||||
<p className="text-gray-300">
|
||||
Platform pembelajaran tari tradisional Indonesia
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-300">Total Video</p>
|
||||
<p className="text-2xl font-bold text-white">24</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-300">Kelas Aktif</p>
|
||||
<p className="text-2xl font-bold text-white">3</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-300">Tugas Selesai</p>
|
||||
<p className="text-2xl font-bold text-white">12</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm7-2a2 2 0 11-4 0v4a2 2 0 014 0V9z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-300">Forum Post</p>
|
||||
<p className="text-2xl font-bold text-white">48</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Recent Videos */}
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">Video Terbaru</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-16 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-white font-medium">Tari Piring - Dasar</h4>
|
||||
<p className="text-sm text-gray-400">2 jam yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-16 h-12 bg-gold-400/20 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-white font-medium">Tari Saman - Gerakan Tangan</h4>
|
||||
<p className="text-sm text-gray-400">5 jam yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Forum Posts */}
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 border border-white/20">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">Diskusi Terbaru</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="border-l border-gold-400 pl-4">
|
||||
<h4 className="text-white font-medium mb-1">Tips untuk pemula Tari Piring</h4>
|
||||
<p className="text-sm text-gray-400 mb-2">Saya ingin berbagi beberapa tips untuk yang baru mulai belajar...</p>
|
||||
<p className="text-xs text-gray-500">Oleh Sarah Pendidik • 1 jam yang lalu</p>
|
||||
</div>
|
||||
<div className="border-l border-gold-400 pl-4">
|
||||
<h4 className="text-white font-medium mb-1">Costum untuk pertunjukan</h4>
|
||||
<p className="text-sm text-gray-400 mb-2">Apakah ada saran untuk costum yang tepat untuk pertunjukan tari...</p>
|
||||
<p className="text-xs text-gray-500">Oleh Budi Student • 3 jam yang lalu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
app/api/auth/[...all]/route.ts
Normal file
17
app/api/auth/[...all]/route.ts
Normal file
@ -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' });
|
||||
}
|
||||
116
app/auth/signin/page.tsx
Normal file
116
app/auth/signin/page.tsx
Normal file
@ -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 (
|
||||
<div className="min-h-screen bg-gradient-to-br from-navy-900 via-navy-800 to-gray-900 flex items-center justify-center px-4">
|
||||
<div className="absolute inset-0 bg-black/20"></div>
|
||||
|
||||
<div className="relative z-10 w-full max-w-md">
|
||||
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">
|
||||
kreati<span className="text-gold-400">Vortex</span>
|
||||
</h1>
|
||||
<p className="text-gray-300">Masuk ke akun Anda</p>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 bg-white/10 border-white/20 rounded focus:ring-gold-400"
|
||||
/>
|
||||
<label htmlFor="remember" className="ml-2 block text-sm text-gray-300">
|
||||
Ingat saya
|
||||
</label>
|
||||
</div>
|
||||
<Link href="/auth/forgot-password" className="text-sm text-gold-400 hover:text-gold-300">
|
||||
Lupa password?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full py-3 px-4 bg-gold-500 hover:bg-gold-400 text-navy-900 font-semibold rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isLoading ? 'Memproses...' : 'Masuk'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Sign up link */}
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-gray-300">
|
||||
Belum punya akun?{' '}
|
||||
<Link href="/auth/signup" className="text-gold-400 hover:text-gold-300 font-medium">
|
||||
Daftar sekarang
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
168
app/auth/signup/page.tsx
Normal file
168
app/auth/signup/page.tsx
Normal file
@ -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<HTMLInputElement | HTMLSelectElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-navy-900 via-navy-800 to-gray-900 flex items-center justify-center px-4 py-12">
|
||||
<div className="absolute inset-0 bg-black/20"></div>
|
||||
|
||||
<div className="relative z-10 w-full max-w-md">
|
||||
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">
|
||||
kreati<span className="text-gold-400">Vortex</span>
|
||||
</h1>
|
||||
<p className="text-gray-300">Buat akun baru</p>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Nama Lengkap
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
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="John Doe"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="role" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Daftar sebagai
|
||||
</label>
|
||||
<select
|
||||
id="role"
|
||||
name="role"
|
||||
value={formData.role}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-gold-400 focus:border-transparent"
|
||||
>
|
||||
<option value="CALON_PENDIDIK" className="bg-navy-800">Calon Pendidik</option>
|
||||
<option value="UMUM" className="bg-navy-800">Umum</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Konfirmasi Password
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
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
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full py-3 px-4 bg-gold-500 hover:bg-gold-400 text-navy-900 font-semibold rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isLoading ? 'Mendaftar...' : 'Daftar'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Sign in link */}
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-gray-300">
|
||||
Sudah punya akun?{' '}
|
||||
<Link href="/auth/signin" className="text-gold-400 hover:text-gold-300 font-medium">
|
||||
Masuk
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
141
app/page.tsx
141
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 (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
<div className="min-h-screen bg-gradient-to-br from-navy-900 via-navy-800 to-gray-900 text-white">
|
||||
{/* Background overlay effect */}
|
||||
<div className="absolute inset-0 bg-black/20"></div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="relative z-10 flex min-h-screen flex-col items-center justify-center px-4">
|
||||
<div className="max-w-4xl w-full text-center">
|
||||
{/* Logo/Title */}
|
||||
<div className="mb-12">
|
||||
<h1 className="text-5xl md:text-7xl font-bold text-white mb-4">
|
||||
kreati<span className="text-gold-400">Vortex</span>
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
<p className="text-xl md:text-2xl text-gray-300">
|
||||
Platform Pembelajaran Tari Online
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-16 max-w-2xl mx-auto">
|
||||
<p className="text-lg text-gray-300 leading-relaxed">
|
||||
Bergabunglah dengan komunitas pembelajaran tari tradisional Indonesia.
|
||||
Pelajari berbagai tarian dari seluruh nusantara dengan panduan dari para ahli.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Link
|
||||
href="/auth/signin"
|
||||
className="px-8 py-4 bg-navy-700 hover:bg-navy-600 text-white font-semibold rounded-lg transition-colors duration-200 min-w-[160px] text-center"
|
||||
>
|
||||
Masuk
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="px-8 py-4 bg-gold-500 hover:bg-gold-400 text-navy-900 font-semibold rounded-lg transition-colors duration-200 min-w-[160px] text-center"
|
||||
>
|
||||
Daftar
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="px-8 py-4 border-2 border-gold-400 text-gold-400 hover:bg-gold-400 hover:text-navy-900 font-semibold rounded-lg transition-colors duration-200 min-w-[160px] text-center"
|
||||
>
|
||||
Mulai Sekarang
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="mt-24 grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-gold-400/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Video Pembelajaran</h3>
|
||||
<p className="text-gray-400">Akses video tutorial tari dari berbagai daerah di Indonesia</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-gold-400/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Forum Diskusi</h3>
|
||||
<p className="text-gray-400">Berinteraksi dengan sesama pecinta tari dan para ahli</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-gold-400/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-gold-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Tugas & Evaluasi</h3>
|
||||
<p className="text-gray-400">Kumpulkan tugas dan dapatkan feedback dari para pendidik</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
11
bun.lock
11
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=="],
|
||||
|
||||
9
components/ActionButton/index.ts
Normal file
9
components/ActionButton/index.ts
Normal file
@ -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
|
||||
9
components/Common/index.ts
Normal file
9
components/Common/index.ts
Normal file
@ -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
|
||||
9
components/Forms/index.ts
Normal file
9
components/Forms/index.ts
Normal file
@ -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
|
||||
9
components/Layouts/index.ts
Normal file
9
components/Layouts/index.ts
Normal file
@ -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
|
||||
9
components/index.ts
Normal file
9
components/index.ts
Normal file
@ -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
|
||||
13
lib/auth-client.ts
Normal file
13
lib/auth-client.ts
Normal file
@ -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",
|
||||
});
|
||||
25
lib/auth.ts
25
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -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",
|
||||
|
||||
@ -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'),
|
||||
|
||||
78
prisma/migrations/20251128173915_init/migration.sql
Normal file
78
prisma/migrations/20251128173915_init/migration.sql
Normal file
@ -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;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -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"
|
||||
@ -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")
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
51
tailwind.config.ts
Normal file
51
tailwind.config.ts
Normal file
@ -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;
|
||||
115
types/api.ts
Normal file
115
types/api.ts
Normal file
@ -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<T = unknown> {
|
||||
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;
|
||||
};
|
||||
}
|
||||
110
types/auth.ts
Normal file
110
types/auth.ts
Normal file
@ -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<string, Permission> = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
174
types/prisma.ts
Normal file
174
types/prisma.ts
Normal file
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user