From 5278bfaf44860f0497ea288d52a453c4f7452b8e Mon Sep 17 00:00:00 2001 From: Majkipl27 Date: Sun, 24 Mar 2024 15:38:35 +0100 Subject: [PATCH] Added backend route for getting products, added products category mapping --- app/api/auth/logout/route.ts | 9 + app/api/interfaces.ts | 10 + app/api/products/getProducts.ts | 249 ++++ app/api/products/route.ts | 100 ++ app/auth/login/page.tsx | 4 +- app/auth/register/page.tsx | 5 +- app/products/page.tsx | 76 ++ components/header-nav.tsx | 37 +- components/header.tsx | 134 ++- components/products/Cases.tsx | 5 + components/products/CpuCooling.tsx | 9 + components/products/Cpus.tsx | 5 + components/products/Gpus.tsx | 5 + components/products/HardDrives.tsx | 9 + components/products/Headsets.tsx | 9 + components/products/Keyboards.tsx | 5 + components/products/Memory.tsx | 5 + components/products/Mice.tsx | 5 + components/products/Monitors.tsx | 5 + components/products/Motherboards.tsx | 5 + components/products/PowerSupplies.tsx | 5 + components/products/Webcams.tsx | 5 + components/productsMap.tsx | 68 ++ components/ui/command.tsx | 155 +++ components/ui/dialog.tsx | 122 ++ lib/interfaces.tsx | 227 +++- package.json | 4 + pnpm-lock.yaml | 1495 ++++++++++++++++++++++++- prisma/dbml/schema.dbml | 196 ++++ prisma/schema.prisma | 231 +++- 30 files changed, 3082 insertions(+), 117 deletions(-) create mode 100644 app/api/auth/logout/route.ts create mode 100644 app/api/interfaces.ts create mode 100644 app/api/products/getProducts.ts create mode 100644 app/api/products/route.ts create mode 100644 app/products/page.tsx create mode 100644 components/products/Cases.tsx create mode 100644 components/products/CpuCooling.tsx create mode 100644 components/products/Cpus.tsx create mode 100644 components/products/Gpus.tsx create mode 100644 components/products/HardDrives.tsx create mode 100644 components/products/Headsets.tsx create mode 100644 components/products/Keyboards.tsx create mode 100644 components/products/Memory.tsx create mode 100644 components/products/Mice.tsx create mode 100644 components/products/Monitors.tsx create mode 100644 components/products/Motherboards.tsx create mode 100644 components/products/PowerSupplies.tsx create mode 100644 components/products/Webcams.tsx create mode 100644 components/productsMap.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 prisma/dbml/schema.dbml diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts new file mode 100644 index 0000000..87da4bd --- /dev/null +++ b/app/api/auth/logout/route.ts @@ -0,0 +1,9 @@ +import { USER_TOKEN } from "@lib/constants"; +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +export async function POST(_: Request) { + cookies().delete("user_info"); + cookies().delete(USER_TOKEN); + return NextResponse.json({ message: "Logged out" }, { status: 200 }); +} diff --git a/app/api/interfaces.ts b/app/api/interfaces.ts new file mode 100644 index 0000000..499a0c3 --- /dev/null +++ b/app/api/interfaces.ts @@ -0,0 +1,10 @@ +interface filtersInterface { + minPrice?: number; + maxPrice?: number; + skip?: number; + take?: number; + date?: "asc" | "desc" | null; + price?: "asc" | "desc" | null; +} + +export type { filtersInterface }; diff --git a/app/api/products/getProducts.ts b/app/api/products/getProducts.ts new file mode 100644 index 0000000..75f4cdf --- /dev/null +++ b/app/api/products/getProducts.ts @@ -0,0 +1,249 @@ +import prisma from "@lib/prisma"; +import { filtersInterface } from "../interfaces"; + +export async function getMotherboards(filters?: filtersInterface) { + const motherboards = await prisma.motherboard.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return motherboards; +} + +export async function getCpus(filters?: filtersInterface) { + const cpus = await prisma.cpu.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return cpus; +} + +export async function getMemory(filters?: filtersInterface) { + const memory = await prisma.memory.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return memory; +} + +export async function getGpus(filters?: filtersInterface) { + const gpus = await prisma.gpu.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return gpus; +} + +export async function getCases(filters?: filtersInterface) { + const cases = await prisma.case.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return cases; +} + +export async function getPowerSupplies(filters?: filtersInterface) { + const powerSupplies = await prisma.powerSupply.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return powerSupplies; +} + +export async function getCpuCooling(filters?: filtersInterface) { + const cpuCooling = await prisma.cpuCooling.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return cpuCooling; +} + +export async function getHardDrives(filters?: filtersInterface) { + const hardDrives = await prisma.hardDrive.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return hardDrives; +} + +export async function getHeadsets(filters?: filtersInterface) { + const headsets = await prisma.headset.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return headsets; +} + +export async function getMice(filters?: filtersInterface) { + const mice = await prisma.mouse.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return mice; +} + +export async function getKeyboards(filters?: filtersInterface) { + const keyboards = await prisma.keyboard.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return keyboards; +} + +export async function getMonitors(filters?: filtersInterface) { + const monitors = await prisma.monitor.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return monitors; +} + +export async function getWebcams(filters?: filtersInterface) { + const webcams = await prisma.webcam.findMany({ + take: filters?.take || 10, + skip: filters?.skip || 0, + where: { + price: { + gte: filters?.minPrice || 0, + lte: filters?.maxPrice || 1000000, + }, + }, + orderBy: { + price: filters?.price || undefined, + createdAt: filters?.date || undefined, + }, + }); + + return webcams; +} diff --git a/app/api/products/route.ts b/app/api/products/route.ts new file mode 100644 index 0000000..86604ed --- /dev/null +++ b/app/api/products/route.ts @@ -0,0 +1,100 @@ +import { NextResponse } from "next/server"; +import { + getCases, + getCpuCooling, + getCpus, + getGpus, + getHardDrives, + getHeadsets, + getKeyboards, + getMemory, + getMice, + getMonitors, + getMotherboards, + getPowerSupplies, +} from "./getProducts"; +import { filtersInterface } from "../interfaces"; + +export async function GET(req: Request) { + try { + const url = new URL(req.url); + const category = url.searchParams.get("category"); + const minPrice = url.searchParams.get("minPrice") || 0; + const maxPrice = url.searchParams.get("maxPrice") || 1000000; + const skip = url.searchParams.get("skip") || 0; + const take = url.searchParams.get("take") || 10; + const date = url.searchParams.get("date") || null; + const price = url.searchParams.get("price") || null; + + if (!category) { + return NextResponse.json({ error: "Provide category!" }, { status: 400 }); + } + + const filters: filtersInterface = { + minPrice: +minPrice, + maxPrice: +maxPrice, + skip: +skip, + take: +take, + date: date as "asc" | "desc" | null, + price: price as "asc" | "desc" | null, + }; + + let data: unknown; + + switch (category) { + case "motherboards": + data = await getMotherboards(filters); + break; + case "cpus": + data = await getCpus(filters); + break; + case "memory": + data = await getMemory(filters); + break; + case "gpus": + data = await getGpus(filters); + break; + case "cases": + data = await getCases(filters); + break; + case "power-supply": + data = await getPowerSupplies(filters); + break; + case "cooling": + data = getCpuCooling(filters); + break; + case "storage": + data = getHardDrives(filters); + break; + case "headsets": + data = getHeadsets(filters); + break; + case "mouses": + data = getMice(filters); + break; + case "keyboards": + data = getKeyboards(filters); + break; + case "monitors": + data = getMonitors(filters); + break; + case "webcams": + data = getMonitors(filters); + break; + } + + return NextResponse.json({ + status: 200, + body: { + category, + data, + }, + }); + } catch (error) { + console.log(error); + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 } + ); + } +} diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx index 7fd2003..489cd56 100644 --- a/app/auth/login/page.tsx +++ b/app/auth/login/page.tsx @@ -50,6 +50,7 @@ export default function LoginPage(): JSX.Element { toast({ title: "Succesfully logged in!", }); + window.location.href = "/"; break; case 401: toast({ @@ -124,7 +125,8 @@ export default function LoginPage(): JSX.Element { diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx index 9f9d745..4b43e39 100644 --- a/app/auth/register/page.tsx +++ b/app/auth/register/page.tsx @@ -18,6 +18,7 @@ import { z } from "zod"; import Bg from "./9205967.jpg"; import Image from "next/image"; import Link from "next/link"; +import { useState } from "react"; const formSchema = z.object({ email: z.string().email({ @@ -63,6 +64,7 @@ export default function RegisterPage(): JSX.Element { toast({ title: "Succesfully registered!", }); + window.location.href = "/auth/login"; break; case 400: toast({ @@ -80,7 +82,8 @@ export default function RegisterPage(): JSX.Element { return (
diff --git a/app/products/page.tsx b/app/products/page.tsx new file mode 100644 index 0000000..d534650 --- /dev/null +++ b/app/products/page.tsx @@ -0,0 +1,76 @@ +"use client"; +import ProductsMap from "@components/productsMap"; +import { toast } from "@components/ui/use-toast"; +import { products } from "@lib/interfaces"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; + +export default function ProductsPage() { + const [products, setProducts] = useState({ + category: "", + data: [], + }); + const [take, setTake] = useState(10); + const [skip, setSkip] = useState(0); + + const router = useRouter(); + const pathname = usePathname(); + const params = useSearchParams(); + const url = params.toString(); + const category = params.get("category"); + + async function fetchProducts() { + try { + const req = await fetch(`/api/products?${url}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + }); + + const res = await req.json(); + if (res.status !== 200) { + throw new Error("Failed to fetch data."); + } else { + setProducts(res.body); + } + } catch (error) { + toast({ + title: "Error!", + description: "Failed to fetch data.", + }); + } + } + + function checkSearchParams(): void { + const newParams = new URLSearchParams(params.toString()); + const takeParam = params.get("take"); + const skipParam = params.get("skip"); + if (takeParam) { + setTake(+takeParam); + } else { + newParams.set("take", take.toString()); + } + if (skipParam) { + setSkip(+skipParam); + } else { + newParams.set("skip", skip.toString()); + } + + router.push(pathname + "?" + newParams.toString()); + } + + useEffect(() => { + checkSearchParams(); + // fetchProducts(); + }, []); + + return ( +
+

Products

+ {category} + +
+ ); +} diff --git a/components/header-nav.tsx b/components/header-nav.tsx index b17bfd0..4da3f49 100644 --- a/components/header-nav.tsx +++ b/components/header-nav.tsx @@ -1,3 +1,4 @@ +"use client"; import Link from "next/link"; import { NavigationMenu, @@ -15,49 +16,49 @@ export default function HeaderNav(): JSX.Element { const components: { title: string; href: string; description: string }[] = [ { title: "Motherboards", - href: "/motherboards", + href: "/products?category=motherboards", description: "Motherboards are the main printed circuit board within a computer.", }, { title: "Cpu's", - href: "/cpus", + href: "/products?category=cpus", description: "Cpu is the component of a computer that acts as its “control center”.", }, { title: "Graphics cards", - href: "/gpus", + href: "/products?category=gpus", description: "A graphics processing unit (GPU) is a specialized processor originally designed to accelerate graphics rendering", }, { title: "Memory", - href: "/memory", + href: "/products?category=memory", description: "RAM is a computer's short-term memory, where the data that the processor is currently using is stored.", }, { title: "Storage", - href: "/storage", + href: "/products?category=storage", description: "Storage is the place where data is held for access by a computer processor.", }, { title: "Power supply", - href: "/power-supply", + href: "/products?category=power-supply", description: "A power supply is an electrical device that supplies electric power.", }, { title: "Cases", - href: "/cases", + href: "/products?category=cases", description: "A computer case, enclosure that contains most of the components.", }, { title: "Cpu cooling", - href: "/cooling", + href: "/products?category=cooling", description: "CPU cooling is device, that cools your cpu.", }, ]; @@ -88,18 +89,22 @@ export default function HeaderNav(): JSX.Element {
A headset combines a headphone with a microphone. - + A computer mouse is a hand-held pointing device that detects two-dimensional motion. A computer monitor is an output device that displays @@ -109,7 +114,7 @@ export default function HeaderNav(): JSX.Element {
A keyboard is a typewriter-style device that uses an @@ -117,12 +122,12 @@ export default function HeaderNav(): JSX.Element { electronic switches. - A printer is a peripheral which makes a persistent human - readable representation of graphics or text on paper. + Webcam is a video camera that feeds or streams its image in + real time to or through a computer to a computer network.
diff --git a/components/header.tsx b/components/header.tsx index 549f75f..3d32afe 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -8,6 +8,22 @@ import { useAtom } from "jotai"; import { userAtom } from "@lib/atoms"; import { Button } from "./ui/button"; import { usePathname } from "next/navigation"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; +import { + IconClock, + IconDoorExit, + IconSearch, + IconShoppingCart, +} from "@tabler/icons-react"; +import { toast } from "./ui/use-toast"; +import { Input } from "./ui/input"; export default function Header(): JSX.Element { const [user, setUser] = useAtom(userAtom); @@ -22,44 +38,90 @@ export default function Header(): JSX.Element { } }, []); + async function logoutHandler() { + const req = await fetch("/api/auth/logout", { + method: "POST", + credentials: "include", + }); + if (req.status !== 200) return toast({ title: "Cannot logout" }); + toast({ + title: "Logged out", + }); + Cookies.remove("user_info"); + setUser(null); + setTimeout(() => { + window.location.href = "/"; + }, 500); + } + return !pathname.startsWith("/auth") ? ( - <> -
-
-
- - {/* */} - Pc-shop - - -
-
- -
+
+
+
+ + {/* */} + Pc_shop + + +
+
+ + +
+
+
-
- +
+
) : (