summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/clients/api_client.ts19
-rw-r--r--src/clients/loader.ts9
-rw-r--r--src/clients/loaders.ts16
-rw-r--r--src/components/product_listing.tsx71
-rw-r--r--src/components/review.tsx30
-rw-r--r--src/main.tsx12
-rw-r--r--src/routes/products/product.tsx37
-rw-r--r--src/routes/products/products.tsx4
8 files changed, 148 insertions, 50 deletions
diff --git a/src/clients/api_client.ts b/src/clients/api_client.ts
index a7b5e0d..0e96c36 100644
--- a/src/clients/api_client.ts
+++ b/src/clients/api_client.ts
@@ -5,13 +5,28 @@ export class ApiClient {
async get(path: string) {
const request_url = `${ this.url }${ path }`;
+ const response = await this.makeRequest(request_url);
+ return response;
+ }
+
+ async getProduct(id: string) {
+ const request_url = `${ this.url }/products/${ id }`;
+ const [product_response, product_reviews] = await Promise.all([
+ this.makeRequest(request_url),
+ this.makeRequest(`${ request_url }/reviews`)
+ ]);
+
+ return [product_response, product_reviews];
+ }
+
+ private async makeRequest(request_url: string) {
try {
const response = await axios.get(request_url);
- return response;
+ return response
} catch(error) {
- return error.response;
+ return error;
}
}
} \ No newline at end of file
diff --git a/src/clients/loader.ts b/src/clients/loader.ts
deleted file mode 100644
index c529806..0000000
--- a/src/clients/loader.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { ApiClient } from "./api_client";
-
-export default async function productLoader({ request }) {
- const client = new ApiClient();
- const url = new URL(request.url)
- const response = await client.get(url.pathname);
-
- return response.data;
-} \ No newline at end of file
diff --git a/src/clients/loaders.ts b/src/clients/loaders.ts
new file mode 100644
index 0000000..43c1581
--- /dev/null
+++ b/src/clients/loaders.ts
@@ -0,0 +1,16 @@
+import { ApiClient } from "./api_client";
+
+export async function loader({ request }) {
+ const client = new ApiClient();
+ const url = new URL(request.url)
+ const response = await client.get(url.pathname);
+
+ return response;
+}
+
+export async function productLoader({ params }) {
+ const client = new ApiClient();
+ const response = await client.getProduct(params.productId);
+
+ return [response[0], response[1]];
+} \ No newline at end of file
diff --git a/src/components/product_listing.tsx b/src/components/product_listing.tsx
index 497bd19..d9c5488 100644
--- a/src/components/product_listing.tsx
+++ b/src/components/product_listing.tsx
@@ -1,5 +1,5 @@
import { Collapse, Ripple, initTE} from "tw-elements";
-initTE({Collapse, Ripple});
+import { Link } from "react-router-dom";
import "./stylesheets/shared.css"
import "./stylesheets/product_listing.css"
@@ -7,60 +7,63 @@ export default function ProductListing({ product }) {
const collapseMenu = `collapse${product.id}`
const collapseTarget = `#${collapseMenu}`
const categories = product.attributes.categories.map(category =>
-
<li>{category}</li>
);
return (
- <div className="flex w-3/5 my-4 border-solid border-2 border-gray-200">
+ <div className="flex w-4/6 my-4 border-solid border-2 border-gray-200">
<img className="listing-image" src={product.attributes.picture} />
<div className="overflow-hidden">
<table>
<thead>
<tr >
- <th scope="col" className="product-listing-text font-bold text-2xl px-6 py-4">{product.attributes.name}</th>
+ <th scope="col" className="product-listing-text font-bold text-2xl px-6 py-4 hover:text-neutral-700">
+ <Link to={`/products/${ product.attributes.public_id }`}>
+ {product.attributes.name}
+ </Link>
+ </th>
</tr>
</thead>
<tbody>
<tr className="border-b dark:border-neutral-500">
- <td className="product-listing-text text-xl">Precio al por menor</td>
- <td className="text-neutral-900 text-xl">{product.attributes.unitary_price} $</td>
+ <td className="product-listing-text text-xl px-6 py-4">Precio al por menor</td>
+ <td className="text-neutral-900 text-xl px-6 py-4">{product.attributes.unitary_price} $</td>
</tr>
<tr className="border-b dark:border-neutral-500">
- <td className="product-listing-text text-xl">Precio al por mayor</td>
- <td className="text-neutral-900 text-xl">{product.attributes.bulk_price} $</td>
+ <td className="product-listing-text text-xl px-6 py-4">Precio al por mayor</td>
+ <td className="text-neutral-900 text-xl px-6 py-4">{product.attributes.bulk_price} $</td>
</tr>
<tr className="border-b dark:border-neutral-500">
- <td className="product-listing-text text-xl">Proveedor</td>
- <td className="text-neutral-900 text-xl">{product.attributes.company.name}</td>
+ <td className="product-listing-text text-xl px-6 py-4">Disponibles</td>
+ <td className="text-neutral-900 text-xl px-6 py-4">{product.attributes.available_quantity}</td>
</tr>
- <tr>
- <td>
- <div className="my-2">
- <button
- className="inline-block rounded bg-primary px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
- type="button"
- data-te-collapse-init
- data-te-ripple-init
- data-te-ripple-color="light"
- data-te-target={collapseTarget}
- aria-expanded="false"
- aria-controls={collapseMenu}>
- Categorías
- </button>
- <div className="!visible hidden" id={collapseMenu} data-te-collapse-item>
- <div
- className="block rounded-lg bg-white p-6 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] dark:bg-neutral-700 dark:text-neutral-50">
- <ul>
- <li>{categories}</li>
- </ul>
- </div>
- </div>
- </div>
- </td>
+ <tr className="border-b dark:border-neutral-500">
+ <td className="product-listing-text text-xl px-6 py-4">Proveedor</td>
+ <td className="text-neutral-900 text-xl px-6 py-4">{product.attributes.company.name}</td>
</tr>
</tbody>
</table>
+ <div className="my-2">
+ <button
+ className="inline-block rounded bg-primary px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
+ type="button"
+ data-te-collapse-init
+ data-te-ripple-init
+ data-te-ripple-color="light"
+ data-te-target={collapseTarget}
+ aria-expanded="false"
+ aria-controls={collapseMenu}>
+ Categorías
+ </button>
+ <div className="!visible hidden" id={collapseMenu} data-te-collapse-item>
+ <div
+ className="flex rounded-lg bg-white p-6 shadow-[0_2px_15px_-3px_rgba(0,0,0,0.07),0_10px_20px_-2px_rgba(0,0,0,0.04)] dark:bg-neutral-700 dark:text-neutral-50">
+ <ul>
+ {categories}
+ </ul>
+ </div>
+ </div>
+ </div>
</div>
</div>
);
diff --git a/src/components/review.tsx b/src/components/review.tsx
new file mode 100644
index 0000000..e3f34df
--- /dev/null
+++ b/src/components/review.tsx
@@ -0,0 +1,30 @@
+import { PersonCircle, StarFill } from "react-bootstrap-icons";
+
+export default function Review({ review }) {
+ const rating = [...Array(review.attributes.rating)].map((value: undefined, index: number) =>
+ <StarFill size={12} color="rgb(156 163 175)"/>
+ );
+
+ return(
+ <>
+ <div className="grid grid-cols-10 w-3/5 my-4">
+ <div className="flex flex-col col-span- justify-center mx-2">
+ <div>
+ {review.attributes.author_name}
+ </div>
+ <div>
+ <PersonCircle size={32} color="rgb(156 163 175)"/>
+ </div>
+ <div className="flex inline-flex my-2">
+ {rating}
+ </div>
+ </div>
+ <div className="flex col-span-8 justify-start">
+ <div className="border-solid border-2 border-gray-200">
+ {review.attributes.review}
+ </div>
+ </div>
+ </div>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 7d0af9a..45f5fd6 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -2,17 +2,23 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'
import Products from "./routes/products/products";
+import Product from "./routes/products/product";
import Layout from "./components/layout";
-import productLoader from "./clients/loader";
-import './index.css'
+import { loader, productLoader } from "./clients/loaders";
+import './index.css';
const routes = [
{
path: '/products',
- loader: productLoader,
+ loader: loader,
element: <Products/>
},
{
+ path: "products/:productId",
+ loader: productLoader,
+ element: <Product/>
+ },
+ {
path: '/',
element: <Navigate to='/products'/>
}
diff --git a/src/routes/products/product.tsx b/src/routes/products/product.tsx
new file mode 100644
index 0000000..1eecbcf
--- /dev/null
+++ b/src/routes/products/product.tsx
@@ -0,0 +1,37 @@
+import { useLoaderData } from "react-router-dom";
+import { CartPlusFill } from "react-bootstrap-icons"
+import ProductListing from "../../components/product_listing";
+import MainContentLayout from "../../components/main_content_layout";
+import Review from "../../components/review";
+import "../../components/stylesheets/shared.css"
+
+export default function Product() {
+ const response = useLoaderData();
+ const product = response[0].data;
+ const reviews = response[1].data.data.map(review =>
+ <li key={review.id}>
+ <Review review={review}/>
+ </li>
+ );
+
+ return (
+ <>
+ <MainContentLayout>
+ <ProductListing product={product.data}/>
+ <div className="my-2 flex flex-flow-reverse w-4/6">
+ <div>
+ <button className="flex inline-block rounded button px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]">
+ <CartPlusFill size={16}/>
+ <span className="mx-2">Añadir al carrito</span>
+ </button>
+ </div>
+ </div>
+ <div className="my-4">
+ <ul>
+ {reviews}
+ </ul>
+ </div>
+ </MainContentLayout>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/routes/products/products.tsx b/src/routes/products/products.tsx
index c7f3a3a..ea06c15 100644
--- a/src/routes/products/products.tsx
+++ b/src/routes/products/products.tsx
@@ -1,11 +1,11 @@
-import { useLoaderData } from "react-router-dom";
+import { Link, useLoaderData } from "react-router-dom";
import ProductListing from "../../components/product_listing";
import SearchBar from "../../components/search_bar";
import MainContentLayout from "../../components/main_content_layout";
export default function Products() {
const products = useLoaderData().data;
- const productList = products.map(product =>
+ const productList = products.data.map(product =>
<li key={product.id}>
<ProductListing product={product}/>
</li>