diff options
-rw-r--r-- | src/clients/actions.ts | 30 | ||||
-rw-r--r-- | src/clients/api_client.ts | 6 | ||||
-rw-r--r-- | src/clients/loaders.ts | 11 | ||||
-rw-r--r-- | src/components/address.tsx | 7 | ||||
-rw-r--r-- | src/components/forms/account_form.tsx | 25 | ||||
-rw-r--r-- | src/components/forms/address_form.tsx | 78 | ||||
-rw-r--r-- | src/components/forms/fields/field.tsx | 23 | ||||
-rw-r--r-- | src/components/forms/fields/field_properties.ts | 9 | ||||
-rw-r--r-- | src/components/forms/fields/select_field.tsx | 10 | ||||
-rw-r--r-- | src/lib/form_utils.ts | 39 | ||||
-rw-r--r-- | src/main.tsx | 13 | ||||
-rw-r--r-- | src/routes/account/addresses/edit.tsx | 18 | ||||
-rw-r--r-- | src/routes/account/edit.tsx | 24 |
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> </> |