diff --git a/.firebase/hosting.ZGlzdA.cache b/.firebase/hosting.ZGlzdA.cache new file mode 100644 index 00000000..8d42e860 --- /dev/null +++ b/.firebase/hosting.ZGlzdA.cache @@ -0,0 +1,40 @@ +404.html,1685981902332,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3 +assets/index.0228c706.js,1685988749718,a87ccf50f6f805c1c02611273b6b142f6cc1edca0382952da8724d11b428dc8b +assets/index.3ad72261.js,1685993895689,f2374ade5cf43868e879e2ede52a4723ec2b70d137aba786edf58ae0c9717b53 +assets/index.4894615e.js,1685983941763,2e56184e1236bed80a28d86331884f14f342a09046b05c76b897a8962ec8a84e +assets/index.5b40586e.js,1685984218502,8c2f38dede2376becd398a4798b80c827e9c7a10449c9e0f9d712ef16da43a5a +assets/index.7473aa66.js,1685982961343,c70e7b923e05b99a46ad392d1c65e39a7c80127c0947e45dcc73316f6dec914b +assets/index.8581d718.js,1685983794746,ee99d3d05b32cb9a850e2698758aaa23711ba1f5410cb7e30e8da37288ccc122 +assets/index.887910e3.js,1685983499655,1aeae7cda75ff1e3bf56cb0a5f074820e2545d9d9f515376810d1d20741eae02 +assets/index.89d7b3f8.js,1685994184383,fab01136f5c0bf3ecc244af49d9939554dad062d3a7a8c165722f4b2d909d4e1 +assets/index.8a8ecbbe.js,1685993208274,9448d5ec413b66c6b15f0668fd665557661709a423da20f184b5ad2005087b19 +assets/index.95ec0159.js,1685993789210,2bd209a673010d779ed7d60552148146e6734ccece6b9f129294b4d5adb4ba08 +assets/index.ad52540f.js,1685989292413,6acde7faa20dcad2c72ea2af0d774db16bbd7dc517a54b9999f49eb9906d361a +assets/index.bab315a4.js,1685984477796,e6690e3b9ccc361ce63db9440a771f1bf2703d9a805c9ac9d23dc257edf8a7b5 +assets/index.cba7cce5.js,1685984098138,be6e92062a129afcc8aaf7bb59bd26eb3017d500640ae65a86efb80a32d1634c +assets/index.d173e84e.js,1685985472572,e6441051576dd867368c8d4af0471f34afc39cdb8cc16075f2c6ce4717bd0123 +assets/index.d8a39a69.js,1685981191140,89ba17138465dfef8cc4294923987ed085b77c075511dc1f11d01a2abbbc35ef +assets/index.ee9b7827.js,1685983867622,fd97b8456dc60902e4ba7322a0814b87bd66deaa80417f41cd8611ffdda30b5f +assets/index.f07bfc0e.js,1685982573798,77dca1d21ede1793250cdce16b65303a681da9dd21254af350975300ce269194 +assets/index.f79b7006.js,1685982750457,bc8f7a5527580fd3982e754b13df6d90b18ebf2eb9c9cef95cbf59baa21503c5 +images/Avatar.png,1685994149226,2306b7c2e69c3b031f09412a04c7dd0248b3243d1195baa2a9e919e1e52cd437 +images/Like.png,1685994149228,4047ec9a7978ed344cd8b98371e97a530eac6b5adcbc8d7c6b607ce1a7d89c3b +images/Likes.png,1685994149228,c05765a83b2002564569857102a720f4825e4db3350d7312b073c84fa3517059 +images/closeModal.png,1685994149229,137e67ae1813d4cb5c289a8c12168fc04e8ed35f0263a9a01432e88c0e0303db +images/createacc.png,1685994149230,fb89c5336963105cca30db76261537e9253a1d3055d5cb0b5ceed10ada196c7a +images/delete.png,1685994149230,7c162b036dba38a1f2605596d7571266534117062c228d8fce6a7e9c5cc9fbca +images/edit.png,1685994149230,3c8a731521c287c4457ab4a195bbc386d36fec89f348716d2ea675136a8aca93 +images/home.png,1685994149231,d5c4a0e4b578e380596984e248ab4ff7292c01c09fdacb9648e86464a8a074a9 +images/logoCompleto.png,1685994149232,01d30e9b98e31990500e66e51d4e90340a3834ebc6ac3703b4d853b34b5d2096 +images/logoEasygym.png,1685994149233,5dd2496aba58b827741cc08629dd8e191c2750685d9c691b7f7616bb123f19e7 +images/logoEasygymOnly.png,1685994149234,e428af64427326ca14985696d147e78593568d1897bfba5cab3e307d50246472 +images/logoGoogle.png,1685994149235,0bfb24f605e9a4aeafab4df2e14942744ab7b1153e7e228731a8245967e3cc40 +images/logoNameEasygym.png,1685994149236,4025c6cf57f628ebffd1c0d1aef9cd97823b64e9da3cc14d803977e6c5ba0858 +images/logout.png,1685994149236,714de0db9f8bc9b68cac053a90f60586756cd92bf4cacd5141a29d4d9e2e4018 +images/logout2.png,1685994149237,6b910d1d3f546cb30524a216cbc511d840c61b49b18c03de4c14cb92a2d9ebc1 +images/menuOptions.png,1685994149238,b8a45cdb084d3134c944dd7e7823c45dd30d1ee13090f0b8e621941ce3e2c0c3 +images/signin.png,1685994149242,66f8dd7afeefc18109c30240839fbb67ccc33c9deeca07783d8f7738795dd278 +images/wall.png,1685994149242,d4ab9de5a1252c522897a23b1e892ac7b3bd43a12556f0937383d6a8746d2726 +index.html,1686064752252,0fc073f0f6131a6645364ea7da2938d1dbf644a3cfbc6f16aa83c7f08e373ec6 +assets/index.a91b78aa.css,1686064752252,3f55dd2b1bb7d1005a13f40a46e66ae6f77e9205634c00d33b378fdddde4acfc +assets/index.3ff87eb7.js,1686064752252,23d47de3c4df5a86b6aa946574d5ebddbbdb30aa5cc5fd9266f3dd0860b57e6b diff --git a/.firebase/hosting.cHVibGlj.cache b/.firebase/hosting.cHVibGlj.cache new file mode 100644 index 00000000..b99a5e0d --- /dev/null +++ b/.firebase/hosting.cHVibGlj.cache @@ -0,0 +1 @@ +index.html,1685980088686,eee70fc660fc5325efe8e1846f70391119a17f9125ce9aaadd0d76e61c51a013 diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..66fdde88 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "easygym-sn-d107b" + } +} diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml new file mode 100644 index 00000000..ca25f7b3 --- /dev/null +++ b/.github/workflows/firebase-hosting-merge.yml @@ -0,0 +1,20 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on merge +'on': + push: + branches: + - main +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: npm ci && npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: '${{ secrets.GITHUB_TOKEN }}' + firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_EASYGYM_SN_D107B }}' + channelId: live + projectId: easygym-sn-d107b diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml new file mode 100644 index 00000000..d4882137 --- /dev/null +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -0,0 +1,17 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on PR +'on': pull_request +jobs: + build_and_preview: + if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: npm ci && npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: '${{ secrets.GITHUB_TOKEN }}' + firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_EASYGYM_SN_D107B }}' + projectId: easygym-sn-d107b diff --git a/README.md b/README.md index c64bad1e..5115b0f9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,13 @@ -# Creando una Red Social +# Red Social - EasyGym ## Índice * [1. Preámbulo](#1-preámbulo) * [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) +* [3. Pantalla Principal](#3-pantalla-principal) +* [4. Pantalla Sign In](#4-pantalla-sign-in) +* [5. Pantalla Create Account](#5-pantalla-create-account) +* [6. Pantalla de Muro](#6-pantalla-de-muro) ## 1. Preámbulo @@ -17,538 +15,44 @@ 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. -![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. +Hay redes sociales de todo tipo y para todo tipo de intereses. ## 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**. +En este proyecto construímos una red social dedicada a una comunidad que puede crear, ver y compartir rutinas rápidas de ejercicio que pueden ser realizadas desde cualquier parte del mundo y en cualquier lugar, ya sea casa, jardín, parque o un centro deportivo. +Para realizarlo creamos un proyecto en Firebas y en este una colección para guardar los post de los usuarios. +Para dar inicio a este proyecto utilizamos Figma para crear los prototipos de baja y alta fidelidad, también utilizamos Trello para definir las historias de usuario y para organizar, planificar y distribuir las tareas para cada sprint. ### 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. - -*** +* Qué cada usuario cree una cuenta. +* Qué cada usuario pueda iniciar sesión. +* Comparta rutinas. +* Revise tips del resto de los usuarios. +* Pueda dar likes a cualquier publicación. +* Pueda editar sus publicaciones. +* Pueda eliminar sus publicaciones. -## 8. Pistas, tips y Lecturas complementarias +## 3. Pantalla Principal -### Mobile first +Esta es la pantalla inicial dónde podemos encontrar una breve descripción del propósito de la página. También nos da la opción de ingresar con Sign In (si ya contamos con una cuenta) o con Create Account (si no tenemos cuenta y deseamos crearla) -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. +![](src/images/home.png) -### Múltiples vistas +## 4. Pantalla Sign In -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. +En esta sección, utilizando Firebase Authentication, podemos ingresar a la página con el usuario y contraseña que previamente creamos. También nos da la opción de ingresar con nuestra cuenta de Google. Finalmente, mediante este sitio, podemos irnos a crear una cuenta si es que todavía no la hemos creado. -### Escritura de datos +![](src/images/signin.png) + +## 5. Pantalla Create Account -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/). +En esta sección, nuevamente utilizando Firebase Authentication, podemos ingresar a la página creando un usuario. Los datos que son solicitados son: nombre, correo electrónico y contraseña. También nos da la opción de ingresar con nuestra cuenta de Google. Finalmente, mediante este sitio, también podemos ingresar si ya tenemos usuario creado; en la parte final aparece un enlace que dice: Sign In. -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). +![](src/images/createacc.png) + +## 6. Pantalla de Muro -Otras: +En esta sección, utilizando Firebase firestore, podemos crear un post acerca de una rutina rápida que deseemos compartir. También podemos ver los comentarios existentes de los demás usuarios que pertenecen a la red social. Podemos dar likes a las publicaciones que sean de nuestro agrado. En este apartado, solamente quienes crearon la publicación, podrán tener habilitada la opción de editarla o eliminarla. Así como el "like" que hayan dado, sólo quien lo dio, podrá retirarlo en caso de que se haya arrepentido. Además, para cuando el usuario lo desee, se agregó un botón para cerrar la sesión. Y por último esta pantalla está protegida, por lo que solo aquellos usuarios conectado pueden ver la pantalla Muro. -* [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/) +![](src/images/wall.png) \ No newline at end of file diff --git a/database.rules.json b/database.rules.json new file mode 100644 index 00000000..f54493db --- /dev/null +++ b/database.rules.json @@ -0,0 +1,7 @@ +{ + /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ + "rules": { + ".read": false, + ".write": false + } +} \ No newline at end of file diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..3e1dac0e --- /dev/null +++ b/firebase.json @@ -0,0 +1,17 @@ +{ + "database": { + "rules": "database.rules.json" + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "hosting": { + "public": "dist", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 00000000..415027e5 --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 00000000..47f46702 --- /dev/null +++ b/firestore.rules @@ -0,0 +1,9 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read, write: if + request.time < timestamp.date(2028, 6, 6); + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index 819726b2..abc94c98 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "eslint": "eslint --ext .js src/ test/", "stylelint": "stylelint --aei src/**/*.css", "pretest": "npm run htmlhint && npm run eslint && npm run stylelint", - "test": "jest --coverage", + "test": "jest --coverage --env=jsdom", "dev": "vite dev src", "start": "npm run dev", "build": "vite build", @@ -41,5 +41,8 @@ "createdAt": "2023-04-04T20:41:27.023Z", "version": "6.1.1", "commit": "ee8564d1261b2e9c9842d13f9ac49b2624b2ee74" + }, + "dependencies": { + "firebase": "^9.22.0" } -} \ No newline at end of file +} diff --git a/public/images/Avatar.png b/public/images/Avatar.png new file mode 100644 index 00000000..ce832050 Binary files /dev/null and b/public/images/Avatar.png differ diff --git a/public/images/Like.png b/public/images/Like.png new file mode 100644 index 00000000..341b7aef Binary files /dev/null and b/public/images/Like.png differ diff --git a/public/images/Likes.png b/public/images/Likes.png new file mode 100644 index 00000000..68bd775e Binary files /dev/null and b/public/images/Likes.png differ diff --git a/public/images/closeModal.png b/public/images/closeModal.png new file mode 100644 index 00000000..8952fc32 Binary files /dev/null and b/public/images/closeModal.png differ diff --git a/public/images/createacc.png b/public/images/createacc.png new file mode 100644 index 00000000..6af57c28 Binary files /dev/null and b/public/images/createacc.png differ diff --git a/public/images/delete.png b/public/images/delete.png new file mode 100644 index 00000000..8cb14de2 Binary files /dev/null and b/public/images/delete.png differ diff --git a/public/images/edit.png b/public/images/edit.png new file mode 100644 index 00000000..e280ccbc Binary files /dev/null and b/public/images/edit.png differ diff --git a/public/images/home.png b/public/images/home.png new file mode 100644 index 00000000..0750d72b Binary files /dev/null and b/public/images/home.png differ diff --git a/public/images/logoCompleto.png b/public/images/logoCompleto.png new file mode 100644 index 00000000..4d764403 Binary files /dev/null and b/public/images/logoCompleto.png differ diff --git a/public/images/logoEasygym.png b/public/images/logoEasygym.png new file mode 100644 index 00000000..e3e7a157 Binary files /dev/null and b/public/images/logoEasygym.png differ diff --git a/public/images/logoEasygymOnly.png b/public/images/logoEasygymOnly.png new file mode 100644 index 00000000..e983c96d Binary files /dev/null and b/public/images/logoEasygymOnly.png differ diff --git a/public/images/logoGoogle.png b/public/images/logoGoogle.png new file mode 100644 index 00000000..7b0c17a6 Binary files /dev/null and b/public/images/logoGoogle.png differ diff --git a/public/images/logoNameEasygym.png b/public/images/logoNameEasygym.png new file mode 100644 index 00000000..b0f1a410 Binary files /dev/null and b/public/images/logoNameEasygym.png differ diff --git a/public/images/logout.png b/public/images/logout.png new file mode 100644 index 00000000..83c895c8 Binary files /dev/null and b/public/images/logout.png differ diff --git a/public/images/logout2.png b/public/images/logout2.png new file mode 100644 index 00000000..1f5d8e75 Binary files /dev/null and b/public/images/logout2.png differ diff --git a/public/images/menuOptions.png b/public/images/menuOptions.png new file mode 100644 index 00000000..285c176d Binary files /dev/null and b/public/images/menuOptions.png differ diff --git a/public/images/signin.png b/public/images/signin.png new file mode 100644 index 00000000..847f6ae1 Binary files /dev/null and b/public/images/signin.png differ diff --git a/public/images/wall.png b/public/images/wall.png new file mode 100644 index 00000000..203c13e2 Binary files /dev/null and b/public/images/wall.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..ad41d9d4 --- /dev/null +++ b/public/index.html @@ -0,0 +1,89 @@ + + + + + + Welcome to Firebase Hosting + + + + + + + + + + + + + + + + + + + +
+

