diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..544138b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/README.md b/README.md index fe12fa3..1052bd2 100644 --- a/README.md +++ b/README.md @@ -2,557 +2,23 @@ ## Índice -* [1. Preámbulo](#1-preámbulo) +* [1. Redis](#1-redis) * [2. Resumen del proyecto](#2-resumen-del-proyecto) -* [3. Objetivos de aprendizaje](#3-objetivos-de-aprendizaje) -* [4. Consideraciones generales](#4-consideraciones-generales) -* [5. Criterios de aceptación mínimos del proyecto](#5-criterios-de-aceptación-mínimos-del-proyecto) -* [6. Hacker edition](#6-hacker-edition) -* [7. Entrega](#7-entrega) -* [8. Pistas, tips y lecturas complementarias](#8-pistas-tips-y-lecturas-complementarias) -## 1. Preámbulo -Instagram, Snapchat, Twitter, Facebook, Twitch, Linkedin, etc. Las redes -sociales han invadido nuestras vidas. Las amamos u odiamos, y muchos no podemos -vivir sin ellas. +## 1. Redis -![adem-ay-Tk9m_HP4rgQ-unsplash](https://user-images.githubusercontent.com/110297/135544666-4efa54f1-4ff6-4c4c-b398-6df04ef56117.jpg) - -Hay redes sociales de todo tipo y para todo tipo de intereses. Por ejemplo, -en una ronda de financiamiento con inversionistas, se presentó una red social -para químicos en la que los usuarios podían publicar artículos sobre sus -investigaciones, comentar en los artículos de sus colegas, y filtrar artículos -de acuerdo a determinadas etiquetas o su popularidad, lo más reciente, o lo -más comentado. +Redis es una red social la cual esta pensada para evitar el derroche alimentario, esto quiere decir evitar el desperdicio de comida a través del trueque (intercambio de alimentos) ya sea por objetos que tengamos en casa y/o por otros alimentos ya sean preparados o no, así como también obsequiar los alimentos (este punto en específico va mas enfocado a los restaurantes) justamente para evitar el desperdicio de comida. ## 2. Resumen del proyecto -En este proyecto construirás una Red Social sobre lo que decidan tú y tu equipo. -Podría ser, por ejemplo, sobre alimentación saludable, feminismo, educación, -salud, energías renovables, amantes de las [Empanadas](https://es.wikipedia.org/wiki/Empanada) -o de los [Tacos de Canasta](https://es.wikipedia.org/wiki/Taco), -de la [Feijoada](https://es.wikipedia.org/wiki/Feijoada), o de lo que sea. - -Tu Red Social tendrá que permitir a cualquier usuario crear una cuenta de acceso -y loguearse con ella; crear, editar, borrar y _"likear"_ publicacciones. - -Por lo tanto, en este proyecto construirás una -[Single-page Application (SPA)](https://es.wikipedia.org/wiki/Single-page_application) -[_responsive_](https://curriculum.laboratoria.la/es/topics/css/02-responsive) (con más de una vista / página) -en la que podamos **leer y escribir datos**. - -### Los objetivos generales de este proyecto son los siguientes - -* Desarrollar una SPA con temática de red social -* Aplicar los conceptos de responsividad en el desarrollo de las vistas (templates) -* Implementar un router para la navegación entre las diferentes vistas de la aplicación -* Emplear un servicio externo para la persistencia de datos de la aplicación -* Crear una suite de pruebas unitarias que permitan testear código asíncrono - -Para lograr estos objetivos, deberás aprender y hacer uso de las siguientes -herramientas o habilidades técnicas: - -## 3. Objetivos de aprendizaje - -Reflexiona y luego marca los objetivos que has llegado a entender y aplicar en tu proyecto. Piensa en eso al decidir tu estrategia de trabajo. - -### HTML - -- [ ] **Uso de HTML semántico** - -
Links

