feat(auth): integrate NextAuth v5 with credentials provider, protected routes, and custom auth layouts
This commit is contained in:
parent
47b89056b5
commit
fd728634ce
16 changed files with 405 additions and 48 deletions
21
README.md
21
README.md
|
|
@ -1,22 +1,29 @@
|
||||||
# Flowstate
|
# Flowstate
|
||||||
|
|
||||||
A modern, high-performance To-Do web application designed to help you lock in and enter the productivity flow.
|
A modern, high-performance Kanban & Workflow web application designed to help you lock in and enter the productivity flow.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
## 🛠️ Tech Stack
|
## 🛠️ Tech Stack
|
||||||
|
|
||||||
- **Framework:** [Next.js 16](https://nextjs.org/) (App Router)
|
- **Framework:** [Next.js 16](https://nextjs.org/) (App Router, Route Groups)
|
||||||
- **UI & Styling:** [React 19](https://react.dev/), [Tailwind CSS v4](https://tailwindcss.com/)
|
- **UI & Styling:** [React 19](https://react.dev/), [Tailwind CSS v4](https://tailwindcss.com/)
|
||||||
- **Backend:** [Node.js](https://nodejs.org/)
|
- **Authentication:** [Auth.js v5 (NextAuth)](https://authjs.dev/) with Credentials Provider
|
||||||
- **Database:** [PostgreSQL 16](https://www.postgresql.org/)
|
- **Database & State:** PostgreSQL / Prisma (in progress)
|
||||||
- **Caching & Queue:** [Redis 7](https://redis.io/)
|
|
||||||
|
## 📂 Architecture & Structure
|
||||||
|
|
||||||
|
The project uses Next.js Route Groups to separate public and protected application areas cleanly:
|
||||||
|
|
||||||
|
- `app/(auth)/` — Public authentication routes (Login, Register)
|
||||||
|
- `app/(app)/` — Protected workspace & dashboard routes (Sidebar, Header, Kanban Board)
|
||||||
|
- `app/api/` — Backend API endpoints & Auth handlers
|
||||||
|
|
||||||
## 🎯 Current Status
|
## 🎯 Current Status
|
||||||
|
|
||||||
_Work in Progress_ — Exploring ideas, setting up the architecture, and defining core features. The journey has just begun!
|
_In Progress_ — Core layout structure, responsive navigation, and authentication (Auth.js v5) are fully set up. Next up: Kanban board columns and task management features.
|
||||||
|
|
|
||||||
6
app/(app)/board/page.tsx
Normal file
6
app/(app)/board/page.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Landing page
|
||||||
|
*/
|
||||||
|
export default function Home() {
|
||||||
|
return <div className="space-y-6">Home</div>;
|
||||||
|
}
|
||||||
34
app/(app)/layout.tsx
Normal file
34
app/(app)/layout.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import Sidebar from "@/app/components/layout/Sidebar";
|
||||||
|
import Header from "@/app/components/layout/Header";
|
||||||
|
import Navbar from "@/app/components/layout/Navbar";
|
||||||
|
|
||||||
|
export default async function AppLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const session = typeof auth === "function" ? await auth() : null;
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="hidden md:flex">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex flex-col min-w-0 overflow-hidden pb-16 md:pb-0">
|
||||||
|
<Header />
|
||||||
|
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<div className="md:hidden flex">
|
||||||
|
<Navbar />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
app/(auth)/layout.tsx
Normal file
11
app/(auth)/layout.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default function AuthLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-dvh w-full items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
130
app/(auth)/login/page.tsx
Normal file
130
app/(auth)/login/page.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
/**
|
||||||
|
* @file LoginPage.tsx
|
||||||
|
* @description Client component providing a user login interface with NextAuth credentials authentication.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the login page containing the authentication form, error handling,
|
||||||
|
* and navigation links for user sign-in.
|
||||||
|
*
|
||||||
|
* @returns {JSX.Element} The rendered login page component.
|
||||||
|
*/
|
||||||
|
export default function LoginPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the submission of the login form, validates credentials via NextAuth,
|
||||||
|
* and manages loading/error states or redirects upon success.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @param {React.SubmitEvent<HTMLFormElement>} e - The form submission event.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
const email = formData.get("email") as string;
|
||||||
|
const password = formData.get("password") as string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await signIn("credentials", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
redirect: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.error) {
|
||||||
|
if (res.error === "CredentialsSignin") {
|
||||||
|
setError("Incorrect email address or password.");
|
||||||
|
} else {
|
||||||
|
setError("An unexpected error has occurred.");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} else {
|
||||||
|
router.push("/");
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setError("Connection error. Please try again later.");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-dvh w-full items-center justify-center">
|
||||||
|
{/* Login Card Container */}
|
||||||
|
<div className="w-full max-w-sm p-6 space-y-6 border border-border rounded-xl bg-card backdrop-blur-md">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="space-y-2 text-center">
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">Flowstate Login</h1>
|
||||||
|
<p className="text-sm text-muted">Sign in to continue.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error Notification Banner */}
|
||||||
|
{error && (
|
||||||
|
<div
|
||||||
|
className="p-3 text-xs font-medium text-destructive bg-destructive-bg border border-destructive-border rounded-lg text-center"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Credentials Form */}
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<label className="block space-y-2">
|
||||||
|
<span className="text-xs font-medium text-muted">E-Mail</span>
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="name@example.com"
|
||||||
|
defaultValue="test@flowstate.io"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 text-sm rounded-lg border border-border bg-background focus:outline-none focus:border-foreground"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="block space-y-2">
|
||||||
|
<span className="text-xs font-medium text-muted">Passwort</span>
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
defaultValue="password"
|
||||||
|
required
|
||||||
|
className="w-full px-3 py-2 text-sm rounded-lg border border-border bg-background focus:outline-none focus:border-foreground"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full py-2 text-sm font-medium rounded-lg bg-foreground text-background hover:opacity-70 transition-opacity disabled:opacity-20 cursor-pointer"
|
||||||
|
>
|
||||||
|
{loading ? "Logging in..." : "Sign In"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Registration Link Footer */}
|
||||||
|
<div className="text-center text-xs text-muted">
|
||||||
|
Don't have an account yet?{" "}
|
||||||
|
<Link href="/register/" className="text-foreground hover:underline">
|
||||||
|
Register
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
2
app/api/auth/[...nextauth]/route.ts
Normal file
2
app/api/auth/[...nextauth]/route.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { handlers } from "@/auth";
|
||||||
|
export const { GET, POST } = handlers;
|
||||||
|
|
@ -3,7 +3,7 @@ export default function BrandLogo() {
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-9 h-9">
|
<div className="w-9 h-9">
|
||||||
<img
|
<img
|
||||||
src="logo.png"
|
src="/logo.png"
|
||||||
alt="Flowstate Logo"
|
alt="Flowstate Logo"
|
||||||
className="w-full h-full object-contain"
|
className="w-full h-full object-contain"
|
||||||
/>
|
/>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import Brand from "./Brand";
|
import BrandLogo from "./BrandLogo";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
|
|
@ -6,7 +6,7 @@ export default function Header() {
|
||||||
<header className="h-20 border-b border-border backdrop-blur-md p-4 flex items-center md:justify-end justify-between sticky top-0 z-50">
|
<header className="h-20 border-b border-border backdrop-blur-md p-4 flex items-center md:justify-end justify-between sticky top-0 z-50">
|
||||||
{/* Brand Element */}
|
{/* Brand Element */}
|
||||||
<div className="md:hidden flex items-center gap-3">
|
<div className="md:hidden flex items-center gap-3">
|
||||||
<Brand />
|
<BrandLogo />
|
||||||
</div>
|
</div>
|
||||||
{/* User Profil */}
|
{/* User Profil */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
import Brand from "./Brand";
|
import BrandLogo from "./BrandLogo";
|
||||||
import Navbar from "./Navbar";
|
import Navbar from "./Navbar";
|
||||||
|
import SignOutButton from "./buttons/SignOutButton";
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
return (
|
return (
|
||||||
<aside className="flex-col h-full w-64 p-6 select-none border-r border-border shrink-0">
|
<aside className="hidden md:flex flex-col justify-between h-full w-64 p-6 select-none border-r border-border shrink-0">
|
||||||
{/* Brand Element */}
|
{/* Upper Section */}
|
||||||
<Brand />
|
<div className="flex flex-col">
|
||||||
{/* Navbar Element */}
|
<BrandLogo />
|
||||||
<Navbar />
|
<Navbar />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Lower Section */}
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<SignOutButton />
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
app/components/layout/buttons/SignOutButton.tsx
Normal file
21
app/components/layout/buttons/SignOutButton.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { signOut } from "@/auth";
|
||||||
|
import { LogOut } from "lucide-react";
|
||||||
|
|
||||||
|
export default function SignOutButton() {
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
action={async () => {
|
||||||
|
"use server";
|
||||||
|
await signOut({ redirectTo: "/" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium text-muted hover:text-foreground transition-colors w-full cursor-pointer"
|
||||||
|
>
|
||||||
|
<LogOut className="w-4 h-4" />
|
||||||
|
<span>Sign out</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
--color-foreground: hsl(0 0% 100%);
|
--color-foreground: hsl(0 0% 100%);
|
||||||
--color-muted: hsl(0 0% 63%);
|
--color-muted: hsl(0 0% 63%);
|
||||||
--color-border: hsl(0 0% 15%);
|
--color-border: hsl(0 0% 15%);
|
||||||
|
--color-card: hsl(0 0% 8%);
|
||||||
|
|
||||||
|
--color-destructive: hsl(0 84% 60%);
|
||||||
|
--color-destructive-bg: hsl(0 84% 10% / 0.4);
|
||||||
|
--color-destructive-border: hsl(0 84% 20% / 0.5);
|
||||||
|
|
||||||
--font-sans:
|
--font-sans:
|
||||||
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Sidebar from "./components/layout/Sidebar";
|
|
||||||
import Header from "./components/layout/Header";
|
|
||||||
import Navbar from "./components/layout/Navbar";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global page metadata
|
|
||||||
*/
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Flowstate - Modern Kanban",
|
title: "Flowstate - Modern Kanban",
|
||||||
description: "Dein High-Performance Workflow Dashboard",
|
description: "Dein High-Performance Workflow Dashboard",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Root application layout providing global shell UI
|
|
||||||
*/
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -22,24 +13,8 @@ export default function RootLayout({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="de" className="dark h-full">
|
<html lang="de" className="dark h-full">
|
||||||
<body className="flex h-dvh overflow-hidden">
|
<body className="flex h-dvh overflow-hidden bg-background text-foreground">
|
||||||
{/* Sidebar (Desktop only) */}
|
{children}
|
||||||
<div className="hidden md:flex ">
|
|
||||||
<Sidebar />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Content Area */}
|
|
||||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden pb-16 md:pb-0">
|
|
||||||
<Header />
|
|
||||||
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
|
|
||||||
{/* Mobile Navbar (Mobile only) */}
|
|
||||||
<div className="md:hidden flex">
|
|
||||||
<Navbar />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
39
app/page.tsx
39
app/page.tsx
|
|
@ -1,6 +1,35 @@
|
||||||
/**
|
import { auth } from "@/auth";
|
||||||
* Landing page
|
import { redirect } from "next/navigation";
|
||||||
*/
|
import Link from "next/link";
|
||||||
export default function Home() {
|
|
||||||
return <div className="space-y-6">Home</div>;
|
export default async function WelcomePage() {
|
||||||
|
const session = typeof auth === "function" ? await auth() : null;
|
||||||
|
if (session) {
|
||||||
|
redirect("/board");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-dvh w-full flex-col items-center justify-center p-6 text-center">
|
||||||
|
<div className="max-w-md space-y-6">
|
||||||
|
<h1 className="text-4xl font-extrabold tracking-tight">Flowstate</h1>
|
||||||
|
<p className="text-muted">Welcome Page</p>
|
||||||
|
<div className="flex items-center justify-center gap-4">
|
||||||
|
<Link
|
||||||
|
href="/login/"
|
||||||
|
className="px-4 py-2 text-sm font-medium rounded-lg bg-foreground text-background hover:opacity-70 transition-opacity"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* TODO */}
|
||||||
|
<Link
|
||||||
|
href="/register/"
|
||||||
|
className="px-4 py-2 text-sm font-medium rounded-lg border border-border hover:opacity-70 transition-opacity"
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
auth.ts
Normal file
26
auth.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import NextAuth from "next-auth";
|
||||||
|
import Credentials from "next-auth/providers/credentials";
|
||||||
|
|
||||||
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
name: "Credentials",
|
||||||
|
credentials: {
|
||||||
|
email: { label: "Email", type: "email" },
|
||||||
|
password: { label: "Password", type: "password" },
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (
|
||||||
|
credentials?.email === "test@flowstate.io" &&
|
||||||
|
credentials?.password === "password"
|
||||||
|
) {
|
||||||
|
return { id: "1", name: "Charlotte W.", email: "test@flowstate.io" };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
pages: {
|
||||||
|
signIn: "/login",
|
||||||
|
},
|
||||||
|
});
|
||||||
103
package-lock.json
generated
103
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-react": "^1.28.0",
|
"lucide-react": "^1.28.0",
|
||||||
"next": "16.2.12",
|
"next": "16.2.12",
|
||||||
|
"next-auth": "^5.0.0-beta.32",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "19.2.4"
|
||||||
},
|
},
|
||||||
|
|
@ -37,6 +38,35 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@auth/core": {
|
||||||
|
"version": "0.41.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.3.tgz",
|
||||||
|
"integrity": "sha512-sJ3JMHHkXMD3aOjopv7mOBTO1Ocw4b0fAEXJBz6k7YHLpYQI6C40jCUPc5fNvUKxXRXNE1/sRISA15UrwWJBTw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@panva/hkdf": "^1.2.1",
|
||||||
|
"jose": "^6.0.6",
|
||||||
|
"oauth4webapi": "^3.3.0",
|
||||||
|
"preact": "10.24.3",
|
||||||
|
"preact-render-to-string": "6.5.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"nodemailer": "^7.0.7 || ^8.0.5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||||
|
|
@ -1345,6 +1375,15 @@
|
||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@panva/hkdf": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
|
|
@ -4667,6 +4706,15 @@
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "6.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.7.tgz",
|
||||||
|
"integrity": "sha512-hq1OB1bALKfydZNoViyg6hPVGV4i93ny9Op+n4zP5RSf7SCZEXa/TsG2O3IEr7+WlHRTPnpqDmHfMH6qXAD60w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
@ -5300,6 +5348,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-auth": {
|
||||||
|
"version": "5.0.0-beta.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.32.tgz",
|
||||||
|
"integrity": "sha512-CGlChIEWZ6LltNVxrE5yiySMID+Idpmry47JYA5lLwgD8Sx02a8M65VL0TWVz9nbnOioS/tCW/rP/0+mE7Qp4Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "0.41.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"next": "^14.0.0-0 || ^15.0.0 || ^16.0.0",
|
||||||
|
"nodemailer": "^7.0.7 || ^8.0.5",
|
||||||
|
"react": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-exports-info": {
|
"node_modules/node-exports-info": {
|
||||||
"version": "1.6.2",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz",
|
||||||
|
|
@ -5329,6 +5404,15 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oauth4webapi": {
|
||||||
|
"version": "3.8.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz",
|
||||||
|
"integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
|
@ -5618,6 +5702,25 @@
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||||
|
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact-render-to-string": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-react": "^1.28.0",
|
"lucide-react": "^1.28.0",
|
||||||
"next": "16.2.12",
|
"next": "16.2.12",
|
||||||
|
"next-auth": "^5.0.0-beta.32",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "19.2.4"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue