summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/clients/actions.ts30
-rw-r--r--src/clients/api_client.ts6
-rw-r--r--src/clients/loaders.ts11
-rw-r--r--src/components/address.tsx7
-rw-r--r--src/components/forms/account_form.tsx25
-rw-r--r--src/components/forms/address_form.tsx78
-rw-r--r--src/components/forms/fields/field.tsx23
-rw-r--r--src/components/forms/fields/field_properties.ts9
-rw-r--r--src/components/forms/fields/select_field.tsx10
-rw-r--r--src/lib/form_utils.ts39
-rw-r--r--src/main.tsx13
-rw-r--r--src/routes/account/addresses/edit.tsx18
-rw-r--r--src/routes/account/edit.tsx24
13 files changed, 260 insertions, 33 deletions
diff --git a/src/clients/actions.ts b/src/clients/actions.ts
index 36935cb..ea3c332 100644
--- a/src/clients/actions.ts
+++ b/src/clients/actions.ts
@@ -1,6 +1,7 @@
import { redirect } from "react-router-dom";
import { ApiClient } from "./api_client";
import { deleteEmptyFields } from "../lib/form_utils";
+import Token from "../lib/token";
export async function editAccount({ request }) {
const client = new ApiClient();
@@ -15,4 +16,33 @@ export async function editAccount({ request }) {
client.token.setRefresh(response.data.refresh);
return redirect("/account");
+}
+
+export async function editAddress({ params, request }) {
+
+ const client = new ApiClient();
+ let form_data = await request.formData();
+ form_data = deleteEmptyFields(form_data);
+
+ try {
+ const response = await client.put(`/account/addresses/${params.addressId}`, form_data);
+
+ if(response.status == 401 || response.status == 404)
+ return redirect("/products");
+
+ return redirect("/account");
+ } catch(error) {
+ if(error.response.status == 401) {
+ new Token().logout;
+
+ return redirect("/products")
+ }
+ else {
+ for(const [key, value] of Object.entries(error.response.data.errors)) {
+ sessionStorage.setItem(key, value);
+ }
+
+ return redirect(`/account/addresses/${params.addressId}/edit`);
+ }
+ }
} \ No newline at end of file
diff --git a/src/clients/api_client.ts b/src/clients/api_client.ts
index 93d713a..4362c4d 100644
--- a/src/clients/api_client.ts
+++ b/src/clients/api_client.ts
@@ -39,9 +39,9 @@ export class ApiClient {
async put(path: string, data: FormData) {
const request_url = `${ this.url }${ path }`;
const options = this.authorizationHeaders();
- const response = await axios.put(request_url, data, options);
- return response;
+ const response = await axios.put(request_url, data, options);
+ return response
}
async getProduct(id: string) {
@@ -67,7 +67,7 @@ export class ApiClient {
private authorizationHeaders() {
return {
headers: {
- Authorization: this.token.get()
+ Authorization: `Bearer ${this.token.get()}`
}
};
}
diff --git a/src/clients/loaders.ts b/src/clients/loaders.ts
index 8d0f06f..618e593 100644
--- a/src/clients/loaders.ts
+++ b/src/clients/loaders.ts
@@ -9,6 +9,17 @@ export async function loader({ request }) {
return response;
}
+export async function addressLoader({ params }) {
+ const client = new ApiClient();
+ const path = `/account/addresses/${params.addressId}`;
+ const request = await client.authenticatedGet(path);
+
+ if(request.response)
+ return redirect("/account");
+
+ return request;
+}
+
export async function accountLoader() {
const client = new ApiClient();
diff --git a/src/components/address.tsx b/src/components/address.tsx
index 1701fd1..281b838 100644
--- a/src/components/address.tsx
+++ b/src/components/address.tsx
@@ -1,6 +1,8 @@
import countryList from "react-select-country-list";
export default function Address({ address }) {
+ const edit_address_route = `/account/addresses/${address.id}/edit`;
+
return(
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row" className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
@@ -19,9 +21,10 @@ export default function Address({ address }) {
{countryList().getLabel(address.attributes.country)}
</td>
<td className="px-6 py-4">
- <button type="button" className="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800">
+ <a type="button" className="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800"
+ href={edit_address_route}>
Editar
- </button>
+ </a>
</td>
</tr>
);
diff --git a/src/components/forms/account_form.tsx b/src/components/forms/account_form.tsx
new file mode 100644
index 0000000..b0f0fc5
--- /dev/null
+++ b/src/components/forms/account_form.tsx
@@ -0,0 +1,25 @@
+import { Form } from "react-router-dom";
+
+export default function AccountForm({ account }) {
+ return(
+ <Form method="post" id="account-form">
+ <div className="mb-6">
+ <label className="block mb-2 text-lg text-gray-900 dark:text-white">Correo electrónico</label>
+ <input type="email" id="email" name="email" 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={account.email}/>
+ </div>
+ <div className="mb-6">
+ <label className="block mb-2 text-lg text-gray-900 dark:text-white">Nombre</label>
+ <input type="text" id="first_name" name="first_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={account.first_name}/>
+ </div>
+ <div className="mb-6">
+ <label className="block mb-2 text-lg text-gray-900 dark:text-white">Apellido</label>
+ <input type="text" id="last_name" name="last_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={account.last_name}/>
+ </div>
+ <div className="mb-6">
+ <label className="block mb-2 text-lg text-gray-900 dark:text-white">Contraseña</label>
+ <input type="password" id="password" name="password" 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"/>
+ </div>
+ <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Enviar</button>
+ </Form>
+ );
+} \ No newline at end of file
diff --git a/src/components/forms/address_form.tsx b/src/components/forms/address_form.tsx
new file mode 100644
index 0000000..d002c4e
--- /dev/null
+++ b/src/components/forms/address_form.tsx
@@ -0,0 +1,78 @@
+import { Form } from "react-router-dom";
+import { formHasErrors } from "../../lib/form_utils";
+import countryList from "react-select-country-list";
+import FieldProperties from "./fields/field_properties";
+import Field from "./fields/field";
+import SelectField from "./fields/select_field";
+
+function getFieldProperties(address: any) {
+ const fields: Array<FieldProperties> = [
+ {
+ id: "street-field",
+ type: "text",
+ name: "street",
+ label: "Calle",
+ placeholder: address.street
+ },
+ {
+ id: "number-field",
+ type: "number",
+ name: "number",
+ label: "Número",
+ placeholder: address.number
+ },
+ {
+ id: "zip-code-field",
+ type: "number",
+ name: "zip_code",
+ label: "Código postal",
+ placeholder: address.zip_code
+ },
+ {
+ id: "city-field",
+ type: "text",
+ name: "city",
+ label: "Ciudad",
+ placeholder: address.city
+ }
+ ];
+
+ for(const field of fields) {
+ if(sessionStorage.getItem(field.name))
+ field.error_message = sessionStorage.getItem(field.name);
+ }
+
+ return fields;
+}
+
+function getCountrySelectOptions(address: any, country_code: string) {
+ if(country_code == address.country)
+ return (<option value={ country_code } selected>{ countryList().getLabel(country_code) }</option>);
+ else
+ return (<option value={ country_code }>{ countryList().getLabel(country_code) }</option>);
+}
+
+export default function AddressForm({ address }) {
+ const select_field_properties: FieldProperties = {
+ id: "country-field",
+ type: "select",
+ name: "city",
+ label: "Ciudad"
+ }
+
+ const options = countryList().getValues().map(country_code =>
+ getCountrySelectOptions(address, country_code)
+ );
+
+ const fields = getFieldProperties(address).map(field_properties =>
+ <Field properties={field_properties}/>
+ );
+
+ return(
+ <Form method="post" id="address-form">
+ {fields}
+ <SelectField properties={select_field_properties} options={options}/>
+ <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Enviar</button>
+ </Form>
+ );
+} \ No newline at end of file
diff --git a/src/components/forms/fields/field.tsx b/src/components/forms/fields/field.tsx
new file mode 100644
index 0000000..d7aa342
--- /dev/null
+++ b/src/components/forms/fields/field.tsx
@@ -0,0 +1,23 @@
+export default function Field({ properties }) {
+ let field_component;
+
+ if(properties.error_message) {
+ 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}/>
+ <p className="mt-2 text-sm text-red-600 dark:text-red-500"> {properties.error_message }</p>
+ </div>
+ );
+ }
+ else {
+ 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}/>
+ </div>
+ );
+ }
+
+ return field_component;
+} \ No newline at end of file
diff --git a/src/components/forms/fields/field_properties.ts b/src/components/forms/fields/field_properties.ts
new file mode 100644
index 0000000..d4e8066
--- /dev/null
+++ b/src/components/forms/fields/field_properties.ts
@@ -0,0 +1,9 @@
+export default interface FieldProperties {
+ type: string;
+ id: string;
+ name: string;
+ label: string;
+ placeholder?: string;
+ error_message?: string;
+}
+
diff --git a/src/components/forms/fields/select_field.tsx b/src/components/forms/fields/select_field.tsx
new file mode 100644
index 0000000..75859d9
--- /dev/null
+++ b/src/components/forms/fields/select_field.tsx
@@ -0,0 +1,10 @@
+export default function SelectField({ properties, options }) {
+ return(
+ <div className="mb-6">
+ <label className="block mb-2 text-lg text-gray-900 dark:text-white">{properties.label}</label>
+ <select id={properties.id} 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">
+ {options}
+ </select>
+ </div>
+ );
+} \ No newline at end of file
diff --git a/src/lib/form_utils.ts b/src/lib/form_utils.ts
index a4e7bff..73c5ba7 100644
--- a/src/lib/form_utils.ts
+++ b/src/lib/form_utils.ts
@@ -1,10 +1,41 @@
+export interface Error {
+ field_div_id: string;
+ field_input_id: string;
+ message: string;
+}
+
export function deleteEmptyFields(form: FormData) {
- const trimmed_form = form;
+ const trimmed_form = new FormData();
- for(const key of trimmed_form) {
- if(trimmed_form.get(key[0]) == '')
- trimmed_form.delete(key[0]);
+ for(const key of form.keys()) {
+ if(form.get(key) != '')
+ trimmed_form.append(key, form.get(key));
}
return trimmed_form;
+}
+
+export function setFormErrorsInSessionStorage(errors: Array<any>) {
+ for(const [key, value] of Object.entries(errors)) {
+ sessionStorage.setItem(key, value);
+ }
+}
+
+export function formHasErrors(fields: Array<string>) {
+ for(const field_key of fields) {
+ if(sessionStorage.getItem(field_key))
+ return true;
+ }
+
+ return false;
+}
+
+export function renderErrors(errors: Array<Error>) {
+ for(const error of errors) {
+ const field_div = document.getElementById(error.field_div_id);
+ const input_label = field_div?.querySelector("label");
+ const input_field = field_div?.querySelector("input");
+ input_label?.classList.add("text-red-700");
+ input_field?.classList.add("bg-red-50 border border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 dark:bg-gray-700 focus:border-red-500");
+ }
} \ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index cd65146..6808475 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,14 +1,15 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
-import { BrowserRouter, createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'
+import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom'
import Products from "./routes/products/products";
import Product from "./routes/products/product";
import Companies from "./routes/companies/companies";
import Account from "./routes/account/account";
import { EditAccount } from './routes/account/edit';
-import { editAccount } from './clients/actions';
+import { Edit as EditAddress } from "./routes/account/addresses/edit";
+import { editAccount, editAddress } from './clients/actions';
import Layout from "./components/layout";
-import { accountLoader, loader, productLoader } from "./clients/loaders";
+import { accountLoader, loader, productLoader, addressLoader } from "./clients/loaders";
import './index.css';
const routes = [
@@ -39,6 +40,12 @@ const routes = [
action: editAccount
},
{
+ path: "/account/addresses/:addressId/edit",
+ element: <EditAddress/>,
+ loader: addressLoader,
+ action: editAddress,
+ },
+ {
path: '/',
element: <Navigate to='/products'/>
}
diff --git a/src/routes/account/addresses/edit.tsx b/src/routes/account/addresses/edit.tsx
new file mode 100644
index 0000000..31bfaed
--- /dev/null
+++ b/src/routes/account/addresses/edit.tsx
@@ -0,0 +1,18 @@
+import AddressForm from "../../../components/forms/address_form";
+import { useLoaderData } from "react-router-dom";
+import MainContentLayout from "../../../components/main_content_layout";
+
+export function Edit() {
+ const address = useLoaderData().data.data.attributes;
+
+ return(
+ <MainContentLayout>
+ <div className="w-4/5 my-6">
+ <h1 className="my-6 text-3xl">
+ Editar cuenta
+ </h1>
+ <AddressForm address={address}/>
+ </div>
+ </MainContentLayout>
+ );
+} \ No newline at end of file
diff --git a/src/routes/account/edit.tsx b/src/routes/account/edit.tsx
index c2dfcb5..a78d8a7 100644
--- a/src/routes/account/edit.tsx
+++ b/src/routes/account/edit.tsx
@@ -1,11 +1,11 @@
import MainContentLayout from "../../components/main_content_layout";
+import AccountForm from "../../components/forms/account_form";
import Token from "../../lib/token";
-import { Form, useLoaderData } from "react-router-dom";
+import { useLoaderData } from "react-router-dom";
import "../../components/stylesheets/shared.css";
export function EditAccount() {
const account = useLoaderData()[0].data.data.attributes;
- const token = new Token();
return(
<>
@@ -14,25 +14,7 @@ export function EditAccount() {
<h1 className="my-6 text-3xl">
Editar cuenta
</h1>
- <Form method="post" id="account-form">
- <div className="mb-6">
- <label className="block mb-2 text-lg text-gray-900 dark:text-white">Correo electrónico</label>
- <input type="email" id="email" name="email" 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={account.email}/>
- </div>
- <div className="mb-6">
- <label className="block mb-2 text-lg text-gray-900 dark:text-white">Nombre</label>
- <input type="text" id="first_name" name="first_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={account.first_name}/>
- </div>
- <div className="mb-6">
- <label className="block mb-2 text-lg text-gray-900 dark:text-white">Apellido</label>
- <input type="text" id="last_name" name="last_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={account.last_name}/>
- </div>
- <div className="mb-6">
- <label className="block mb-2 text-lg text-gray-900 dark:text-white">Contraseña</label>
- <input type="password" id="password" name="password" 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"/>
- </div>
- <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Enviar</button>
- </Form>
+ <AccountForm account={account}/>
</div>
</MainContentLayout>
</>