diff options
author | HombreLaser <sebastian-440@live.com> | 2023-04-26 21:37:38 -0600 |
---|---|---|
committer | HombreLaser <sebastian-440@live.com> | 2023-04-26 21:37:38 -0600 |
commit | c7e493ce3d2855e61787d86714625bc7fc51f9bd (patch) | |
tree | b7b9b97fbe26d6258a553c8dc4655d5fcf7cdb92 /src | |
parent | 70db52d02dfe5da7397e5ba801b01739a5d0ceaa (diff) |
Añade vista de producto individual
Diffstat (limited to 'src')
-rw-r--r-- | src/clients/api_client.ts | 19 | ||||
-rw-r--r-- | src/clients/loader.ts | 9 | ||||
-rw-r--r-- | src/clients/loaders.ts | 16 | ||||
-rw-r--r-- | src/components/product_listing.tsx | 71 | ||||
-rw-r--r-- | src/components/review.tsx | 30 | ||||
-rw-r--r-- | src/main.tsx | 12 | ||||
-rw-r--r-- | src/routes/products/product.tsx | 37 | ||||
-rw-r--r-- | src/routes/products/products.tsx | 4 |
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> |