diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/clients/actions.ts | 34 | ||||
-rw-r--r-- | src/clients/api_client.ts | 8 | ||||
-rw-r--r-- | src/components/forms/fields/field.tsx | 4 | ||||
-rw-r--r-- | src/components/info_modal.tsx | 37 | ||||
-rw-r--r-- | src/components/product_cart.tsx | 35 | ||||
-rw-r--r-- | src/main.tsx | 8 | ||||
-rw-r--r-- | src/models/product.ts | 4 | ||||
-rw-r--r-- | src/routes/products/product.tsx | 51 | ||||
-rw-r--r-- | src/routes/products/products.tsx | 2 |
9 files changed, 162 insertions, 21 deletions
diff --git a/src/clients/actions.ts b/src/clients/actions.ts index cee9a85..bd99519 100644 --- a/src/clients/actions.ts +++ b/src/clients/actions.ts @@ -77,4 +77,38 @@ export async function create({ request }) { return requestErrorsToArray(error.response.data.errors); } } +} + +export async function addToCart({ params, request }) { + const client = new ApiClient(); + const product = await client.get(`/products/${ params.productId }`); + const post_request_path = "/account/cart"; + const id = product.data.data.id; + const form = await request.formData(); + + form.append('product_id', id); + + if(form.get('quantity') == '') + form.set('quantity', 1); + + try { + await client.post(post_request_path, form, client.authorizationHeaders()); + } catch(error) { + return error.response.status; + } + + return 200; +} + +export async function deleteFromCart({ request }) { + const client = new ApiClient(); + const form = await request.formData(); + const to_delete = `/account/cart/${ form.get('product_id') }`; + const req = await client.del(to_delete); + + if(req.response) { // 404 + return redirect("/account/cart"); + } + + return req.status; }
\ No newline at end of file diff --git a/src/clients/api_client.ts b/src/clients/api_client.ts index 81ae482..e544f16 100644 --- a/src/clients/api_client.ts +++ b/src/clients/api_client.ts @@ -44,6 +44,14 @@ export class ApiClient { return response } + async del(path: string) { + const request_url = `${ this.url }${ path }`; + const options = this.authorizationHeaders(); + + const response = await axios.delete(request_url, options); + return response; + } + async getProduct(id: string) { const request_url = `${ this.url }/products/${ id }`; const [product_response, product_reviews] = await Promise.all([ diff --git a/src/components/forms/fields/field.tsx b/src/components/forms/fields/field.tsx index d7aa342..8faa425 100644 --- a/src/components/forms/fields/field.tsx +++ b/src/components/forms/fields/field.tsx @@ -5,7 +5,7 @@ export default function Field({ properties }) { field_component = ( <div className="mb-6"> <label className="block mb-2 text-sm font-medium text-red-700 dark:text-red-500">{ properties.label }</label> - <input type={properties.type} id={properties.id} name={properties.name} className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500" placeholder={properties.placeholder}/> + <input type={properties.type} id={properties.id} name={properties.name} className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500" placeholder={properties.placeholder} value={properties.value}/> <p className="mt-2 text-sm text-red-600 dark:text-red-500"> {properties.error_message }</p> </div> ); @@ -14,7 +14,7 @@ export default function Field({ properties }) { field_component = ( <div className="mb-6"> <label className="block mb-2 text-lg text-gray-900 dark:text-white">{properties.label}</label> - <input type={properties.type} id={properties.id} name={properties.name} className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder={properties.placeholder}/> + <input type={properties.type} id={properties.id} name={properties.name} className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder={properties.placeholder} value={properties.value}/> </div> ); } diff --git a/src/components/info_modal.tsx b/src/components/info_modal.tsx new file mode 100644 index 0000000..1123901 --- /dev/null +++ b/src/components/info_modal.tsx @@ -0,0 +1,37 @@ +import { Modal } from "flowbite"; +import "./stylesheets/shared.css"; + +function createModal() { + const target = document.getElementById('info-modal'); + const button = document.getElementById('modal-button'); + const options = { + placement: 'center-center', + backdrop: 'dynamic', + backdropClasses: 'bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40', + closable: true + }; + + const new_modal = new Modal(target, options); + button?.addEventListener('click', () => new_modal.hide()); + + return new_modal; +} + +export function InfoModal({ text }) { + + return ( + <div id="info-modal" tabIndex="-1" aria-hidden="true" className="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full"> + <div className="relative bg-white w-full max-w-2xl max-h-full"> + <div className="p-6 space-y-6"> + <p className="text-base leading-relaxed text-gray-900 dark:text-gray-400"> + {text} + </p> + </div> + + <div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600"> + <button id='modal-button' type="button" className=" button text-white hover:bg-blue-800 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-blue-800">Ok</button> + </div> + </div> + </div> + ); +}
\ No newline at end of file diff --git a/src/components/product_cart.tsx b/src/components/product_cart.tsx index 86d2615..4e78d1a 100644 --- a/src/components/product_cart.tsx +++ b/src/components/product_cart.tsx @@ -1,27 +1,44 @@ +import { Form } from "react-router-dom"; import "./stylesheets/product_listing.css"; export default function ProductCart({ product }) { return ( - <div className="my-2 w-4/6 grid grid-cols-3 gap-2 border-2 border-gray-300"> - <div className="col-span-2"> + <div className="my-2 w-3/6 grid grid-cols-2 gap-2 border-2 border-gray-300"> + <div> <img className="listing-image" src={product.picture} /> </div> - <div className="grid grid-rows-3"> + <div className="grid grid-rows-6"> <div className="product-listing-text text-2xl"> {product.name} </div> + <div className="product-listing-text text-xl"> + Precio unitario + </div> <div className="text-lg"> - <span className="product-listing-text text-xl"> - Precio unitario - </span> {product.unitary_price} </div> + <div className="product-listing-text text-xl"> + Precio al por mayor + </div> <div className="text-lg"> - <span className="product-listing-text text-xl"> - Precio al por mayor - </span> {product.bulk_price} </div> + <div className="flex flex-row"> + <div className="product-listing-text text-lg"> + Cantidad en carrito: + </div> + <div className="mx-2 text-lg"> + {product.quantity} + </div> + <div className="mx-2"> + <Form method="post" id="delete-product-cart-form"> + <input type="hidden" id="product-id" name="product_id" value={product.id} /> + <button type="submit" className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"> + Borrar + </button> + </Form> + </div> + </div> </div> </div> ); diff --git a/src/main.tsx b/src/main.tsx index ebe246a..147d913 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,7 +8,7 @@ import Account from "./routes/account/account"; import Cart from "./routes/account/cart"; import { EditAccount } from './routes/account/edit'; import { Edit as EditAddress } from "./routes/account/addresses/edit"; -import { create, editAccount, authenticatedEdit } from './clients/actions'; +import { create, editAccount, authenticatedEdit, addToCart, deleteFromCart } from './clients/actions'; import Layout from "./components/layout"; import { accountLoader, loader, productLoader, addressLoader, cardLoader, cartLoader } from "./clients/loaders"; import './index.css'; @@ -25,7 +25,8 @@ const routes = [ { path: "products/:productId", loader: productLoader, - element: <Product/> + action: addToCart, + element: <Product/>, }, { path: "/companies", @@ -68,7 +69,8 @@ const routes = [ { path: "/account/cart", element: <Cart/>, - loader: cartLoader + loader: cartLoader, + action: deleteFromCart }, { path: '/', diff --git a/src/models/product.ts b/src/models/product.ts index b375537..c3323e0 100644 --- a/src/models/product.ts +++ b/src/models/product.ts @@ -5,7 +5,7 @@ export interface Product { unitary_price: number; bulk_price: number; available_quantity: number; - company_name: string; + quantity: number; } export function mapProduct(data: any) { @@ -19,7 +19,7 @@ export function mapProduct(data: any) { unitary_price: data.attributes.unitary_price, bulk_price: data.attributes.bulk_price, available_quantity: data.attributes.available_quantity, - company_name: data.attributes.company.name + quantity: data.attributes.quantity }; return product; diff --git a/src/routes/products/product.tsx b/src/routes/products/product.tsx index 1eecbcf..cca4073 100644 --- a/src/routes/products/product.tsx +++ b/src/routes/products/product.tsx @@ -1,12 +1,18 @@ -import { useLoaderData } from "react-router-dom"; +import { useLoaderData, Form, useActionData } from "react-router-dom"; import { CartPlusFill } from "react-bootstrap-icons" +import { InfoModal } from "../../components/info_modal"; +import { Modal } from "flowbite"; import ProductListing from "../../components/product_listing"; import MainContentLayout from "../../components/main_content_layout"; import Review from "../../components/review"; import "../../components/stylesheets/shared.css" +import { useEffect } from "react"; + export default function Product() { + let quantity_field; const response = useLoaderData(); + const response_status = useActionData(); const product = response[0].data; const reviews = response[1].data.data.map(review => <li key={review.id}> @@ -14,23 +20,60 @@ export default function Product() { </li> ); + useEffect(() => { + const target = document.getElementById("info-modal"); + const options = { + placement: 'center-center', + backdrop: 'dynamic', + backdropClasses: 'bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40', + closable: true + }; + const modal = new Modal(target, options); + document.getElementById('modal-button')?.addEventListener("click", () => { + modal.hide(); + }); + + if(response_status == 200) + modal.show(); + + }, [response_status]); + + if(response_status == 422) { + quantity_field = ( + <div className="mx-2 w-17"> + <input type="number" id="quantity-field" name="quantity" className="bg-red-50 border border-red-500 text-red-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + min='1' max={product.data.attributes.available_quantity} placeholder={1}/> + </div> + ); + } + else { + quantity_field = ( + <div className="mx-2 w-17"> + <input type="number" id="quantity-field" name="quantity" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + min='1' max={product.data.attributes.available_quantity} placeholder={1} placeholder={1}/> + </div> + ); + } + 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)]"> + <Form method="post" id="cart-button" className="flex h-10"> + {quantity_field} + <button className="flex inline-block rounded button w-50 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> + </Form> </div> <div className="my-4"> <ul> {reviews} </ul> </div> + <InfoModal text="El producto se ha añadido a su carrito"/> </MainContentLayout> </> ); diff --git a/src/routes/products/products.tsx b/src/routes/products/products.tsx index ea06c15..27192d3 100644 --- a/src/routes/products/products.tsx +++ b/src/routes/products/products.tsx @@ -1,4 +1,4 @@ -import { Link, useLoaderData } from "react-router-dom"; +import { useLoaderData } from "react-router-dom"; import ProductListing from "../../components/product_listing"; import SearchBar from "../../components/search_bar"; import MainContentLayout from "../../components/main_content_layout"; |