The npm install command failed in my project. Analyze the following er
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
|
|
||||||
AUTH_SECRET=7f8a7e6d5c4b3a291f0e9d8c7b6a5f4e3d2c1b0a9e8f7d6c5b4a39281f0e9d8c
|
AUTH_SECRET=your-super-secret-auth-secret-change-me
|
||||||
|
AUTH_URL=http://localhost:3000
|
||||||
Generated
+129
@@ -44,6 +44,7 @@
|
|||||||
"genkit": "^1.14.1",
|
"genkit": "^1.14.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
|
"next-auth": "5.0.0-beta.19",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
@@ -92,6 +93,46 @@
|
|||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@auth/core": {
|
||||||
|
"version": "0.32.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.32.0.tgz",
|
||||||
|
"integrity": "sha512-3+ssTScBd+1fd0/fscAyQN1tSygXzuhysuVVzB942ggU4mdfiTbv36P0ccVnExKWYJKvu3E2r3/zxXCCAmTOrg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@panva/hkdf": "^1.1.1",
|
||||||
|
"@types/cookie": "0.6.0",
|
||||||
|
"cookie": "0.6.0",
|
||||||
|
"jose": "^5.1.3",
|
||||||
|
"oauth4webapi": "^2.9.0",
|
||||||
|
"preact": "10.11.3",
|
||||||
|
"preact-render-to-string": "5.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"nodemailer": "^6.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@auth/core/node_modules/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.26.9",
|
"version": "7.26.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
|
||||||
@@ -2673,6 +2714,15 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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/@pdf-lib/standard-fonts": {
|
"node_modules/@pdf-lib/standard-fonts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||||
@@ -4240,6 +4290,12 @@
|
|||||||
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
|
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||||
@@ -7306,6 +7362,15 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "5.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||||
|
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||||
|
"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",
|
||||||
@@ -7849,6 +7914,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-auth": {
|
||||||
|
"version": "5.0.0-beta.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.19.tgz",
|
||||||
|
"integrity": "sha512-YHu1igcAxZPh8ZB7GIM93dqgY6gcAzq66FOhQFheAdOx1raxNcApt05nNyNCSB6NegSiyJ4XOPsaNow4pfDmsg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "0.32.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"next": "^14 || ^15.0.0-0",
|
||||||
|
"nodemailer": "^6.6.5",
|
||||||
|
"react": "^18.2.0 || ^19.0.0-0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -7940,6 +8032,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oauth4webapi": {
|
||||||
|
"version": "2.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz",
|
||||||
|
"integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==",
|
||||||
|
"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",
|
||||||
@@ -8421,6 +8522,28 @@
|
|||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
||||||
|
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact-render-to-string": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pretty-format": "^3.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
@@ -8447,6 +8570,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pretty-format": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"name": "nextn",
|
"name": "nextn",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"genkit": "^1.14.1",
|
"genkit": "^1.14.1",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
|
"next-auth": "5.0.0-beta.19",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
"use client"
|
import { auth, signOut } from "@/auth"
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
@@ -13,8 +11,6 @@ import {
|
|||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
SidebarTrigger,
|
|
||||||
useSidebar,
|
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
import {
|
import {
|
||||||
Home,
|
Home,
|
||||||
@@ -29,14 +25,21 @@ import {
|
|||||||
Workflow
|
Workflow
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { logout } from "@/lib/actions/auth";
|
import { logout } from "@/lib/actions/auth";
|
||||||
|
|
||||||
function AdminLayout({
|
async function AdminLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
const session = await auth()
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
// This should be handled by middleware, but as a fallback
|
||||||
|
const { redirect } = await import("next/navigation")
|
||||||
|
redirect("/login")
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
import NextAuth from 'next-auth';
|
||||||
|
import { authConfig } from '@/auth.config';
|
||||||
|
|
||||||
|
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
|
||||||
|
|
||||||
|
export const GET = handlers.GET;
|
||||||
|
export const POST = handlers.POST;
|
||||||
+28
-55
@@ -1,13 +1,10 @@
|
|||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useActionState } from 'react';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import * as z from 'zod';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -16,48 +13,23 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { login } from '@/lib/actions/auth';
|
import { login } from '@/lib/actions/auth';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
|
import { AlertCircle } from 'lucide-react';
|
||||||
|
|
||||||
const loginSchema = z.object({
|
function SubmitButton() {
|
||||||
email: z.string().email({ message: 'Invalid email address.' }),
|
// This component will be updated by useFormStatus in a real app,
|
||||||
password: z.string().min(1, { message: 'Password is required.' }),
|
// but for now, we just show a static text.
|
||||||
});
|
return (
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type LoginFormValues = z.infer<typeof loginSchema>;
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const [state, formAction] = useActionState(login, undefined);
|
||||||
const { toast } = useToast();
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
formState: { errors, isSubmitting },
|
|
||||||
} = useForm<LoginFormValues>({
|
|
||||||
resolver: zodResolver(loginSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = async (data: LoginFormValues) => {
|
|
||||||
try {
|
|
||||||
const result = await login(data);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
toast({
|
|
||||||
title: 'Login Successful',
|
|
||||||
description: 'Redirecting to your dashboard...',
|
|
||||||
});
|
|
||||||
router.push('/admin');
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
toast({
|
|
||||||
variant: 'destructive',
|
|
||||||
title: 'Login Failed',
|
|
||||||
description: error.message || 'An unexpected error occurred.',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
|
<main className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
|
||||||
@@ -71,36 +43,37 @@ export default function LoginPage() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form action={formAction} className="space-y-4">
|
||||||
|
{state?.message && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>{state.message}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email">Email</Label>
|
<Label htmlFor="email">Email</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="admin@example.com"
|
placeholder="admin@example.com"
|
||||||
{...register('email')}
|
defaultValue="admin@example.com"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
{errors.email && (
|
|
||||||
<p className="text-sm text-destructive">{errors.email.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="password">Password</Label>
|
<Label htmlFor="password">Password</Label>
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="password"
|
||||||
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="password"
|
placeholder="password"
|
||||||
{...register('password')}
|
defaultValue="password"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
{errors.password && (
|
|
||||||
<p className="text-sm text-destructive">
|
|
||||||
{errors.password.message}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
<SubmitButton />
|
||||||
{isSubmitting ? 'Signing In...' : 'Sign In'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
import type { NextAuthConfig } from 'next-auth';
|
||||||
|
import Credentials from 'next-auth/providers/credentials';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import db from '@/lib/db';
|
||||||
|
import { BetterSqlite3Adapter } from "next-auth/adapters"
|
||||||
|
|
||||||
|
export const authConfig = {
|
||||||
|
pages: {
|
||||||
|
signIn: '/login',
|
||||||
|
},
|
||||||
|
adapter: BetterSqlite3Adapter(db),
|
||||||
|
session: {
|
||||||
|
strategy: 'database',
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
async authorize(credentials) {
|
||||||
|
const parsedCredentials = z
|
||||||
|
.object({ email: z.string().email(), password: z.string().min(1) })
|
||||||
|
.safeParse(credentials);
|
||||||
|
|
||||||
|
if (parsedCredentials.success) {
|
||||||
|
const { email, password } = parsedCredentials.data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userStmt = db.prepare('SELECT * FROM users WHERE email = ?');
|
||||||
|
const user = userStmt.get(email) as any;
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
// WARNING: Storing passwords in plaintext is insecure.
|
||||||
|
// This is for demonstration purposes only.
|
||||||
|
// In a real application, you MUST hash and salt passwords.
|
||||||
|
const passwordsMatch = password === user.password;
|
||||||
|
|
||||||
|
if (passwordsMatch) return user;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
authorized({ auth, request: { nextUrl } }) {
|
||||||
|
const isLoggedIn = !!auth?.user;
|
||||||
|
const isOnAdmin = nextUrl.pathname.startsWith('/admin');
|
||||||
|
|
||||||
|
if (isOnAdmin) {
|
||||||
|
return isLoggedIn;
|
||||||
|
} else if (isLoggedIn) {
|
||||||
|
// Redirect logged-in users from the login page to the admin dashboard
|
||||||
|
if (nextUrl.pathname === '/login') {
|
||||||
|
return Response.redirect(new URL('/admin', nextUrl));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies NextAuthConfig;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
import NextAuth from 'next-auth';
|
||||||
|
import { authConfig } from './auth.config';
|
||||||
|
|
||||||
|
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
|
||||||
+16
-32
@@ -1,44 +1,28 @@
|
|||||||
|
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
import { signIn, signOut } from '@/auth';
|
||||||
import { z } from 'zod';
|
|
||||||
import db from '@/lib/db';
|
|
||||||
|
|
||||||
const loginSchema = z.object({
|
|
||||||
email: z.string().email(),
|
|
||||||
password: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function login(data: z.infer<typeof loginSchema>): Promise<{ success: boolean, message: string }> {
|
|
||||||
const validatedFields = loginSchema.safeParse(data);
|
|
||||||
|
|
||||||
if (!validatedFields.success) {
|
|
||||||
return { success: false, message: 'Invalid fields.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email, password } = validatedFields.data;
|
|
||||||
|
|
||||||
|
export async function login(
|
||||||
|
prevState: { message: string } | undefined,
|
||||||
|
formData: FormData
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const stmt = db.prepare('SELECT * FROM users WHERE email = ? AND password = ?');
|
await signIn('credentials', formData);
|
||||||
const user = stmt.get(email, password);
|
} catch (error: any) {
|
||||||
|
if (error) {
|
||||||
if (user) {
|
switch (error.type) {
|
||||||
// In a real app, you would set a session cookie here.
|
case 'CredentialsSignin':
|
||||||
// For this simulated login, we'll just return success.
|
return { message: 'Invalid credentials.' };
|
||||||
return { success: true, message: 'Login successful.' };
|
default:
|
||||||
} else {
|
return { message: 'Something went wrong.' };
|
||||||
return { success: false, message: 'Invalid email or password.' };
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
throw error;
|
||||||
console.error('Login error:', error);
|
|
||||||
return { success: false, message: 'An internal error occurred.' };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
// In a real app with authentication, this would handle signing out the user.
|
await signOut({ redirectTo: '/login' });
|
||||||
// For now, it redirects to the login page to simulate logging out.
|
|
||||||
redirect('/login');
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-15
@@ -4,6 +4,7 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import db from '@/lib/db';
|
import db from '@/lib/db';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import { auth } from '@/auth';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1, 'Name is required'),
|
name: z.string().min(1, 'Name is required'),
|
||||||
@@ -14,28 +15,29 @@ const formSchema = z.object({
|
|||||||
type UserFormValues = z.infer<typeof formSchema>;
|
type UserFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the user from the database.
|
* Gets the currently logged-in user from the session.
|
||||||
* Since authentication isn't fully implemented, it defaults to the user with id 1.
|
|
||||||
*/
|
*/
|
||||||
export async function getUser(): Promise<{ id: number; name: string; email: string } | null> {
|
export async function getUser(): Promise<{ id: string; name: string; email: string } | null> {
|
||||||
try {
|
const session = await auth();
|
||||||
const stmt = db.prepare('SELECT id, name, email FROM users WHERE id = ?');
|
if (!session?.user?.id || !session.user.email || !session.user.name) {
|
||||||
// For now, we'll hardcode the user ID to 1 as login is simulated.
|
|
||||||
const user = stmt.get(1) as { id: number; name: string; email: string } | undefined;
|
|
||||||
if (!user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get user:', error);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
id: session.user.id,
|
||||||
|
email: session.user.email,
|
||||||
|
name: session.user.name,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a user's profile information in the database.
|
* Updates a user's profile information in the database.
|
||||||
*/
|
*/
|
||||||
export async function updateUser(data: UserFormValues): Promise<{ success: boolean; message: string }> {
|
export async function updateUser(data: UserFormValues): Promise<{ success: boolean; message: string }> {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return { success: false, message: 'Not authenticated.' };
|
||||||
|
}
|
||||||
|
|
||||||
const validation = formSchema.safeParse(data);
|
const validation = formSchema.safeParse(data);
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
return { success: false, message: 'Invalid data provided.' };
|
return { success: false, message: 'Invalid data provided.' };
|
||||||
@@ -44,8 +46,7 @@ export async function updateUser(data: UserFormValues): Promise<{ success: boole
|
|||||||
const { name, email, password } = validation.data;
|
const { name, email, password } = validation.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For now, we'll assume we're updating the user with ID 1.
|
const userId = session.user.id;
|
||||||
const userId = 1;
|
|
||||||
|
|
||||||
// Check if the new email is already taken by another user
|
// Check if the new email is already taken by another user
|
||||||
const checkEmailStmt = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?');
|
const checkEmailStmt = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?');
|
||||||
@@ -58,6 +59,7 @@ export async function updateUser(data: UserFormValues): Promise<{ success: boole
|
|||||||
if (password) {
|
if (password) {
|
||||||
// If a new password is provided, update it along with name and email
|
// If a new password is provided, update it along with name and email
|
||||||
const stmt = db.prepare('UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?');
|
const stmt = db.prepare('UPDATE users SET name = ?, email = ?, password = ? WHERE id = ?');
|
||||||
|
// In a real app, hash the password! For this example, we store it as plain text.
|
||||||
stmt.run(name, email, password, userId);
|
stmt.run(name, email, password, userId);
|
||||||
} else {
|
} else {
|
||||||
// If no new password, only update name and email
|
// If no new password, only update name and email
|
||||||
|
|||||||
+49
-7
@@ -3,17 +3,58 @@ import Database from 'better-sqlite3';
|
|||||||
|
|
||||||
// Use a file-based database in development
|
// Use a file-based database in development
|
||||||
const db = new Database('local.db');
|
const db = new Database('local.db');
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
|
||||||
// --- SCHEMA CREATION ---
|
// --- SCHEMA CREATION ---
|
||||||
|
// Auth.js tables
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id TEXT PRIMARY KEY,
|
||||||
email TEXT UNIQUE NOT NULL,
|
name TEXT,
|
||||||
password TEXT NOT NULL,
|
email TEXT UNIQUE,
|
||||||
name TEXT NOT NULL
|
emailVerified INTEGER,
|
||||||
|
image TEXT,
|
||||||
|
password TEXT
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
|
userId TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
providerAccountId TEXT NOT NULL,
|
||||||
|
refresh_token TEXT,
|
||||||
|
access_token TEXT,
|
||||||
|
expires_at INTEGER,
|
||||||
|
token_type TEXT,
|
||||||
|
scope TEXT,
|
||||||
|
id_token TEXT,
|
||||||
|
session_state TEXT,
|
||||||
|
PRIMARY KEY (provider, providerAccountId),
|
||||||
|
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
sessionToken TEXT NOT NULL PRIMARY KEY,
|
||||||
|
userId TEXT NOT NULL,
|
||||||
|
expires INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS verification_tokens (
|
||||||
|
identifier TEXT NOT NULL,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
expires INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (identifier, token)
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS settings (
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
@@ -54,13 +95,14 @@ db.exec(`
|
|||||||
console.log('Running database checks and seeding if necessary...');
|
console.log('Running database checks and seeding if necessary...');
|
||||||
|
|
||||||
// Seed default user
|
// Seed default user
|
||||||
const userStmt = db.prepare('SELECT id FROM users WHERE id = ?');
|
const userStmt = db.prepare('SELECT id FROM users WHERE email = ?');
|
||||||
const defaultUser = userStmt.get(1);
|
const defaultUser = userStmt.get('admin@example.com');
|
||||||
if (!defaultUser) {
|
if (!defaultUser) {
|
||||||
const insertUser = db.prepare(
|
const insertUser = db.prepare(
|
||||||
"INSERT INTO users (id, email, password, name) VALUES (?, ?, ?, ?)"
|
"INSERT INTO users (id, email, password, name) VALUES (?, ?, ?, ?)"
|
||||||
);
|
);
|
||||||
insertUser.run(1, 'admin@example.com', 'password', 'Admin User');
|
// Note: In a real app, hash the password!
|
||||||
|
insertUser.run('cl-admin-user-id', 'admin@example.com', 'password', 'Admin User');
|
||||||
console.log('Default user created.');
|
console.log('Default user created.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
import NextAuth from 'next-auth';
|
||||||
|
import { authConfig } from './auth.config';
|
||||||
|
|
||||||
|
export default NextAuth(authConfig).auth;
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
|
||||||
|
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user