- - * [HTML semántico](https://curriculum.laboratoria.la/es/topics/html/02-html5/02-semantic-html) - * [Semantics - MDN Web Docs Glossary](https://developer.mozilla.org/en-US/docs/Glossary/Semantics#Semantics_in_HTML) -

- -### CSS - -- [ ] **Uso de selectores de CSS** - -
Links

- - * [Intro a CSS](https://curriculum.laboratoria.la/es/topics/css/01-css/01-intro-css) - * [CSS Selectors - MDN](https://developer.mozilla.org/es/docs/Web/CSS/CSS_Selectors) -

- -- [ ] **Modelo de caja (box model): borde, margen, padding** - -
Links

- - * [Box Model & Display](https://curriculum.laboratoria.la/es/topics/css/01-css/02-boxmodel-and-display) - * [The box model - MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model) - * [Introduction to the CSS box model - MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model) - * [CSS display - MDN](https://developer.mozilla.org/pt-BR/docs/Web/CSS/display) - * [display - CSS Tricks](https://css-tricks.com/almanac/properties/d/display/) -

- -- [ ] **Uso de flexbox en CSS** - -
Links

- - * [A Complete Guide to Flexbox - CSS Tricks](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) - * [Flexbox Froggy](https://flexboxfroggy.com/#es) - * [Flexbox - MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox) -

- -- [ ] **Uso de CSS Grid Layout** - -
Links

- - * [A Complete Guide to Grid - CSS Tricks](https://css-tricks.com/snippets/css/complete-guide-grid/) - * [Grids - MDN](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids) -

- -### Web APIs - -- [ ] **Uso de selectores del DOM** - -
Links

- - * [Manipulación del DOM](https://curriculum.laboratoria.la/es/topics/browser/02-dom/03-1-dom-methods-selection) - * [Introducción al DOM - MDN](https://developer.mozilla.org/es/docs/Web/API/Document_Object_Model/Introduction) - * [Localizando elementos DOM usando selectores - MDN](https://developer.mozilla.org/es/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors) -

- -- [ ] **Manejo de eventos del DOM (listeners, propagación, delegación)** - -
Links

- - * [Introducción a eventos - MDN](https://developer.mozilla.org/es/docs/Learn/JavaScript/Building_blocks/Events) - * [EventTarget.addEventListener() - MDN](https://developer.mozilla.org/es/docs/Web/API/EventTarget/addEventListener) - * [EventTarget.removeEventListener() - MDN](https://developer.mozilla.org/es/docs/Web/API/EventTarget/removeEventListener) - * [El objeto Event](https://developer.mozilla.org/es/docs/Web/API/Event) -

- -- [ ] **Manipulación dinámica del DOM** - -
Links

- - * [Introducción al DOM](https://developer.mozilla.org/es/docs/Web/API/Document_Object_Model/Introduction) - * [Node.appendChild() - MDN](https://developer.mozilla.org/es/docs/Web/API/Node/appendChild) - * [Document.createElement() - MDN](https://developer.mozilla.org/es/docs/Web/API/Document/createElement) - * [Document.createTextNode()](https://developer.mozilla.org/es/docs/Web/API/Document/createTextNode) - * [Element.innerHTML - MDN](https://developer.mozilla.org/es/docs/Web/API/Element/innerHTML) - * [Node.textContent - MDN](https://developer.mozilla.org/es/docs/Web/API/Node/textContent) -

- -- [ ] **Ruteado (History API, evento hashchange, window.location)** - -
Links

- - * [Manipulando el historial del navegador - MDN](https://developer.mozilla.org/es/docs/DOM/Manipulando_el_historial_del_navegador) -

- -### JavaScript - -- [ ] **Arrays (arreglos)** - -
Links

- - * [Arreglos](https://curriculum.laboratoria.la/es/topics/javascript/04-arrays) - * [Array - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/) - * [Array.prototype.sort() - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) - * [Array.prototype.forEach() - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) - * [Array.prototype.map() - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/map) - * [Array.prototype.filter() - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) - * [Array.prototype.reduce() - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) -

- -- [ ] **Objetos (key, value)** - -
Links

- - * [Objetos en JavaScript](https://curriculum.laboratoria.la/es/topics/javascript/05-objects/01-objects) -

- -- [ ] **Diferenciar entre tipos de datos primitivos y no primitivos** - -- [ ] **Variables (declaración, asignación, ámbito)** - -
Links

- - * [Valores, tipos de datos y operadores](https://curriculum.laboratoria.la/es/topics/javascript/01-basics/01-values-variables-and-types) - * [Variables](https://curriculum.laboratoria.la/es/topics/javascript/01-basics/02-variables) -

- -- [ ] **Uso de condicionales (if-else, switch, operador ternario, lógica booleana)** - -
Links

- - * [Estructuras condicionales y repetitivas](https://curriculum.laboratoria.la/es/topics/javascript/02-flow-control/01-conditionals-and-loops) - * [Tomando decisiones en tu código — condicionales - MDN](https://developer.mozilla.org/es/docs/Learn/JavaScript/Building_blocks/conditionals) -

- -- [ ] **Uso de bucles/ciclos (while, for, for..of)** - -
Links

- - * [Bucles (Loops)](https://curriculum.laboratoria.la/es/topics/javascript/02-flow-control/02-loops) - * [Bucles e iteración - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Guide/Loops_and_iteration) -

- -- [ ] **Funciones (params, args, return)** - -
Links

- - * [Funciones (control de flujo)](https://curriculum.laboratoria.la/es/topics/javascript/02-flow-control/03-functions) - * [Funciones clásicas](https://curriculum.laboratoria.la/es/topics/javascript/03-functions/01-classic) - * [Arrow Functions](https://curriculum.laboratoria.la/es/topics/javascript/03-functions/02-arrow) - * [Funciones — bloques de código reutilizables - MDN](https://developer.mozilla.org/es/docs/Learn/JavaScript/Building_blocks/Functions) -

- -- [ ] **Pruebas unitarias (unit tests)** - -
Links

- - * [Empezando con Jest - Documentación oficial](https://jestjs.io/docs/es-ES/getting-started) -

- -- [ ] **Pruebas asíncronas** - -
Links

- - * [Tests de código asincrónico con Jest - Documentación oficial](https://jestjs.io/docs/es-ES/asynchronous) -

- -- [ ] **Uso de mocks y espías** - -
Links

- - * [Manual Mocks con Jest - Documentación oficial](https://jestjs.io/docs/es-ES/manual-mocks) -

- -- [ ] **Módulos de ECMAScript (ES Modules)** - -
Links

- - * [import - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Statements/import) - * [export - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Statements/export) -

- -- [ ] **Uso de linter (ESLINT)** - -- [ ] **Uso de identificadores descriptivos (Nomenclatura y Semántica)** - -- [ ] **Diferenciar entre expresiones (expressions) y sentencias (statements)** - -- [ ] **Callbacks** - -
Links

- - * [Función Callback - MDN](https://developer.mozilla.org/es/docs/Glossary/Callback_function) -

- -- [ ] **Promesas** - -
Links

- - * [Promise - MDN](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Promise) - * [How to Write a JavaScript Promise - freecodecamp (en inglés)](https://www.freecodecamp.org/news/how-to-write-a-javascript-promise-4ed8d44292b8/) -

- -### Control de Versiones (Git y GitHub) - -- [ ] **Git: Instalación y configuración** - -- [ ] **Git: Control de versiones con git (init, clone, add, commit, status, push, pull, remote)** - -- [ ] **Git: Integración de cambios entre ramas (branch, checkout, fetch, merge, reset, rebase, tag)** - -- [ ] **GitHub: Creación de cuenta y repos, configuración de llaves SSH** - -- [ ] **GitHub: Despliegue con GitHub Pages** - -
Links

- - * [Sitio oficial de GitHub Pages](https://pages.github.com/) -

- -- [ ] **GitHub: Colaboración en Github (branches | forks | pull requests | code review | tags)** - -- [ ] **GitHub: Organización en Github (projects | issues | labels | milestones | releases)** - -### Centrado en el usuario - -- [ ] **Diseñar y desarrollar un producto o servicio poniendo a las usuarias en el centro** - -### Diseño de producto - -- [ ] **Crear prototipos de alta fidelidad que incluyan interacciones** - -- [ ] **Seguir los principios básicos de diseño visual** - -### Investigación - -- [ ] **Planear y ejecutar testeos de usabilidad de prototipos en distintos niveles de fidelidad** - -
Links

- - * [Intro a testeos usabilidad](https://coda.io/@bootcamp-laboratoria/contenido-ux/test-de-usabilidad-15) - * [Pruebas con Usuarios 1 — ¿Qué, cuándo y para qué testeamos?](https://eugeniacasabona.medium.com/pruebas-con-usuarios-1-qu%C3%A9-cu%C3%A1ndo-y-para-qu%C3%A9-testeamos-7c3a89b4b5e7) -

- -### Firebase - -- [ ] **Firebase Auth** - -
Links

- - * [Primeros pasos con Firebase Authentication en sitios web - Documentación oficial](https://firebase.google.com/docs/auth/web/start?hl=es) - * [Administra usuarios en Firebase (onAuthStateChanged)](https://firebase.google.com/docs/auth/web/manage-users?hl=es#get_the_currently_signed-in_user) -

- -- [ ] **Firestore** - -
Links

- - * [Firestore - Documentación oficial](https://firebase.google.com/docs/firestore?hl=es) - * [Reglas de seguridad de Firestore - Documentación oficial](https://firebase.google.com/docs/rules?hl=es) - * [Obtén actualizaciones en tiempo real con Cloud Firestore - Documentación oficial](https://firebase.google.com/docs/firestore/query-data/listen?hl=es) -

- -## 4. Consideraciones generales - -* Este proyecto se debe trabajar en equipos de tres. - -* El rango de tiempo estimado para completar el proyecto es de 4 a 5 Sprints. - -* La lógica del proyecto debe estar implementada completamente en JavaScript - (ES6+), HTML y CSS :smiley:. Para este proyecto **no está permitido** utilizar - _frameworks_ o librerías de CSS y JS. - -* La división y organización del trabajo debe permitir, sin excepciones, que - **cada integrante** del equipo practique el aprendizaje de todo lo involucrado - en **cada historia**. _No se dividan el trabajo como en una fábrica._ - - ¿Hasta acá has avanzado en tus proyectos con cierta fluidez y sin mayores - problemas? Sé generosa con tus compañeras, permíteles aprender y practicar - sin restricciones, aunque tome un poco más de tiempo. Aproveha de - _coachearlas_, de hacer _pair programming_, una de las mejores maneras de - aprender es explicando verbalmente. - - - ¿Se te está haciendo difícil y te cuesta un poco más avanzar? No te quedes - con las partes "fáciles" del proyecto, conversa, negocia, exige tu oportunidad - para practicar y aprender lo que se te hace más difícil. - -* Solamente pueden trabajar en una única historia por vez, no pueden avanzar a - la siguiente sin haber completado la anterior. La historia se completa cuando - se cumplen **todos** sus Criterios de Aceptación + **toda** su Definición - de Terminado. - -Para comenzar tendrás que hacer un _fork_ y _clonar_ este repositorio. - -## 5. Criterios de aceptación mínimos del proyecto - -### 5.1 Boilerplate - -Este proyecto no incluye un _boilerplate_ completo, solo algunos archivos de -configuración basico, así es que tendrás que definir la estructura de carpetas -y escribir tus propias Pruebas Unitarias (_tests_). Para hacerlo, puedes guiarte -de los proyectos anteriores y/o organizar los archivos siguiendo una estructura -de [Modelo-Vista-Controlador](https://developer.mozilla.org/es/docs/Glossary/MVC). - -En este proyecto vamos a usar una herramienta llamada -[Vite](https://es.vitejs.dev/) para empaquetar nuestros módulos y arrancar -el servidor de desarrollo, el cual provee nuestros archivos utilizando -la estrategia `Hot Module Replacement` -[(HMR)](https://es.vitejs.dev/guide/features.html#hot-module-replacement), -esto significa que cuando hagas cambios en los archivos que estén siendo -servidos, el navegador automáticamente se actualizará sin tener que refrescar -y volver a cargar todo el sitio. Debes tener especial cuidado de no tener -ninguna _dependencia circular_ en tu código ya que -[eso puede ocasionar problemas con HMR](https://es.vitejs.dev/guide/troubleshooting.html#ocurre-un-refresco-completo-en-lugar-de-hmr). -(`eslint-plugin-import` tiene una regla -[import/no-cycle](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md) -que va a avisar si las tiene.) - -### 5.2 Definición del producto - -En el `README.md` cuéntanos brevemente cómo descubriste las necesidades de los -usuarios y cómo llegaste a la definición final de tu producto. Es importante -que detalles: - -* Quiénes son los principales usuarios de producto. -* Qué problema resuelve el producto / para qué le servirá a estos usuarios. - -### 5.3 Historias de usuario - -Una vez que entiendas las necesidades de tus usuarixs, escribe las Historias de -Usuario que representen todo lo que necesitan hacer/ver en la Red Social. Cada -una de tus Historias de Usuario debe tener: - -* **Criterios de Aceptación:** todo lo que debe ocurrir para satisfacer las - necesidades del usuario. - -* **Definición de terminado:** todos los aspectos técnicos que deben cumplirse - para que, como equipo, sepan que esa historia está terminada y lista - para publicarse. **Todas** tus Historias de Usuario (salvo excepciones), deben - incluir estos aspectos en su Definición de Terminado (más todo lo que - necesiten agregar): - - - Debe ser una SPA. - - Debe ser _responsive_. - - Deben haber recibido _code review_ de al menos una compañera de otro equipo. - - Hicieron los _test_ unitarios - - Testearon manualmente buscando errores e imperfecciones simples. - - Hicieron _pruebas_ de usabilidad e incorporaron el _feedback_ de los - usuarios como mejoras. - - Desplegaron su aplicación y etiquetaron la versión (git tag). - -### 5.4 Diseño de la Interfaz de Usuario (prototipo de baja fidelidad) - -Debes definir cuál será el flujo que seguirá el usuario dentro de tu aplicación -y, con eso, diseña la Interfaz de Usuario (UI por sus siglas en inglés) que -siga este flujo. - -### 5.5 Responsive - -Debe verse bien en dispositivos de pantallas grandes -(computadoras/es, laptops, etc.) y pequeñas (_tablets_, celulares, etc.). Te -sugerimos seguir la técnica de _`mobile first`_ (más detalles sobre esta técnica -al final). - -### 5.6 Consideraciones del comportamiento de la interfaz de usuario (UI) - -Estas consideraciones te ayudarán a escribir las Definiciones de Terminado de -tus H.U.: - -#### Creación de cuenta de usuario e inicio de sesión - -* _Login_ con Firebase: - - Para el _login_ y las publicaciones en el muro puedes utilizar [Firebase](https://firebase.google.com/products/database/) - - Creación de cuenta de acceso y autenticación con cuenta de correo y - contraseña, y también con una cuenta de Google. -* Validaciones: - - Solamente se permite el acceso a usuarios con cuentas válidas. - - No pueden haber usuarios repetidos. - - La cuenta de usuario debe ser un correo electrónico válido. - - Lo que se escriba en el campo (_input_) de contraseña debe ser secreto. -* Comportamiento: - - Al enviarse el formulario de registro o inicio de sesión, debe validarse. - - Si hay errores, se deben mostrar mensajes descriptivos para ayudar al - usuario a corregirlos. - -#### Muro/timeline - -* Validaciones: - - Al publicar, se debe validar que exista contenido en el _input_. -* Comportamiento: - - Al recargar la aplicación, se debe verificar si el usuario está _logueado_ - antes de mostrar contenido. - - Poder publicar un _post_. - - Poder dar y quitar _like_ a una publicación. Máximo uno por usuario. - - Llevar un conteo de los _likes_. - - Poder eliminar un post específico. - - Pedir confirmación antes de eliminar un _post_. - - Al dar _click_ para editar un _post_, debe cambiar el texto por un _input_ - que permita editar el texto y luego guardar los cambios. - - Al guardar los cambios debe cambiar de vuelta a un texto normal pero con la - información editada. - - Al recargar la página debo de poder ver los textos editados. - -### 5.7 Consideraciones técnicas Front-end - -* Separar la manipulación del DOM de la lógica (Separación de responsabilidades). -* Contar con múltiples vistas. Para esto, tu aplicación debe ser una - [Single Page Application (SPA)](https://es.wikipedia.org/wiki/Single-page_application) -* Alterar y persistir datos. Los datos que agregues o modifiques deberán - persistir a lo largo de la aplicación. Te recomendamos que uses - [Firebase](https://firebase.google.com/) para eso también. - -#### Pruebas unitarias (unit tests) - -* Recuerda que no hay un _setup_ de **tests** definido, dependerá de - la estructura de tu proyecto. Algo que no debes de olvidar es pensar en éstas - pruebas, te pueden ayudar a definir la estructura y nomenclatura de tu lógica. - -* Los tests unitarios deben cubrir un mínimo del 70% de _statements_, _functions_, - _lines_, y _branches_. - -### 5.8 Consideraciones técnicas UX - -* Hacer al menos 2 entrevistas con usuarios. -* Hacer un prototipo de baja fidelidad. -* Asegurarte de que la implementación en código siga los lineamientos del - diseño. -* Hacer sesiones de _testing de usabilidad_ con el producto en HTML. - -## 6. Hacker edition - -Las secciones llamadas _Hacker Edition_ son **opcionales**. Si **terminaste** -con todo lo anterior y te queda tiempo, intenta completarlas. Así podrás -profundizar y/o ejercitar más sobre los objetivos de aprendizaje del proyecto. - -* Permite crear posts con imágenes. -* Permite buscar usuarios, agregar y eliminar "amigos". -* Permite definir la privacidad de los _posts_ (público o solamente para amigos). -* Permite ver su muro de cualquier usuario "no-amigo" (solamente los - posts _públicos_). -* Permite comentar o responder una publicación. -* Permite editar perfil. - -## 7. Entrega - -El proyecto será _entregado_ subiendo tu código a GitHub (`commit`/`push`) y la -interfaz será desplegada usando GitHub pages u otro servicio de hosting -(Firebase, Netlify, Vercel, etc) que puedas haber encontrado en el camino. -Revisa la [documentación de Vite](https://vitejs.dev/guide/static-deploy.html) -para guiarte con eso. - -*** - -## 8. Pistas, tips y Lecturas complementarias - -Súmate al canal de Slack -[#project-social-network](https://claseslaboratoria.slack.com/archives/C03SE63GFJQ) -para conversar y pedir ayuda del proyecto. - -### Mobile first - -El concepto de [_mobile first_](https://www.mediaclick.es/blog/diseno-web-responsive-design-y-la-importancia-del-mobile-first/) -hace referencia a un proceso de diseño y desarrollo donde partimos de cómo se ve -y cómo funciona la aplicación en un dispositivo móvil primero, y más adelante se -ve como adaptar la aplicación a pantallas progresivamente grandes y -características específicas del entorno desktop. Esto es en contraposición al -modelo tradicional, donde primero se diseñaban los websites (o webapps) para -desktop y después se trataba de _arrugar_ el diseño para que entre en pantallas -más chicas. La clave acá es asegurarse de que desde el principio diseñan usando -la vista _responsive_ de las herramientas de desarrollador (developer tools) del -navegador. De esa forma, partimos de cómo se ve y comporta la aplicación en una -pantalla y entorno móvil. - -### Múltiples vistas - -En proyectos anteriores nuestras aplicaciones habían estado compuestas de una -sola _vista_ principal (una sóla _página_). En este proyecto se introduce la -necesidad de tener que dividir nuestra interfaz en varias _vistas_ o _páginas_ -y ofrecer una manera de navegar entre estas vistas. Este problema se puede -afrontar de muchas maneras: con archivos HTML independientes (cada uno con su -URL) y links tradicionales, manteniendo estado en memoria y rederizando -condicionalmente (sin refrescar la página), [manipulando el historial del -navegador](https://developer.mozilla.org/es/docs/DOM/Manipulando_el_historial_del_navegador) -con [`window.history`](https://developer.mozilla.org/es/docs/Web/API/Window/history). -En este proyecto te invitamos a explorar opciones y decidir una opción -de implementación. - -### Escritura de datos +En esta red social, el usuario podrá registrarse con su usuario y contraseña o registrarte con google. Tendrá acceso a publicar post y eliminarlos solamente si es el autor de los mismos. -En los proyectos anteriores hemos consumido (leído) datos, pero todavía no -habíamos escrito datos (salvar cambios, crear datos, borrar, ...). En este -proyecto tendrás que crear (salvar) nuevos datos, así como leer, actualizar y -modificar datos existentes. Estos datos se podrán guardar de forma remota -usando [Firebase](https://firebase.google.com/). +A continuación se dejan ejemplos de la red + +![Página Principal](/src/image/pantalla-inicio.png) -Para usar Firebase hay que crear un proyecto en la consola de Firebase e -instalar la dependencia `firebase` utilizando `npm`. -Lee [las instrucciones paso a paso aqui](https://firebase.google.com/docs/web/setup). +![Página Principal](/src/image/pantalla-registro.png) -Otras: +![Página Principal](/src/image/pantalla-muro.png) -* [Modulos: Export](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/export) -* [Modulos: Import](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/import) -* [Diseño web, responsive design y la importancia del mobile first - Media Click](https://www.mediaclick.es/blog/diseno-web-responsive-design-y-la-importancia-del-mobile-first/) -* [Mobile First: el enfoque actual del diseño web móvil - 1and1](https://www.1and1.es/digitalguide/paginas-web/diseno-web/mobile-first-la-nueva-tendencia-del-diseno-web/) -* [Mobile First - desarrolloweb.com](https://desarrolloweb.com/articulos/mobile-first-responsive.html) -* [Mobile First Is NOT Mobile Only - Nielsen Norman Group](https://www.nngroup.com/articles/mobile-first-not-mobile-only/) diff --git a/package.json b/package.json index 1d89e14..eb37f51 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,8 @@ "createdAt": "2023-06-06T21:37:46.504Z", "version": "6.3.0", "commit": "a942adeb868f1fe54b86e34cc4fc4ddb0601700d" + }, + "dependencies": { + "firebase": "^10.1.0" } -} \ No newline at end of file +} diff --git a/src/components/firebase.js b/src/components/firebase.js new file mode 100644 index 0000000..e29d79f --- /dev/null +++ b/src/components/firebase.js @@ -0,0 +1,48 @@ +import { initializeApp } from 'firebase/app'; +import { + getFirestore, + collection, + addDoc, + getDocs, + deleteDoc, + doc, + getDoc, + // signOut, +} from 'firebase/firestore'; +import { + getAuth, + signInWithPopup, + GoogleAuthProvider, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from 'firebase/auth'; + +const firebaseConfig = { + apiKey: 'AIzaSyDhmNVMP2orY3iOmOT6DdaVABzLyzydVLY', + authDomain: 'redis-ccc00.firebaseapp.com', + projectId: 'redis-ccc00', + storageBucket: 'redis-ccc00.appspot.com', + messagingSenderId: '718676390172', + appId: '1:718676390172:web:516edc445106e832a98ad9', + measurementId: 'G-JQF50P4QXX', +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const db = getFirestore(app); +const provider = new GoogleAuthProvider(); +const auth = getAuth(app); + +export const createCountEmailPassword = (email, password) => + createUserWithEmailAndPassword(auth, email, password); + +export const googleLogin = () => signInWithPopup(auth, provider); +export const savePosts = (post, correo) => addDoc(collection(db, 'posts'), { post, correo }); +export const getPosts = () => getDocs(collection(db, 'posts')); +export const getCurrentUser = () => auth.currentUser; +export const deletePost = (id) => deleteDoc(doc(db, 'posts', id)); + +export const singInEmailPass = (email, password) => + signInWithEmailAndPassword(auth, email, password); + +export const getPost = (id) => getDoc(doc(db, 'posts', id)); diff --git a/src/components/signIn.js b/src/components/signIn.js new file mode 100644 index 0000000..2abefa8 --- /dev/null +++ b/src/components/signIn.js @@ -0,0 +1,108 @@ +import { googleLogin, singInEmailPass } from './firebase'; + +export const signIn = (onNavigate) => { + // Se crean los elementos de HTML + + const signInDiv = document.createElement('div'); + signInDiv.classList.add('signInDiv'); + const imgContainer = document.createElement('div'); + imgContainer.classList.add('imgContainer'); + const imageSignIn = document.createElement('img'); + imageSignIn.classList.add('imageSignIn'); + imageSignIn.src = '../image/imagesignIn.jpg'; + const imageGoogle = document.createElement('img'); + imageGoogle.classList.add('imageGoogle'); + imageGoogle.src = '../image/btn-jpeg.jpg'; + + // se crea un select para almacenar el formulario + const formSignIn = document.createElement('form'); + formSignIn.classList.add('formSignIn'); + const description = document.createElement('h1'); + description.classList.add('description'); + const sentence = document.createElement('p'); + sentence.classList.add('sentence'); + + const email = document.createElement('input'); + email.classList.add('email'); + const password = document.createElement('input'); + password.classList.add('password'); + password.setAttribute('type', 'password'); + const signInButton = document.createElement('button'); + signInButton.classList.add('signInButton'); + const googleButton = document.createElement('button'); + googleButton.classList.add('googleButton'); + const dontYouHaveAnAccount = document.createElement('p'); + dontYouHaveAnAccount.classList.add('dontYouHaveAnAccount'); + const signUpButton = document.createElement('button'); + signUpButton.classList.add('signUpButtonInicio'); + + // Se inserta el nombre a los elementos creados + description.textContent = + '¡Únete a nuestra red de apoyo y reduce el derroche alimentario!'; + sentence.textContent = + 'Somos una comunidad donde encontrarás personas con quien podrás intercambiar alimentos desde canasta básica a totalmente elaborados para evitar el desperdicio.'; + email.placeholder = 'Correo Electrónico'; + password.placeholder = 'Contraseña'; + signInButton.textContent = 'Inicia sesión'; + googleButton.textContent = 'Google'; + dontYouHaveAnAccount.textContent = '¿No tienes una cuenta?'; + signUpButton.textContent = 'Registrate'; + + formSignIn.addEventListener('submit', (e) => { + e.preventDefault(); + }); + + signInButton.addEventListener('click', () => { + singInEmailPass(email.value, password.value) + .then(() => { + onNavigate('/wall'); + console.log(singInEmailPass); + }) + .catch((error) => { + if (error.code === 'auth/wrong-password') { + alert('La contraseña es invalida'); + } else if (error.code === 'auth/user-not-found') { + alert('El usario es invalido'); + } else { + alert(error.message); + } + }); + }); + + googleButton.addEventListener('click', (e) => { + e.preventDefault(); + googleLogin() + .then((result) => { + onNavigate('/wall'); + const user = result.user; + console.log(user); + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + alert(error.message); + console.log(errorMessage); + console.log(errorCode); + }); + }); + signUpButton.addEventListener('click', () => { + onNavigate('/signup'); + }); + + // Se insertan los hijos + + formSignIn.appendChild(email); + formSignIn.appendChild(password); + formSignIn.appendChild(signInButton); + formSignIn.appendChild(googleButton); + googleButton.appendChild(imageGoogle); + formSignIn.appendChild(dontYouHaveAnAccount); + formSignIn.appendChild(signUpButton); + imgContainer.appendChild(imageSignIn); + signInDiv.appendChild(imgContainer); + signInDiv.appendChild(description); + signInDiv.appendChild(sentence); + signInDiv.appendChild(formSignIn); + console.log(formSignIn); + return signInDiv; +}; diff --git a/src/components/signUp.js b/src/components/signUp.js new file mode 100644 index 0000000..7c23ba5 --- /dev/null +++ b/src/components/signUp.js @@ -0,0 +1,75 @@ +import { createCountEmailPassword } from './firebase'; + +export const signUp = (onNavigate) => { + const singUpDiv = document.createElement('div'); + singUpDiv.classList.add('signUpDiv'); + const signUpButton = document.createElement('button'); + signUpButton.classList.add('signUpButton'); + const backToLogin = document.createElement('button'); + backToLogin.classList.add('backToLogin'); + const signUpText = document.createElement('h3'); + signUpText.classList.add('signUpText'); + const usersName = document.createElement('input'); + usersName.classList.add('usersName'); + const usersEmail = document.createElement('input'); + usersEmail.classList.add('usersEmail'); + const usersPassword = document.createElement('input'); + usersPassword.classList.add('usersPassword'); + const termsCheckbox = document.createElement('input'); + termsCheckbox.classList.add('termsCheckbox'); + const termsText = document.createElement('p'); + termsText.classList.add('termsText'); + const doYouHaveAnAccount = document.createElement('p'); + doYouHaveAnAccount.classList.add('doYouHaveAnAccount'); + const imageSignUp = document.createElement('img'); + imageSignUp.classList.add('imageSignUp'); + imageSignUp.src = ('../image/imageninicio.jpg'); + + doYouHaveAnAccount.textContent = '¿Ya tienes una cuenta?'; + termsText.textContent = + 'Acepto Términos, Condiciones y política de Privacidad'; + termsCheckbox.type = 'checkbox'; + signUpText.textContent = '¡Regístrate!'; + usersName.placeholder = 'Nombre y Apellidos'; + usersEmail.placeholder = 'Correo Electrónico'; + usersPassword.placeholder = 'Contraseña'; + signUpButton.textContent = 'Crear cuenta'; + backToLogin.textContent = 'Inicia sesión'; + + backToLogin.addEventListener('click', () => { + onNavigate('/'); + }); + signUpButton.addEventListener('click', () => { + createCountEmailPassword(usersEmail.value, usersPassword.value) + .then(() => { + onNavigate('/wall'); + console.log(usersEmail.value); + console.log(usersPassword.value); + }) + .catch((error) => { + const errorCode = error.code; + // const errorMessage = error.message; + if (errorCode === 'auth/email-already-in-use') { + alert('Ya existe una cuenta con ese correo'); + } else if (errorCode === 'auth/invalid-email') { + alert('Ingresa un correo electrónico válido'); + } else if (errorCode === 'auth/weak-password') { + alert('Ingresa una constraseña de al menos 6 caracteres'); + } else if (errorCode) { + alert('Algo salió mal'); + } + }); + }); + singUpDiv.appendChild(imageSignUp); + singUpDiv.appendChild(signUpText); + singUpDiv.appendChild(usersName); + singUpDiv.appendChild(usersEmail); + singUpDiv.appendChild(usersPassword); + singUpDiv.appendChild(termsText); + termsText.appendChild(termsCheckbox); + + singUpDiv.appendChild(signUpButton); + singUpDiv.appendChild(doYouHaveAnAccount); + singUpDiv.appendChild(backToLogin); + return singUpDiv; +}; diff --git a/src/components/wall.js b/src/components/wall.js new file mode 100644 index 0000000..4baedf7 --- /dev/null +++ b/src/components/wall.js @@ -0,0 +1,106 @@ +import { + savePosts, + getPosts, + getCurrentUser, + deletePost, + getPost, +} from './firebase.js'; + +export const wall = (onNavigate) => { + const wallDiv = document.createElement('div'); + const allPosts = document.createElement('form'); + const containerPosts = document.createElement('div'); + containerPosts.classList.add('containerPosts'); + const signOutButton = document.createElement('button'); + signOutButton.classList.add('signOutButton'); + const postButton = document.createElement('button'); + postButton.classList.add('postButton'); + const post = document.createElement('textarea'); + post.classList.add('post'); + allPosts.classList.add('allPosts'); + const header = document.createElement('header'); + header.classList.add('header'); + const logo = document.createElement('img'); + logo.classList.add('logo'); + logo.src = '../image/unnamed.png'; + + post.placeholder = '¿Qué vas hay de comer hoy?'; + post.rows = '4'; + postButton.textContent = 'Publicar'; + signOutButton.textContent = 'Cerrar sesión'; + + allPosts.addEventListener('submit', (e) => { + e.preventDefault(); + const user = getCurrentUser(); + savePosts(post.value, user.email).then(() => { + allPosts.reset(); + window.location.reload(); + console.log(allPosts); + }); + }); + getPosts().then((posts) => { + posts.forEach((doc) => { + const data = doc.data(); + const user = getCurrentUser(); + + if (data.correo === user.email) { + containerPosts.innerHTML += ` +
+ ${data.correo}
+ +
+

${data.post}

+ + +

+ `; + } else { + containerPosts.innerHTML += ` +
+ ${data.correo}
+ +
+

${data.post}

+

+ `; + console.log(user.email); + } + + const btnsDelete = containerPosts.querySelectorAll('.btn-delete'); + + btnsDelete.forEach((btn) => { + btn.addEventListener('click', ({ target: { dataset } }) => { + deletePost(dataset.id).then(() => { + window.location.reload(); + }); + }); + }); + const btnEdit = containerPosts.querySelectorAll('.btn-edit'); + btnEdit.forEach((btn) => { + btn.addEventListener('click', ({ target: { dataset } }) => { + getPost(dataset.id).then(() => { + post.value = data.post; + console.log(data); + }); + }); + }); + // console.log(btnsDelete); + // console.log(data); + }); + // console.log(posts); + }); + + signOutButton.addEventListener('click', () => { + onNavigate('/'); + }); + + header.appendChild(logo); + wallDiv.appendChild(header); + + allPosts.append(post, postButton); + wallDiv.appendChild(allPosts); + wallDiv.appendChild(containerPosts); + wallDiv.appendChild(signOutButton); + + return wallDiv; +}; diff --git a/src/image/9e9ea003-ee27-4b86-b897-0d0869eaa24d.jfif b/src/image/9e9ea003-ee27-4b86-b897-0d0869eaa24d.jfif new file mode 100644 index 0000000..1fe594e Binary files /dev/null and b/src/image/9e9ea003-ee27-4b86-b897-0d0869eaa24d.jfif differ diff --git a/src/image/btn-jpeg.jpg b/src/image/btn-jpeg.jpg new file mode 100644 index 0000000..0b2987f Binary files /dev/null and b/src/image/btn-jpeg.jpg differ diff --git a/src/image/btn_google_signin_dark_normal_web.png b/src/image/btn_google_signin_dark_normal_web.png new file mode 100644 index 0000000..b1327b4 Binary files /dev/null and b/src/image/btn_google_signin_dark_normal_web.png differ diff --git a/src/image/imageninicio.jpg b/src/image/imageninicio.jpg new file mode 100644 index 0000000..17a03a8 Binary files /dev/null and b/src/image/imageninicio.jpg differ diff --git a/src/image/imagesignIn.jpg b/src/image/imagesignIn.jpg new file mode 100644 index 0000000..c2c7477 Binary files /dev/null and b/src/image/imagesignIn.jpg differ diff --git a/src/image/pantalla-inicio.png b/src/image/pantalla-inicio.png new file mode 100644 index 0000000..c171b82 Binary files /dev/null and b/src/image/pantalla-inicio.png differ diff --git a/src/image/pantalla-muro.png b/src/image/pantalla-muro.png new file mode 100644 index 0000000..a9a7b45 Binary files /dev/null and b/src/image/pantalla-muro.png differ diff --git a/src/image/pantalla-registro.png b/src/image/pantalla-registro.png new file mode 100644 index 0000000..4a7c933 Binary files /dev/null and b/src/image/pantalla-registro.png differ diff --git a/src/image/sign-up.jfif b/src/image/sign-up.jfif new file mode 100644 index 0000000..1a2bfa6 Binary files /dev/null and b/src/image/sign-up.jfif differ diff --git a/src/image/unnamed.png b/src/image/unnamed.png new file mode 100644 index 0000000..194826f Binary files /dev/null and b/src/image/unnamed.png differ diff --git a/src/index.html b/src/index.html index 788db3c..a2a1dbd 100644 --- a/src/index.html +++ b/src/index.html @@ -4,9 +4,16 @@ - Document + Social Network + + + + + + +
\ No newline at end of file diff --git a/src/lib/index.js b/src/lib/firebase.js similarity index 52% rename from src/lib/index.js rename to src/lib/firebase.js index d193089..8911a41 100644 --- a/src/lib/index.js +++ b/src/lib/firebase.js @@ -1,4 +1,4 @@ -// aqui exportaras las funciones que necesites +// aqui exportaras las funciones o metodos que interactuan y tienen un proposito export const myFunction = () => { // aqui tu codigo diff --git a/src/main.js b/src/main.js index ac27e91..afc36af 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,37 @@ -// Este es el punto de entrada de tu aplicacion +// rauter , gestiona las rutas +import { signIn } from './components/signIn'; +import { signUp } from './components/signUp'; +import { wall } from './components/wall'; -import { myFunction } from './lib/index.js'; +const rootDiv = document.getElementById('root'); +// funciones a ejecutar por elección del usuario +const routes = { + '/': signIn, + '/signup': signUp, + '/wall': wall, +}; +// onNavigate navegar entre paginas +const onNavigate = (pathname) => { +// history es la historia de las pagina, pushState agrega una página nueva al historial y cambia el URL + window.history.pushState({}, pathname, window.location.origin + pathname); + // while se utiliza si tengo que remover mas de un hijo + // while (rootDiv.firstChild) { + rootDiv.removeChild(rootDiv.firstChild); + // Limpiar la página + // Agrega contenido del nuevo url (pathname) + rootDiv.appendChild(routes[pathname](onNavigate)); +}; +// Selecciona pagina o componente para mostrarlo dependiendo URL +// window.location = ubicacion donde esta el usuario y agrega el pathname o ruta +const component = routes[window.location.pathname]; +console.log(window.location); -myFunction(); +// Recuperar las paginas cuando selecciono hacia atras o hacia adelante del historial(las saca del historial) +window.addEventListener('popstate', () => { + const paginas = routes[window.location.pathname]; + rootDiv.removeChild(rootDiv.firstChild); + rootDiv.appendChild(paginas(onNavigate)); + console.log(window.location.pathname); +}); +// Agrega el contenido a la pantalla +rootDiv.appendChild(component(onNavigate)); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..4b2062f --- /dev/null +++ b/src/style.css @@ -0,0 +1,323 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body{ + background-color: #F3F1F1; +} + +#root { + display: flex; + align-items: center; + flex-direction: column; + margin-top: 0px ; + margin-bottom: 0px; + +} + +.signInDiv { + display:flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.imgContainer{ + display:flex; + align-items: center; + justify-content: center; + max-width: 200%; + +} +.imageSignIn{ + margin-top: 5%; + max-width: 90%; + border-radius: 5% ; + margin-bottom: 5%; + max-height: 200%; + +} +.formSignIn{ + display:flex; + align-items: center; + flex-direction: column; + justify-content: center; + padding: 5%; + margin: 0% 5% 5% 5%; + border-radius: 5%; +} + +.description{ + text-align: center; + font-size: 120%; + color: #080808; + margin-top: 5%; + margin-bottom: 5%; + +} + +.sentence{ + text-align: center; + font-size: 100%; + color: #080808; + margin-bottom: 5%; + margin-right: 10%; + margin-left: 10%; + +} + +.signInButton{ + background-color:#6FB94C; + padding: 12px ; + border-radius: 8px; + border: 1px solid black; + margin-bottom: 25px; + width: 120%; + + +} + +.email{ + margin-bottom: 15px; + padding: 15px; + border-radius: 8px; + width: 150% ; + +} + + +.password{ + margin-bottom: 15px; + padding: 15px; + border-radius: 8px; + width: 150%; +} + +.email::placeholder{ + text-align: center; + font-size: 15px ; + +} + +.password::placeholder{ + text-align: center; + font-size: 15px ; + +} + +.googleButton{ + width: 100px ; + height: 20px; + background-image:; + margin-bottom: 20px; + + +} + + +.dontYouHaveAnAccount{ + margin-bottom: 5%; +} + +.card{ + padding: 30px; + border: 1px solid black; + border-radius: 5px; + margin-top: 0; + margin-bottom: 15px; + background-color: #fff; + box-shadow: 5px 5px #626461; + text-align: left; + vertical-align: top; + width: 120%; + margin-left: 3%; + + +} + +.card1{ + padding: 20px; + border: 1px solid black; + border-radius: 5px; + margin-bottom: 1px; + background-color: #6FB94C; + box-shadow: 5px 5px #626461; + width: 120%; + margin-left: 3%; +} + +.post{ + padding: 10px; + border: 1px solid black; + + box-shadow: 5px 5px #626461; + font-family: 'Poppins', sans-serif; + font-size: 15px; + border-radius: 5px; + margin-bottom: 4px; + margin-left: 3%; + width: 120%; + + +} + +.header{ + padding: 30px; + border: 1px solid black; + background-color: #CE7F46; + width: 125%; + margin-bottom: 18px; + border-radius: 5px; + margin-left: 0%; + + +} + +.postButton{ + margin-left: 95%; + margin-bottom: 15px; + background-color: #CE7F46; + opacity: 85%; + width: 70px; + height: 30px; + font-family: 'Poppins', sans-serif; + font-size: 12spx; + border-radius: 5px; + color:#fff; + box-shadow: 5px 5px #626461; + +} + +.btn-delete{ + margin-left: 90%; + margin-top: 5%; + background-color: #CE7F46; + width: 50px; + height: 20px; + font-family: 'Poppins', sans-serif;; + font-size: 11px; + border-radius: 5px; + color:#fff; + +} + + +.logo{ + width: 25%; + height: 40%; + align-items: center; + margin-left: 40%; +} + +.imageGoogle{ + margin-top: -20%; + margin-bottom: 20%; + margin-left: -55%; +} + +.dontYouHaveAnAccount{ + margin-top: 10%; +} + +.imageSignUp{ + margin-top: 5%; + max-width: 90%; + border-radius: 5% ; + margin-bottom: 5%; + max-height: 200%; + margin-left: 5.5%; +} + +.usersEmail{ + margin-bottom: 15px; + padding: 15px; + border-radius: 8px; + width: 80% ; + margin-left: 10% ; +} + +.usersName{ + margin-bottom: 15px; + padding: 15px; + border-radius: 8px; + width: 80% ; + margin-left: 10% ; +} + +.usersPassword{ + margin-bottom: 15px; + padding: 15px; + border-radius: 8px; + width: 80% ; + margin-left: 10% ; +} + +.signUpText{ + margin-top: 5%; + margin-bottom: 10%; + margin-left: 35%; + font-size: 25px; +} + +.termsText{ + margin-top:5%; + margin-left: 2%; + margin-bottom: 5%; + +} + +.termsCheckbox{ + margin-left: 2%; +} + +.signUpButton{ + background-color:#6FB94C; + padding: 12px ; + border-radius: 8px; + border: 1px solid black; + margin-bottom: 5%; + margin-left: 40%; + +} + +.doYouHaveAnAccount{ + margin-left: 34% ; + margin-bottom: 5%; +} + +.backToLogin{ + background-color:#6FB94C; + padding: 12px ; + border-radius: 8px; + border: 1px solid black; + margin-bottom: 5%; + margin-left: 40%; + +} + +.signOutButton{ + margin-bottom: 15px; + background-color: #CE7F46; + opacity: 85%; + font-family: 'Poppins', sans-serif; + font-size: 12spx; + border-radius: 5px; + color:#fff; + box-shadow: 5px 5px #626461; + margin-bottom: 5%; + margin-left: 100%; + width: 20%; + + +} + +.signUpButtonInicio{ + background-color:#6FB94C; + padding: 12px ; + border-radius: 8px; + border: 1px solid black; + margin-bottom: 5%; + margin-left: 5%; + +} \ No newline at end of file