├── 04-05-PWA
└── ob-04-mi-primera-pwa
│ ├── Readme.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
│ └── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ ├── service-worker.js
│ ├── serviceWorkerRegistration.js
│ └── setupTests.js
├── 07-NotificacionesPush
└── ob-04-mi-primera-pwa
│ ├── Readme.md
│ ├── build
│ ├── asset-manifest.json
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── robots.txt
│ ├── service-worker.js
│ ├── service-worker.js.map
│ └── static
│ │ ├── css
│ │ ├── main.8c8b27cf.chunk.css
│ │ └── main.8c8b27cf.chunk.css.map
│ │ └── js
│ │ ├── 2.47e1c757.chunk.js
│ │ ├── 2.47e1c757.chunk.js.LICENSE.txt
│ │ ├── 2.47e1c757.chunk.js.map
│ │ ├── 3.5481896b.chunk.js
│ │ ├── 3.5481896b.chunk.js.map
│ │ ├── main.2bad46f6.chunk.js
│ │ ├── main.2bad46f6.chunk.js.map
│ │ ├── runtime-main.49629437.js
│ │ └── runtime-main.49629437.js.map
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
│ ├── server
│ ├── index.js
│ ├── package-lock.json
│ └── package.json
│ └── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ ├── service-worker.js
│ ├── serviceWorkerRegistration.js
│ └── setupTests.js
├── 08-13-15-proyecto-final
├── README.md
├── docs
│ ├── assets
│ │ ├── anchor.js
│ │ ├── bass-addons.css
│ │ ├── bass.css
│ │ ├── fonts
│ │ │ ├── EOT
│ │ │ │ ├── SourceCodePro-Bold.eot
│ │ │ │ └── SourceCodePro-Regular.eot
│ │ │ ├── LICENSE.txt
│ │ │ ├── OTF
│ │ │ │ ├── SourceCodePro-Bold.otf
│ │ │ │ └── SourceCodePro-Regular.otf
│ │ │ ├── TTF
│ │ │ │ ├── SourceCodePro-Bold.ttf
│ │ │ │ └── SourceCodePro-Regular.ttf
│ │ │ ├── WOFF
│ │ │ │ ├── OTF
│ │ │ │ │ ├── SourceCodePro-Bold.otf.woff
│ │ │ │ │ └── SourceCodePro-Regular.otf.woff
│ │ │ │ └── TTF
│ │ │ │ │ ├── SourceCodePro-Bold.ttf.woff
│ │ │ │ │ └── SourceCodePro-Regular.ttf.woff
│ │ │ ├── WOFF2
│ │ │ │ ├── OTF
│ │ │ │ │ ├── SourceCodePro-Bold.otf.woff2
│ │ │ │ │ └── SourceCodePro-Regular.otf.woff2
│ │ │ │ └── TTF
│ │ │ │ │ ├── SourceCodePro-Bold.ttf.woff2
│ │ │ │ │ └── SourceCodePro-Regular.ttf.woff2
│ │ │ └── source-code-pro.css
│ │ ├── github.css
│ │ ├── site.js
│ │ ├── split.css
│ │ ├── split.js
│ │ └── style.css
│ └── index.html
├── jest.config.js
├── package-lock.json
├── package.json
├── public
│ ├── images
│ │ └── favicon.ico
│ └── index.html
└── src
│ └── components
│ ├── App.jsx
│ ├── lists
│ ├── TaskList.backup.jsx
│ ├── TaskList.jsx
│ └── TaskList.test.js
│ └── settings
│ └── Settings.jsx
├── 10-11-testing
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ ├── InputNuevaNota.jsx
│ └── ListadoNotas.jsx
│ ├── index.css
│ ├── index.js
│ └── obtest
│ ├── index.js
│ └── index.test.js
├── 12-tdd-react
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.js
├── 16-ui
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ └── Boton.jsx
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── stories
│ └── Boton.stories.js
├── 17-18-24-proyecto-final-2
├── Dockerfile
├── README.md
├── build
│ ├── asset-manifest.json
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── robots.txt
│ └── static
│ │ ├── css
│ │ ├── main.4d88340e.css
│ │ └── main.4d88340e.css.map
│ │ └── js
│ │ ├── main.e9711eac.js
│ │ ├── main.e9711eac.js.LICENSE.txt
│ │ └── main.e9711eac.js.map
├── cypress.json
├── cypress
│ ├── _integration
│ │ ├── 0-notes
│ │ │ └── notes_app.spec.js
│ │ ├── 1-getting-started
│ │ │ └── todo.spec.js
│ │ └── 2-advanced-examples
│ │ │ ├── actions.spec.js
│ │ │ ├── aliasing.spec.js
│ │ │ ├── assertions.spec.js
│ │ │ ├── connectors.spec.js
│ │ │ ├── cookies.spec.js
│ │ │ ├── cypress_api.spec.js
│ │ │ ├── files.spec.js
│ │ │ ├── local_storage.spec.js
│ │ │ ├── location.spec.js
│ │ │ ├── misc.spec.js
│ │ │ ├── navigation.spec.js
│ │ │ ├── network_requests.spec.js
│ │ │ ├── querying.spec.js
│ │ │ ├── spies_stubs_clocks.spec.js
│ │ │ ├── traversal.spec.js
│ │ │ ├── utilities.spec.js
│ │ │ ├── viewport.spec.js
│ │ │ ├── waiting.spec.js
│ │ │ └── window.spec.js
│ ├── fixtures
│ │ ├── example.json
│ │ ├── profile.json
│ │ └── users.json
│ ├── integration
│ │ └── 0-notes
│ │ │ └── notes_app.spec.js
│ ├── plugins
│ │ └── index.js
│ ├── screenshots
│ │ └── All Integration Specs
│ │ │ └── my-image.png
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── firebase.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── components
│ │ ├── App.jsx
│ │ ├── lists
│ │ │ └── TaskList.jsx
│ │ └── settings
│ │ │ └── Settings.jsx
│ ├── firebase
│ │ ├── index.js
│ │ └── tasksController.js
│ ├── hooks
│ │ └── useLocalStorage.js
│ ├── index.css
│ └── index.js
└── tailwind.config.js
├── 19-20-21-22-23-27-28-firebase-shopping
├── README.md
├── build
│ └── index.html
├── firebase-debug.log
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── firebase-messaging-sw.js
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.js
│ ├── components
│ │ ├── Footer.jsx
│ │ ├── Header.jsx
│ │ └── TaskList.jsx
│ ├── firebase
│ │ ├── index.js
│ │ └── taskController.js
│ ├── index.css
│ ├── index.js
│ └── routes
│ │ ├── Home.jsx
│ │ ├── Login.jsx
│ │ ├── Register.jsx
│ │ └── Shopping.jsx
└── tailwind.config.js
├── 25-metodologias
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── components
│ ├── 01_atomos
│ │ ├── Button.css
│ │ ├── Button.jsx
│ │ ├── Container.css
│ │ ├── Container.jsx
│ │ ├── Input.css
│ │ └── Input.jsx
│ ├── 02_moleculas
│ │ ├── FormAzul.jsx
│ │ ├── FormRojo.jsx
│ │ └── Header.jsx
│ ├── 03_organismos
│ │ ├── Formulario.jsx
│ │ └── OrgHeader.jsx
│ └── 04_plantillas
│ │ └── Plantilla.jsx
│ ├── controllers
│ ├── dbController.js
│ ├── dbMockController.js
│ └── frontController.js
│ ├── front
│ ├── AtomicDesign.jsx
│ └── Notes.jsx
│ ├── index.css
│ └── index.js
└── 26-react-ts
├── README.md
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── components
│ ├── Header.tsx
│ ├── Product.tsx
│ ├── ProductList.tsx
│ └── Title.tsx
├── controllers
│ └── productController.ts
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── reportWebVitals.ts
└── setupTests.ts
├── tailwind.config.js
└── tsconfig.json
/04-05-PWA/ob-04-mi-primera-pwa/Readme.md:
--------------------------------------------------------------------------------
1 | # Primera aplicación con PWA
2 | En las lecciones 04 y 05 crearemos una aplicación en React con capacidades PWA (Progressive Web App)
3 |
4 | ## Lección 04 - Introducción a los Service Workers e instalación en un nuevo proyecto
5 | 1. Crearemos una lista de la compra en React
6 | 2. Crearemos la configuración inicial del Service Worker con PWA
7 | 3. Analizaremos el ciclo de vida de los Service Workers en nuestra aplicación
8 | ### Tarea Lección 04
9 | Crea una función que muestre por consola un "Hola mundo" cuando el Service Worker de nuestra aplicación se haya registrado (no instalado)
10 |
11 | ## Lección 05 - Profundizando en los Service Workers y gestionando las actualizaciones
12 | 1. Analizaremos a fondo la configuración del archivo service-worker.js
13 | 2. Añadiremos la lógica de control de versiones a través de las funcionalidades PWA
14 | ### Tarea Lección 05
15 | Crea un nuevo eventListener que, cada vez que se realice una petición de un archivo (js, css o html) muestre por consola el nombre del archivo que se ha solicitado
16 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ob-04-mi-primera-pwa",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@3m1/service-worker-updater": "^2.0.3",
7 | "@testing-library/jest-dom": "^5.15.0",
8 | "@testing-library/react": "^11.2.7",
9 | "@testing-library/user-event": "^12.8.3",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "4.0.3",
13 | "web-vitals": "^0.2.4",
14 | "workbox-background-sync": "^5.1.4",
15 | "workbox-broadcast-update": "^5.1.4",
16 | "workbox-cacheable-response": "^5.1.4",
17 | "workbox-core": "^5.1.4",
18 | "workbox-expiration": "^5.1.4",
19 | "workbox-google-analytics": "^5.1.4",
20 | "workbox-navigation-preload": "^5.1.4",
21 | "workbox-precaching": "^5.1.4",
22 | "workbox-range-requests": "^5.1.4",
23 | "workbox-routing": "^5.1.4",
24 | "workbox-strategies": "^5.1.4",
25 | "workbox-streams": "^5.1.4"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject",
32 | "server-prod": "npm run build && serve -s build -l 5000"
33 | },
34 | "eslintConfig": {
35 | "extends": [
36 | "react-app",
37 | "react-app/jest"
38 | ]
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/04-05-PWA/ob-04-mi-primera-pwa/public/favicon.ico
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 | React App
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/04-05-PWA/ob-04-mi-primera-pwa/public/logo192.png
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/04-05-PWA/ob-04-mi-primera-pwa/public/logo512.png
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import { withServiceWorkerUpdater } from '@3m1/service-worker-updater';
4 |
5 |
6 | const App = (props) => {
7 | const { newServiceWorkerDetected, onLoadNewServiceWorkerAccept } = props;
8 |
9 | const [newItem, setNewItem] = React.useState("");
10 | const [items, setItems] = React.useState([]);
11 |
12 | const addNewItem = () => {
13 | setItems([...items, newItem]);
14 | setNewItem("");
15 | }
16 |
17 | return (
18 |
32 | );
33 | }
34 |
35 | export default withServiceWorkerUpdater(App);
36 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorkerRegistration from './serviceWorkerRegistration';
6 | import reportWebVitals from './reportWebVitals';
7 | import { onServiceWorkerUpdate } from '@3m1/service-worker-updater'
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
16 | // If you want your app to work offline and load faster, you can change
17 | // unregister() to register() below. Note this comes with some pitfalls.
18 | // Learn more about service workers: https://cra.link/PWA
19 | serviceWorkerRegistration.register({
20 | onUpdate: onServiceWorkerUpdate
21 | });
22 |
23 | // If you want to start measuring performance in your app, pass a function
24 | // to log results (for example: reportWebVitals(console.log))
25 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
26 | reportWebVitals();
27 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 |
3 | // This service worker can be customized!
4 | // See https://developers.google.com/web/tools/workbox/modules
5 | // for the list of available Workbox modules, or add any other
6 | // code you'd like.
7 | // You can also remove this file if you'd prefer not to use a
8 | // service worker, and the Workbox build step will be skipped.
9 |
10 | import { clientsClaim } from 'workbox-core';
11 | import { ExpirationPlugin } from 'workbox-expiration';
12 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
13 | import { registerRoute } from 'workbox-routing';
14 | import { StaleWhileRevalidate } from 'workbox-strategies';
15 |
16 | clientsClaim();
17 |
18 | // Precache all of the assets generated by your build process.
19 | // Their URLs are injected into the manifest variable below.
20 | // This variable must be present somewhere in your service worker file,
21 | // even if you decide not to use precaching. See https://cra.link/PWA
22 | precacheAndRoute(self.__WB_MANIFEST);
23 |
24 | // Set up App Shell-style routing, so that all navigation requests
25 | // are fulfilled with your index.html shell. Learn more at
26 | // https://developers.google.com/web/fundamentals/architecture/app-shell
27 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
28 | registerRoute(
29 | // Return false to exempt requests from being fulfilled by index.html.
30 | ({ request, url }) => {
31 | // If this isn't a navigation, skip.
32 | if (request.mode !== 'navigate') {
33 | return false;
34 | } // If this is a URL that starts with /_, skip.
35 |
36 | if (url.pathname.startsWith('/_')) {
37 | return false;
38 | } // If this looks like a URL for a resource, because it contains // a file extension, skip.
39 |
40 | if (url.pathname.match(fileExtensionRegexp)) {
41 | return false;
42 | } // Return true to signal that we want to use the handler.
43 |
44 | return true;
45 | },
46 | createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
47 | );
48 |
49 | // An example runtime caching route for requests that aren't handled by the
50 | // precache, in this case same-origin .png requests like those from in public/
51 | registerRoute(
52 | // Add in any other file extensions or routing criteria as needed.
53 | ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
54 | new StaleWhileRevalidate({
55 | cacheName: 'images',
56 | plugins: [
57 | // Ensure that once this runtime cache reaches a maximum size the
58 | // least-recently used images are removed.
59 | new ExpirationPlugin({ maxEntries: 50 }),
60 | ],
61 | })
62 | );
63 |
64 | // This allows the web app to trigger skipWaiting via
65 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
66 | self.addEventListener('message', (event) => {
67 | if (event.data && event.data.type === 'SKIP_WAITING') {
68 | self.skipWaiting();
69 | }
70 | });
71 |
72 | // Any other custom service worker logic can go here.
73 | // Aquí vamos a poner todo nuestro código custom
74 | const version = "app-compra-v8";
75 |
76 | self.addEventListener('install', event => {
77 | console.log(`Instalando versión ${version}`)
78 | });
79 |
80 | self.addEventListener('activate', event => {
81 | console.log(`¡Activada versión ${version}!`)
82 | });
--------------------------------------------------------------------------------
/04-05-PWA/ob-04-mi-primera-pwa/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/Readme.md:
--------------------------------------------------------------------------------
1 | # Notificaciones Push en React
2 | En la lección 07 seguimos profundizando en la configuración del Service Worker y añadimos funcionalidades para Notificaciones Push
3 |
4 | ## Lección 07 - Profundizando en los Service Workers y añadiendo funcionalidad de notificaciones Push
5 | 1. Veremos ejemplos de notificaciones push en otros proyectos
6 | 2. Seguimos trabajando en la aplicación de la lista de la compra que empezamos en la sesión 04
7 | 3. Instalamos las dependencias para generar las vapid keys, con el fin de poder gestionar las notificaciones push en nuestro proyecto
8 | 4. Modificamos la función register()
9 | 5. Gestionamos los permisos del navegador
10 | 6. Crearemos una notificación push indicando que existe una nueva versión del código
11 | ### Tarea Lecciones 07 y 08 (ve antes el vídeo de la lección 08)
12 | 1. Añade un cuadro de texto con un botón en el Front
13 | 2. Crea las funciones necesarias en el servidor para que la aplicación pueda enviar una notificación push con el contenido escrito en el cuadro de texto cada vez que pulse el botón
14 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.8c8b27cf.chunk.css",
4 | "main.js": "/static/js/main.2bad46f6.chunk.js",
5 | "main.js.map": "/static/js/main.2bad46f6.chunk.js.map",
6 | "runtime-main.js": "/static/js/runtime-main.49629437.js",
7 | "runtime-main.js.map": "/static/js/runtime-main.49629437.js.map",
8 | "static/js/2.47e1c757.chunk.js": "/static/js/2.47e1c757.chunk.js",
9 | "static/js/2.47e1c757.chunk.js.map": "/static/js/2.47e1c757.chunk.js.map",
10 | "static/js/3.5481896b.chunk.js": "/static/js/3.5481896b.chunk.js",
11 | "static/js/3.5481896b.chunk.js.map": "/static/js/3.5481896b.chunk.js.map",
12 | "index.html": "/index.html",
13 | "service-worker.js": "/service-worker.js",
14 | "service-worker.js.map": "/service-worker.js.map",
15 | "static/css/main.8c8b27cf.chunk.css.map": "/static/css/main.8c8b27cf.chunk.css.map",
16 | "static/js/2.47e1c757.chunk.js.LICENSE.txt": "/static/js/2.47e1c757.chunk.js.LICENSE.txt"
17 | },
18 | "entrypoints": [
19 | "static/js/runtime-main.49629437.js",
20 | "static/js/2.47e1c757.chunk.js",
21 | "static/css/main.8c8b27cf.chunk.css",
22 | "static/js/main.2bad46f6.chunk.js"
23 | ]
24 | }
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/build/favicon.ico
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/index.html:
--------------------------------------------------------------------------------
1 | React App
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/build/logo192.png
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/build/logo512.png
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/static/css/main.8c8b27cf.chunk.css:
--------------------------------------------------------------------------------
1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{animation:App-logo-spin 20s linear infinite}}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
2 | /*# sourceMappingURL=main.8c8b27cf.chunk.css.map */
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/static/css/main.8c8b27cf.chunk.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://src/index.css","webpack://src/App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEY,CACZ,kCAAmC,CACnC,iCACF,CAEA,KACE,yEAEF,CCZA,KACE,iBACF,CAEA,UACE,aAAc,CACd,mBACF,CAEA,8CACE,UACE,2CACF,CACF,CAEA,YACE,wBAAyB,CACzB,gBAAiB,CACjB,YAAa,CACb,qBAAsB,CACtB,kBAAmB,CACnB,sBAAuB,CACvB,4BAA6B,CAC7B,UACF,CAEA,UACE,aACF,CAEA,yBACE,GACE,sBACF,CACA,GACE,uBACF,CACF","file":"main.8c8b27cf.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n"]}
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/static/js/2.47e1c757.chunk.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /** @license React v0.20.2
8 | * scheduler.production.min.js
9 | *
10 | * Copyright (c) Facebook, Inc. and its affiliates.
11 | *
12 | * This source code is licensed under the MIT license found in the
13 | * LICENSE file in the root directory of this source tree.
14 | */
15 |
16 | /** @license React v17.0.2
17 | * react-dom.production.min.js
18 | *
19 | * Copyright (c) Facebook, Inc. and its affiliates.
20 | *
21 | * This source code is licensed under the MIT license found in the
22 | * LICENSE file in the root directory of this source tree.
23 | */
24 |
25 | /** @license React v17.0.2
26 | * react-jsx-runtime.production.min.js
27 | *
28 | * Copyright (c) Facebook, Inc. and its affiliates.
29 | *
30 | * This source code is licensed under the MIT license found in the
31 | * LICENSE file in the root directory of this source tree.
32 | */
33 |
34 | /** @license React v17.0.2
35 | * react.production.min.js
36 | *
37 | * Copyright (c) Facebook, Inc. and its affiliates.
38 | *
39 | * This source code is licensed under the MIT license found in the
40 | * LICENSE file in the root directory of this source tree.
41 | */
42 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/build/static/js/runtime-main.49629437.js:
--------------------------------------------------------------------------------
1 | !function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],f=0,s=[];f0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/public/favicon.ico
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 | React App
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/public/logo192.png
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/07-NotificacionesPush/ob-04-mi-primera-pwa/public/logo512.png
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const cors = require("cors");
3 | const webpush = require("web-push");
4 |
5 | // Middlewares
6 | const app = express();
7 |
8 | app.use(cors());
9 | app.use(express.urlencoded({ extended: false }));
10 | app.use(express.json());
11 |
12 | // Constantes
13 | const pushSubscription = {
14 | endpoint: 'https://fcm.googleapis.com/fcm/send/cj9FWINYmAs:APA91bGCeaH1tCJP01YtllyttleuoiQCcL-7_71ccurpb_JsEy-s_83xg_sNvlCrmNXC0rzBuJM0M_eA5XPxRohvvkljqLrms-s1BM9yco4ATFxXA9u_QjDZNkDRztj2Fjj0kXhGzYWE',
15 | expirationTime: null,
16 | keys: {
17 | p256dh: 'BDbmbQbnC7k7TLXVsfwxN8hx2ZQ8hl4zhCxbErgNA17Dcn2xAU-GFiQpPdBoA3COoeFffTcSJmRBnErmGZADS2o',
18 | auth: 'i9oEcykHI1rkNcNC0eDNdw'
19 | }
20 | }
21 | const vapidKeys = {
22 | publicKey: "BJ7oPFz6nH60tZYqw0ccdh4h28Bf6-Yujvij7BgMv0kRlRTSCkL1oPFBKQISRtS0uNRR249nWK4I-GfEPdvhCtc",
23 | privateKey: "5wGMH_OOjeFL03YbFC7SP6fJulbTfoEuxuNW37EURag"
24 | }
25 | webpush.setVapidDetails(
26 | 'mailto:gorka@mail.com',
27 | vapidKeys.publicKey,
28 | vapidKeys.privateKey
29 | );
30 |
31 |
32 |
33 | // Routes
34 | app.get('/', async (req, res) => {
35 | const payload = JSON.stringify({ title: "Título de Notificación", message: "Mensaje de la notificación" });
36 | try {
37 | await webpush.sendNotification(pushSubscription, payload);
38 | await res.send("Enviado");
39 | } catch (e) { console.log(e) }
40 | });
41 |
42 | app.post('/subscription', (req, res) => {
43 | console.log(req.body);
44 | res.sendStatus(200).json();
45 | })
46 |
47 | app.listen(8000, () => console.log("Server listening on port 8000"))
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "cors": "^2.8.5",
13 | "express": "^4.17.1",
14 | "web-push": "^3.4.5"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import { withServiceWorkerUpdater } from '@3m1/service-worker-updater';
4 |
5 |
6 | const App = (props) => {
7 | const { newServiceWorkerDetected, onLoadNewServiceWorkerAccept } = props;
8 |
9 | const [newItem, setNewItem] = React.useState("");
10 | const [items, setItems] = React.useState([]);
11 |
12 | const addNewItem = () => {
13 | setItems([...items, newItem]);
14 | setNewItem("");
15 | }
16 |
17 | return (
18 |
32 | );
33 | }
34 |
35 | export default withServiceWorkerUpdater(App);
36 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorkerRegistration from './serviceWorkerRegistration';
6 | import reportWebVitals from './reportWebVitals';
7 | import { onServiceWorkerUpdate } from '@3m1/service-worker-updater'
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
16 | // If you want your app to work offline and load faster, you can change
17 | // unregister() to register() below. Note this comes with some pitfalls.
18 | // Learn more about service workers: https://cra.link/PWA
19 | serviceWorkerRegistration.register({
20 | onUpdate: onServiceWorkerUpdate
21 | });
22 |
23 | // If you want to start measuring performance in your app, pass a function
24 | // to log results (for example: reportWebVitals(console.log))
25 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
26 | reportWebVitals();
27 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/07-NotificacionesPush/ob-04-mi-primera-pwa/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/README.md:
--------------------------------------------------------------------------------
1 | # ob-react-final - Inclusión de funcionalidades PWA
2 |
3 | Incluimos funcionalidades PWA a nuestro proyecto Final curso OB de React JS
4 |
5 | ## Lección 08 - Añadiendo notificaciones push a nuestro proyecto final
6 | 1. Instalamos el service worker en nuestro proyecto
7 | 2. Instalamos las dependencias para generar las vapid-keys y poder gestionar las notificaciones push en nuestro navegador
8 | 3. Probamos las funcionalidades PWA y las notificaciones push a través del navegador
9 | 4. Creamos un servidor en Express.js que vaya a enviar notificaciones push al cliente
10 | 5. Enviamos notificaciones a través de nuestro servidor a todos los clientes que tengan el service worker de nuestra aplicación
11 | ### Tarea Lecciones 07 y 08 (ve antes el vídeo de la lección 08)
12 | 1. Añade un cuadro de texto con un botón en el Front
13 | 2. Crea las funciones necesarias en el servidor para que la aplicación pueda enviar una notificación push con el contenido escrito en el cuadro de texto cada vez que pulse el botón
14 |
15 | ## Lección 13 - Aplicamos Tests a nuestro proyecto final
16 | 1. Instalamos las dependencias de test en nuestro proyecto final
17 | 2. Modificamos los archivos de configuración de nuestro proyecto para poder realizar los tests correctamente
18 | 3. Realizamos tests con el objetivo de obtener errores
19 | 4. Solucionamos los errores a través de la refactorización de los componentes
20 | 5. Lanzamos de nuevo los tests con el objetivo de que sean satisfactorios
21 | ### Tarea Lección 13
22 | Crea un nuevo test suite que contenga dos test cases:
23 | 1. Testeamos que las tareas cambien de estado cuando hagamos clic en ellas
24 | 2. Testeamos que se pueden crear nuevas tareas
25 |
26 | ## Lección 15 - Creamos la documentación de nuestro proyecto final
27 | 1. Instalamos las dependencias para la creación de la documentación del proyecto final
28 | 2. Documentamos cada una de las funciones y métodos de nuestro proyecto siguiendo el estándar DocJS
29 | 3. Creamos nuevas funcionalidades en nuestro proyecto, dark mode, etc.
30 | 4. Documentamos las nuevas funcionalidades de nuestro proyecto
31 | 5. Ejecutamos el script de DocumentationJS para generar la documentación
32 | 6. Analizamos el output de la documentación del proyecto final
33 | ### Tarea Lección 15
34 | Añade el código necesario para que el parámetro "index" en TaskList.jsx tenga como valor por defecto index=0
35 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/bass-addons.css:
--------------------------------------------------------------------------------
1 | .input {
2 | font-family: inherit;
3 | display: block;
4 | width: 100%;
5 | height: 2rem;
6 | padding: .5rem;
7 | margin-bottom: 1rem;
8 | border: 1px solid #ccc;
9 | font-size: .875rem;
10 | border-radius: 3px;
11 | box-sizing: border-box;
12 | }
13 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/EOT/SourceCodePro-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/EOT/SourceCodePro-Bold.eot
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/EOT/SourceCodePro-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/EOT/SourceCodePro-Regular.eot
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/OTF/SourceCodePro-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/OTF/SourceCodePro-Bold.otf
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/OTF/SourceCodePro-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/OTF/SourceCodePro-Regular.otf
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/fonts/source-code-pro.css:
--------------------------------------------------------------------------------
1 | @font-face{
2 | font-family: 'Source Code Pro';
3 | font-weight: 400;
4 | font-style: normal;
5 | font-stretch: normal;
6 | src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'),
7 | url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'),
8 | url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'),
9 | url('OTF/SourceCodePro-Regular.otf') format('opentype'),
10 | url('TTF/SourceCodePro-Regular.ttf') format('truetype');
11 | }
12 |
13 | @font-face{
14 | font-family: 'Source Code Pro';
15 | font-weight: 700;
16 | font-style: normal;
17 | font-stretch: normal;
18 | src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'),
19 | url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'),
20 | url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'),
21 | url('OTF/SourceCodePro-Bold.otf') format('opentype'),
22 | url('TTF/SourceCodePro-Bold.ttf') format('truetype');
23 | }
24 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/github.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | github.com style (c) Vasily Polovnyov
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | color: #333;
12 | background: #f8f8f8;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .diff .hljs-header,
18 | .hljs-javadoc {
19 | color: #998;
20 | font-style: italic;
21 | }
22 |
23 | .hljs-keyword,
24 | .css .rule .hljs-keyword,
25 | .hljs-winutils,
26 | .nginx .hljs-title,
27 | .hljs-subst,
28 | .hljs-request,
29 | .hljs-status {
30 | color: #1184CE;
31 | }
32 |
33 | .hljs-number,
34 | .hljs-hexcolor,
35 | .ruby .hljs-constant {
36 | color: #ed225d;
37 | }
38 |
39 | .hljs-string,
40 | .hljs-tag .hljs-value,
41 | .hljs-phpdoc,
42 | .hljs-dartdoc,
43 | .tex .hljs-formula {
44 | color: #ed225d;
45 | }
46 |
47 | .hljs-title,
48 | .hljs-id,
49 | .scss .hljs-preprocessor {
50 | color: #900;
51 | font-weight: bold;
52 | }
53 |
54 | .hljs-list .hljs-keyword,
55 | .hljs-subst {
56 | font-weight: normal;
57 | }
58 |
59 | .hljs-class .hljs-title,
60 | .hljs-type,
61 | .vhdl .hljs-literal,
62 | .tex .hljs-command {
63 | color: #458;
64 | font-weight: bold;
65 | }
66 |
67 | .hljs-tag,
68 | .hljs-tag .hljs-title,
69 | .hljs-rules .hljs-property,
70 | .django .hljs-tag .hljs-keyword {
71 | color: #000080;
72 | font-weight: normal;
73 | }
74 |
75 | .hljs-attribute,
76 | .hljs-variable,
77 | .lisp .hljs-body {
78 | color: #008080;
79 | }
80 |
81 | .hljs-regexp {
82 | color: #009926;
83 | }
84 |
85 | .hljs-symbol,
86 | .ruby .hljs-symbol .hljs-string,
87 | .lisp .hljs-keyword,
88 | .clojure .hljs-keyword,
89 | .scheme .hljs-keyword,
90 | .tex .hljs-special,
91 | .hljs-prompt {
92 | color: #990073;
93 | }
94 |
95 | .hljs-built_in {
96 | color: #0086b3;
97 | }
98 |
99 | .hljs-preprocessor,
100 | .hljs-pragma,
101 | .hljs-pi,
102 | .hljs-doctype,
103 | .hljs-shebang,
104 | .hljs-cdata {
105 | color: #999;
106 | font-weight: bold;
107 | }
108 |
109 | .hljs-deletion {
110 | background: #fdd;
111 | }
112 |
113 | .hljs-addition {
114 | background: #dfd;
115 | }
116 |
117 | .diff .hljs-change {
118 | background: #0086b3;
119 | }
120 |
121 | .hljs-chunk {
122 | color: #aaa;
123 | }
124 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/split.css:
--------------------------------------------------------------------------------
1 | .gutter {
2 | background-color: #f5f5f5;
3 | background-repeat: no-repeat;
4 | background-position: 50%;
5 | }
6 |
7 | .gutter.gutter-vertical {
8 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
9 | cursor: ns-resize;
10 | }
11 |
12 | .gutter.gutter-horizontal {
13 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
14 | cursor: ew-resize;
15 | }
16 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/docs/assets/style.css:
--------------------------------------------------------------------------------
1 | .documentation {
2 | font-family: Helvetica, sans-serif;
3 | color: #666;
4 | line-height: 1.5;
5 | background: #f5f5f5;
6 | }
7 |
8 | .black {
9 | color: #666;
10 | }
11 |
12 | .bg-white {
13 | background-color: #fff;
14 | }
15 |
16 | h4 {
17 | margin: 20px 0 10px 0;
18 | }
19 |
20 | .documentation h3 {
21 | color: #000;
22 | }
23 |
24 | .border-bottom {
25 | border-color: #ddd;
26 | }
27 |
28 | a {
29 | color: #1184ce;
30 | text-decoration: none;
31 | }
32 |
33 | .documentation a[href]:hover {
34 | text-decoration: underline;
35 | }
36 |
37 | a:hover {
38 | cursor: pointer;
39 | }
40 |
41 | .py1-ul li {
42 | padding: 5px 0;
43 | }
44 |
45 | .max-height-100 {
46 | max-height: 100%;
47 | }
48 |
49 | .height-viewport-100 {
50 | height: 100vh;
51 | }
52 |
53 | section:target h3 {
54 | font-weight: 700;
55 | }
56 |
57 | .documentation td,
58 | .documentation th {
59 | padding: 0.25rem 0.25rem;
60 | }
61 |
62 | h1:hover .anchorjs-link,
63 | h2:hover .anchorjs-link,
64 | h3:hover .anchorjs-link,
65 | h4:hover .anchorjs-link {
66 | opacity: 1;
67 | }
68 |
69 | .fix-3 {
70 | width: 25%;
71 | max-width: 244px;
72 | }
73 |
74 | .fix-3 {
75 | width: 25%;
76 | max-width: 244px;
77 | }
78 |
79 | @media (min-width: 52em) {
80 | .fix-margin-3 {
81 | margin-left: 25%;
82 | }
83 | }
84 |
85 | .pre,
86 | pre,
87 | code,
88 | .code {
89 | font-family: Source Code Pro, Menlo, Consolas, Liberation Mono, monospace;
90 | font-size: 14px;
91 | }
92 |
93 | .fill-light {
94 | background: #f9f9f9;
95 | }
96 |
97 | .width2 {
98 | width: 1rem;
99 | }
100 |
101 | .input {
102 | font-family: inherit;
103 | display: block;
104 | width: 100%;
105 | height: 2rem;
106 | padding: 0.5rem;
107 | margin-bottom: 1rem;
108 | border: 1px solid #ccc;
109 | font-size: 0.875rem;
110 | border-radius: 3px;
111 | box-sizing: border-box;
112 | }
113 |
114 | table {
115 | border-collapse: collapse;
116 | }
117 |
118 | .prose table th,
119 | .prose table td {
120 | text-align: left;
121 | padding: 8px;
122 | border: 1px solid #ddd;
123 | }
124 |
125 | .prose table th:nth-child(1) {
126 | border-right: none;
127 | }
128 | .prose table th:nth-child(2) {
129 | border-left: none;
130 | }
131 |
132 | .prose table {
133 | border: 1px solid #ddd;
134 | }
135 |
136 | .prose-big {
137 | font-size: 18px;
138 | line-height: 30px;
139 | }
140 |
141 | .quiet {
142 | opacity: 0.7;
143 | }
144 |
145 | .minishadow {
146 | box-shadow: 2px 2px 10px #f3f3f3;
147 | }
148 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.jsx?$": "babel-jest"
4 | },
5 | testEnvironment: "jsdom"
6 | }
7 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proyecto-final",
3 | "version": "1.0.0",
4 | "description": "Proyecto final curso React JS",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest --watchAll",
8 | "lint": "eslint ./src",
9 | "lint:fix": "eslint --fix ./src",
10 | "deploy:dev": "webpack-dev-server --mode development --hot --open --static",
11 | "docs:build": "documentation build src/** -f html -o docs",
12 | "build": "webpack --mode development",
13 | "build:prod": "webpack --mode production",
14 | "deploy:prod": "netlify deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_AUTH_TOKEN --prod"
15 | },
16 | "keywords": [
17 | "react"
18 | ],
19 | "author": "masajo",
20 | "license": "ISC",
21 | "devDependencies": {
22 | "@babel/cli": "^7.16.0",
23 | "@babel/core": "^7.16.0",
24 | "@babel/plugin-transform-modules-commonjs": "^7.16.0",
25 | "@babel/plugin-transform-runtime": "^7.16.0",
26 | "@babel/preset-env": "^7.16.5",
27 | "@babel/preset-react": "^7.16.0",
28 | "@testing-library/dom": "^8.11.1",
29 | "@testing-library/react": "^12.1.2",
30 | "babel-jest": "^27.4.5",
31 | "babel-loader": "^8.2.3",
32 | "css-loader": "^6.5.0",
33 | "documentation": "^13.2.5",
34 | "eslint": "^7.32.0",
35 | "eslint-config-airbnb": "^18.2.1",
36 | "eslint-loader": "^4.0.2",
37 | "eslint-plugin-import": "^2.25.2",
38 | "eslint-plugin-jsx-a11y": "^6.4.1",
39 | "eslint-plugin-react": "^7.26.1",
40 | "eslint-plugin-react-hooks": "^4.2.0",
41 | "file-loader": "^6.2.0",
42 | "html-webpack-plugin": "^5.5.0",
43 | "jest": "^27.4.5",
44 | "mini-css-extract-plugin": "^2.4.3",
45 | "netlify-cli": "^6.14.16",
46 | "node-sass": "^6.0.1",
47 | "sass-loader": "^12.3.0",
48 | "source-map-loader": "^3.0.0",
49 | "webpack": "^5.61.0",
50 | "webpack-cli": "^4.9.1",
51 | "webpack-dev-server": "^4.4.0"
52 | },
53 | "dependencies": {
54 | "axios": "^0.24.0",
55 | "bootstrap": "^5.1.3",
56 | "react": "^17.0.2",
57 | "react-dom": "^17.0.2",
58 | "react-redux": "^7.2.6",
59 | "react-router-dom": "^5.3.0",
60 | "redux": "^4.1.2",
61 | "redux-saga": "^1.1.3"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/08-13-15-proyecto-final/public/images/favicon.ico
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Proyecto React JS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useEffect } from "react";
3 | import Tasklist from "./lists/TaskList";
4 | import Settings from "./settings/Settings";
5 |
6 | /**
7 | * Función Anónima para crear un Componente principal
8 | * @returns {React.Component} Componente principal de nuestra aplicación
9 | */
10 | const App = () => {
11 | const [dark, setDark] = React.useState(false);
12 |
13 | /**
14 | * Documentación del useEffect
15 | * Se crea una variable de estado donde se almacena el valor de la configuración en localStorage
16 | */
17 |
18 | useEffect(() => {
19 | const config = JSON.parse(localStorage.getItem("config"));
20 | setDark(config.theme);
21 | }, []);
22 |
23 | /**
24 | * Función para intercambiar la variable de estado light <-> dark
25 | */
26 |
27 | const toggleDark = () => setDark(!dark);
28 | return (
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default App;
38 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/src/components/lists/TaskList.backup.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import useList from "../../hooks/useList";
3 |
4 | const TaskList = () => {
5 | return Task List
;
6 | };
7 | // const tasks = useList([]);
8 | // const [newTask, setNewTask] = useState("");
9 |
10 | // const addNewTask = () => {
11 | // tasks.push(newTask);
12 | // setNewTask("");
13 | // };
14 |
15 | // return (
16 | //
17 | //
Task List
18 | //
19 | // setNewTask(e.target.value)}
22 | // placeholder="New Task"
23 | // type="text"
24 | // />
25 | //
26 | //
27 | // {tasks.isEmpty() ? (
28 | //
Task List is Empty
29 | // ) : (
30 | //
42 | // )}
43 | //
44 | // );
45 | // };
46 |
47 | export default TaskList;
48 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/src/components/lists/TaskList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | /**
4 | * Componente que gestiona la lista de tareas
5 | *
6 | * @returns {React.Component}
7 | */
8 |
9 | const TaskList = () => {
10 | const [newTask, setNewTask] = useState("");
11 | const [tasklist, setTasklist] = useState([]);
12 |
13 | /**
14 | * Añade una nueva tarea a la lista
15 | * v2: La nueva tarea se añade como un objeto { task: nombre de la tarea, completed: si está completada o no}
16 | */
17 |
18 | const addNewTask = () => {
19 | setTasklist([...tasklist, { task: newTask, completed: false }]);
20 | setNewTask("");
21 | return true;
22 | };
23 |
24 | /**
25 | * Función para chequear si la lista de tareas está vacía
26 | * @returns true si tasklist.length === 0
27 | */
28 | const isTasksEmpty = () => tasklist.length === 0;
29 |
30 | /**
31 | * Editar el nombre de la nueva tarea
32 | * @param {*} e - Evento de onChange proveniente de React
33 | */
34 |
35 | const editNewItem = (e) => setNewTask(e.target.value);
36 |
37 | /**
38 | * Función para eliminar una tarea en concreto
39 | * @param {*} index - Índice de la tarea a eliminar
40 | */
41 |
42 | const removeItem = (index) => {
43 | const newtasklist = tasklist.filter((t, i) => i !== index);
44 | setTasklist(newtasklist);
45 | };
46 |
47 | /**
48 | * Cambia el item por completado <-> pendiente
49 | * @param {*} index
50 | */
51 |
52 | const toggleCompleteItem = (index) => {
53 | let newTaskList = tasklist;
54 | newTaskList[index].completed = !newTaskList[index].completed;
55 | setTasklist([...newTaskList]);
56 | };
57 |
58 | /**
59 | * Añade una nueva tarea cuando se presiona la tecla Enter
60 | * @param {*} e - Evento onKeyDown que proviene por defecto de React
61 | */
62 |
63 | const insertNewItemOnEnterKey = (e) => e.key === "Enter" && addNewTask();
64 | return (
65 | <>
66 | Task List
67 |
68 |
76 |
79 |
80 | {isTasksEmpty() ? (
81 | Task List is Empty
82 | ) : (
83 |
97 | )}
98 | >
99 | );
100 | };
101 |
102 | export default TaskList;
103 |
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/src/components/lists/TaskList.test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react"
2 | import TaskList from "./TaskList"
3 | import React from "react"
4 |
5 | // 0 - Renderiza el componente
6 | test('0 - Renderiza el componente', () => {
7 | const r = render();
8 | expect(r).toBeDefined();
9 | })
--------------------------------------------------------------------------------
/08-13-15-proyecto-final/src/components/settings/Settings.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import useLocalStorage from "../../hooks/useLocalStorage";
3 |
4 | const defaultConfig = {
5 | theme: "dark",
6 | lang: "es",
7 | };
8 |
9 | export default function Settings({ toggleDark }) {
10 | const [config, setConfig] = useLocalStorage("config", defaultConfig);
11 |
12 | /**
13 | * Función para intercambiar light <-> dark tanto en localStorage como en estado de la aplicación
14 | * @param {*} event - Evento de click proveniente de React
15 | */
16 |
17 | const toggleMode = (event) => {
18 | event.preventDefault();
19 | setConfig((oldConfig) => ({
20 | ...oldConfig,
21 | theme: oldConfig.theme === "light" ? "dark" : "light",
22 | }));
23 | toggleDark();
24 | };
25 |
26 | return (
27 |
28 |
APP SETTINGS
29 |
Actual Config: {config.theme}
30 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/10-11-testing/README.md:
--------------------------------------------------------------------------------
1 | # Testing en ReactJS
2 | En las lecciones 10 y 11 exploramos el mundo del testing en JS y más concretamente en ReactJS.
3 |
4 | ## Lección 10 - Introducción al Testing dentro de Javascript
5 | 1. ¿Qué es el testing y para qué sirve?
6 | 2. Creación de primeros ficheros de test *.test.js
7 | 3. Test suites vs Test cases
8 | 4. Creación de primeros casos de test de forma "manual"
9 | 5. Introducción a test en Javascript con JEST
10 | 6. Matchers y spies con Jest
11 | 7. Introducción al Mocking con Jest
12 | ### Tarea Lección 10
13 | 1. Crea una función que obtenga el factorial de un número
14 | 2. Crea un caso de test para esta función
15 |
16 | ## Lección 11 - Introducción al Testing con ReactJS
17 | 1. Creamos una app simple de notas con ReactJS
18 | 2. Creamos varios componentes con el objetivo de testearlos
19 | 3. Crearemos el test suite donde realizaremos los tests de los componentes que acabamos de crear
20 | 4. Importamos la dependencia React Testing Library y los componentes que queremos testear
21 | 5. Testeamos los componentes que hemos creado
22 | 6. Realizamos el test de Integración de la App al completo
23 | ### Tarea Lección 11
24 | 1. Crea un componente que contenga un contador (un valor que vaya aumentando al hacer clic en un botón)
25 | 2. Crea dos casos de test para este componente
26 | - Se renderiza el componente correctamente
27 | - Al hacer clic el botón, el valor aumenta exactamente una unidad
28 |
--------------------------------------------------------------------------------
/10-11-testing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing-sesiones-10-11",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "5.0.0",
12 | "web-vitals": "^2.1.2"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/10-11-testing/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/10-11-testing/public/favicon.ico
--------------------------------------------------------------------------------
/10-11-testing/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/10-11-testing/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/10-11-testing/public/logo192.png
--------------------------------------------------------------------------------
/10-11-testing/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/10-11-testing/public/logo512.png
--------------------------------------------------------------------------------
/10-11-testing/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/10-11-testing/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/10-11-testing/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: darkslategray;
3 | color : white;
4 | text-align : center;
5 | }
6 |
7 | .lista-notas {
8 | padding: 10px
9 | }
10 |
11 | .lista-notas div {
12 | margin-top: 5px;
13 | padding: 10px 5px;
14 | background-color: tomato;
15 | }
16 |
17 | .input-nueva-nota input {
18 | outline: none;
19 | padding: 10px 5px;
20 | border-radius: 5px;
21 | border: none;
22 | box-shadow: 0 0 10px #fff;
23 | margin-right: 5px
24 | }
--------------------------------------------------------------------------------
/10-11-testing/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import './App.css';
3 | import InputNuevaNota from './components/InputNuevaNota';
4 | import ListadoNotas from './components/ListadoNotas';
5 |
6 | function App() {
7 | const [notas, setNotas] = useState(["hacer la compra"])
8 |
9 | const addNuevaNota = (nuevaNota) => {
10 | setNotas([...notas, nuevaNota])
11 | }
12 |
13 | return (
14 |
15 |
Bienvenid@ a la sesión número 11
16 | Esto va a ser una (otra) aplicación de notas
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/10-11-testing/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, fireEvent } from "@testing-library/react"
2 | import App from "./App";
3 | import InputNuevaNota from "./components/InputNuevaNota";
4 | import ListadoNotas from './components/ListadoNotas'
5 |
6 | describe('REACT - Testeamos los componentes', () => {
7 | test('El listado se renderiza correctamente', () => {
8 | const r = render();
9 | expect(r).toBeDefined();
10 | })
11 | test('El listado renderiza un listado correctamente', () => {
12 | const notas = ["bajar la basura", "Comprar huevos"];
13 | const r = render();
14 | expect(r).toBeDefined();
15 | })
16 | test('El listado renderiza sólo las notas que debe renderizar', () => {
17 | const notas = ["bajar la basura", "Comprar huevos"];
18 | const r = render();
19 | const div = r.getByLabelText('listado-notas');
20 | expect(div.childElementCount).toBe(2);
21 | })
22 | })
23 |
24 | describe('REACT - Hacemos un test de integración', () => {
25 | test('Renderizamos la app', () => {
26 | const r = render()
27 | expect(r).toBeDefined();
28 | })
29 | test('Se renderiza el input', () => {
30 | const placeholdertext = "Introduce una nueva nota";
31 | const r = render()
32 | const input = r.getByPlaceholderText(placeholdertext);
33 | expect(input).toBeDefined();
34 | })
35 | test('Cuando hacemos clic en el botón Añadir, se lanza el evento', () => {
36 | const funcionMock = jest.fn();
37 | const r = render();
38 | const button = r.getByText("Añadir");
39 | fireEvent.click(button);
40 | expect(funcionMock).toHaveBeenCalledTimes(1);
41 | })
42 | test('Añadimos una nueva nota', () => {
43 | const placeholdertext = "Introduce una nueva nota";
44 | const r = render()
45 | const input = r.getByPlaceholderText(placeholdertext);
46 | const button = r.getByText("Añadir");
47 | const div = r.getByLabelText('listado-notas');
48 | const hijosInicial = div.childElementCount;
49 | fireEvent.change(input, { target: { value: 'Poner gasolina' } });
50 | fireEvent.click(button);
51 | const hijosFinal = div.childElementCount;
52 | expect(hijosFinal).toBeGreaterThan(hijosInicial);
53 | expect(hijosInicial).toBeLessThan(hijosFinal);
54 | expect(hijosInicial).not.toBe(hijosFinal);
55 | })
56 | })
--------------------------------------------------------------------------------
/10-11-testing/src/components/InputNuevaNota.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | const InputNuevaNota = ({ addNuevaNota }) => {
4 | const [nuevaNota, setNuevaNota] = useState("");
5 | return (
6 |
7 | e.key === "Enter" && addNuevaNota(nuevaNota)}
12 | onChange={(e) => setNuevaNota(e.target.value)}
13 | />
14 |
15 |
16 | );
17 | };
18 |
19 | export default InputNuevaNota;
20 |
--------------------------------------------------------------------------------
/10-11-testing/src/components/ListadoNotas.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ListadoNotas = ({ notas = [] }) => {
4 | return (
5 |
6 | {notas.map((nota, i) => (
7 |
{nota}
8 | ))}
9 |
10 | );
11 | };
12 |
13 | export default ListadoNotas;
14 |
--------------------------------------------------------------------------------
/10-11-testing/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/10-11-testing/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/10-11-testing/src/obtest/index.js:
--------------------------------------------------------------------------------
1 | // Aquí vamos a declarar todas las funciones y variables que necesitemos para ilustrar los casos de prueba
2 | // const sumar = (a, b) => {
3 | // return (a + b);
4 | // }
5 |
6 | export const sumar = (a, b) => (a + b);
7 | export const restar = (a, b) => (a - b);
8 | export const multiplicar = (a, b) => (a * b);
9 | export const dividir = (a, b) => (a / b);
10 |
11 | export const devuelveEmail = () => 'gorka@gorka.com';
12 |
13 | export const devuelveObjeto = () => {
14 | return {
15 | ancho: 10,
16 | alto: 19
17 | }
18 | }
19 |
20 | export const devuelveArrayNum = () => [1, 2, 3, 4, 5]
21 | export const devuelveArrayObj = () => [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]
22 | export const devuelveArrayStr = () => ['leche', 'huevos', 'cereales', 'jamón', 'yogures']
23 |
24 | export const devuelveTrue = () => true;
25 | export const devuelveFalse = () => false;
26 | export const devuelveNull = () => null;
27 | export const devuelveUndefined = () => undefined;
28 |
29 | export class Rectangulo {
30 | constructor(ancho, alto) {
31 | this.ancho = ancho;
32 | this.alto = alto;
33 | }
34 |
35 | calculaArea() {
36 | this.ancho * this.alto
37 | }
38 | }
--------------------------------------------------------------------------------
/12-tdd-react/README.md:
--------------------------------------------------------------------------------
1 | # Test Driven Development en ReactJS
2 | En la lección 12 nos introducimos en el TDD, o Test Driven Development a través de un sencillo proyecto en ReactJS
3 |
4 | ## Lección 12 - Introducción a TDD con ReactJS
5 | 1. ¿Qué es el TDD, o Test Driven Development?
6 | 2. Ejemplos de TDD
7 | 3. Construiremos una app de selección y búsqueda de Emsioras de Radio en Streaming
8 | 4. Definimos las características que queremos que tenga nuestra aplicación
9 | 5. Creamos los ficheros de test, o test suites
10 | 6. Creamos los casos de test en función de las características definidas en el punto 4
11 | 7. Lanzamos el script de test con el objetivo de que salga fallido
12 | 8. Refactorizamos los componentes
13 | 9. Lanzamos el script de test con el objetivo de que salga exitoso
14 | ### Tarea Lección 12
15 | ¡Tenemos un nuevo requerimiento por parte del cliente! Debemos implementarlo utilizando TDD
16 | Cliente: "Necesito que la aplicación muestre por defecto un mensaje cuando todavía no se ha realizado ninguna búsqueda, este mensaje debe decir: Busca tus emisoras favoritas"
17 | 1. Crea dos test cases diferentes para este nuevo requerimiento
18 | 2. Haz que ambos casos de test salgan fallidos
19 | 3. Crea la funcionalidad correspondiente para que estos casos de test pasen
20 | 4. Lanza los tests y haz que pasen todos (también los que ya teníamos)
--------------------------------------------------------------------------------
/12-tdd-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tdd-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^0.24.0",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-icons": "^4.3.1",
13 | "react-scripts": "5.0.0",
14 | "web-vitals": "^2.1.2"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/12-tdd-react/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/12-tdd-react/public/favicon.ico
--------------------------------------------------------------------------------
/12-tdd-react/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/12-tdd-react/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/12-tdd-react/public/logo192.png
--------------------------------------------------------------------------------
/12-tdd-react/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/12-tdd-react/public/logo512.png
--------------------------------------------------------------------------------
/12-tdd-react/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/12-tdd-react/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/12-tdd-react/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/12-tdd-react/src/App.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState } from 'react';
3 | import './App.css';
4 | import { IoPlay } from 'react-icons/io5'
5 |
6 | function App() {
7 | const [busqueda, setBusqueda] = useState("");
8 | const [listado, setListado] = useState([]);
9 |
10 | const hazBusqueda = () => {
11 | const url = `https://fr1.api.radio-browser.info/json/stations/byname/${busqueda}`;
12 | axios.get(url)
13 | .then(r => setListado(r.data))
14 | .catch(e => console.error(e))
15 | }
16 |
17 | const playRadio = (radio) => {
18 | const audio = new Audio(radio.url)
19 | audio.play();
20 | }
21 |
22 | return (
23 |
24 |
Bienvenid@ a la aplicación OpenRadioCamp
25 |
setBusqueda(e.target.value)} />
26 |
27 | {listado.length > 0 &&
}
28 |
29 | {listado.map((emisora, i) => {emisora.name} playRadio(emisora)} />
)}
30 |
31 |
32 | );
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/12-tdd-react/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/12-tdd-react/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/12-tdd-react/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/12-tdd-react/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/12-tdd-react/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/16-ui/README.md:
--------------------------------------------------------------------------------
1 | # Introduciendo nuevas herramientas para ayudarnos con el diseño de nuestras aplicaciones en ReactJS
2 |
3 | En la lección 16 veremos diversas herramientas para que nuestras aplicaciones luzcan modernas y con una apariencia espectacular
4 |
5 | ## Lección 16 - Herramientas de interés
6 |
7 | 1. StoryBook
8 | 2. Librerías principales de animaciones - FramerMotion y ReactSpring
9 | 3. Material UI
10 | 4. Chakra UI
11 | 5. Tailwind CSS
12 | 6. React Toolbox
13 | 7. React Bootstrap
14 |
15 | ### Extra. Realizamos un pequeño proyecto con Material UI y StoryBook
16 |
17 | ### Tarea Lección 16
18 |
19 | Replica el proyecto que hemos realizado con TailwindCSS, FramerMotion y StoryBook.
20 | (Pista: Primero crea un botón con TailwindCSS, después añade animaciones al hacer Hover sobre él, por último documéntalo con StoryBook.)
21 |
--------------------------------------------------------------------------------
/16-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leccion-16-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.7.1",
7 | "@emotion/styled": "^11.6.0",
8 | "@mui/material": "^5.2.5",
9 | "@testing-library/jest-dom": "^5.16.1",
10 | "@testing-library/react": "^12.1.2",
11 | "@testing-library/user-event": "^13.5.0",
12 | "react": "^17.0.2",
13 | "react-dom": "^17.0.2",
14 | "react-scripts": "5.0.0",
15 | "web-vitals": "^2.1.2"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject",
22 | "storybook": "start-storybook -p 6006 -s public",
23 | "build-storybook": "build-storybook -s public"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ],
30 | "overrides": [
31 | {
32 | "files": [
33 | "**/*.stories.*"
34 | ],
35 | "rules": {
36 | "import/no-anonymous-default-export": "off"
37 | }
38 | }
39 | ]
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | },
53 | "devDependencies": {
54 | "@storybook/addon-actions": "^6.4.9",
55 | "@storybook/addon-essentials": "^6.4.9",
56 | "@storybook/addon-links": "^6.4.9",
57 | "@storybook/builder-webpack5": "^6.4.9",
58 | "@storybook/manager-webpack5": "^6.4.9",
59 | "@storybook/node-logger": "^6.4.9",
60 | "@storybook/preset-create-react-app": "^4.0.0",
61 | "@storybook/react": "^6.4.9",
62 | "webpack": "^5.65.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/16-ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/16-ui/public/favicon.ico
--------------------------------------------------------------------------------
/16-ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/16-ui/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/16-ui/public/logo192.png
--------------------------------------------------------------------------------
/16-ui/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/16-ui/public/logo512.png
--------------------------------------------------------------------------------
/16-ui/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/16-ui/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/16-ui/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/16-ui/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import './App.css';
3 | import Boton from './components/Boton'
4 |
5 | function App() {
6 | return (
7 |
16 | );
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/16-ui/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/16-ui/src/components/Boton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Button from "@mui/material/Button";
3 | import PropTypes from "prop-types";
4 |
5 | const Boton = ({ text = "Botón", color = "primary" }) => {
6 | return (
7 |
10 | );
11 | };
12 |
13 | Boton.propTypes = {
14 | text: PropTypes.string,
15 | color: PropTypes.oneOf(["primary", "secondary", "warning", "success"]),
16 | };
17 |
18 | export default Boton;
19 |
--------------------------------------------------------------------------------
/16-ui/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/16-ui/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/16-ui/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/16-ui/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/16-ui/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/16-ui/src/stories/Boton.stories.js:
--------------------------------------------------------------------------------
1 | import Boton from '../components/Boton'
2 |
3 | export default {
4 | title: "Botón",
5 | component: Boton
6 | };
7 |
8 | // export const BotonMUI = () =>
9 | const Template = args =>
10 |
11 | export const Principal = Template.bind({});
12 | Principal.args = {
13 | color: "primary",
14 | text: "Hola"
15 | }
16 | export const Secundario = Template.bind({});
17 | Secundario.args = {
18 | color: "secondary",
19 | text: "Secundario"
20 | }
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | WORKDIR /app
4 |
5 | COPY package*.json ./
6 |
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | ENV PORT=3000
12 |
13 | EXPOSE 3000
14 |
15 | CMD [ "npm", "start" ]
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/README.md:
--------------------------------------------------------------------------------
1 | # Mejorando aún más nuestro proyecto final
2 | En las lecciones 17, 18 y 24 seguiremos añadiendo funcionalidades a nuestro proyecto final.
3 |
4 | ## Lección 17 - Añadiendo herramientas de interés a nuestro proyecto final
5 | 1. Recuperamos el proyecto final y añadiremos TailwindCSS para darle estilos
6 | 2. Realizamos la instalación de TailwindCSS y sus dependencias (https://tailwindcss.com/docs/guides/create-react-app)
7 | 3. Damos formato a toda nuestra aplicación con TailwindCSS
8 | ### Tarea Lección 17
9 | Utiliza Framer Motion para animar el check/uncheck de las tareas
10 |
11 | ## Lección 18 - Introducción a Docker
12 | 1. ¿Qué es Docker, y por qué es tan importante?
13 | 2. Ventajas de Docker
14 | 3. Cómo instalar Docker
15 | 4. Instalamos Docker en nuestro proyecto final
16 | 5. Creamos el Dockerfile con la configuración de nuestro contenedor
17 | 6. Creamos una cuenta en Docker Hub (https://hub.docker.com)
18 | 7. Desplegamos nuestra aplicación con "docker push"
19 | ### Tarea Lección 18
20 | Añade un nuevo componente y vuelve a desplegar la aplicación utilizando "docker push"
21 |
22 | ## Lección 24 - Desplegando nuestro proyecto en Firebase
23 | 1. Creamos el proyecto en Firebase
24 | 2. Aplicamos funcionalidades de Firebase Hosting
25 | 3. Inicializamos una base de datos de Firestore en nuestro proyecto
26 | 4. Realizamos el primer despliegue en Firebase Hosting de forma manual
27 | 5. Creamos los workflows para las automatizaciones de Github (Github Actions)
28 | 6. Realizamos el despliegue continuo (Continuous Integration) a través de git push
29 | ### Tarea Lección 24
30 | 1. Crea un nuevo componente en nuestro proyecto
31 | 2. Utiliza git para crear un nuevo commit
32 | 3. Realiza el push de esta nueva versión
33 | 4. Verifica que el nuevo proyecto se ha desplegado correctamente
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.4d88340e.css",
4 | "main.js": "/static/js/main.e9711eac.js",
5 | "index.html": "/index.html",
6 | "main.4d88340e.css.map": "/static/css/main.4d88340e.css.map",
7 | "main.e9711eac.js.map": "/static/js/main.e9711eac.js.map"
8 | },
9 | "entrypoints": [
10 | "static/css/main.4d88340e.css",
11 | "static/js/main.e9711eac.js"
12 | ]
13 | }
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/build/favicon.ico
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/index.html:
--------------------------------------------------------------------------------
1 | React App
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/build/logo192.png
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/build/logo512.png
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/build/static/js/main.e9711eac.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /*! *****************************************************************************
8 | Copyright (c) Microsoft Corporation.
9 |
10 | Permission to use, copy, modify, and/or distribute this software for any
11 | purpose with or without fee is hereby granted.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19 | PERFORMANCE OF THIS SOFTWARE.
20 | ***************************************************************************** */
21 |
22 | /** @license React v0.20.2
23 | * scheduler.production.min.js
24 | *
25 | * Copyright (c) Facebook, Inc. and its affiliates.
26 | *
27 | * This source code is licensed under the MIT license found in the
28 | * LICENSE file in the root directory of this source tree.
29 | */
30 |
31 | /** @license React v17.0.2
32 | * react-dom.production.min.js
33 | *
34 | * Copyright (c) Facebook, Inc. and its affiliates.
35 | *
36 | * This source code is licensed under the MIT license found in the
37 | * LICENSE file in the root directory of this source tree.
38 | */
39 |
40 | /** @license React v17.0.2
41 | * react-jsx-runtime.production.min.js
42 | *
43 | * Copyright (c) Facebook, Inc. and its affiliates.
44 | *
45 | * This source code is licensed under the MIT license found in the
46 | * LICENSE file in the root directory of this source tree.
47 | */
48 |
49 | /** @license React v17.0.2
50 | * react.production.min.js
51 | *
52 | * Copyright (c) Facebook, Inc. and its affiliates.
53 | *
54 | * This source code is licensed under the MIT license found in the
55 | * LICENSE file in the root directory of this source tree.
56 | */
57 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3000/"
3 | }
4 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/0-notes/notes_app.spec.js:
--------------------------------------------------------------------------------
1 | describe('Testeamos nuestra aplicación de notas', () => {
2 | it('Se renderiza correctamente', () => {
3 | cy
4 | .visit('/')
5 | .contains('Task List v2 - hosted on: Firebase');
6 | })
7 | })
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/aliasing.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Aliasing', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/aliasing')
6 | })
7 |
8 | it('.as() - alias a DOM element for later use', () => {
9 | // https://on.cypress.io/as
10 |
11 | // Alias a DOM element for use later
12 | // We don't have to traverse to the element
13 | // later in our code, we reference it with @
14 |
15 | cy.get('.as-table').find('tbody>tr')
16 | .first().find('td').first()
17 | .find('button').as('firstBtn')
18 |
19 | // when we reference the alias, we place an
20 | // @ in front of its name
21 | cy.get('@firstBtn').click()
22 |
23 | cy.get('@firstBtn')
24 | .should('have.class', 'btn-success')
25 | .and('contain', 'Changed')
26 | })
27 |
28 | it('.as() - alias a route for later use', () => {
29 | // Alias the route to wait for its response
30 | cy.intercept('GET', '**/comments/*').as('getComment')
31 |
32 | // we have code that gets a comment when
33 | // the button is clicked in scripts.js
34 | cy.get('.network-btn').click()
35 |
36 | // https://on.cypress.io/wait
37 | cy.wait('@getComment').its('response.statusCode').should('eq', 200)
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/connectors.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Connectors', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/connectors')
6 | })
7 |
8 | it('.each() - iterate over an array of elements', () => {
9 | // https://on.cypress.io/each
10 | cy.get('.connectors-each-ul>li')
11 | .each(($el, index, $list) => {
12 | console.log($el, index, $list)
13 | })
14 | })
15 |
16 | it('.its() - get properties on the current subject', () => {
17 | // https://on.cypress.io/its
18 | cy.get('.connectors-its-ul>li')
19 | // calls the 'length' property yielding that value
20 | .its('length')
21 | .should('be.gt', 2)
22 | })
23 |
24 | it('.invoke() - invoke a function on the current subject', () => {
25 | // our div is hidden in our script.js
26 | // $('.connectors-div').hide()
27 |
28 | // https://on.cypress.io/invoke
29 | cy.get('.connectors-div').should('be.hidden')
30 | // call the jquery method 'show' on the 'div.container'
31 | .invoke('show')
32 | .should('be.visible')
33 | })
34 |
35 | it('.spread() - spread an array as individual args to callback function', () => {
36 | // https://on.cypress.io/spread
37 | const arr = ['foo', 'bar', 'baz']
38 |
39 | cy.wrap(arr).spread((foo, bar, baz) => {
40 | expect(foo).to.eq('foo')
41 | expect(bar).to.eq('bar')
42 | expect(baz).to.eq('baz')
43 | })
44 | })
45 |
46 | describe('.then()', () => {
47 | it('invokes a callback function with the current subject', () => {
48 | // https://on.cypress.io/then
49 | cy.get('.connectors-list > li')
50 | .then(($lis) => {
51 | expect($lis, '3 items').to.have.length(3)
52 | expect($lis.eq(0), 'first item').to.contain('Walk the dog')
53 | expect($lis.eq(1), 'second item').to.contain('Feed the cat')
54 | expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
55 | })
56 | })
57 |
58 | it('yields the returned value to the next command', () => {
59 | cy.wrap(1)
60 | .then((num) => {
61 | expect(num).to.equal(1)
62 |
63 | return 2
64 | })
65 | .then((num) => {
66 | expect(num).to.equal(2)
67 | })
68 | })
69 |
70 | it('yields the original subject without return', () => {
71 | cy.wrap(1)
72 | .then((num) => {
73 | expect(num).to.equal(1)
74 | // note that nothing is returned from this callback
75 | })
76 | .then((num) => {
77 | // this callback receives the original unchanged value 1
78 | expect(num).to.equal(1)
79 | })
80 | })
81 |
82 | it('yields the value yielded by the last Cypress command inside', () => {
83 | cy.wrap(1)
84 | .then((num) => {
85 | expect(num).to.equal(1)
86 | // note how we run a Cypress command
87 | // the result yielded by this Cypress command
88 | // will be passed to the second ".then"
89 | cy.wrap(2)
90 | })
91 | .then((num) => {
92 | // this callback receives the value yielded by "cy.wrap(2)"
93 | expect(num).to.equal(2)
94 | })
95 | })
96 | })
97 | })
98 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/cookies.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Cookies', () => {
4 | beforeEach(() => {
5 | Cypress.Cookies.debug(true)
6 |
7 | cy.visit('https://example.cypress.io/commands/cookies')
8 |
9 | // clear cookies again after visiting to remove
10 | // any 3rd party cookies picked up such as cloudflare
11 | cy.clearCookies()
12 | })
13 |
14 | it('cy.getCookie() - get a browser cookie', () => {
15 | // https://on.cypress.io/getcookie
16 | cy.get('#getCookie .set-a-cookie').click()
17 |
18 | // cy.getCookie() yields a cookie object
19 | cy.getCookie('token').should('have.property', 'value', '123ABC')
20 | })
21 |
22 | it('cy.getCookies() - get browser cookies', () => {
23 | // https://on.cypress.io/getcookies
24 | cy.getCookies().should('be.empty')
25 |
26 | cy.get('#getCookies .set-a-cookie').click()
27 |
28 | // cy.getCookies() yields an array of cookies
29 | cy.getCookies().should('have.length', 1).should((cookies) => {
30 | // each cookie has these properties
31 | expect(cookies[0]).to.have.property('name', 'token')
32 | expect(cookies[0]).to.have.property('value', '123ABC')
33 | expect(cookies[0]).to.have.property('httpOnly', false)
34 | expect(cookies[0]).to.have.property('secure', false)
35 | expect(cookies[0]).to.have.property('domain')
36 | expect(cookies[0]).to.have.property('path')
37 | })
38 | })
39 |
40 | it('cy.setCookie() - set a browser cookie', () => {
41 | // https://on.cypress.io/setcookie
42 | cy.getCookies().should('be.empty')
43 |
44 | cy.setCookie('foo', 'bar')
45 |
46 | // cy.getCookie() yields a cookie object
47 | cy.getCookie('foo').should('have.property', 'value', 'bar')
48 | })
49 |
50 | it('cy.clearCookie() - clear a browser cookie', () => {
51 | // https://on.cypress.io/clearcookie
52 | cy.getCookie('token').should('be.null')
53 |
54 | cy.get('#clearCookie .set-a-cookie').click()
55 |
56 | cy.getCookie('token').should('have.property', 'value', '123ABC')
57 |
58 | // cy.clearCookies() yields null
59 | cy.clearCookie('token').should('be.null')
60 |
61 | cy.getCookie('token').should('be.null')
62 | })
63 |
64 | it('cy.clearCookies() - clear browser cookies', () => {
65 | // https://on.cypress.io/clearcookies
66 | cy.getCookies().should('be.empty')
67 |
68 | cy.get('#clearCookies .set-a-cookie').click()
69 |
70 | cy.getCookies().should('have.length', 1)
71 |
72 | // cy.clearCookies() yields null
73 | cy.clearCookies()
74 |
75 | cy.getCookies().should('be.empty')
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/files.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | /// JSON fixture file can be loaded directly using
4 | // the built-in JavaScript bundler
5 | // @ts-ignore
6 | const requiredExample = require('../../fixtures/example')
7 |
8 | context('Files', () => {
9 | beforeEach(() => {
10 | cy.visit('https://example.cypress.io/commands/files')
11 | })
12 |
13 | beforeEach(() => {
14 | // load example.json fixture file and store
15 | // in the test context object
16 | cy.fixture('example.json').as('example')
17 | })
18 |
19 | it('cy.fixture() - load a fixture', () => {
20 | // https://on.cypress.io/fixture
21 |
22 | // Instead of writing a response inline you can
23 | // use a fixture file's content.
24 |
25 | // when application makes an Ajax request matching "GET **/comments/*"
26 | // Cypress will intercept it and reply with the object in `example.json` fixture
27 | cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment')
28 |
29 | // we have code that gets a comment when
30 | // the button is clicked in scripts.js
31 | cy.get('.fixture-btn').click()
32 |
33 | cy.wait('@getComment').its('response.body')
34 | .should('have.property', 'name')
35 | .and('include', 'Using fixtures to represent data')
36 | })
37 |
38 | it('cy.fixture() or require - load a fixture', function () {
39 | // we are inside the "function () { ... }"
40 | // callback and can use test context object "this"
41 | // "this.example" was loaded in "beforeEach" function callback
42 | expect(this.example, 'fixture in the test context')
43 | .to.deep.equal(requiredExample)
44 |
45 | // or use "cy.wrap" and "should('deep.equal', ...)" assertion
46 | cy.wrap(this.example)
47 | .should('deep.equal', requiredExample)
48 | })
49 |
50 | it('cy.readFile() - read file contents', () => {
51 | // https://on.cypress.io/readfile
52 |
53 | // You can read a file and yield its contents
54 | // The filePath is relative to your project's root.
55 | cy.readFile('cypress.json').then((json) => {
56 | expect(json).to.be.an('object')
57 | })
58 | })
59 |
60 | it('cy.writeFile() - write to a file', () => {
61 | // https://on.cypress.io/writefile
62 |
63 | // You can write to a file
64 |
65 | // Use a response from a request to automatically
66 | // generate a fixture file for use later
67 | cy.request('https://jsonplaceholder.cypress.io/users')
68 | .then((response) => {
69 | cy.writeFile('cypress/fixtures/users.json', response.body)
70 | })
71 |
72 | cy.fixture('users').should((users) => {
73 | expect(users[0].name).to.exist
74 | })
75 |
76 | // JavaScript arrays and objects are stringified
77 | // and formatted into text.
78 | cy.writeFile('cypress/fixtures/profile.json', {
79 | id: 8739,
80 | name: 'Jane',
81 | email: 'jane@example.com',
82 | })
83 |
84 | cy.fixture('profile').should((profile) => {
85 | expect(profile.name).to.eq('Jane')
86 | })
87 | })
88 | })
89 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/local_storage.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Local Storage', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/local-storage')
6 | })
7 | // Although local storage is automatically cleared
8 | // in between tests to maintain a clean state
9 | // sometimes we need to clear the local storage manually
10 |
11 | it('cy.clearLocalStorage() - clear all data in local storage', () => {
12 | // https://on.cypress.io/clearlocalstorage
13 | cy.get('.ls-btn').click().should(() => {
14 | expect(localStorage.getItem('prop1')).to.eq('red')
15 | expect(localStorage.getItem('prop2')).to.eq('blue')
16 | expect(localStorage.getItem('prop3')).to.eq('magenta')
17 | })
18 |
19 | // clearLocalStorage() yields the localStorage object
20 | cy.clearLocalStorage().should((ls) => {
21 | expect(ls.getItem('prop1')).to.be.null
22 | expect(ls.getItem('prop2')).to.be.null
23 | expect(ls.getItem('prop3')).to.be.null
24 | })
25 |
26 | cy.get('.ls-btn').click().should(() => {
27 | expect(localStorage.getItem('prop1')).to.eq('red')
28 | expect(localStorage.getItem('prop2')).to.eq('blue')
29 | expect(localStorage.getItem('prop3')).to.eq('magenta')
30 | })
31 |
32 | // Clear key matching string in Local Storage
33 | cy.clearLocalStorage('prop1').should((ls) => {
34 | expect(ls.getItem('prop1')).to.be.null
35 | expect(ls.getItem('prop2')).to.eq('blue')
36 | expect(ls.getItem('prop3')).to.eq('magenta')
37 | })
38 |
39 | cy.get('.ls-btn').click().should(() => {
40 | expect(localStorage.getItem('prop1')).to.eq('red')
41 | expect(localStorage.getItem('prop2')).to.eq('blue')
42 | expect(localStorage.getItem('prop3')).to.eq('magenta')
43 | })
44 |
45 | // Clear keys matching regex in Local Storage
46 | cy.clearLocalStorage(/prop1|2/).should((ls) => {
47 | expect(ls.getItem('prop1')).to.be.null
48 | expect(ls.getItem('prop2')).to.be.null
49 | expect(ls.getItem('prop3')).to.eq('magenta')
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/location.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Location', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/location')
6 | })
7 |
8 | it('cy.hash() - get the current URL hash', () => {
9 | // https://on.cypress.io/hash
10 | cy.hash().should('be.empty')
11 | })
12 |
13 | it('cy.location() - get window.location', () => {
14 | // https://on.cypress.io/location
15 | cy.location().should((location) => {
16 | expect(location.hash).to.be.empty
17 | expect(location.href).to.eq('https://example.cypress.io/commands/location')
18 | expect(location.host).to.eq('example.cypress.io')
19 | expect(location.hostname).to.eq('example.cypress.io')
20 | expect(location.origin).to.eq('https://example.cypress.io')
21 | expect(location.pathname).to.eq('/commands/location')
22 | expect(location.port).to.eq('')
23 | expect(location.protocol).to.eq('https:')
24 | expect(location.search).to.be.empty
25 | })
26 | })
27 |
28 | it('cy.url() - get the current URL', () => {
29 | // https://on.cypress.io/url
30 | cy.url().should('eq', 'https://example.cypress.io/commands/location')
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/navigation.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Navigation', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io')
6 | cy.get('.navbar-nav').contains('Commands').click()
7 | cy.get('.dropdown-menu').contains('Navigation').click()
8 | })
9 |
10 | it('cy.go() - go back or forward in the browser\'s history', () => {
11 | // https://on.cypress.io/go
12 |
13 | cy.location('pathname').should('include', 'navigation')
14 |
15 | cy.go('back')
16 | cy.location('pathname').should('not.include', 'navigation')
17 |
18 | cy.go('forward')
19 | cy.location('pathname').should('include', 'navigation')
20 |
21 | // clicking back
22 | cy.go(-1)
23 | cy.location('pathname').should('not.include', 'navigation')
24 |
25 | // clicking forward
26 | cy.go(1)
27 | cy.location('pathname').should('include', 'navigation')
28 | })
29 |
30 | it('cy.reload() - reload the page', () => {
31 | // https://on.cypress.io/reload
32 | cy.reload()
33 |
34 | // reload the page without using the cache
35 | cy.reload(true)
36 | })
37 |
38 | it('cy.visit() - visit a remote url', () => {
39 | // https://on.cypress.io/visit
40 |
41 | // Visit any sub-domain of your current domain
42 |
43 | // Pass options to the visit
44 | cy.visit('https://example.cypress.io/commands/navigation', {
45 | timeout: 50000, // increase total time for the visit to resolve
46 | onBeforeLoad (contentWindow) {
47 | // contentWindow is the remote page's window object
48 | expect(typeof contentWindow === 'object').to.be.true
49 | },
50 | onLoad (contentWindow) {
51 | // contentWindow is the remote page's window object
52 | expect(typeof contentWindow === 'object').to.be.true
53 | },
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/viewport.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Viewport', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/viewport')
6 | })
7 |
8 | it('cy.viewport() - set the viewport size and dimension', () => {
9 | // https://on.cypress.io/viewport
10 |
11 | cy.get('#navbar').should('be.visible')
12 | cy.viewport(320, 480)
13 |
14 | // the navbar should have collapse since our screen is smaller
15 | cy.get('#navbar').should('not.be.visible')
16 | cy.get('.navbar-toggle').should('be.visible').click()
17 | cy.get('.nav').find('a').should('be.visible')
18 |
19 | // lets see what our app looks like on a super large screen
20 | cy.viewport(2999, 2999)
21 |
22 | // cy.viewport() accepts a set of preset sizes
23 | // to easily set the screen to a device's width and height
24 |
25 | // We added a cy.wait() between each viewport change so you can see
26 | // the change otherwise it is a little too fast to see :)
27 |
28 | cy.viewport('macbook-15')
29 | cy.wait(200)
30 | cy.viewport('macbook-13')
31 | cy.wait(200)
32 | cy.viewport('macbook-11')
33 | cy.wait(200)
34 | cy.viewport('ipad-2')
35 | cy.wait(200)
36 | cy.viewport('ipad-mini')
37 | cy.wait(200)
38 | cy.viewport('iphone-6+')
39 | cy.wait(200)
40 | cy.viewport('iphone-6')
41 | cy.wait(200)
42 | cy.viewport('iphone-5')
43 | cy.wait(200)
44 | cy.viewport('iphone-4')
45 | cy.wait(200)
46 | cy.viewport('iphone-3')
47 | cy.wait(200)
48 |
49 | // cy.viewport() accepts an orientation for all presets
50 | // the default orientation is 'portrait'
51 | cy.viewport('ipad-2', 'portrait')
52 | cy.wait(200)
53 | cy.viewport('iphone-4', 'landscape')
54 | cy.wait(200)
55 |
56 | // The viewport will be reset back to the default dimensions
57 | // in between tests (the default can be set in cypress.json)
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/waiting.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Waiting', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/waiting')
6 | })
7 | // BE CAREFUL of adding unnecessary wait times.
8 | // https://on.cypress.io/best-practices#Unnecessary-Waiting
9 |
10 | // https://on.cypress.io/wait
11 | it('cy.wait() - wait for a specific amount of time', () => {
12 | cy.get('.wait-input1').type('Wait 1000ms after typing')
13 | cy.wait(1000)
14 | cy.get('.wait-input2').type('Wait 1000ms after typing')
15 | cy.wait(1000)
16 | cy.get('.wait-input3').type('Wait 1000ms after typing')
17 | cy.wait(1000)
18 | })
19 |
20 | it('cy.wait() - wait for a specific route', () => {
21 | // Listen to GET to comments/1
22 | cy.intercept('GET', '**/comments/*').as('getComment')
23 |
24 | // we have code that gets a comment when
25 | // the button is clicked in scripts.js
26 | cy.get('.network-btn').click()
27 |
28 | // wait for GET comments/1
29 | cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/_integration/2-advanced-examples/window.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Window', () => {
4 | beforeEach(() => {
5 | cy.visit('https://example.cypress.io/commands/window')
6 | })
7 |
8 | it('cy.window() - get the global window object', () => {
9 | // https://on.cypress.io/window
10 | cy.window().should('have.property', 'top')
11 | })
12 |
13 | it('cy.document() - get the document object', () => {
14 | // https://on.cypress.io/document
15 | cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
16 | })
17 |
18 | it('cy.title() - get the title', () => {
19 | // https://on.cypress.io/title
20 | cy.title().should('include', 'Kitchen Sink')
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/fixtures/profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 8739,
3 | "name": "Jane",
4 | "email": "jane@example.com"
5 | }
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/integration/0-notes/notes_app.spec.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | describe('Testeamos nuestra aplicación de notas', () => {
4 | beforeEach(() => {
5 | cy.visit('/')
6 | })
7 | it('Se renderiza correctamente', () => {
8 | cy.contains('Task List v2 - hosted on: Firebase');
9 | })
10 | it('Podemos añadir una nueva tarea', () => {
11 | const textNewTask = "Testeamos en cypress"
12 | cy.get('input[placeholder="New Task"]').type(textNewTask)
13 | cy.get('button.btn-add-task').click()
14 | cy.wait(3000);
15 | cy.get('.todo-list li').last().contains(textNewTask)
16 | // Aquí tendríamos que eliminar la última entrada -> tasksController de firebase
17 | })
18 | })
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | // eslint-disable-next-line no-unused-vars
19 | module.exports = (on, config) => {
20 | // `on` is used to hook into various events Cypress emits
21 | // `config` is the resolved Cypress config
22 | }
23 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/screenshots/All Integration Specs/my-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/cypress/screenshots/All Integration Specs/my-image.png
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add('login', (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cra-final",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "firebase": "^9.6.1",
10 | "framer-motion": "^5.5.5",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-scripts": "5.0.0",
14 | "web-vitals": "^2.1.2"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject",
21 | "cypress:open": "cypress open"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "autoprefixer": "^10.4.0",
43 | "cypress": "^9.2.0",
44 | "postcss": "^8.4.5",
45 | "tailwindcss": "^3.0.7"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/public/favicon.ico
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/public/logo192.png
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/17-18-24-proyecto-final-2/public/logo512.png
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Tasklist from "./lists/TaskList";
3 | import Settings from "./settings/Settings";
4 | import { motion, AnimatePresence } from "framer-motion";
5 |
6 | /**
7 | * Función Anónima para crear un Componente principal
8 | * @returns {React.Component} Componente principal de nuestra aplicación
9 | */
10 | const App = () => {
11 | const [dark, setDark] = React.useState(false);
12 | const [showSettings, setShowSettings] = useState(false);
13 |
14 | /**
15 | * Documentación del useEffect
16 | * Se crea una variable de estado donde se almacena el valor de la configuración en localStorage
17 | */
18 |
19 | useEffect(() => {
20 | // try {
21 | // // const config = JSON.parse(localStorage.getItem("config"));
22 | // setDark(config.theme);
23 | // } catch (e) {
24 | // }
25 | setDark(false);
26 | }, []);
27 |
28 | /**
29 | * Función para intercambiar la variable de estado light <-> dark
30 | */
31 |
32 | const toggleDark = () => setDark(!dark);
33 | return (
34 |
35 |
38 |
42 |
null}
46 | >
47 | {showSettings && (
48 |
53 |
54 |
55 | )}
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default App;
63 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/components/settings/Settings.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import useLocalStorage from "../../hooks/useLocalStorage";
3 |
4 | const defaultConfig = {
5 | theme: "dark",
6 | lang: "es",
7 | };
8 |
9 | export default function Settings({ toggleDark }) {
10 | const [config, setConfig] = useLocalStorage("config", defaultConfig);
11 |
12 | /**
13 | * Función para intercambiar light <-> dark tanto en localStorage como en estado de la aplicación
14 | * @param {*} event - Evento de click proveniente de React
15 | */
16 |
17 | const toggleMode = (event) => {
18 | event.preventDefault();
19 | setConfig((oldConfig) => ({
20 | ...oldConfig,
21 | theme: oldConfig.theme === "light" ? "dark" : "light",
22 | }));
23 | toggleDark();
24 | };
25 |
26 | return (
27 |
28 |
29 |
APP SETTINGS
30 |
Actual Config: {config.theme}
31 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/firebase/index.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | // TODO: Add SDKs for Firebase products that you want to use
4 | // https://firebase.google.com/docs/web/setup#available-libraries
5 | import { getFirestore } from "firebase/firestore";
6 |
7 | // Your web app's Firebase configuration
8 | const firebaseConfig = {
9 | apiKey: "AIzaSyD4Uo1iIzVhVZ9F3PFD0kkLfOgx71uocVY",
10 | authDomain: "ob-tasklist-firebase.firebaseapp.com",
11 | projectId: "ob-tasklist-firebase",
12 | storageBucket: "ob-tasklist-firebase.appspot.com",
13 | messagingSenderId: "45362721152",
14 | appId: "1:45362721152:web:eb9a450e4b347e5fcf1165"
15 | };
16 |
17 | const developmentFirebaseConfig = {
18 | apiKey: "AIzaSyAIWlkvSQuVj5SKzWoDX7y0hMbpqnSM9SI",
19 | authDomain: "dev-ob-tasklist-firebase.firebaseapp.com",
20 | projectId: "dev-ob-tasklist-firebase",
21 | storageBucket: "dev-ob-tasklist-firebase.appspot.com",
22 | messagingSenderId: "321960608896",
23 | appId: "1:321960608896:web:59d9cb7512a4f934f65265"
24 | };
25 |
26 | // Initialize Firebase
27 | let app;
28 | if (process.env.NODE_ENV === 'production') {
29 | app = initializeApp(firebaseConfig);
30 | } else {
31 | app = initializeApp(developmentFirebaseConfig);
32 | }
33 | // Inicializar Firestore
34 | const db = getFirestore();
35 |
36 | export {
37 | app,
38 | db
39 | }
40 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/firebase/tasksController.js:
--------------------------------------------------------------------------------
1 | import { addDoc, collection, getDocs, setDoc, doc, deleteDoc } from "firebase/firestore"
2 | import { db } from "."
3 |
4 | export const addTask = task => {
5 | return addDoc(collection(db, 'tasks'), task);
6 | }
7 |
8 | export const getTasks = async () => {
9 | const querySnapshot = await getDocs(collection(db, 'tasks'))
10 | const tasks = querySnapshot.docs.map(doc => {
11 | return { ...doc.data(), id: doc.id }
12 | })
13 | return tasks;
14 | }
15 |
16 | export const toggleComplete = (task) => {
17 | return setDoc(doc(db, 'tasks', task.id), {
18 | ...task,
19 | completed: !task.completed
20 | });
21 | }
22 |
23 | export const deleteTasks = async () => {
24 | if (process.env.NODE_ENV === 'production') return;
25 | }
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/hooks/useLocalStorage.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | const useLocalStorage = (key, defaultValue = null) => {
4 | const [value, setValue] = useState(() => {
5 | try {
6 | const item = localStorage.getItem(key);
7 | if (item !== null) {
8 | return JSON.parse(item);
9 | }
10 | return defaultValue;
11 | } catch (error) {
12 | return defaultValue;
13 | }
14 | });
15 |
16 | useEffect(() => {
17 | localStorage.setItem(key, JSON.stringify(value));
18 | }, [key, value]);
19 |
20 | return [value, setValue];
21 | };
22 |
23 | export default useLocalStorage;
24 |
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer components {
6 | .btn {
7 | @apply py-1 px-2 shadow rounded bg-sky-300 hover:bg-sky-400 hover:text-white transition dark:bg-sky-600 dark:hover:bg-sky-800
8 | }
9 | }
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/src/index.js:
--------------------------------------------------------------------------------
1 | // Imports de React
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import App from './components/App';
5 | import './index.css'
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root'),
12 | );
--------------------------------------------------------------------------------
/17-18-24-proyecto-final-2/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}",],
3 | darkMode: 'class',
4 | theme: {
5 | extend: {
6 |
7 | },
8 | },
9 | plugins: [],
10 | }
11 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebase-shopping",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "firebase": "^9.6.1",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-hot-toast": "^2.1.1",
13 | "react-icons": "^4.3.1",
14 | "react-scripts": "5.0.0",
15 | "web-vitals": "^2.1.2"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "autoprefixer": "^10.4.0",
43 | "postcss": "^8.4.5",
44 | "tailwindcss": "^3.0.7"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/19-20-21-22-23-27-28-firebase-shopping/public/favicon.ico
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | // Give the service worker access to Firebase Messaging.
2 | // Note that you can only use Firebase Messaging here. Other Firebase libraries
3 | // are not available in the service worker.
4 | importScripts('https://www.gstatic.com/firebasejs/9.6.0/firebase-app-compat.js');
5 | importScripts('https://www.gstatic.com/firebasejs/9.6.0/firebase-messaging-compat.js');
6 | // Initialize the Firebase app in the service worker by passing in
7 | // your app's Firebase config object.
8 | // https://firebase.google.com/docs/web/setup#config-object
9 | firebase.initializeApp({
10 | apiKey: "AIzaSyD4w_NN9PlyMNGJSWxRvLEXhKPqt4sLPQQ",
11 | authDomain: "fir-shopping-d850e.firebaseapp.com",
12 | projectId: "fir-shopping-d850e",
13 | storageBucket: "fir-shopping-d850e.appspot.com",
14 | messagingSenderId: "941636314377",
15 | appId: "1:941636314377:web:2e6f89ca7f6860b1075fd8"
16 | });
17 | // Retrieve an instance of Firebase Messaging so that it can handle background
18 | // messages.
19 | const messaging = firebase.messaging();
20 |
21 |
22 | // If you would like to customize notifications that are received in the
23 | // background (Web app is closed or not in browser focus) then you should
24 | // implement this optional method.
25 | // Keep in mind that FCM will still show notification messages automatically
26 | // and you should use data messages for custom notifications.
27 | // For more info see:
28 | // https://firebase.google.com/docs/cloud-messaging/concept-options
29 | // messaging.onBackgroundMessage(function (payload) {
30 | // console.log('[firebase-messaging-sw.js] Received background message ', payload);
31 | // // Customize notification here
32 | // const notificationTitle = 'Background Message Title';
33 | // const notificationOptions = {
34 | // body: 'Background Message body.',
35 | // icon: '/firebase-logo.png'
36 | // };
37 |
38 | // self.registration.showNotification(notificationTitle,
39 | // notificationOptions);
40 | // });
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/19-20-21-22-23-27-28-firebase-shopping/public/logo192.png
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/19-20-21-22-23-27-28-firebase-shopping/public/logo512.png
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext } from 'react';
2 | import { app, messaging } from './firebase'
3 | import Header from './components/Header';
4 | import Home from './routes/Home';
5 | import Login from './routes/Login';
6 | import Register from './routes/Register';
7 | import Shopping from './routes/Shopping';
8 | import TaskList from './components/TaskList';
9 | import { Toaster, toast } from 'react-hot-toast';
10 | import { onMessage } from "firebase/messaging"
11 | import Footer from './components/Footer';
12 |
13 | export const AppContext = createContext(null);
14 |
15 | onMessage(messaging, payload => {
16 | // console.log('Nueva notificación en directo', payload);
17 | toast.custom(t => (
18 |
19 |
{payload.notification.title}
20 |
{payload.notification.body}
21 |
22 | )
23 | );
24 | })
25 |
26 | function App() {
27 | const [route, setRoute] = useState("home")
28 | const [user, setUser] = useState(null);
29 | console.log(user);
30 | return (
31 |
32 |
33 |
34 |
35 |
36 | {route === "home" && }
37 | {route === "login" && }
38 | {route === "register" && }
39 | {route === "shopping" && }
40 | {route === "tasklist" && }
41 | {user && Usuario logueado: {user.email}
}
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { IoHomeSharp } from "react-icons/io5"
3 | import { BsFillCartFill, BsList } from "react-icons/bs"
4 | import { AppContext } from '../App'
5 |
6 | const Footer = () => {
7 | const { setRoute } = useContext(AppContext);
8 | return (
9 |
20 | )
21 | }
22 |
23 | export default Footer
24 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { SiFirebase } from "react-icons/si";
3 | import { AppContext } from "../App";
4 | import { getAuth, signOut } from "firebase/auth";
5 | import toast from "react-hot-toast";
6 |
7 | const auth = getAuth();
8 |
9 | const Header = () => {
10 | const { setRoute, user, setUser } = useContext(AppContext);
11 | const hazLogout = () => {
12 | signOut(auth)
13 | .then(() => {
14 | setRoute("login");
15 | setUser(null);
16 | toast("Usuario ha hecho logout");
17 | })
18 | .catch((e) => console.error(e));
19 | };
20 | return (
21 |
22 | setRoute("home")}
25 | >
26 |
27 |
28 | FireShopping
29 |
30 |
31 |
32 | {user ? (
33 | <>
34 |
35 | >
36 | ) : (
37 | <>
38 |
44 |
47 | >
48 | )}
49 |
50 |
51 | );
52 | };
53 |
54 | export default Header;
55 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/firebase/index.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | // TODO: Add SDKs for Firebase products that you want to use
4 | // https://firebase.google.com/docs/web/setup#available-libraries
5 | import { getMessaging, getToken } from "firebase/messaging"
6 | import { getFirestore } from "firebase/firestore";
7 |
8 | const vapidKey = "BCvoTGdzUG6EqGo8w5LJksrzlAgbfFkDrCIEAt6KRzize45m6wmVCL6KVF-xSBx_1dFIjOaDRYvU3FF26dmP_60"
9 |
10 | // Your web app's Firebase configuration
11 | const firebaseConfig = {
12 | apiKey: "AIzaSyD4w_NN9PlyMNGJSWxRvLEXhKPqt4sLPQQ",
13 | authDomain: "fir-shopping-d850e.firebaseapp.com",
14 | projectId: "fir-shopping-d850e",
15 | storageBucket: "fir-shopping-d850e.appspot.com",
16 | messagingSenderId: "941636314377",
17 | appId: "1:941636314377:web:2e6f89ca7f6860b1075fd8"
18 | };
19 |
20 | // currentToken = "dzHLgtB6zWC7EXiRwhK5_5:APA91bHT4n3l0iEMye2pUuN4k2Hm0V3zIft-Pq1E9e0aavUNScNZ--Cb9uAGmTr5KS6OgXBv_j2b69AWvuCF93dhwZoEcwomdkVVOAEw1LzDXppm7SENLaCYBNYIChOZPOg5nGvu-O1e";
21 |
22 | // Initialize Firebase
23 | export const app = initializeApp(firebaseConfig);
24 | export const messaging = getMessaging();
25 | getToken(messaging, { vapidKey })
26 | .then(currentToken => {
27 | if (currentToken) {
28 | // Send the token to your server and update the UI if necessary
29 | // ...
30 | // console.log('currentToken', currentToken);
31 | sendTokenToServer(currentToken);
32 | } else {
33 | // Show permission request UI
34 | console.log('No registration token available. Request permission to generate one.');
35 | // ...
36 | }
37 | }).catch((err) => {
38 | console.log('An error occurred while retrieving token. ', err);
39 | // ...
40 | });
41 |
42 | const sendTokenToServer = token => {
43 | if (localStorage.getItem('tokenSentToServer')) return;
44 | // TO-DO: Implementar la lógica de que en el servidor se almacene el token
45 | localStorage.setItem('tokenSentToServer', '1');
46 | }
47 |
48 | export const db = getFirestore();
49 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/firebase/taskController.js:
--------------------------------------------------------------------------------
1 | // En este fichero crearemos toda la lógica de base de datos para las tasks
2 | import { db } from "./index";
3 | import { doc, collection, addDoc, setDoc, getDocs, deleteDoc } from "firebase/firestore";
4 |
5 | // CRUD - Create, Read, Update, Delete
6 |
7 | export const addNewTask = async task => {
8 | await addDoc(collection(db, 'tasks'), task);
9 | }
10 |
11 | export const getTasks = async () => {
12 | const querySnapshot = await getDocs(collection(db, 'tasks'));
13 | // console.log(querySnapshot);
14 | // querySnapshot.forEach(doc => {
15 | // console.log(doc.id, ' => ', doc.data())
16 | // })
17 |
18 | const tasks = querySnapshot.docs.map(doc => {
19 | return { ...doc.data(), id: doc.id }
20 | })
21 | // console.log(tasks);
22 | return tasks;
23 | }
24 |
25 | export const updateTask = async (task) => {
26 | // console.log(task);
27 | await setDoc(doc(db, 'tasks', task.id), {
28 | title: task.title,
29 | description: task.description
30 | })
31 | }
32 |
33 | export const deleteTask = async (id) => {
34 | await deleteDoc(doc(db, 'tasks', id));
35 | }
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/routes/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Home = () => {
4 | return (
5 |
6 |
Esta es la home
7 |
8 | )
9 | }
10 |
11 | export default Home
12 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/routes/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import {
3 | GoogleAuthProvider,
4 | getAuth,
5 | signInWithPopup,
6 | signInWithEmailAndPassword,
7 | } from "firebase/auth";
8 | import toast from "react-hot-toast";
9 | import { AppContext } from "../App";
10 |
11 | const provider = new GoogleAuthProvider();
12 | const auth = getAuth();
13 |
14 | const Login = () => {
15 | const [email, setEmail] = useState("");
16 | const [password, setPassword] = useState("");
17 | const { setUser } = useContext(AppContext);
18 | const hazLoginGoogle = () => {
19 | signInWithPopup(auth, provider)
20 | .then((result) => {
21 | // This gives you a Google Access Token. You can use it to access the Google API.
22 | const credential = GoogleAuthProvider.credentialFromResult(result);
23 | const token = credential.accessToken;
24 | // The signed-in user info.
25 | const user = result.user;
26 | // ...
27 | console.log("token", token);
28 | console.log("user", user);
29 | toast("Inicio de sesión válido");
30 | setUser(user);
31 | })
32 | .catch((error) => {
33 | // Handle Errors here.
34 | const errorCode = error.code;
35 | const errorMessage = error.message;
36 | // The email of the user's account used.
37 | const email = error.email;
38 | // The AuthCredential type that was used.
39 | const credential = GoogleAuthProvider.credentialFromError(error);
40 | // ...
41 | });
42 | };
43 | const hazLoginConEmail = (e) => {
44 | e.preventDefault();
45 | signInWithEmailAndPassword(auth, email, password)
46 | .then((userCredential) => {
47 | // Signed in
48 | const user = userCredential.user;
49 | // ...
50 | toast("Inicio de sesión válido");
51 | setUser(user);
52 | })
53 | .catch((error) => {
54 | const errorCode = error.code;
55 | const errorMessage = error.message;
56 | });
57 | };
58 | return (
59 |
60 |
61 | Este es el login
62 |
63 |
64 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default Login;
91 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/routes/Register.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
3 | import toast from "react-hot-toast";
4 | import { AppContext } from "../App";
5 |
6 | const auth = getAuth();
7 |
8 | const Register = () => {
9 | const [email, setEmail] = useState("");
10 | const [password, setPassword] = useState("");
11 | const { setRoute, setUser } = useContext(AppContext);
12 | const creaUsuario = () => {
13 | createUserWithEmailAndPassword(auth, email, password)
14 | .then((userCredential) => {
15 | // Signed in
16 | const user = userCredential.user;
17 | // ...
18 | console.log(user);
19 | toast(`¡Usuario ${email} registrado correctamente!`)
20 | // setEmail("");
21 | // setPassword("");
22 | setUser(user);
23 | setRoute("home");
24 | })
25 | .catch((error) => {
26 | const errorCode = error.code;
27 | const errorMessage = error.message;
28 | // ..
29 | });
30 | };
31 | const handleSubmit = e => {
32 | e.preventDefault();
33 | creaUsuario();
34 | }
35 | return (
36 |
37 |
¡Regístrate para obtener acceso a la mejor app del mundo!
38 |
43 |
44 | );
45 | };
46 |
47 | export default Register;
48 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/src/routes/Shopping.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Shopping = () => {
4 | return (
5 |
6 | Bienvenid@ a la tienda online FireShopping 🔥
7 |
8 | )
9 | }
10 |
11 | export default Shopping
12 |
--------------------------------------------------------------------------------
/19-20-21-22-23-27-28-firebase-shopping/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | }
8 |
--------------------------------------------------------------------------------
/25-metodologias/README.md:
--------------------------------------------------------------------------------
1 | # Metodologías de desarrollo de Software
2 |
3 | En la lección 25 analizamos las metodologías más utilizadas a la hora de desarrollar aplicaciones, o software especializado.
4 |
5 | ## Arquitectura Hexagonal
6 |
7 | 1. ¿Qué es?
8 | 2. ¿En qué consiste?
9 | 3. Ejemplos
10 |
11 | ## Arquitectura Limpia en React
12 |
13 | 1. ¿Qué es?
14 | 2. ¿En qué consiste?
15 | 3. Ejemplos
16 |
17 | ## Atomic Design
18 |
19 | 1. ¿Qué es?
20 | 2. Ejemplos
21 | 3. Desarrollo de un proyecto basándonos en BDD utilizando Atomic Design
22 |
23 | ### Tarea Lección 25
24 |
25 | Termina de crear una plantilla para nuestra aplicación, **utilizando sólo los componentes dentro de la carpeta organismos**
26 |
--------------------------------------------------------------------------------
/25-metodologias/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leccion-25",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "firebase": "^9.6.1",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-scripts": "5.0.0",
13 | "web-vitals": "^2.1.2"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app",
24 | "react-app/jest"
25 | ]
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/25-metodologias/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/25-metodologias/public/favicon.ico
--------------------------------------------------------------------------------
/25-metodologias/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/25-metodologias/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/25-metodologias/public/logo192.png
--------------------------------------------------------------------------------
/25-metodologias/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/25-metodologias/public/logo512.png
--------------------------------------------------------------------------------
/25-metodologias/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/25-metodologias/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/25-metodologias/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/25-metodologias/src/App.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import './App.css';
3 | import { getNotes } from './controllers/frontController';
4 | import AtomicDesign from './front/AtomicDesign';
5 | import Notes from './front/Notes';
6 |
7 | function App() {
8 | const [notes, setNotes] = useState([])
9 | useEffect(() => {
10 | getNotes()
11 | .then(n => setNotes(n))
12 | .catch(e => console.error(e))
13 | }, [])
14 | return (
15 |
16 |
Bienvenid@ a la aplicación de notas
17 | {/*
*/}
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Button.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | border: none;
3 | padding: 10px 20px;
4 | border-radius: 10px;
5 | font-size: 14pt;
6 | cursor: pointer;
7 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
8 | margin: 10px;
9 | }
10 |
11 | .btn.red {
12 | background-color: red;
13 | color: white;
14 | }
15 | .btn.blue {
16 | background-color: blue;
17 | color: white;
18 | }
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Button.css'
3 |
4 | const Button = ({ color = "blue" }) => {
5 | return (<>
6 |
9 | >
10 | )
11 | }
12 |
13 | export default Button
14 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Container.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | align-items: center;
4 | background-color: rgb(158, 255, 242);
5 | min-width: 20px;
6 | min-height: 20px;
7 | margin: 20px;
8 | padding: 20px;
9 | border-radius: 15px;
10 | }
11 |
12 | .container.header {
13 | background-color: #eee;
14 | border-radius: 0px;
15 | margin: 0px;
16 | }
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Container.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Container.css'
3 |
4 | const Container = ({ children, type = "" }) => {
5 | return (
6 |
7 | {children}
8 |
9 | )
10 | }
11 |
12 | export default Container
13 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Input.css:
--------------------------------------------------------------------------------
1 | .input {
2 | outline: none;
3 | padding: 10px 20px;
4 | font-size: 14pt;
5 | border-radius: 5px;
6 | border: none;
7 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2)
8 | }
--------------------------------------------------------------------------------
/25-metodologias/src/components/01_atomos/Input.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Input.css'
3 |
4 | const Input = () => {
5 | return (
6 |
7 | )
8 | }
9 |
10 | export default Input
11 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/02_moleculas/FormAzul.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Button from "../01_atomos/Button"
3 | import Container from "../01_atomos/Container"
4 | import Input from "../01_atomos/Input"
5 |
6 | const FormAzul = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default FormAzul
18 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/02_moleculas/FormRojo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Button from "../01_atomos/Button"
3 | import Container from "../01_atomos/Container"
4 | import Input from "../01_atomos/Input"
5 |
6 | const FormRojo = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default FormRojo
18 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/02_moleculas/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Container from "../01_atomos/Container";
3 | import Button from "../01_atomos/Button";
4 |
5 | const Header = ({ children }) => {
6 | return (
7 |
8 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | export default Header;
15 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/03_organismos/Formulario.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormRojo from '../02_moleculas/FormRojo'
3 | import FormAzul from '../02_moleculas/FormAzul'
4 |
5 | const Formulario = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | export default Formulario
15 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/03_organismos/OrgHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormRojo from '../02_moleculas/FormRojo'
3 | import Header from '../02_moleculas/Header'
4 |
5 | const OrgHeader = () => {
6 | return (
7 |
10 | )
11 | }
12 |
13 | export default OrgHeader
14 |
--------------------------------------------------------------------------------
/25-metodologias/src/components/04_plantillas/Plantilla.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Formulario from '../03_organismos/Formulario'
3 |
4 | const Plantilla = () => {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
12 | export default Plantilla
13 |
--------------------------------------------------------------------------------
/25-metodologias/src/controllers/dbController.js:
--------------------------------------------------------------------------------
1 | // Base de datos en Firestore
2 | // Import the functions you need from the SDKs you need
3 | import { initializeApp } from "firebase/app";
4 | // TODO: Add SDKs for Firebase products that you want to use
5 | // https://firebase.google.com/docs/web/setup#available-libraries
6 | import { getFirestore, getDocs, collection } from "firebase/firestore"
7 |
8 | // Your web app's Firebase configuration
9 | const firebaseConfig = {
10 | apiKey: "AIzaSyD4Uo1iIzVhVZ9F3PFD0kkLfOgx71uocVY",
11 | authDomain: "ob-tasklist-firebase.firebaseapp.com",
12 | projectId: "ob-tasklist-firebase",
13 | storageBucket: "ob-tasklist-firebase.appspot.com",
14 | messagingSenderId: "45362721152",
15 | appId: "1:45362721152:web:eb9a450e4b347e5fcf1165"
16 | };
17 |
18 | // Initialize Firebase
19 | export const app = initializeApp(firebaseConfig);
20 | export const db = getFirestore();
21 |
22 | export const getNotesFromDB = async () => {
23 | const querySnapshot = await getDocs(collection(db, 'notes'));
24 | const notes = querySnapshot.docs.map(doc => {
25 | return { ...doc.data(), id: doc.id }
26 | })
27 | return notes;
28 | }
--------------------------------------------------------------------------------
/25-metodologias/src/controllers/dbMockController.js:
--------------------------------------------------------------------------------
1 | export const getNotesFromDB = () => {
2 | const notes = [
3 | { note: "Primera nota de mock" },
4 | { note: "Segunda nota de mock" },
5 | { note: "Tercera nota de mock" },
6 | { note: "Cuarta nota de mock" },
7 | ]
8 | return notes;
9 | }
--------------------------------------------------------------------------------
/25-metodologias/src/controllers/frontController.js:
--------------------------------------------------------------------------------
1 | import { getNotesFromDB } from './dbController';
2 | // import { getNotesFromDB } from './dbMockController';
3 |
4 |
5 |
6 | export const getNotes = async () => {
7 | const notes = await getNotesFromDB();
8 | return notes;
9 | }
--------------------------------------------------------------------------------
/25-metodologias/src/front/AtomicDesign.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Button from '../components/01_atomos/Button'
3 | import Input from '../components/01_atomos/Input'
4 | import Container from '../components/01_atomos/Container'
5 | import FormAzul from '../components/02_moleculas/FormAzul'
6 | import FormRojo from '../components/02_moleculas/FormRojo'
7 | import Formulario from '../components/03_organismos/Formulario'
8 | import Header from '../components/02_moleculas/Header'
9 | import OrgHeader from '../components/03_organismos/OrgHeader'
10 |
11 | const AtomicDesign = () => {
12 | return (
13 |
14 |
Vamos a diseñar nuestros átomos
15 |
Átomos
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Moléculas
23 |
24 |
25 |
26 |
27 |
28 |
Organismos
29 |
30 |
31 |
32 |
33 |
Plantillas
34 |
35 |
36 |
37 |
Páginas
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default AtomicDesign
46 |
--------------------------------------------------------------------------------
/25-metodologias/src/front/Notes.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Notes = ({ notes }) => {
4 | return (
5 |
6 | Esta es la lista de notas pendientes
7 |
8 | {notes.map((note, i) => (
9 | - {note.note}
10 | ))}
11 |
12 |
13 | );
14 | };
15 |
16 | export default Notes;
17 |
--------------------------------------------------------------------------------
/25-metodologias/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
--------------------------------------------------------------------------------
/25-metodologias/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
--------------------------------------------------------------------------------
/26-react-ts/README.md:
--------------------------------------------------------------------------------
1 | # ReactJS con TypeScript
2 | En la lección 26 analizaremos el comportamiento de ReactJS junto con las mejoras que ofrece TypeScript
3 |
4 | ## Lección 26 - React JS con TypeScript
5 | 1. Introducción a TS
6 | 2. Diferencias y Ventajas de TS frente a JS
7 | 3. Ejemplos simples de comportamiento de TypeScript en comparación con JavaScript
8 | 4. Creamos una app con el template de TypeScript
9 | 5. Trabajamos con TypeScript en un proyecto de ReactJS
10 | ### Tarea Lección 26
11 | Crea un componente con Typescript para cambiar el modo de nuestra aplicación (light mode <-> dark mode)
12 | (Pista: Revisa la lección 17)
13 |
--------------------------------------------------------------------------------
/26-react-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ts",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.1",
7 | "@testing-library/react": "^12.1.2",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.0.3",
10 | "@types/node": "^16.11.17",
11 | "@types/react": "^17.0.38",
12 | "@types/react-dom": "^17.0.11",
13 | "axios": "^0.24.0",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "react-icons": "^4.3.1",
17 | "react-scripts": "5.0.0",
18 | "typescript": "^4.5.4",
19 | "web-vitals": "^2.1.2"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "devDependencies": {
46 | "autoprefixer": "^10.4.0",
47 | "postcss": "^8.4.5",
48 | "tailwindcss": "^3.0.8"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/26-react-ts/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/26-react-ts/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/26-react-ts/public/favicon.ico
--------------------------------------------------------------------------------
/26-react-ts/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/26-react-ts/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/26-react-ts/public/logo192.png
--------------------------------------------------------------------------------
/26-react-ts/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Open-Bootcamp/React-JS-Avanzado/f421d55bd336e4c9f5efec942a4708b6a918353b/26-react-ts/public/logo512.png
--------------------------------------------------------------------------------
/26-react-ts/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/26-react-ts/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/26-react-ts/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/26-react-ts/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render();
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/26-react-ts/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 | import Header from "./components/Header";
4 | import ProductList from "./components/ProductList";
5 | import Title from "./components/Title";
6 |
7 | function App(): JSX.Element {
8 | return (
9 |
16 | );
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/26-react-ts/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BsCart } from "react-icons/bs";
3 |
4 | interface HeaderProps {
5 | name: string;
6 | }
7 |
8 | const Header = ({ name }: HeaderProps): JSX.Element => {
9 | return (
10 |
11 |
12 |
13 | {name}
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Header;
21 |
--------------------------------------------------------------------------------
/26-react-ts/src/components/Product.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface ProductProps {
4 | image: string;
5 | title: string;
6 | price: number;
7 | }
8 |
9 | const Product = ({ image, title, price }: ProductProps): JSX.Element => {
10 | return (
11 |
12 |

13 |
{title}
14 |
{price} €
15 |
16 | );
17 | };
18 |
19 | export default Product;
20 |
--------------------------------------------------------------------------------
/26-react-ts/src/components/ProductList.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { getProducts } from "../controllers/productController";
3 | import Product from "./Product";
4 |
5 | interface ProductInterface {
6 | category: string;
7 | description: string;
8 | id: number;
9 | image: string;
10 | price: number;
11 | rating: object;
12 | title: string;
13 | }
14 |
15 | const ProductList = (): JSX.Element => {
16 | const [productList, setProductList] = useState([]);
17 |
18 | useEffect(() => {
19 | getProducts()
20 | .then((r) => setProductList(r.data))
21 | .catch((e) => console.error(e));
22 | }, []);
23 |
24 | return (
25 | <>
26 | {productList.length === 0 ? (
27 | "No hay productos"
28 | ) : (
29 |
30 | {productList.map(
31 | (product: ProductInterface, i: number): JSX.Element => (
32 |
38 | )
39 | )}
40 |
41 | )}
42 | >
43 | );
44 | };
45 |
46 | export default ProductList;
47 |
--------------------------------------------------------------------------------
/26-react-ts/src/components/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Title = ({ title }: { title: string }): JSX.Element => {
4 | return {title}
;
5 | };
6 |
7 | export default Title;
8 |
--------------------------------------------------------------------------------
/26-react-ts/src/controllers/productController.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosResponse } from "axios";
2 |
3 | export const getProducts = async () => {
4 | const res: AxiosResponse = await axios.get(
5 | "https://fakestoreapi.com/products"
6 | );
7 | return res;
8 | };
9 |
--------------------------------------------------------------------------------
/26-react-ts/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/26-react-ts/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/26-react-ts/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/26-react-ts/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/26-react-ts/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/26-react-ts/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/26-react-ts/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | }
8 |
--------------------------------------------------------------------------------
/26-react-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------