Skip to content
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

Prueba técnica Bazar OnLine por DuffmanCC #315

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pruebas/02-bazar-universal/DuffmanCC/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HOST='your host'
3 changes: 3 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
4 changes: 4 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.next/
.swc/
.env
26 changes: 26 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

- Copy .env.example to .env and add your host
- Then, run the development server

```bash
npm run dev
```

To start a production server run

```bash
npm run build && npm run start
```

To run the tests

```bash
npm run test:e2e
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

This app was publish on Vercel [https://bazar-online-three.vercel.app/](https://bazar-online-three.vercel.app/)
25 changes: 25 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

body {
color: rgb(var(--foreground-rgb));
}

html {
height: 100%;
}
23 changes: 23 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/items/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import SingleProduct from "@/components/SingleProduct";
import { fetchSingleProduct } from "@/lib/requests";
import { Product } from "@/types";

interface Props {
params: {
id: string;
};
}

export default async function Items({ params }: Props) {
const { id } = params;

const product: Product = await fetchSingleProduct(parseInt(id));

return product ? (
<SingleProduct product={product} />
) : (
<p>
<span className="font-bold">404</span> No existe el producto
</p>
);
}
23 changes: 23 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/items/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { Cart } from "@/components/Cart";
import Header from "@/components/Header";
import { CartProvider } from "@/context/cart";

export default function ProductsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<CartProvider>
<div className="flex flex-col gap-8 w-full relative p-4 grow">
<Cart />

<Header />

{children}
</div>
</CartProvider>
);
}
25 changes: 25 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/items/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ProductsList from "@/components/ProductsList";
import Results from "@/components/Results";
import { fetchProducts } from "@/lib/requests";

interface Props {
searchParams: {
search: string;
};
}

export default async function Items({ searchParams }: Props) {
const { search } = searchParams;

const items = (await fetchProducts(search)) || [];

return (
<div className="flex flex-col gap-8">
<h1 className="sr-only">Lista de productos en nuestro bazar</h1>

<Results items={items} query={search} />

<ProductsList items={items} />
</div>
);
}
24 changes: 24 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "./globals.css";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<title>Tu bazar de confianza</title>
<meta name="description" content="Tu bazar de confianza" />
<link rel="icon" href="/favicon.png" type="image/svg+xml" />
<meta name="robots" content="index, follow"></meta>
</head>

<body className="flex flex-col text-black items-center container mx-auto min-h-screen">
<main className="flex flex-col grow items-center w-full">
{children}
</main>
</body>
</html>
);
}
14 changes: 14 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Truck } from "@/components/Icons";
import SearchWithButton from "@/components/SearchWithButton";

export default function Home() {
return (
<div className="flex flex-col gap-8 items-center justify-center grow">
<Truck width="200" />

<h1 className="text-4xl font-bold">Bazar Online</h1>

<SearchWithButton />
</div>
);
}
15 changes: 15 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/components/Badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface Props {
category: {
name: string;
count: number;
};
color: string;
}

export default function Badge({ category, color }: Props) {
return (
<div className={["px-4 py-2 ", color].join("")}>
{category.name} - {category.count}
</div>
);
}
100 changes: 100 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/components/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { CartContext } from "@/context/cart";
import { toDollar } from "@/lib/tools";
import { useContext, useMemo } from "react";
import { TrashIcon } from "./Icons";

export function Cart() {
const {
productsInCart,
showCart,
addQuantity,
removeQuantity,
removeFromCart,
clearCart,
} = useContext(CartContext);

const totalPrice = useMemo(() => {
if (productsInCart.length === 0) {
return;
}

const prices = productsInCart.map(
({ product, quantity }) => product.price * quantity
);

return toDollar(prices.reduce((a, b) => a + b));
}, [productsInCart]);

return (
<>
{showCart && (
<aside className="absolute top-0 pt-20 bottom-0 right-0 w-96 bg-gray-100 z-20 p-4">
{productsInCart.length !== 0 ? (
<div className="flex flex-col gap-8">
<ul className="flex flex-col gap-2">
{productsInCart.map(({ product, quantity }) => (
<li
key={product.id}
className="flex gap-8 items-center justify-end"
>
<div className="w-96">{product.title}</div>

<div className="flex gap-4 items-center justify-end">
<div className="flex gap-2 items-center">
<div>{quantity}</div>

<button
className="w-6 h-6 border rounded-full flex justify-center items-center"
onClick={() => addQuantity(product)}
aria-label="botón para agregar una unidad del producto al carro de compra"
>
+
</button>

<button
className="w-6 h-6 border rounded-full flex justify-center items-center"
onClick={() => removeQuantity(product)}
aria-label="botón para remover una unidad del producto del carro de compra"
>
-
</button>
</div>

<div className="w-16 flex justify-end">
{toDollar(product.price)}
</div>

<button
className=""
onClick={() => removeFromCart(product)}
aria-label="botón para remover el producto del carro de compra"
>
<TrashIcon width="20" />
</button>
</div>
</li>
))}
</ul>

<div className="flex gap-8 items-center justify-end">
<div>Total</div>

<div className="flex gap-4 items-center justify-end">
<div className="w-16 justify-end flex">{totalPrice}</div>

<button className="" onClick={clearCart}>
<TrashIcon width="20" />
</button>
</div>
</div>
</div>
) : (
<p className="text-xl font-bold text-right mt-8">
Your cart is empty
</p>
)}
</aside>
)}
</>
);
}
77 changes: 77 additions & 0 deletions pruebas/02-bazar-universal/DuffmanCC/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"use client";

import { CartContext } from "@/context/cart";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useContext, useMemo, useState } from "react";
import { CartIcon, SearchIcon, Truck } from "./Icons";

export default function Header() {
const [search, setSearch] = useState<string>("");
const { productsInCart, setShowCart, showCart } = useContext(CartContext);
const router = useRouter();

function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
e.preventDefault();

const query = e.target.value.toLowerCase();

setSearch(query);

if (query.length > 2) {
router.push(`/items?search=${query}`);
} else {
router.push(`/items`);
}
}

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
}

const totalItems = useMemo(() => {
if (productsInCart.length === 0) {
return 0;
}

return productsInCart.reduce((a, b) => a + b.quantity, 0);
}, [productsInCart]);

return (
<header className="flex gap-4 items-center justify-center md:justify-between relative">
<Link href="/">
<Truck width="50" />
</Link>

<form onSubmit={handleSubmit} className="flex w-full max-w-sm relative">
<input
type="search"
onChange={handleChange}
value={search}
className="pl-4 pr-10 py-2 rounded border bg-gray-100 w-full"
placeholder="laptops, smartphones..."
/>

<SearchIcon width="25" className="absolute right-3 top-2" />
</form>

<div className="relative z-50">
<button
onClick={() => setShowCart(!showCart)}
aria-label="botón para mostrar el carro de compra"
>
<CartIcon width="50" className="" />
</button>

{productsInCart.length > 0 && (
<div
className="absolute top-0 right-0 rounded-full border w-6 h-6 flex justify-center items-center bg-blue-500 text-white text-xs"
aria-label="número de productos en el carro"
>
{totalItems}
</div>
)}
</div>
</header>
);
}
Loading