Compare commits

...

4 Commits

Author SHA1 Message Date
andrzej 4ae9625653 misc 2024-09-11 13:06:58 +02:00
andrzej ffb94accf6 build login form 2024-09-11 13:06:43 +02:00
andrzej 0096057ec9 basic authentication function 2024-09-11 13:06:31 +02:00
andrzej 3611609474 check user 2024-09-11 11:13:00 +02:00
8 changed files with 1175 additions and 152 deletions

955
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,11 +24,14 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-table": "^8.17.3",
"@types/bcrypt": "^5.0.2",
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"lucide-react": "^0.394.0",
"next": "14.2.3",
"next-auth": "^4.24.7",
"next-themes": "^0.3.0",
"react": "^18",
"react-day-picker": "^8.10.1",
@ -37,10 +40,11 @@
"recharts": "^2.12.7",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
"@types/node": "^20.16.5",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.19",

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"
}
model User {
id Int @id @default(autoincrement())
email String
password String
}
model Story {
id Int @id @default(autoincrement())
word_count Int

24
src/app/api/login.ts Normal file
View File

@ -0,0 +1,24 @@
import prisma from "app/lib/db";
import bcrypt from 'bcrypt';
export type User = {
id?: number,
email: string,
password: string,
}
export default async function authenticate(clientUser: User) {
const dbUser: User = await prisma.user.findFirst({ where: { email: clientUser.email } })
if (!dbUser) return "user doesn't exist"
const passwordMatches = await
bcrypt.compare(clientUser.password, dbUser.password)
if (!passwordMatches) return "password doesn't match"
return "password matches!"
}
let res = await authenticate({ email: "nobody", password: "nothing" })
console.log("nonexistent user: " + res)
res = await authenticate({ email: "demo@demo.demo", password: "nothing" })
console.log("existent user, bad password: " + res)
res = await authenticate({ email: "demo@demo.demo", password: "password" })
console.log("existent user, good password: " + res)

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

@ -0,0 +1,68 @@
"use client"
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "@/components/ui/use-toast";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(6)
})
export default function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
})
function onSubmit(values: z.infer<typeof formSchema>) {
toast({
title: "You submitted:",
description: JSON.stringify(values)
})
}
function onErrors(errors) {
toast({
title: "WHOOPS",
description: JSON.stringify(errors)
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit, onErrors)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input placeholder="email goes here" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
></FormField>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="password goes here" type="password"{...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
></FormField>
<Button type="submit">SUBMIT</Button>
</form>
</Form>
)
}

View File

@ -634,6 +634,10 @@ body {
border-width: 0;
}
.pointer-events-none {
pointer-events: none;
}
.pointer-events-auto {
pointer-events: auto;
}
@ -666,6 +670,10 @@ body {
left: 0.5rem;
}
.left-3 {
left: 0.75rem;
}
.left-\[50\%\] {
left: 50%;
}
@ -686,6 +694,10 @@ body {
top: 0px;
}
.top-1\/2 {
top: 50%;
}
.top-2 {
top: 0.5rem;
}
@ -740,6 +752,10 @@ body {
margin-bottom: 1.5rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.mb-4 {
margin-bottom: 1rem;
}
@ -764,6 +780,10 @@ body {
margin-top: 1rem;
}
.mt-5 {
margin-top: 1.25rem;
}
.mt-6 {
margin-top: 1.5rem;
}
@ -772,6 +792,10 @@ body {
margin-top: auto;
}
.block {
display: block;
}
.flex {
display: flex;
}
@ -792,6 +816,10 @@ body {
display: grid;
}
.hidden {
display: none;
}
.size-full {
width: 100%;
height: 100%;
@ -813,6 +841,10 @@ body {
height: 0.5rem;
}
.h-20 {
height: 5rem;
}
.h-24 {
height: 6rem;
}
@ -853,6 +885,14 @@ body {
height: 1.2rem;
}
.h-\[18px\] {
height: 18px;
}
.h-\[48px\] {
height: 48px;
}
.h-\[var\(--radix-select-trigger-height\)\] {
height: var(--radix-select-trigger-height);
}
@ -906,6 +946,10 @@ body {
width: 0.875rem;
}
.w-32 {
width: 8rem;
}
.w-4 {
width: 1rem;
}
@ -914,10 +958,18 @@ body {
width: 10rem;
}
.w-5 {
width: 1.25rem;
}
.w-5\/6 {
width: 83.333333%;
}
.w-6 {
width: 1.5rem;
}
.w-7 {
width: 1.75rem;
}
@ -934,6 +986,10 @@ body {
width: 1.2rem;
}
.w-\[18px\] {
width: 18px;
}
.w-\[240px\] {
width: 240px;
}
@ -972,6 +1028,10 @@ body {
min-width: fit-content;
}
.max-w-\[400px\] {
max-width: 400px;
}
.max-w-full {
max-width: 100%;
}
@ -988,10 +1048,22 @@ body {
max-width: 20rem;
}
.max-w-6xl {
max-width: 72rem;
}
.flex-1 {
flex: 1 1 0%;
}
.shrink-0 {
flex-shrink: 0;
}
.grow {
flex-grow: 1;
}
.caption-bottom {
caption-side: bottom;
}
@ -1000,6 +1072,11 @@ body {
border-collapse: collapse;
}
.-translate-y-1\/2 {
--tw-translate-y: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-\[-50\%\] {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -1076,6 +1153,10 @@ body {
align-items: flex-start;
}
.items-end {
align-items: flex-end;
}
.items-center {
align-items: center;
}
@ -1108,6 +1189,10 @@ body {
gap: 1rem;
}
.gap-12 {
gap: 3rem;
}
.gap-x-16 {
-moz-column-gap: 4rem;
column-gap: 4rem;
@ -1169,6 +1254,18 @@ body {
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-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@ -1197,10 +1294,18 @@ body {
white-space: nowrap;
}
.text-balance {
text-wrap: balance;
}
.rounded-full {
border-radius: 9999px;
}
.rounded-lg {
border-radius: var(--radius);
}
.rounded-md {
border-radius: calc(var(--radius) - 2px);
}
@ -1243,6 +1348,11 @@ body {
border-color: hsl(var(--destructive));
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-input {
border-color: hsl(var(--input));
}
@ -1267,6 +1377,11 @@ body {
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 {
background-color: hsl(var(--border));
}
@ -1279,6 +1394,11 @@ body {
background-color: hsl(var(--destructive));
}
.bg-gray-50 {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.bg-input {
background-color: hsl(var(--input));
}
@ -1364,6 +1484,11 @@ body {
padding-right: 1rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.px-8 {
padding-left: 2rem;
padding-right: 2rem;
@ -1399,6 +1524,24 @@ body {
padding-bottom: 1rem;
}
.py-\[9px\] {
padding-top: 9px;
padding-bottom: 9px;
}
.py-12 {
padding-top: 3rem;
padding-bottom: 3rem;
}
.pb-4 {
padding-bottom: 1rem;
}
.pl-10 {
padding-left: 2.5rem;
}
.pl-3 {
padding-left: 0.75rem;
}
@ -1427,6 +1570,10 @@ body {
padding-top: 0.25rem;
}
.pt-8 {
padding-top: 2rem;
}
.text-left {
text-align: left;
}
@ -1439,6 +1586,11 @@ body {
vertical-align: middle;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
@ -1538,6 +1690,21 @@ body {
color: hsl(var(--foreground) / 0.5);
}
.text-gray-50 {
--tw-text-opacity: 1;
color: rgb(249 250 251 / var(--tw-text-opacity));
}
.text-gray-500 {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-muted-foreground {
color: hsl(var(--muted-foreground));
}
@ -1554,6 +1721,11 @@ body {
color: hsl(var(--primary-foreground));
}
.text-red-500 {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-secondary-foreground {
color: hsl(var(--secondary-foreground));
}
@ -1617,6 +1789,10 @@ body {
outline-style: solid;
}
.outline-2 {
outline-width: 2px;
}
.ring-offset-background {
--tw-ring-offset-color: hsl(var(--background));
}
@ -1894,6 +2070,16 @@ body {
font-weight: 500;
}
.placeholder\:text-gray-500::-moz-placeholder {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.placeholder\:text-gray-500::placeholder {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.placeholder\:text-muted-foreground::-moz-placeholder {
color: hsl(var(--muted-foreground));
}
@ -1942,10 +2128,20 @@ body {
background-color: hsl(var(--secondary) / 0.8);
}
.hover\:bg-sky-100:hover {
--tw-bg-opacity: 1;
background-color: rgb(224 242 254 / var(--tw-bg-opacity));
}
.hover\:text-accent-foreground:hover {
color: hsl(var(--accent-foreground));
}
.hover\:text-blue-600:hover {
--tw-text-opacity: 1;
color: rgb(37 99 235 / var(--tw-text-opacity));
}
.hover\:text-foreground:hover {
color: hsl(var(--foreground));
}
@ -2075,6 +2271,11 @@ body {
--tw-ring-offset-color: #dc2626;
}
.peer:focus ~ .peer-focus\:text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.peer:disabled ~ .peer-disabled\:cursor-not-allowed {
cursor: not-allowed;
}
@ -2347,9 +2548,70 @@ body {
}
@media (min-width: 768px) {
.md\:-mt-32 {
margin-top: -8rem;
}
.md\:block {
display: block;
}
.md\:h-36 {
height: 9rem;
}
.md\:h-screen {
height: 100vh;
}
.md\:w-36 {
width: 9rem;
}
.md\:max-w-\[420px\] {
max-width: 420px;
}
.md\:flex-none {
flex: none;
}
.md\:justify-start {
justify-content: flex-start;
}
.md\:p-2 {
padding: 0.5rem;
}
.md\:px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
}
@media (min-width: 1024px) {
.lg\:grid {
display: grid;
}
.lg\:min-h-\[100vh\] {
min-height: 100vh;
}
.lg\:w-96 {
width: 24rem;
}
.lg\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1280px) {
.xl\:min-h-\[100vh\] {
min-height: 100vh;
}
}
.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]) {