implement first half of tutorial

start from "Updating the login form"
This commit is contained in:
andrzej 2024-08-12 16:21:08 +02:00
parent 89e338a0ac
commit 67af7fac8c
12 changed files with 773 additions and 117 deletions

2
.env
View File

@ -5,3 +5,5 @@
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db" DATABASE_URL="file:./dev.db"
AUTH_SECRET="fjvZUGDnuNa4/9kUxrIytM45gHiQhLTvSsdvsMcB/Rg="

21
auth.config.ts Normal file
View File

@ -0,0 +1,21 @@
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;

46
auth.ts Normal file
View File

@ -0,0 +1,46 @@
import NextAuth, { User } from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';
import prisma from 'app/lib/db';
import bcrypt from 'bcrypt';
async function getUser(email: string): Promise<User | undefined> {
try {
const user = await prisma.user.findFirst({
where: {
email
}
})
return user
} catch (error) {
throw new Error("failed to fetch user")
}
}
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.safeParse(credentials);
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;
const user = await getUser(email);
if (!user) return null;
const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
}
console.log('Invalid credentials');
return null;
},
})],
});

9
middleware.ts Normal file
View File

@ -0,0 +1,9 @@
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$).*)'],
};

732
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,11 +24,13 @@
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-table": "^8.17.3", "@tanstack/react-table": "^8.17.3",
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"lucide-react": "^0.394.0", "lucide-react": "^0.394.0",
"next": "14.2.3", "next": "14.2.3",
"next-auth": "^5.0.0-beta.20",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18", "react": "^18",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",

Binary file not shown.

BIN
prisma/dev.db-journal Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL
);

View File

@ -10,6 +10,12 @@ datasource db {
url = "file:./dev.db" url = "file:./dev.db"
} }
model User {
id Int @id @default(autoincrement())
email String
password String
}
model Story { model Story {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
word_count Int word_count Int

17
src/app/login/page.tsx Normal file
View File

@ -0,0 +1,17 @@
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';
export default function LoginPage() {
return (
<main className="flex items-center justify-center md:h-screen">
<div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
<div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
<div className="w-32 text-white md:w-36">
<AcmeLogo />
</div>
</div>
<LoginForm />
</div>
</main>
);
}

View File

@ -813,6 +813,10 @@ body {
height: 0.5rem; height: 0.5rem;
} }
.h-20 {
height: 5rem;
}
.h-24 { .h-24 {
height: 6rem; height: 6rem;
} }
@ -906,6 +910,10 @@ body {
width: 0.875rem; width: 0.875rem;
} }
.w-32 {
width: 8rem;
}
.w-4 { .w-4 {
width: 1rem; width: 1rem;
} }
@ -972,6 +980,10 @@ body {
min-width: fit-content; min-width: fit-content;
} }
.max-w-\[400px\] {
max-width: 400px;
}
.max-w-full { .max-w-full {
max-width: 100%; max-width: 100%;
} }
@ -1076,6 +1088,10 @@ body {
align-items: flex-start; align-items: flex-start;
} }
.items-end {
align-items: flex-end;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@ -1169,6 +1185,12 @@ body {
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
} }
.space-y-2\.5 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.625rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.625rem * var(--tw-space-y-reverse));
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) { .space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@ -1201,6 +1223,10 @@ body {
border-radius: 9999px; border-radius: 9999px;
} }
.rounded-lg {
border-radius: var(--radius);
}
.rounded-md { .rounded-md {
border-radius: calc(var(--radius) - 2px); border-radius: calc(var(--radius) - 2px);
} }
@ -1267,6 +1293,11 @@ body {
background-color: rgb(0 0 0 / 0.8); background-color: rgb(0 0 0 / 0.8);
} }
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
.bg-border { .bg-border {
background-color: hsl(var(--border)); background-color: hsl(var(--border));
} }
@ -2347,6 +2378,22 @@ body {
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.md\:-mt-32 {
margin-top: -8rem;
}
.md\:h-36 {
height: 9rem;
}
.md\:h-screen {
height: 100vh;
}
.md\:w-36 {
width: 9rem;
}
.md\:max-w-\[420px\] { .md\:max-w-\[420px\] {
max-width: 420px; max-width: 420px;
} }