summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHombreLaser <sebastian-440@live.com>2023-05-24 20:18:00 -0600
committerHombreLaser <sebastian-440@live.com>2023-05-24 20:18:00 -0600
commitda2631822f902094e691143302d6fc6b68e1cf56 (patch)
tree68b893c04233f779f5a65151ea21d673b6f7a8fc
parent21508a3514500f8f38ddaa8bef7a9cd420d76628 (diff)
Añade lógica de carrito
-rw-r--r--src/clients/actions.ts34
-rw-r--r--src/clients/api_client.ts8
-rw-r--r--src/components/forms/fields/field.tsx4
-rw-r--r--src/components/info_modal.tsx37
-rw-r--r--src/components/product_cart.tsx35
-rw-r--r--src/main.tsx8
-rw-r--r--src/models/product.ts4
-rw-r--r--src/routes/products/product.tsx51
-rw-r--r--src/routes/products/products.tsx2
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";