feat: username/ww login, tijdzone fix, Vannacht → Eergisternacht"
Some checks failed
Build & Deploy / build (push) Failing after 1m11s

This commit is contained in:
2026-05-15 18:12:47 +02:00
parent cfd9ed4eae
commit a094e67f69
10 changed files with 316 additions and 70 deletions

View File

@@ -1,33 +1,47 @@
import { NextAuthOptions } from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import bcrypt from "bcryptjs";
import { prisma } from "./prisma";
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
// JWT strategy zodat de middleware de sessie kan verifieren
// zonder een database call te doen bij elk request
session: {
strategy: "jwt",
},
session: { strategy: "jwt" },
providers: [
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
}),
CredentialsProvider({
name: "credentials",
credentials: {
username: { label: "Gebruikersnaam", type: "text" },
password: { label: "Wachtwoord", type: "password" },
},
async authorize(credentials) {
if (!credentials?.username || !credentials?.password) return null;
const user = await prisma.user.findUnique({
where: { username: credentials.username },
});
if (!user?.password) return null;
const valid = await bcrypt.compare(credentials.password, user.password);
if (!valid) return null;
return { id: user.id, name: user.name, email: user.email, image: user.image };
},
}),
],
callbacks: {
jwt({ token, user }) {
// user is alleen aanwezig bij eerste login
if (user) {
token.id = user.id;
}
if (user) token.id = user.id;
return token;
},
session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
}
if (session.user) session.user.id = token.id as string;
return session;
},
},

View File

@@ -4,19 +4,15 @@ export function formatDuration(minutes: number): string {
return `${h}u ${m.toString().padStart(2, "0")}min`;
}
export function minutesToHoursDecimal(minutes: number): number {
return Math.round((minutes / 60) * 10) / 10;
}
export function getSleepQuality(minutes: number): {
label: string;
color: string;
bg: string;
} {
if (minutes < 300) return { label: "Heel kort", color: "text-red-400", bg: "bg-red-500/20 text-red-400" };
if (minutes < 360) return { label: "Kort", color: "text-orange-400", bg: "bg-orange-500/20 text-orange-400" };
if (minutes < 420) return { label: "Matig", color: "text-yellow-400", bg: "bg-yellow-500/20 text-yellow-400" };
if (minutes <= 540) return { label: "Goed", color: "text-green-400", bg: "bg-green-500/20 text-green-400" };
if (minutes < 300) return { label: "Heel kort", color: "text-red-400", bg: "bg-red-500/20 text-red-400" };
if (minutes < 360) return { label: "Kort", color: "text-orange-400", bg: "bg-orange-500/20 text-orange-400" };
if (minutes < 420) return { label: "Matig", color: "text-yellow-400", bg: "bg-yellow-500/20 text-yellow-400" };
if (minutes <= 540) return { label: "Goed", color: "text-green-400", bg: "bg-green-500/20 text-green-400" };
return { label: "Lang", color: "text-blue-400", bg: "bg-blue-500/20 text-blue-400" };
}
@@ -26,30 +22,32 @@ export function getPhaseQuality(
type: "deep" | "light" | "rem" | "awake"
): { label: string; className: string } {
const pct = (phaseMinutes / totalMinutes) * 100;
if (type === "deep") {
if (pct >= 13 && pct <= 25) return { label: "Normaal", className: "bg-green-500/20 text-green-400" };
if (pct > 25) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
if (pct > 25) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
}
if (type === "light") {
if (pct >= 45 && pct <= 65) return { label: "Normaal", className: "bg-green-500/20 text-green-400" };
if (pct > 65) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
if (pct > 65) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
}
if (type === "rem") {
if (pct >= 15 && pct <= 25) return { label: "Normaal", className: "bg-green-500/20 text-green-400" };
if (pct > 25) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
if (pct > 25) return { label: "Lang", className: "bg-orange-500/20 text-orange-400" };
return { label: "Kort", className: "bg-red-500/20 text-red-400" };
}
// awake
if (pct <= 5) return { label: "Normaal", className: "bg-green-500/20 text-green-400" };
if (pct <= 10) return { label: "Matig", className: "bg-yellow-500/20 text-yellow-400" };
return { label: "Veel", className: "bg-red-500/20 text-red-400" };
if (pct <= 5) return { label: "Normaal", className: "bg-green-500/20 text-green-400" };
if (pct <= 10) return { label: "Matig", className: "bg-yellow-500/20 text-yellow-400" };
return { label: "Veel", className: "bg-red-500/20 text-red-400" };
}
// Prisma @db.Date geeft UTC middernacht terug → trek datum uit ISO string
// om tijdzone-offset te vermijden
export function formatDate(date: string | Date): string {
return new Date(date).toLocaleDateString("nl-BE", {
const iso = typeof date === "string" ? date : date.toISOString();
const [year, month, day] = iso.slice(0, 10).split("-").map(Number);
return new Date(year, month - 1, day).toLocaleDateString("nl-BE", {
weekday: "short",
day: "numeric",
month: "short",
@@ -62,6 +60,6 @@ export function calculateTotalFromTimes(bedtime: string, wakeTime: string): numb
const [bh, bm] = bedtime.split(":").map(Number);
const [wh, wm] = wakeTime.split(":").map(Number);
let total = wh * 60 + wm - (bh * 60 + bm);
if (total <= 0) total += 24 * 60; // crosses midnight
if (total <= 0) total += 24 * 60; // kruist middernacht
return total;
}
}