-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Feat/login #23
[WIP] Feat/login #23
Changes from all commits
f311a20
97db4df
8d1c254
e7e1214
83149eb
3a9c090
da45722
efbac51
e41d9d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"singleQuote": true, | ||
"trailingComma": "all" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
Warnings: | ||
|
||
- Added the required column `password` to the `User` table without a default value. This is not possible if the table is not empty. | ||
|
||
*/ | ||
-- AlterTable | ||
ALTER TABLE "User" ADD COLUMN "password" TEXT NOT NULL; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,5 @@ model User { | |
id Int @id @default(autoincrement()) | ||
email String @unique | ||
name String? | ||
password String | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"use client"; | ||
|
||
import { RedirectType, redirect } from "next/navigation"; | ||
import { toast } from "@/components/ui/use-toast"; | ||
|
||
export async function createUser(formData: FormData) { | ||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
name: formData.get("name"), | ||
email: formData.get("email"), | ||
password: formData.get("password"), | ||
}), | ||
}); | ||
const data = await res.json(); | ||
if (!res.ok) { | ||
toast({ | ||
title: res.status + " " + res.statusText, | ||
description: data.message, | ||
}); | ||
} else { | ||
toast({ | ||
title: "Success", | ||
description: "User created successfully.", | ||
}); | ||
redirect("/user", RedirectType.push); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
"use client"; | ||
|
||
import { redirect, RedirectType } from "next/navigation"; | ||
|
||
// components | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardFooter, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Button } from "@/components/ui/button"; | ||
import { toast } from "@/components/ui/use-toast"; | ||
|
||
export type User = { id: number; name?: string; email?: string }; | ||
|
||
export default function UserCard({ user }: { user: User }) { | ||
async function updateUser(event: React.FormEvent<HTMLFormElement>) { | ||
event.preventDefault(); | ||
const { id, ...updateData } = Object.fromEntries( | ||
new FormData(event.currentTarget), | ||
); | ||
const res = await fetch( | ||
`${process.env.NEXT_PUBLIC_API_URL}/user/${user.id}`, | ||
{ | ||
method: "PATCH", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(updateData), | ||
}, | ||
); | ||
const data = await res.json(); | ||
if (!res.ok) { | ||
toast({ | ||
title: res.status + " " + res.statusText, | ||
description: data.message, | ||
}); | ||
} else { | ||
toast({ | ||
title: "Success", | ||
description: "User updated successfully.", | ||
}); | ||
redirect("/user", RedirectType.push); | ||
} | ||
} | ||
async function deleteUser(event: React.SyntheticEvent) { | ||
event.preventDefault(); | ||
const res = await fetch( | ||
`${process.env.NEXT_PUBLIC_API_URL}/user/${user.id}`, | ||
{ | ||
method: "DELETE", | ||
}, | ||
); | ||
const data = await res.json(); | ||
if (!res.ok) { | ||
toast({ | ||
title: res.status + " " + res.statusText, | ||
description: data.message, | ||
}); | ||
} else { | ||
toast({ | ||
title: "Success", | ||
description: "User deleted successfully.", | ||
}); | ||
redirect("/user", RedirectType.push); | ||
} | ||
} | ||
return ( | ||
<> | ||
<Card className="w-[300px]"> | ||
<CardHeader>ID: {user.id}</CardHeader> | ||
<CardContent> | ||
<form onSubmit={updateUser} id={"UpdateUserForm." + user.id}> | ||
<div className="grid w-full items-center gap-4"> | ||
<div className="flex flex-col space-y-1.5"> | ||
<Label htmlFor="name">Name</Label> | ||
<Input | ||
id="name" | ||
type="text" | ||
name="name" | ||
defaultValue={user.name} | ||
/> | ||
</div> | ||
<div className="flex flex-col space-y-1.5"> | ||
<Label htmlFor="email">Email</Label> | ||
<Input | ||
id="email" | ||
type="email" | ||
name="email" | ||
defaultValue={user.email} | ||
/> | ||
</div> | ||
</div> | ||
</form> | ||
</CardContent> | ||
<CardFooter className="flex justify-between"> | ||
<Button type="button" onClick={deleteUser}> | ||
Delete | ||
</Button> | ||
<Button | ||
variant="outline" | ||
type="submit" | ||
form={"UpdateUserForm." + user.id} | ||
> | ||
Update | ||
</Button> | ||
</CardFooter> | ||
</Card> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||
"use client"; | ||||||
|
||||||
import { createUser } from "@/app/lib/client-actions"; | ||||||
|
||||||
// components | ||||||
import { Input } from "@/components/ui/input"; | ||||||
import { Label } from "@/components/ui/label"; | ||||||
import { Button } from "@/components/ui/button"; | ||||||
|
||||||
export default function UserCreateForm() { | ||||||
return ( | ||||||
<form action={createUser}> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - <form action={createUser}>
+ <form onSubmit={(event) => {event.preventDefault(); createUser();}}> Commitable suggestion (Beta)
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is React's new feature. You should look into it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @usatie Thank you for pointing out the new feature in React. I appreciate your input and the link you provided. I've reviewed the documentation and it seems you're correct. The |
||||||
<div className="grid w-full items-center gap-8"> | ||||||
<div className="flex flex-col space-y-1.5"> | ||||||
<Label htmlFor="name">User Name</Label> | ||||||
<Input id="name" type="text" name="name" placeholder="e.g. nop" /> | ||||||
</div> | ||||||
<div className="flex flex-col space-y-1.5"> | ||||||
<Label htmlFor="email">Email</Label> | ||||||
<Input | ||||||
id="email" | ||||||
type="email" | ||||||
name="email" | ||||||
placeholder="e.g. [email protected]" | ||||||
/> | ||||||
</div> | ||||||
<div className="flex flex-col space-y-1.5"> | ||||||
<Label htmlFor="password">Password</Label> | ||||||
<Input | ||||||
id="password" | ||||||
type="password" | ||||||
name="password" | ||||||
placeholder="Must have at least 6 characters" | ||||||
/> | ||||||
</div> | ||||||
<Button type="submit">Sign Up</Button> | ||||||
</div> | ||||||
</form> | ||||||
); | ||||||
} | ||||||
Comment on lines
+10
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The form lacks state management for the input fields. You should use React's export default function UserCreateForm() {
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const handleSubmit = (event) => {
event.preventDefault();
createUser({ name, email, password });
};
return (
<form onSubmit={handleSubmit}>
<div className="grid w-full items-center gap-8">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">User Name</Label>
<Input id="name" type="text" name="name" placeholder="e.g. nop" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" name="email" placeholder="e.g. [email protected]" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" name="password" placeholder="Must have at least 6 characters" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
<Button type="submit">Sign Up</Button>
</div>
</form>
);
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,3 @@ | ||
"use client"; | ||
|
||
import { useRouter } from "next/navigation"; | ||
|
||
// components | ||
import { | ||
Card, | ||
|
@@ -11,63 +7,16 @@ import { | |
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Button } from "@/components/ui/button"; | ||
import { useToast } from "@/components/ui/use-toast"; | ||
import Form from "@/app/ui/user/create-form"; | ||
|
||
export default function SignUp() { | ||
const router = useRouter(); | ||
const { toast } = useToast(); | ||
async function createUser(event: React.FormEvent<HTMLFormElement>) { | ||
event.preventDefault(); | ||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify( | ||
Object.fromEntries(new FormData(event.currentTarget)), | ||
), | ||
}); | ||
const data = await res.json(); | ||
if (!res.ok) { | ||
toast({ | ||
title: res.status + " " + res.statusText, | ||
description: data.message, | ||
}); | ||
} else { | ||
toast({ | ||
title: "Success", | ||
description: "User created successfully.", | ||
}); | ||
router.push("/user"); | ||
router.refresh(); | ||
} | ||
} | ||
|
||
return ( | ||
<Card className="w-[300px]"> | ||
<CardHeader>Create Account</CardHeader> | ||
<CardHeader> | ||
<CardTitle>Create Account</CardTitle> | ||
</CardHeader> | ||
<CardContent> | ||
<form onSubmit={createUser}> | ||
<div className="grid w-full items-center gap-8"> | ||
<div className="flex flex-col space-y-1.5"> | ||
<Label htmlFor="name">Name</Label> | ||
<Input id="name" type="text" name="name" placeholder="nop" /> | ||
</div> | ||
<div className="flex flex-col space-y-1.5"> | ||
<Label htmlFor="email">Email</Label> | ||
<Input | ||
id="email" | ||
type="email" | ||
name="email" | ||
placeholder="[email protected]" | ||
/> | ||
</div> | ||
<Button type="submit">Sign Up</Button> | ||
</div> | ||
</form> | ||
<Form /> | ||
</CardContent> | ||
</Card> | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,4 @@ | |
"components": "@/components", | ||
"utils": "@/lib/utils" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/** @type {import('next').NextConfig} */ | ||
const nextConfig = {} | ||
const nextConfig = {}; | ||
|
||
module.exports = nextConfig | ||
module.exports = nextConfig; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,4 @@ module.exports = { | |
tailwindcss: {}, | ||
autoprefixer: {}, | ||
}, | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
password
field is being stored as a plainString
. This is a serious security issue as it exposes sensitive user data. Passwords should be hashed before being stored in the database. Consider using a library like bcrypt to hash passwords.The
@db.VarChar(60)
directive is used to ensure the hashed password fits into the database column. Bcrypt, for example, generates a string of length 60.Commitable suggestion (Beta)