diff --git a/Makefile b/Makefile index 9df7aeb4..a248fb30 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ prod: .PHONY: fmt fmt: - npx prettier --write frontend/src backend/src + npx prettier --write frontend backend .PHONY: update update: diff --git a/backend/.prettierrc b/backend/.prettierrc index dcb72794..a20502b7 100644 --- a/backend/.prettierrc +++ b/backend/.prettierrc @@ -1,4 +1,4 @@ { "singleQuote": true, "trailingComma": "all" -} \ No newline at end of file +} diff --git a/backend/prisma/migrations/20231103070602_user_password/migration.sql b/backend/prisma/migrations/20231103070602_user_password/migration.sql new file mode 100644 index 00000000..05edc77c --- /dev/null +++ b/backend/prisma/migrations/20231103070602_user_password/migration.sql @@ -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; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index e11e5421..2016f4c3 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -14,4 +14,5 @@ model User { id Int @id @default(autoincrement()) email String @unique name String? + password String } diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index f6735043..25ff847e 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -17,7 +17,7 @@ export class UserController { @Post() create( - @Body() userData: { name?: string; email: string }, + @Body() userData: { name?: string; email: string; password: string }, ): Promise { return this.userService.create(userData); } @@ -35,7 +35,7 @@ export class UserController { @Patch(':id') update( @Param('id') id: string, - @Body() userData: { name?: string; email?: string }, + @Body() userData: { name?: string; email?: string; password?: string }, ) { return this.userService.update(+id, userData); } diff --git a/backend/test/jest-e2e.json b/backend/test/jest-e2e.json index 049364a1..8607b62d 100644 --- a/backend/test/jest-e2e.json +++ b/backend/test/jest-e2e.json @@ -1,6 +1,6 @@ { "moduleNameMapper": { - "^src/(.*)$": "/../src/$1" + "^src/(.*)$": "/../src/$1" }, "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 73213b73..30336c50 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -4,9 +4,11 @@ import "./globals.css"; // components import { ThemeProvider } from "@/components/theme-provider"; -import Nav from "@/components/Nav"; import { Toaster } from "@/components/ui/toaster"; +// ui +import Nav from "@/app/ui/nav"; + const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { diff --git a/frontend/app/lib/client-actions.ts b/frontend/app/lib/client-actions.ts new file mode 100644 index 00000000..1b201152 --- /dev/null +++ b/frontend/app/lib/client-actions.ts @@ -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); + } +} diff --git a/frontend/components/Nav.tsx b/frontend/app/ui/nav.tsx similarity index 86% rename from frontend/components/Nav.tsx rename to frontend/app/ui/nav.tsx index 9352149e..d6e3b041 100644 --- a/frontend/components/Nav.tsx +++ b/frontend/app/ui/nav.tsx @@ -1,5 +1,5 @@ import Image from "next/image"; -import { ModeToggle } from "./toggle-mode"; +import { ModeToggle } from "@/components/toggle-mode"; import Link from "next/link"; export default function Nav() { @@ -18,9 +18,7 @@ export default function Nav() { />
  • - - Home - + Home User List Sign Up diff --git a/frontend/app/ui/user/card.tsx b/frontend/app/ui/user/card.tsx new file mode 100644 index 00000000..0776dc02 --- /dev/null +++ b/frontend/app/ui/user/card.tsx @@ -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) { + 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 ( + <> + + ID: {user.id} + +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + + + +
    + + ); +} diff --git a/frontend/app/ui/user/create-form.tsx b/frontend/app/ui/user/create-form.tsx new file mode 100644 index 00000000..6d3b46ba --- /dev/null +++ b/frontend/app/ui/user/create-form.tsx @@ -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 ( +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    + ); +} diff --git a/frontend/app/user/[id]/page.tsx b/frontend/app/user/[id]/page.tsx index 55e400d2..baf149de 100644 --- a/frontend/app/user/[id]/page.tsx +++ b/frontend/app/user/[id]/page.tsx @@ -1,4 +1,4 @@ -import UserCard from "@/components/UserCard"; +import UserCard from "@/app/ui/user/card"; async function getUser(id: number) { const res = await fetch(`${process.env.API_URL}/user/${id}`, { diff --git a/frontend/app/user/signup/page.tsx b/frontend/app/user/signup/page.tsx index ac1d5b7c..847aa7de 100644 --- a/frontend/app/user/signup/page.tsx +++ b/frontend/app/user/signup/page.tsx @@ -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) { - 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 ( - Create Account + + Create Account + -
    -
    -
    - - -
    -
    - - -
    - -
    -
    +
    ); diff --git a/frontend/components.json b/frontend/components.json index 379cd70f..f335e28b 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -13,4 +13,4 @@ "components": "@/components", "utils": "@/lib/utils" } -} \ No newline at end of file +} diff --git a/frontend/next.config.js b/frontend/next.config.js index 767719fc..658404ac 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,4 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = {}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 33ad091d..12a703d9 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 0377ea1d..c05b2ff6 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -2,11 +2,11 @@ module.exports = { darkMode: ["class"], content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + ], theme: { container: { center: true, @@ -73,4 +73,4 @@ module.exports = { }, }, plugins: [require("tailwindcss-animate")], -} \ No newline at end of file +};