feat: implement structured grading system with predefined assessment types
- Add structured grading policy with predefined types (Continuous Assessment, Midterm, Final Test, Exam) - Replace free-text category input with combobox selection in Grading Policy page - Update Lesson Editor to use dropdown selector for grading category assignment - Fix create_grading_category handler to capture organization context - Fix update_course handler to set audit context in database transaction - Implement getImageUrl helper for proper asset path resolution - Add unoptimized prop to organization logo images to bypass Next.js optimization - Add database migrations for organization_id in content tables - Seed default tutorial courses for Admin, Instructor, and Student roles - Fix audit log constraints and content schema issues
This commit is contained in:
@@ -14,12 +14,15 @@ COPY web/experience/ .
|
||||
RUN npm run build
|
||||
|
||||
# Final stage
|
||||
FROM node:18-alpine AS runner
|
||||
FROM node:18-slim AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Install system dependencies for Rust binary
|
||||
RUN apk add --no-cache openssl libgcc libstdc++
|
||||
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install sharp for Next.js image optimization
|
||||
RUN npm install sharp
|
||||
|
||||
# Copy LMS binary
|
||||
COPY --from=rust-builder /usr/src/app/target/release/lms-service ./
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { lmsApi } from "@/lib/api";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { GraduationCap, Lock, Mail, User, Building2 } from "lucide-react";
|
||||
|
||||
export default function ExperienceLoginPage() {
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -31,8 +33,7 @@ export default function ExperienceLoginPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem("experience_token", response.token);
|
||||
localStorage.setItem("experience_user", JSON.stringify(response.user));
|
||||
login(response.user, response.token);
|
||||
router.push("/");
|
||||
} else {
|
||||
const response = await lmsApi.register({
|
||||
@@ -42,8 +43,7 @@ export default function ExperienceLoginPage() {
|
||||
organization_name: organizationName,
|
||||
});
|
||||
|
||||
localStorage.setItem("experience_token", response.token);
|
||||
localStorage.setItem("experience_user", JSON.stringify(response.user));
|
||||
login(response.user, response.token);
|
||||
router.push("/");
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import "./globals.css";
|
||||
import Link from "next/link";
|
||||
import { AuthProvider } from "@/context/AuthContext";
|
||||
import { BrandingProvider } from "@/context/BrandingContext";
|
||||
import AuthGuard from "@/components/AuthGuard";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -24,15 +25,17 @@ export default function RootLayout({
|
||||
<body className={`${inter.className} bg-[#050505] text-[#e5e5e5] min-h-screen flex flex-col`}>
|
||||
<BrandingProvider>
|
||||
<AuthProvider>
|
||||
<AppHeader />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<footer className="py-12 px-6 border-t border-white/5 text-center bg-black/20">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-600">
|
||||
Powered by OpenCCB © 2023. Advanced Agentic Coding.
|
||||
</p>
|
||||
</footer>
|
||||
<AuthGuard>
|
||||
<AppHeader />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<footer className="py-12 px-6 border-t border-white/5 text-center bg-black/20">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.2em] text-gray-600">
|
||||
Powered by OpenCCB © 2023. Advanced Agentic Coding.
|
||||
</p>
|
||||
</footer>
|
||||
</AuthGuard>
|
||||
</AuthProvider>
|
||||
</BrandingProvider>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function AuthGuard({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
const isAuthPage = pathname?.startsWith("/auth");
|
||||
if (!user && !isAuthPage) {
|
||||
router.push("/auth/login");
|
||||
} else if (user && isAuthPage) {
|
||||
router.push("/");
|
||||
}
|
||||
}
|
||||
}, [user, loading, pathname, router]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#050505] flex items-center justify-center">
|
||||
<div className="w-12 h-12 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isAuthPage = pathname?.startsWith("/auth");
|
||||
if (!user && !isAuthPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user