diff --git a/.learn/config.json b/.learn/config.json
new file mode 100644
index 000000000..5a3c721fd
--- /dev/null
+++ b/.learn/config.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "editor": {
+ "agent": "vscode"
+ },
+ "autoPlay": true
+ },
+ "currentExercise": null
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 40ae560d5..beed9fd14 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.18.0"
},
"devDependencies": {
@@ -22,6 +23,9 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.8"
+ },
+ "engines": {
+ "node": ">=20.0.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -3281,6 +3285,15 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-icons": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
+ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/package.json b/package.json
index 7d59d13c8..2567d9f0f 100755
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-icons": "^5.5.0",
"react-router-dom": "^6.18.0"
},
"devDependencies": {
@@ -27,6 +28,6 @@
"vite": "^4.4.8"
},
"engines": {
- "node": ">=20.0.0"
- }
+ "node": ">=20.0.0"
+ }
}
diff --git a/src/ContactForm.css b/src/ContactForm.css
new file mode 100644
index 000000000..303ca84df
--- /dev/null
+++ b/src/ContactForm.css
@@ -0,0 +1,164 @@
+
+body {
+ background-color: #f8f9fa;
+}
+
+.contact-list-container {
+ max-width: 900px;
+ margin: 20px auto;
+ padding: 20px;
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.contact-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.form-container {
+ max-width: 600px;
+ margin: 40px auto;
+ padding: 20px;
+ background-color: #fff;
+}
+
+.form-title {
+ text-align: center;
+ font-size: 24px;
+ font-weight: 500;
+ margin-bottom: 30px;
+ color: #333;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.label-text {
+ display: none;
+}
+
+.form-input {
+ width: 100%;
+ padding: 12px 15px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 16px;
+ color: #6c757d;
+}
+
+.form-input:focus {
+ border-color: #007bff;
+ outline: none;
+ box-shadow: 0 0 5px rgba(0, 123, 255, 0.2);
+}
+
+.save-button {
+ width: 100%;
+ padding: 15px;
+ margin-top: 10px;
+ background-color: #0f8878;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ font-size: 18px;
+ text-transform: uppercase;
+ cursor: pointer;
+ font-weight: 600;
+}
+
+.save-button:hover {
+ background-color: #0056b3;
+}
+
+.back-link-container {
+ text-align: left;
+ margin-top: 15px;
+}
+
+.back-link {
+ color: #007bff;
+ font-size: 14px;
+ text-decoration: underline;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+
+/* Contenedor General */
+.contact-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.create-contact-btn {
+ padding: 10px 15px;
+ background-color: #007bff; /* Color primario */
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+.contact-ul {
+ list-style: none; /* Quita los puntos de la lista */
+ padding: 0;
+}
+
+/* Tarjeta de Contacto (Filas) */
+.contact-card {
+ display: flex;
+ justify-content: space-between; /* Alinea los detalles a la izquierda y las acciones a la derecha */
+ align-items: center;
+ padding: 15px 20px;
+ margin-bottom: 15px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+ background-color: white;
+}
+
+.contact-details {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+}
+
+.contact-info p {
+ margin: 5px 0; /* Espaciado entre las líneas de información */
+ font-size: 0.95em;
+}
+
+/* Acciones (Botones) */
+.contact-actions {
+ display: flex;
+ gap: 10px;
+}
+
+.edit-btn,
+.delete-btn {
+ padding: 8px 12px;
+ border: 1px solid transparent;
+ border-radius: 5px;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.edit-btn {
+ background-color: #ffc107; /* Amarillo */
+ color: #333;
+}
+
+.delete-btn {
+ background-color: #dc3545; /* Rojo */
+ color: white;
+}
\ No newline at end of file
diff --git a/src/components/ScrollToTop.jsx b/src/components/ScrollToTop.jsx
index fe79dc5be..fd27b1ab6 100644
--- a/src/components/ScrollToTop.jsx
+++ b/src/components/ScrollToTop.jsx
@@ -2,25 +2,25 @@ import { useEffect, useRef } from "react";
import PropTypes from "prop-types";
// This component allows the scroll to go to the beginning when changing the view,
-// otherwise it would remain in the position of the previous view.
-// Investigate more about this React behavior :D
+// otherwise it would remain in the position of the previous view.
+// Investigate more about this React behavior :D
const ScrollToTop = ({ location, children }) => {
- const prevLocation = useRef(location);
+ const prevLocation = useRef(location);
- useEffect(() => {
- if (location !== prevLocation.current) {
- window.scrollTo(0, 0);
- }
- prevLocation.current = location;
- }, [location]);
+ useEffect(() => {
+ if (location !== prevLocation.current) {
+ window.scrollTo(0, 0);
+ }
+ prevLocation.current = location;
+ }, [location]);
- return children;
+ return children;
};
export default ScrollToTop;
ScrollToTop.propTypes = {
- location: PropTypes.object,
- children: PropTypes.any
-};
\ No newline at end of file
+ location: PropTypes.object,
+ children: PropTypes.any,
+};
diff --git a/src/main.jsx b/src/main.jsx
index 3a122d76a..2f311e47b 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,22 +1,57 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import './index.css' // Global styles for your application
-import { RouterProvider } from "react-router-dom"; // Import RouterProvider to use the router
-import { router } from "./routes"; // Import the router configuration
-import { StoreProvider } from './hooks/useGlobalReducer'; // Import the StoreProvider for global state management
+import React, { createContext, useReducer } from "react";
+import ReactDOM from "react-dom/client";
+import "./index.css"; // Global styles for your application
+import { RouterProvider } from "react-router-dom"; // Import RouterProvider to use the router
+import { router } from "./routes"; // Import the router configuration
+
+export const Context = createContext();
+
+const reducter = (store, action) => {
+ if (action.type === "CREATE_CONTACT") {
+ const updatedContactList = [...store.contactList, action.value];
+ return { ...store, contactList: updatedContactList };
+ }
+ if (action.type === "EDIT_CONTACT") {
+ const updatedContactList = store.contactList.map((contact, index) => {
+ if (index === action.position) {
+ return action.value;
+ }
+ return contact;
+ });
+ return { ...store, contactList: updatedContactList };
+ }
+
+ if (action.type === "REMOVE_CONTACT") {
+ const updatedContactList = store.contactList.filter((_, index) => {
+ return action.position !== index;
+ });
+ return { ...store, contactList: updatedContactList };
+ }
+ return store;
+};
const Main = () => {
- return (
-
📧 Email: {contact.email}
+📞 Teléfono: {contact.phone}
++ 🏠 Dirección: {contact.address} +
+