Welcome

+

Firebase Hosting Setup Complete

+

You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!

+ Open Hosting Documentation +
+

Firebase SDK Loading…

+ + + + diff --git a/src/index.html b/src/index.html index 788db3c9..aafdaeb4 100644 --- a/src/index.html +++ b/src/index.html @@ -1,12 +1,21 @@ + - Document + + Easy Gym + - +
+ + + + + + \ No newline at end of file diff --git a/src/lib/firebase.js b/src/lib/firebase.js new file mode 100644 index 00000000..ac28d777 --- /dev/null +++ b/src/lib/firebase.js @@ -0,0 +1,26 @@ +/* eslint-disable no-console */ +/* eslint-disable import/no-extraneous-dependencies */ +import { initializeApp } from 'firebase/app'; +import { getAuth } from 'firebase/auth'; +import { collection, getFirestore } from 'firebase/firestore'; +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +const firebaseConfig = { + apiKey: 'AIzaSyCe2vaz8aRBEWYlG2J1sf7ZtCOHZ-vdA2E', + authDomain: 'easygym-sn-d107b.firebaseapp.com', + projectId: 'easygym-sn-d107b', + storageBucket: 'easygym-sn-d107b.appspot.com', + messagingSenderId: '510233203554', + appId: '1:510233203554:web:d9d74905e251918902923b', + measurementId: 'G-EKGBH6JRB2', +}; + +// Initialize Firebase +export const app = initializeApp(firebaseConfig); +export const auth = getAuth(app); +export const db = getFirestore(app); +export const colRef = collection(db, 'Posts'); + +console.log(app); diff --git a/src/lib/functions.js b/src/lib/functions.js new file mode 100644 index 00000000..b1263314 --- /dev/null +++ b/src/lib/functions.js @@ -0,0 +1,182 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable max-len */ +/* eslint-disable no-shadow */ +/* eslint-disable no-console */ +/* eslint-disable consistent-return */ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable import/named */ +import { + signInWithEmailAndPassword, + getAuth, + GoogleAuthProvider, + signInWithPopup, + createUserWithEmailAndPassword, + onAuthStateChanged, +} from 'firebase/auth'; +import { + collection, arrayRemove, arrayUnion, doc, updateDoc, getDoc, getDocs, addDoc, deleteDoc, onSnapshot, query, orderBy, serverTimestamp, +} from 'firebase/firestore'; +import { + app, auth, colRef, db, +} from './firebase'; + +export const timeStamp = serverTimestamp; +const orderedQuery = query(colRef, orderBy('fecha', 'asc'));// Consulta la colección y la ordena los posts por su fecha/hora de publicación// Consulta la colección y la ordena los posts por su fecha/hora de publicación +// const orderedQuery = query(colRef); +export const getPost = (callback) => onSnapshot(orderedQuery, callback); +// export function login(email, password) { +// const auth1 = getAuth(app); +// const resultPromise = signInWithEmailAndPassword(auth1, email, password); +// const promise2 = resultPromise.then((userCredential) => userCredential.email); +// return promise2; +// } + +// function callback(credential) { +// return credential.email; +// } + +// const callback2 = (credential) => credential.user.email; + +// export function login(email, password) { +// const auth1 = getAuth(app); +// const resultPromise = signInWithEmailAndPassword(auth1, email, password); +// const promise2 = resultPromise.then(callback2); +// return promise2; +// } +// LOGIN CON FIREBASE +export async function login(email, password) { + const auth1 = getAuth(app); + const result = await signInWithEmailAndPassword(auth1, email, password); + return result.user.email; +} + +// CONTINUAR CON GOOGLE DESDE SIGNIN y CREATE ACCOUNT - FIREBASE +export async function signInWithGoogle() { + const provider = new GoogleAuthProvider(); + try { + const credentials = await signInWithPopup(auth, provider); + return credentials.user; + } catch (error) { + console.log(error); + } +} + +// CREATE ACCOUNT CON FIREBASE +export async function register(auth, email, password) { + try { + const userCredentials = await createUserWithEmailAndPassword(auth, email, password); + console.log(userCredentials); + } catch (error) { + console.log(error.message); + console.log(error.code); + throw error; + } +} + +// detecta los cambios en el estado de autenticación + +export function authDetector() { + const auth2 = getAuth(app); + // let userEmail = null; + return new Promise((resolve) => { + onAuthStateChanged(auth2, (user) => { + if (user) { + resolve(user.email); + /* userEmail = user.email; + console.log(userEmail); */ + } else { + resolve(null); + // userEmail = null; + } + }); + }); +} +authDetector(); + +// user email +export const userEmail = () => auth.currentUser.email; + +// Obtener collección Posts +export const postPromise = getDocs(collection(db, 'Posts')); + +// Agregar un post +export const postCol = collection(db, 'Posts'); +export async function addPost(postCollection, data) { + try { + await addDoc(postCollection, data); + return addPost; + } catch (error) { + console.log(error); + } +} + +// Dar y quitar Likes +export const likeCounter = async (postId) => { + const postDocRef = doc(colRef, postId); + const docPost = await getDoc(postDocRef); + const likesCount = docPost.data().likesCount || 0; // Si no existe la propiedad likesCount, la inicializamos en 0 + return updateDoc(postDocRef, { + likes: arrayUnion(auth.currentUser.email), + likesCount: likesCount + 1, + }); +}; + +export const dislikeCounter = async (postId) => { + const postDocRef = doc(colRef, postId); + const docPost = await getDoc(postDocRef); + const likesCount = docPost.data().likesCount || 0; // Si no existe la propiedad likesCount, la inicializamos en 0 + if (likesCount > 0) { // Solo disminuimos el conteo si es mayor a 0 + return updateDoc(postDocRef, { + likes: arrayRemove(auth.currentUser.email), + likesCount: likesCount - 1, + }); + } +}; + +// verificar like +export const verifyLikes = async (postId, emailUser) => { + const postDocRef = doc(db, 'Posts', postId); + const docPost = await getDoc(postDocRef); + let userLiked = false; + let likesCount = 0; + if (docPost.exists) { + const data = docPost.data(); + const likesArray = data.likes; + if (likesArray != null) { + likesCount = likesArray.length; // Get the likes count + if (likesArray.includes(emailUser)) { + userLiked = true; + } + } + } + return { userLiked, likesCount }; +}; + +// Eliminar el post +export async function deletePost(postId) { + try { + await deleteDoc(doc(db, 'Posts', postId)); + } catch (error) { + console.log(error); + } +} + +// Edit post +export async function editpost(postId, textEdit) { + try { + const updatedPost = await updateDoc(doc(colRef, postId), { descripción: textEdit }); + return updatedPost; + } catch (error) { + console.log(error); + } +} + +export async function signOut() { + auth.signOut() + .then(() => { + + }) + .catch((error) => { + + }); +} diff --git a/src/lib/index.js b/src/lib/index.js deleted file mode 100644 index d1930899..00000000 --- a/src/lib/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// aqui exportaras las funciones que necesites - -export const myFunction = () => { - // aqui tu codigo - console.log('Hola mundo!'); -}; diff --git a/src/main.js b/src/main.js index ac27e91a..7d25cbb8 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,60 @@ -// Este es el punto de entrada de tu aplicacion +/* eslint-disable no-console */ +/* eslint-disable max-len */ +/* eslint-disable import/named */ +/* eslint-disable no-unused-vars */ +// Importar las vistas +import { onAuthStateChanged } from 'firebase/auth'; +import { home } from './pages/home.js'; +import { createAccount } from './pages/createAccount.js'; +import { signIn } from './pages/signIn.js'; +import { wall } from './pages/wall.js'; +import { auth } from './lib/firebase.js'; -import { myFunction } from './lib/index.js'; +const root = document.getElementById('root'); -myFunction(); +const routes = { + '/': home, + '/createAccount': createAccount, + '/signIn': signIn, + '/wall': wall, +}; + +function navigateTo(pathname) { + /* objeto que proporciona acceso y control sobre el historial de navegación del navegador */ + /* pushState (state, title, url) es un método del objeto history que permite modificar la URL del navegador sin recargar la página */ + window.history.pushState({}, pathname, window.location.origin + pathname); // Guarda el historial + root.innerHTML = ''; + const view = routes[pathname]; + root.appendChild(view(navigateTo)); +} + +onAuthStateChanged(auth, (user) => { + if (user) { // si hay usuario puede permanecer activo(permanece en wall) + navigateTo('/wall'); + } else { // si no, lo mantiene o lleva a la página welcome (no se puede ir a wall) + navigateTo('/'); + } +}); + +/* window.onpopstate se utiliza para manejar el evento de cambio en el historial de navegación del navegador */ +window.onpopstate = () => { + root.innerHTML = ''; + const path = window.location.pathname; + const view = routes[path]; + root.appendChild(view(navigateTo)); +}; + +// function reloadPage() { +// const currentPath = window.location.pathname; +// localStorage.setItem('currentPage', currentPath); +// window.location.reload(); +// } + +// Obtener la página actual almacenada en localStorage +const currentPage = window.location.pathname; +console.log(currentPage); +if (currentPage && routes[currentPage]) { + navigateTo(currentPage); +} else { + navigateTo('/'); +} diff --git a/src/pages/createAccount.js b/src/pages/createAccount.js new file mode 100644 index 00000000..2ab8907a --- /dev/null +++ b/src/pages/createAccount.js @@ -0,0 +1,126 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-console */ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-alert */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { async } from 'regenerator-runtime'; +import { auth } from '../lib/firebase.js'; +import { signInWithGoogle, register } from '../lib/functions.js'; + +export function createAccount(navigateTo) { + // Crear elementos + const container = document.createElement('div'); + const header = document.createElement('header'); + const logo = document.createElement('img'); + const form = document.createElement('form'); + const nameLabel = document.createElement('label'); + const nameInput = document.createElement('input'); + const emailLabel = document.createElement('label'); + const emailInput = document.createElement('input'); + const passwordLabel = document.createElement('label'); + const passwordInput = document.createElement('input'); + const questionAccount = document.createElement('p'); + const linkSignIn = document.createElement('a'); + const createButton = document.createElement('button'); + const or = document.createElement('p'); + const continueWithGoogleButton = document.createElement('button'); + const logoGoogle = document.createElement('img'); + logoGoogle.src = '/images/logoGoogle.png'; + + // Establecer atributos y contenido + container.classList.add('container'); + logo.setAttribute('src', '/images/logoNameEasygym.png'); + nameLabel.textContent = 'Name'; + nameInput.classList.add('insertInfo'); + nameInput.setAttribute('placeholder', 'My name'); + emailLabel.textContent = 'Email'; + emailInput.classList.add('insertInfo'); + emailInput.setAttribute('placeholder', 'example@gmail.com'); + passwordLabel.textContent = 'Password'; + passwordInput.classList.add('insertInfo'); + emailInput.setAttribute('type', 'email'); + passwordInput.setAttribute('type', 'password'); + passwordInput.setAttribute('placeholder', 'Mypassword'); + questionAccount.textContent = 'Already have an account?'; + linkSignIn.setAttribute('href', '#'); + linkSignIn.textContent = 'Sign In'; + createButton.classList.add('button'); + createButton.textContent = 'Create account'; + or.textContent = '───────── OR ─────────'; + or.classList.add('or'); + continueWithGoogleButton.classList.add('button'); + continueWithGoogleButton.classList.add('googleButton'); + logoGoogle.classList.add('logoGoogle'); + + // Agregar elementos al header + header.appendChild(logo); + + continueWithGoogleButton.appendChild(logoGoogle); + continueWithGoogleButton.insertAdjacentText('beforeend', 'Continue with Google'); + + // Agregar elementos al formulario + form.appendChild(nameLabel); + form.appendChild(nameInput); + form.appendChild(emailLabel); + form.appendChild(emailInput); + form.appendChild(passwordLabel); + form.appendChild(passwordInput); + form.appendChild(createButton); + form.appendChild(or); + form.appendChild(continueWithGoogleButton); + form.appendChild(questionAccount); + form.appendChild(linkSignIn); + + // Agregar elementos al contenedor (div) especificado + container.appendChild(header); + container.appendChild(form); + + // Add event listeners + linkSignIn.addEventListener('click', (e) => { + e.preventDefault(); + navigateTo('/signIn'); + }); + + /** Crear cuenta */ + createButton.addEventListener('click', (e) => { + e.preventDefault(); + // const name = nameInput.value; + const email = emailInput.value; + const password = passwordInput.value; + const registerPromise = register(auth, email, password); + registerPromise.then((user) => { + alert('your account was created'); + navigateTo('/wall'); + }).catch((error) => { + console.log(email, password); + // promesa de la funcion, bloque try.. catch debe ir acompañado de async(funcion asyncrona) + // await espera que la funcion cumpla con los parametros para ver un resultado o error + + if (error.code === 'auth/email-already-in-use') { + alert('Email already in use'); + } else if (error.code === 'auth/missing-email') { + alert('Introduce your email'); + } else if (error.code === 'auth/weak-password') { + alert('Your password must have a minimum of 6 characters '); + } else if (error.code === 'auth/missing-password') { + alert('Introduce your password'); + } else if (error.code === 'auth/invalid-email') { + alert('Invalid email'); + } else if (error.code) { + alert('Something went wrong'); + } + }); + }); + + continueWithGoogleButton.addEventListener('click', async (e) => { + e.preventDefault(); + const promiseWithGoogle = signInWithGoogle(); + promiseWithGoogle.then((user) => { + alert(`Welcome ${user.displayName}!`); + navigateTo('/wall'); + }).catch((error) => { + alert('Registrateee'); + }); + }); + return container; +} diff --git a/src/pages/home.js b/src/pages/home.js new file mode 100644 index 00000000..b77c3edf --- /dev/null +++ b/src/pages/home.js @@ -0,0 +1,45 @@ +/* +* @param [function] navigateTo: metodo para navegar a otra vista +*/ +export function home(navigateTo) { + const container = document.createElement('div'); + const header = document.createElement('header'); + const logo = document.createElement('img'); + const divButtons = document.createElement('div'); + const description = document.createElement('p'); + const signInButton = document.createElement('button'); + const createAccount = document.createElement('button'); + + // Establecer atributos y contenido + container.classList.add('container'); + logo.setAttribute('src', '/images/logoEasygymOnly.png'); + divButtons.classList.add('descriptionPage'); + description.textContent = 'We are a community where you can view and share quick routines to perform from anywhere in the world.'; + signInButton.classList.add('button'); + signInButton.textContent = 'Sign In'; + createAccount.classList.add('button'); + createAccount.textContent = 'Create account'; + + // Agregar elementos al header + header.appendChild(logo); + + // Agregar elementos al div + divButtons.appendChild(description); + divButtons.appendChild(signInButton); + divButtons.appendChild(createAccount); + + // Agregar elementos al contenedor (div) especificado + container.appendChild(header); + container.appendChild(divButtons); + + // llamado a los botones + createAccount.addEventListener('click', () => { + navigateTo('/createAccount'); + }); + + signInButton.addEventListener('click', () => { + navigateTo('/signIn'); + }); + + return container; +} diff --git a/src/pages/signIn.js b/src/pages/signIn.js new file mode 100644 index 00000000..4b11f285 --- /dev/null +++ b/src/pages/signIn.js @@ -0,0 +1,122 @@ +/* eslint-disable no-multi-assign */ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-vars */ +/* eslint-disable max-len */ +/* eslint-disable no-console */ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-alert */ +import { login, signInWithGoogle } from '../lib/functions.js'; + +export function signIn(navigateTo) { + // Crear elementos + const container = document.createElement('div'); + const header = document.createElement('header'); + const logo = document.createElement('img'); + const form = document.createElement('form'); + const emailLabel = document.createElement('label'); + const emailInput = document.createElement('input'); + const passwordLabel = document.createElement('label'); + const passwordInput = document.createElement('input'); + const questionAccount = document.createElement('p'); + const linkSignIn = document.createElement('a'); + const signInButton = document.createElement('button'); + const or = document.createElement('p'); + const continueWithGoogleButton = document.createElement('button'); + const logoGoogle = document.createElement('img'); + logoGoogle.src = '/images/logoGoogle.png'; + + // Establecer atributos y contenido + container.classList.add('container'); + logo.setAttribute('src', '/images/logoNameEasygym.png'); + emailLabel.textContent = 'Email'; + emailInput.classList.add('insertInfo'); + emailInput.setAttribute('placeholder', 'example@gmail.com'); + passwordLabel.textContent = 'Password'; + passwordInput.classList.add('insertInfo'); + emailInput.setAttribute('type', 'email'); + passwordInput.setAttribute('type', 'password'); + passwordInput.setAttribute('placeholder', 'Mypassword'); + questionAccount.textContent = 'Don\'t have an account?'; + linkSignIn.setAttribute('href', '#'); + linkSignIn.textContent = 'Create account'; + signInButton.classList.add('button'); + signInButton.textContent = 'Sign In'; + or.textContent = '───────── OR ─────────'; + or.classList.add('or'); + continueWithGoogleButton.classList.add('button'); + continueWithGoogleButton.classList.add('googleButton'); + logoGoogle.classList.add('logoGoogle'); + + // Agregar elementos al header + header.appendChild(logo); + + continueWithGoogleButton.appendChild(logoGoogle); + continueWithGoogleButton.insertAdjacentText('beforeend', 'Continue with Google'); + + // Agregar elementos al formulario + form.appendChild(emailLabel); + form.appendChild(emailInput); + form.appendChild(passwordLabel); + form.appendChild(passwordInput); + form.appendChild(signInButton); + form.appendChild(or); + form.appendChild(continueWithGoogleButton); + form.appendChild(questionAccount); + form.appendChild(linkSignIn); + + // Agregar elementos al contenedor (div) especificado + container.appendChild(header); + container.appendChild(form); + + // Add event listeners + linkSignIn.addEventListener('click', (e) => { + e.preventDefault(); + navigateTo('/createAccount'); + }); + + signInButton.addEventListener('click', async (e) => { + e.preventDefault(); + const email = emailInput.value; + const password = passwordInput.value; + + console.log(email, password); + // promesa de la funcion, bloque try.. catch debe ir acompañado de async(funcion asyncrona) + // await espera que la funcion cumpla con los parametros para ver un resultado o error + try { + const userEmail = await login(email, password); + console.log('Este es el email: ', userEmail); + navigateTo('/wall'); + // export { userEmail }; + // console.log(userCredentials); + } catch (error) { + console.log(error.message); + console.log(error.code); + + if (error.code === 'auth/wrong-password') { + alert('Wrong password !'); + } else if (error.code === 'auth/user-not-found') { + alert('User not found !'); + } else if (error.code === 'auth/missing-password') { + alert('Introduce your password'); + } else if (error.code === 'auth/invalid-email') { + alert('Invalid user !'); + } else if (error.code === 'auth/internal-error') { + alert('Internal error, try again !'); + } else if (error.code) { + alert('Something went wrong !'); + } + } + }); + + continueWithGoogleButton.addEventListener('click', async (e) => { + e.preventDefault(); + const promiseWithGoogle = signInWithGoogle(); + promiseWithGoogle.then((user) => { + alert(`Welcome ${user.displayName}!`); + navigateTo('/wall'); + }).catch((error) => { + alert('Registrateee'); + }); + }); + return container; +} diff --git a/src/pages/wall.js b/src/pages/wall.js new file mode 100644 index 00000000..cd0dbeb2 --- /dev/null +++ b/src/pages/wall.js @@ -0,0 +1,340 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable no-unused-expressions */ +/* eslint-disable max-len */ +/* eslint-disable object-shorthand */ +/* eslint-disable no-undef */ +/* eslint-disable no-console */ +/* eslint-disable import/no-extraneous-dependencies */ + +import { + authDetector, userEmail, dislikeCounter, likeCounter, verifyLikes, deletePost, editpost, postPromise, + addPost, postCol, signOut, getPost, timeStamp, +} from '../lib/functions'; + +export function wall() { + // Crear elementos + const container = document.createElement('div'); + const navegator = document.createElement('nav'); + const logoRefresh = document.createElement('img'); + const logOut = document.createElement('img'); + const divposts = document.createElement('div'); + const buttonCreatePost = document.createElement('button'); + const textarea = document.createElement('textarea'); + const writeAndPost = document.createElement('div'); + + // Establecer atributos y contenido + logoRefresh.setAttribute('src', '/images/logoEasygym.png'); + logoRefresh.setAttribute('onclick', 'location.reload()'); + logOut.setAttribute('src', '/images/logout.png'); + logOut.classList.add('logOut'); + container.id = 'container'; + divposts.id = 'posts'; + buttonCreatePost.classList.add('buttonCreatePost'); + buttonCreatePost.textContent = 'Post'; + textarea.classList.add('textArea'); + textarea.placeholder = 'write your tips today?'; + // textarea.setAttribute('rows', '4'); + writeAndPost.classList.add('writeAndPost'); + + // Imagen LogOut + logOut.addEventListener('click', () => { + signOut(); + }); + + // Logo refresh + logoRefresh.classList.add('refresh'); + + // Agregar elementos a nav + navegator.appendChild(logoRefresh); + navegator.appendChild(logOut); + // Agregar elementos a divposts + writeAndPost.appendChild(textarea); + writeAndPost.appendChild(buttonCreatePost); + + // Agregar elementos al contenedor (div) especificado + container.appendChild(navegator); + container.appendChild(writeAndPost); + container.appendChild(divposts); + + const createPost = (poster, postId) => { + // CREAR MODAL OPCIONES + const modalOptions = document.createElement('dialog'); + modalOptions.classList.add('modalOptions'); + const modalImgEdit = document.createElement('img'); + modalImgEdit.setAttribute('src', '/images/edit.png'); + modalImgEdit.classList.add('modalImgEdit'); + const editLabel = document.createElement('label'); + editLabel.classList.add('editLabel'); + editLabel.textContent = ('Edit'); + const modalImgDel = document.createElement('img'); + modalImgDel.setAttribute('src', '/images/delete.png'); + modalImgDel.classList.add('modalImgDel'); + const deleteLabel = document.createElement('label'); + deleteLabel.classList.add('deleteLabel'); + deleteLabel.textContent = ('Delete'); + const xModal = document.createElement('img'); + xModal.setAttribute('src', '/images/closeModal.png'); + xModal.classList.add('xModal'); + const space = document.createElement('br'); + + modalOptions.appendChild(modalImgEdit); + modalOptions.appendChild(editLabel); + modalOptions.appendChild(modalImgDel); + modalOptions.appendChild(deleteLabel); + deleteLabel.appendChild(space); + modalOptions.appendChild(xModal); + // CREAR MODAL EDIT + const modalEdit = document.createElement('dialog'); + modalEdit.id = 'modalEdit'; + const txtaEdit = document.createElement('textarea'); + txtaEdit.classList.add('textArea'); + const btnCancel = document.createElement('button'); + btnCancel.textContent = 'Cancel'; + btnCancel.classList.add('button'); + btnCancel.id = 'btn-modal'; + const btnSave = document.createElement('button'); + btnSave.textContent = 'Save'; + btnSave.id = 'btn-modal'; + btnSave.classList.add('button'); + document.body.appendChild(modalEdit); + + modalEdit.appendChild(txtaEdit); + modalEdit.appendChild(btnCancel); + modalEdit.appendChild(btnSave); + + // CREAR MODAL ELIMINAR + const modalDelete = document.createElement('dialog'); + modalDelete.id = 'modalDelete'; + const question = document.createElement('p'); + question.textContent = 'Do you want to delete this post?'; + question.classList.add('question'); + const btnYes = document.createElement('button'); + btnYes.textContent = 'Yes'; + btnYes.classList.add('button'); + btnYes.id = 'btn-modal'; + const btnNo = document.createElement('button'); + btnNo.textContent = 'No'; + btnNo.classList.add('button'); + btnNo.id = 'btn-modal'; + document.body.appendChild(modalDelete); + + modalDelete.appendChild(question); + modalDelete.appendChild(btnYes); + modalDelete.appendChild(btnNo); + + // crear que va a mostrar + const post = document.createElement('div'); + const infoUser = document.createElement('div'); + const avatar = document.createElement('img'); + const publicDate = document.createElement('time'); + const userName = document.createElement('h5'); + const imagenPost = document.createElement('img'); + const descriptionAndLikes = document.createElement('p'); + const menuLikeSection = document.createElement('section'); + const likesAndCount = document.createElement('div'); + const menuOptions = document.createElement('img'); + const likesPic = document.createElement('img'); + const likesLab = document.createElement('label'); + + // Establecer las propiedades de los elementos + + post.className = 'post'; + avatar.className = 'avatar'; + userName.className = 'userName'; + publicDate.className = 'publicDate'; + avatar.src = poster.avatar; + + const currentDate = poster.fecha.toDate(); // devuelve la fecha local + const day = currentDate.getDate();// devuelve el día solamente + const month = currentDate.getMonth() + 1; // Los meses comienzan desde 0 + const year = currentDate.getFullYear(); + const formattedDate = `${day}/${month}/${year}`; + + // const dateObject = timestamp.toDate(); // Convert to Date object + // const dateString = dateObject.toISOString(); // Convert to string in ISO format + + publicDate.textContent = formattedDate; + publicDate.type = poster.fecha; + userName.textContent = poster.usuario; + imagenPost.src = 'ruta/al/imagen2'; + descriptionAndLikes.textContent = poster.descripción; + menuLikeSection.classList.add('menuLikeSection'); + menuOptions.classList.add('menuOptions'); + menuOptions.setAttribute('src', '/images/menuOptions.png'); + likesAndCount.classList.add('likesAndCount'); + likesPic.classList.add('likesPic'); + likesPic.setAttribute('src', '/images/Like.png'); + likesLab.classList.add('likesLab'); + likesLab.textContent = (poster.likes && poster.likes.length) || 0; + // likesLab.textContent = poster.likes?.length || 0; // ? si likes no existe q no falle al cargar los posts + + // Armar la estructura del nodo + infoUser.id = 'infoUser'; + infoUser.appendChild(avatar); + infoUser.appendChild(userName); + infoUser.appendChild(publicDate); + infoUser.appendChild(descriptionAndLikes); + post.appendChild(infoUser); + post.appendChild(menuLikeSection); + menuLikeSection.appendChild(menuOptions); + menuLikeSection.appendChild(likesAndCount); + likesAndCount.appendChild(likesPic); + likesAndCount.appendChild(likesLab); + divposts.insertBefore(post, divposts.firstChild); // Utilizar insertBefore para insertar al principio + document.body.appendChild(modalOptions); + + // Mostrar menuOptions para editar y eliminar cuando los post son propios + if (userEmail() === poster.usuario) { + menuOptions.style.visibility = 'visible'; + } else { + menuOptions.style.visibility = 'hidden'; + } + // const openModalDelEdit = document.querySelector('.menuOptions'); + // const modalContainer = document.querySelector('.modalContainer'); + // const modalClose = document.querySelector('.modalClose'); + + // Listener para mostrar el diálogo de opciones + menuOptions.addEventListener('click', (e) => { + e.preventDefault(); + if (modalOptions.isConnected && !modalOptions.hasAttribute('open')) { + modalOptions.showModal(); + } + }); + + // Listener para el cierre del diálogo de opciones + xModal.addEventListener('click', () => { + modalOptions.close(); + }); + + // Listener para el cambio al diálogo de edición + modalImgEdit.addEventListener('click', (e) => { + e.preventDefault(); + modalOptions.close(); + if (modalEdit.isConnected && !modalEdit.hasAttribute('open')) { + modalEdit.showModal(); + } + }); + + // Listener para el cierre del diálogo de edición + btnCancel.addEventListener('click', () => { + modalEdit.close(); + }); + + // Listener para llamar pregunta de confirmación eliminar + modalImgDel.addEventListener('click', (e) => { + e.preventDefault(); + modalOptions.close(); + if (modalDelete.isConnected && !modalDelete.hasAttribute('open')) { + modalDelete.showModal(); + } + }); + + btnNo.addEventListener('click', () => { + modalDelete.close(); + }); + + btnYes.addEventListener('click', async () => { + await deletePost(postId); + modalDelete.close(); + post.remove(); + }); + + // Editar + modalImgEdit.addEventListener('click', () => { + txtaEdit.innerHTML = poster.descripción; + }); + btnSave.addEventListener('click', async (e) => { + e.preventDefault; + const newContent = txtaEdit.value; + try { + const result = await editpost(postId, newContent); + console.log(result); + modalEdit.close(); + } catch (error) { + console.log('Error al actualizar la descripción:', error); + } + }); + + // Mostrar la imagen antes de hacer like + const likesArray = poster.likes; + if (likesArray != null && likesArray.includes(userEmail())) { + likesPic.setAttribute('src', '/images/Likes.png'); + } + // Al dar like hacer cambio de imagen y numero + likesPic.addEventListener('click', async () => { + const { userLiked, likesCount } = await verifyLikes(postId, userEmail()); + if (userLiked) { + await dislikeCounter(postId); + likesPic.setAttribute('src', '/images/Like.png'); + } else { + await likeCounter(postId); + likesPic.setAttribute('src', '/images/Likes.png'); + } + // obtén el recuento actualizado de "likes" y actualiza la interfaz de usuario + const updatedLikes = await verifyLikes(postId, userEmail()); + likesLab.textContent = `${updatedLikes.likesCount}`; + }); + + // const user = auth.currentUser.uid; /* toma el id único del usuario autenticado actualmente */ + // const likesArray = docss.data().likeCounter; + + // if (!likesArray.includes(user)) { + // likeCounter(docss.id); + // likesPic.setAttribute('src', './images/Likes.png'); + // } + }; + + // Llamar a la función getPost para obtener los datos de los posts + getPost((queryData) => { + console.log('Current data: ', queryData); + divposts.innerHTML = ''; + queryData.forEach((postPost) => { + const postData = postPost.data(); + const postId = postPost.id; + createPost(postData, postId); + }); + }); + + // Crea el post en Firebase, guarda en postData y le asigna un Id + // postPromise.then((postList) => { + // postList.forEach((postPost) => { + // const postData = postPost.data(); + // const postId = postPost.id; + // createPost(postData, postId); + // }); + // }); + console.log(authDetector); + + buttonCreatePost.addEventListener('click', async () => { + const userDetector = await authDetector();// Obtener el email del usuario + + const data = { + avatar: '/images/Avatar.png', + descripción: textarea.value, + fecha: timeStamp(), + usuario: userDetector, // Asignar el email del usuario a "usuario" + likes: [], + + }; + + const result = await addPost(postCol, data); + console.log(result); + // Crear el nuevo post y agregarlo al principio + createPost(data, result.id); + textarea.value = ''; + }); + + // damm likes, primero se necesitan 3 cosas: user email, id post, campo likes + + // DOMContentLoaded se dispara cuando se ha cargado + // completamente el árbol DOM de una página web por + // lo q no sirve en este caso ya q se cambia lo q esta en root + // window.addEventListener('DOMContentLoaded', async () => { + // const querySnapshot = await getPost();0'+''''''zz + // querySnapshot.forEach((doc) => { + // const postdata = doc.data(); + // createPost(postdata); + // }); + // }); + return container; +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 00000000..9e8c68a0 --- /dev/null +++ b/src/style.css @@ -0,0 +1,381 @@ +main { + margin: 0; + justify-content: center; +} + +body { + height: 100%; + background: #98DEE9; + +} + +form { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +#root { + + margin-top: 0; + height: 95vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; +} + +header { + height: 250px; + display: flex; + align-items: flex-end; +} + +nav { + + height: 70px; + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.refresh { + margin: 2px; + margin-bottom: 10px; +} + +.refresh:hover { + margin: 0px; + height: 90%; + width: auto; + cursor: pointer; +} + +.logOut { + margin: 2px; + height: 70%; + width: auto; + transform: scaleX(-1); +} + +.logOut:hover { + margin: 0px; + height: 80%; + width: auto; + cursor: pointer; +} + +#exit { + width: 50px; + height: 200px; +} + +label { + font-size: 20px; + color: #0A4D68; +} + +.insertInfo { + background: #D9D9D9; + width: 250px; + height: 35px; + border: 1px solid #000000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 25px; + display: flex; + text-align: center; + margin-bottom: 10px; + justify-content: center; +} + + +input::placeholder { + font-size: 15px; +} + +.button { + font-size: 17px; + background: #279A82; + width: 230px; + height: 40px; + border: 1px solid #000000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 25px; + color: #FFFFFF; + cursor: pointer; +} + +.button:hover { + background-color: #1f836d; + cursor: pointer; +} + +/* CSS DE PAGE HOME */ + +.container { + margin-top: 5%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.descriptionPage { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + width: 60%; +} + +p { + font-size: 17px; + font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; + color: #0A4D68; + grid-column: 1 / span 2; + grid-row: 3 / 3; +} + +a { + font-size: 17px; + font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; +} + +#posts { + width: 70%; + height: fit-content; + display: flex; + flex-direction: column; + align-items: center; + margin-top: 5%; + margin-left: 15%; + padding-top: 10px; + gap: 20px; + overflow: auto; + overflow-x: hidden; + + /* Oculta la barra de desplazamiento en navegadores compatibles */ + scrollbar-color: transparent transparent; + /* Establece el color de la barra de desplazamiento transparente */ +} + +#posts::-webkit-scrollbar-track { + background-color: #6f2121; + /* Color de fondo de la barra de desplazamiento */ +} + + +.post { + width: 90%; + display: flex; + background-color: #D9D9D9; + padding: 10px; + border: 1px solid #000000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 25px; +} + +.avatar { + width: 50px; + border-radius: 50%; + margin-left: 0; + margin-top: 5px; + grid-column: 1; + grid-row: 1/2; + justify-self: center; + +} + +#infoUser { + width: 90%; + /* align-items: flex-start; + padding: 10px; + display: flex; + flex-direction: column; */ + display: grid; + grid-template-columns: 0.3fr 1fr; + /* Dos columnas, la primera se ajusta al contenido y la segunda ocupa el espacio restante */ + /*grid-template-rows: 1fr 1fr 1fr; Tres filas, cada una se ajusta al contenido */ + grid-auto-rows: min-content; + grid-gap: 10px; + /* Espacio entre las filas y columnas */ + align-items: center; + /* Alineación vertical centrada */ +} + +.userName { + margin-top: 9px; + grid-column-start: 2; + grid-column-end: 2; + grid-row: 1 / 2; + margin-bottom: 40px; + justify-self: left; +} + + +#container { + margin-top: 0; + padding: 0%; + height: 100%; + +} + +.logoGoogle { + width: 25px; +} + +.googleButton { + display: flex; + align-items: center; + align-content: space-between; + justify-content: space-evenly; + background-color: aliceblue; + color: black; + width: 180px; + height: 35px; + font-size: 13px; +} + +.or { + margin: 0; + font-size: 13px; +} + +.writeAndPost { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} + +.textArea { + width: 60%; + height: 60px; + padding: 20px; + background-color: #D9D9D9; + border-radius: 31px; + border: 1px solid #000000; +} + +.buttonCreatePost { + Width: 30%; + Height: 35px; + background-color: #279A82; + color: white; + border: 1px solid #000000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + border-radius: 25px; + cursor: pointer; + /*float: left; + Alinea el botón a la izquierda, para responsive tal vez sea mejor ponerlo en "none" + margin-left: 15px; + hacer responsive dsps*/ +} + +.buttonCreatePost:hover { + background-color: #1f836d; + cursor: pointer; + +} + +.likesPic { + width: 28px; + right: 10px; + bottom: 10px; +} + +.likesPic:hover { + width: 32px; + right: 5px; + bottom: 5px; + cursor: pointer; +} + +.menuLikeSection { + width: 10%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + +} + +.menuOptions { + width: 39px; + margin-top: 10px; +} + +.menuOptions:hover { + width: 43px; + margin-top: 7px; + cursor: pointer; +} + +.likesAndCount { + display: flex; + flex-direction: column; + align-items: center; + height: 25%; + margin-bottom: 15px; +} + +/* .userNameandDate{ + grid-column: 2 / span 2; + grid-row: 1 / 1; +} */ +.publicDate { + margin-top: 5px; + grid-column-start: 2; + grid-column-end: 2; + grid-row: 1 / 2; + justify-self: left; +} + +/* CSS MODAL */ +#modalEdit::backdrop, +.modalOptions::backdrop, +#modalDelete::backdrop { + background-color: rgba(0, 0, 0, 0.65); +} + +#modalEdit, +#modalDelete { + text-align: center; + background-color: #98DEE9; +} + +.modalOptions { + background-color: #98DEE9; + text-align: center; + +} + +.modalImgEdit, +.modalImgDel { + width: 50px; + display: flex; + align-items: center; +} + +.editLabel, +.deleteLabel { + text-align: center; +} + +.xModal { + width: 20px; + margin-top: 30px; +} + +.modalImgDel { + margin-top: 15px; +} + +#btn-modal, +#btnYes, +#btnNo { + margin: 5px; +} \ No newline at end of file diff --git a/test/index.spec.js b/test/index.spec.js index 91f11a33..1e032cb0 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,8 +1,61 @@ +/* coverage/Icov-report/index.html, click derecho y +reveal in file explorer, manda a carpeta alli abrir index */ // importamos la funcion que vamos a testear -import { myFunction } from '../src/lib/index'; +import { home } from '../src/pages/home.js'; +import { createAccount } from '../src/pages/createAccount.js'; +import { signIn } from '../src/pages/signIn.js'; +import { wall } from '../src/pages/wall.js'; -describe('myFunction', () => { - it('debería ser una función', () => { - expect(typeof myFunction).toBe('function'); +jest.mock('../lib/functions.js', () => { + const originalModule = jest.requireActual('../lib/functions.js'); + + // Mock the default export and named export 'foo' + return { + __esModule: true, + ...originalModule, + authDetector: jest.fn(() => new Promise((resolve) => { + resolve('mock@mock.cl'); + })), + addPost: jest.fn(() => new Promise((resolve) => { resolve({ id: 'Value' }); })), + }; +}); + +describe(); + +// test de render de paginas +describe('Function home', () => { + it('render home', () => { + home(); + }); +}); +describe('Function createAccount', () => { + it('render createAccount', () => { + createAccount(); + }); +}); +describe('Function signIn', () => { + it('render signIn', () => { + signIn(); + }); +}); + +// WALL TESTS + +// display wall +describe('Function wall', () => { + it('render wall', () => { + wall(); }); }); + +// wall es una función +describe('wall', () => { + test('wall is a function', () => { + expect(typeof wall).toBe('function'); + }); +}); + +// si buttonCreatePost existe +test('buttonCreatePost exist', () => { + +});