├── .eslintignore ├── projects ├── 07-midu-router │ ├── src │ │ ├── App.css │ │ ├── index.css │ │ ├── utils │ │ │ ├── getCurrentPath.js │ │ │ └── consts.js │ │ ├── components │ │ │ ├── Route.jsx │ │ │ ├── Link.jsx │ │ │ └── Router.jsx │ │ ├── index.jsx │ │ ├── main.jsx │ │ ├── pages │ │ │ ├── Search.jsx │ │ │ ├── Home.jsx │ │ │ ├── 404.jsx │ │ │ └── About.jsx │ │ ├── App.jsx │ │ └── Router.test.jsx │ ├── lib │ │ ├── Route.js │ │ ├── index.js │ │ ├── Link.js │ │ └── Router.js │ ├── .npmignore │ ├── vite.config.js │ ├── index.html │ ├── README.md │ ├── .swcrc │ ├── package.json │ └── public │ │ └── vite.svg ├── 08-todo-app-typescript │ ├── src │ │ ├── index.css │ │ ├── consts.ts │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ ├── components │ │ │ ├── Copyright.tsx │ │ │ ├── Copyright.css │ │ │ ├── Header.tsx │ │ │ ├── CreateTodo.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Filters.tsx │ │ │ ├── Todos.tsx │ │ │ └── Todo.tsx │ │ ├── types.d.ts │ │ ├── services │ │ │ └── todos.ts │ │ ├── App.tsx │ │ └── mocks │ │ │ └── todos.ts │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg ├── 12-comments-react-query │ ├── src │ │ ├── App.css │ │ ├── vite-env.d.ts │ │ ├── index.css │ │ ├── main.tsx │ │ ├── components │ │ │ ├── Form.tsx │ │ │ └── Results.tsx │ │ └── service │ │ │ └── comments.ts │ ├── postcss.config.js │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── tailwind.config.js │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg ├── 09-google-translate-clone │ ├── .env │ ├── src │ │ ├── vite-env.d.ts │ │ ├── App.css │ │ ├── constants.ts │ │ ├── main.tsx │ │ ├── App.test.tsx │ │ ├── types.d.ts │ │ ├── hooks │ │ │ └── useDebounce.ts │ │ ├── components │ │ │ ├── Icons.tsx │ │ │ ├── LanguageSelector.tsx │ │ │ └── TextArea.tsx │ │ └── index.css │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── .gitignore │ ├── index.html │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg ├── 10-crud-redux │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.css │ │ ├── hooks │ │ │ ├── store.ts │ │ │ └── useUserActions.ts │ │ ├── main.tsx │ │ ├── App.tsx │ │ ├── App.css │ │ ├── store │ │ │ ├── index.ts │ │ │ └── users │ │ │ │ └── slice.ts │ │ └── components │ │ │ └── CreateNewUser.tsx │ ├── postcss.config.js │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── rome.json │ ├── tailwind.config.js │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg ├── 11-typescript-prueba-tecnica │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ ├── App.css │ │ ├── index.css │ │ ├── components │ │ │ └── UsersList.tsx │ │ └── types.d.ts │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── public │ │ └── vite.svg ├── 14-hacker-news-prueba-tecnica │ ├── src │ │ ├── vite-env.d.ts │ │ ├── main.tsx │ │ ├── components │ │ │ ├── Header.tsx │ │ │ ├── Header.css.ts │ │ │ ├── CommentLoader.tsx │ │ │ ├── StoryLoader.tsx │ │ │ ├── Story.css.ts │ │ │ ├── ListOfComments.tsx │ │ │ └── Story.tsx │ │ ├── index.css │ │ ├── App.tsx │ │ ├── services │ │ │ └── hacker-news.ts │ │ ├── App.css │ │ ├── utils │ │ │ └── getRelativeTime.ts │ │ └── pages │ │ │ ├── Detail.tsx │ │ │ └── TopStories.tsx │ ├── public │ │ ├── logo.gif │ │ └── vite.svg │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── 13-javascript-quiz-con-zustand │ ├── src │ │ ├── vite-env.d.ts │ │ ├── services │ │ │ └── questions.ts │ │ ├── types.d.ts │ │ ├── Start.tsx │ │ ├── hooks │ │ │ └── useQuestionsData.ts │ │ ├── main.tsx │ │ ├── Footer.tsx │ │ ├── Results.tsx │ │ ├── JavaScriptLogo.tsx │ │ ├── App.css │ │ ├── index.css │ │ └── App.tsx │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── package.json │ └── public │ │ └── vite.svg ├── 06-shopping-cart │ ├── src │ │ ├── config.js │ │ ├── components │ │ │ ├── Header.jsx │ │ │ ├── Filters.css │ │ │ ├── Footer.jsx │ │ │ ├── Footer.css │ │ │ ├── Products.css │ │ │ ├── Cart.css │ │ │ ├── Cart.jsx │ │ │ ├── Products.jsx │ │ │ └── Filters.jsx │ │ ├── main.jsx │ │ ├── hooks │ │ │ ├── useCart.js │ │ │ └── useFilters.js │ │ ├── context │ │ │ ├── filters.jsx │ │ │ └── cart.jsx │ │ ├── App.jsx │ │ ├── index.css │ │ └── reducers │ │ │ └── cart.js │ ├── vite.config.js │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── README.md │ └── public │ │ └── vite.svg ├── 11b-typescript-prueba-tecnica-with-react-query │ ├── src │ │ ├── vite-env.d.ts │ │ ├── components │ │ │ ├── Results.tsx │ │ │ └── UsersList.tsx │ │ ├── App.css │ │ ├── main.tsx │ │ ├── services │ │ │ └── users.ts │ │ ├── hooks │ │ │ └── useUsers.ts │ │ ├── index.css │ │ └── types.d.ts │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── index.html │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── public │ │ └── vite.svg ├── 05-react-buscador-peliculas │ ├── src │ │ ├── mocks │ │ │ ├── no-results.json │ │ │ └── with-results.json │ │ ├── main.jsx │ │ ├── services │ │ │ └── movies.js │ │ ├── components │ │ │ └── Movies.jsx │ │ ├── App.css │ │ └── hooks │ │ │ └── useMovies.js │ ├── vite.config.js │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── README.md │ └── public │ │ └── vite.svg ├── 04-react-prueba-tecnica │ ├── vite.config.js │ ├── src │ │ ├── App.css │ │ ├── services │ │ │ └── facts.js │ │ ├── Components │ │ │ └── Otro.jsx │ │ ├── hooks │ │ │ ├── useCatFact.js │ │ │ └── useCatImage.js │ │ └── App.jsx │ ├── main.jsx │ ├── counter.js │ ├── README.md │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── tests │ │ └── example.spec.js │ ├── javascript.svg │ ├── public │ │ └── vite.svg │ └── style.css ├── 02-tic-tac-toe │ ├── vite.config.js │ ├── src │ │ ├── constants.js │ │ ├── main.jsx │ │ ├── components │ │ │ ├── Square.jsx │ │ │ └── WinnerModal.jsx │ │ ├── logic │ │ │ ├── storage │ │ │ │ └── index.js │ │ │ └── board.js │ │ └── App.css │ ├── .gitignore │ ├── index.html │ ├── package.json │ └── public │ │ └── vite.svg ├── 03-mouse-follower │ ├── vite.config.js │ ├── src │ │ ├── main.jsx │ │ ├── App.css │ │ ├── index.css │ │ └── App.jsx │ ├── .gitignore │ ├── index.html │ ├── package.json │ └── public │ │ └── vite.svg └── 01-twitter-follow-card │ ├── vite.config.js │ ├── src │ ├── index.css │ ├── main.jsx │ ├── App.jsx │ ├── TwitterFollowCard.jsx │ └── App.css │ ├── .gitignore │ ├── index.html │ ├── package.json │ └── public │ └── vite.svg ├── .gitignore ├── pnpm-workspace.yaml ├── package.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /projects/07-midu-router/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/.env: -------------------------------------------------------------------------------- 1 | VITE_OPENAI_API_KEY="TU API KEY AQUI" -------------------------------------------------------------------------------- /projects/10-crud-redux/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/07-midu-router/lib/Route.js: -------------------------------------------------------------------------------- 1 | export function Route({path,Component}){return null} -------------------------------------------------------------------------------- /projects/12-comments-react-query/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/10-crud-redux/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | .DS_Store 4 | projects/**/.DS_Store 5 | projects/**/dist -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # todos los proyectos dentro de projects son paquetes 3 | - 'projects/**' -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/config.js: -------------------------------------------------------------------------------- 1 | export const IS_DEVELOPMENT = process.env.NODE_ENV !== 'production' 2 | -------------------------------------------------------------------------------- /projects/07-midu-router/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | public 3 | index.html 4 | pnpm-lock.yaml 5 | vite.config.js 6 | .swcrc -------------------------------------------------------------------------------- /projects/07-midu-router/lib/index.js: -------------------------------------------------------------------------------- 1 | export{Router}from"./Router";export{Link}from"./Link";export{Route}from"./Route"; -------------------------------------------------------------------------------- /projects/07-midu-router/src/utils/getCurrentPath.js: -------------------------------------------------------------------------------- 1 | export const getCurrentPath = () => window.location.pathname 2 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/components/Route.jsx: -------------------------------------------------------------------------------- 1 | export function Route ({ path, Component }) { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/src/mocks/no-results.json: -------------------------------------------------------------------------------- 1 | { 2 | "Response": "False", 3 | "Error": "Movie not found!" 4 | } -------------------------------------------------------------------------------- /projects/09-google-translate-clone/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 800px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | } 6 | -------------------------------------------------------------------------------- /projects/10-crud-redux/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/public/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midudev/aprendiendo-react/master/projects/14-hacker-news-prueba-tecnica/public/logo.gif -------------------------------------------------------------------------------- /projects/07-midu-router/src/index.jsx: -------------------------------------------------------------------------------- 1 | export { Router } from './components/Router' 2 | export { Link } from './components/Link' 3 | export { Route } from './components/Route' 4 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/utils/consts.js: -------------------------------------------------------------------------------- 1 | export const EVENTS = { 2 | PUSHSTATE: 'pushstate', 3 | POPSTATE: 'popstate' 4 | } 5 | 6 | export const BUTTONS = { 7 | primary: 0 8 | } 9 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()] 6 | }) 7 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/src/App.css: -------------------------------------------------------------------------------- 1 | main { 2 | display: flex; 3 | flex-direction: column; 4 | place-items: center; 5 | max-width: 800px; 6 | margin: 0 auto; 7 | font-family: system-ui; 8 | } -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/main.jsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { App } from './src/App.jsx' 3 | 4 | const root = createRoot(document.getElementById('app')) 5 | 6 | root.render() 7 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/10-crud-redux/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /projects/03-mouse-follower/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/services/questions.ts: -------------------------------------------------------------------------------- 1 | export const getAllQuestions = async () => { 2 | const res = await fetch('http://localhost:5173/data.json') 3 | const json = await res.json() 4 | return json 5 | } 6 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | ) 9 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { Filters } from './Filters.jsx' 2 | 3 | export function Header () { 4 | return ( 5 |
6 |

React Shop 🛒

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const TODO_FILTERS = { 2 | ALL: 'all', 3 | ACTIVE: 'active', 4 | COMPLETED: 'completed' 5 | } as const 6 | 7 | export const KEY_CODES = { 8 | ENTER: 13, 9 | ESCAPE: 27 10 | } as const 11 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/src/components/Results.tsx: -------------------------------------------------------------------------------- 1 | import { useUsers } from '../hooks/useUsers' 2 | 3 | export const Results = () => { 4 | const { users } = useUsers() 5 | 6 | return

Results {users.length}

7 | } 8 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | ) 9 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /projects/10-crud-redux/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/components/Filters.css: -------------------------------------------------------------------------------- 1 | .filters { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-between; 5 | font-size: 14px; 6 | font-weight: 700; 7 | } 8 | 9 | .filters > div { 10 | display: flex; 11 | gap: 1rem; 12 | } -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | ) 9 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Question { 2 | id: number 3 | question: string 4 | code: string 5 | answers: string[] 6 | correctAnswer: number 7 | userSelectedAnswer?: number 8 | isCorrectUserAnswer?: boolean 9 | } 10 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_API_BIN_KEY: string 5 | // more env variables... 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv 10 | } 11 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | './index.html', 5 | './src/**/*.{js,ts,jsx,tsx}' 6 | ], 7 | theme: { 8 | extend: {} 9 | }, 10 | plugins: [] 11 | } 12 | -------------------------------------------------------------------------------- /projects/07-midu-router/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | test: { 8 | environment: 'happy-dom' 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | background: #222; 4 | font-family: system-ui; 5 | display: grid; 6 | place-content: center; 7 | min-height: 100vh; 8 | } 9 | 10 | .App { 11 | display: flex; 12 | flex-direction: column; 13 | gap: 8px; 14 | } -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { App } from './App.jsx' 4 | import './index.css' 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')) 7 | 8 | root.render( 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/constants.js: -------------------------------------------------------------------------------- 1 | export const TURNS = { // turnos 2 | X: '❌', 3 | O: '⚪' 4 | } 5 | 6 | export const WINNER_COMBOS = [ 7 | [0, 1, 2], 8 | [3, 4, 5], 9 | [6, 7, 8], 10 | [0, 3, 6], 11 | [1, 4, 7], 12 | [2, 5, 8], 13 | [0, 4, 8], 14 | [2, 4, 6] 15 | ] 16 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | import 'todomvc-app-css/index.css' 5 | 6 | ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement) 8 | .render( 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/src/services/facts.js: -------------------------------------------------------------------------------- 1 | const CAT_ENDPOINT_RANDOM_FACT = 'https://catfact.ninja/fact' 2 | 3 | export const getRandomFact = async () => { 4 | const res = await fetch(CAT_ENDPOINT_RANDOM_FACT) 5 | const data = await res.json() 6 | const { fact } = data 7 | return fact 8 | } 9 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/components/Copyright.tsx: -------------------------------------------------------------------------------- 1 | import './Copyright.css' 2 | 3 | export const Copyright: React.FC = () => ( 4 |
5 |

Curso de React desde cero ⚛️ - @midudev

6 |
Creando un TODO con TypeScript
7 |
8 | ) 9 | -------------------------------------------------------------------------------- /projects/03-mouse-follower/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SUPPORTED_LANGUAGES = { 2 | en: 'English', 3 | es: 'Español', 4 | de: 'Deutsch' 5 | } 6 | 7 | export const VOICE_FOR_LANGUAGE = { 8 | en: 'en-GB', 9 | es: 'es-MX', 10 | de: 'de-DE' 11 | } 12 | 13 | export const AUTO_LANGUAGE = 'auto' 14 | -------------------------------------------------------------------------------- /projects/10-crud-redux/rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/rome/configuration_schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true 10 | } 11 | }, 12 | "formatter": { 13 | "enabled": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/counter.js: -------------------------------------------------------------------------------- 1 | export function setupCounter (element) { 2 | let counter = 0 3 | const setCounter = (count) => { 4 | counter = count 5 | element.innerHTML = `count is ${counter}` 6 | } 7 | element.addEventListener('click', () => setCounter(counter + 1)) 8 | setCounter(0) 9 | } 10 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/pages/Search.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | export default function SearchPage ({ routeParams }) { 4 | useEffect(() => { 5 | document.title = `Has buscado ${routeParams.query}` 6 | }, []) 7 | 8 | return ( 9 |

Has buscado {routeParams.query}

10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/main.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App' 3 | import { FiltersProvider } from './context/filters.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite' 3 | import react from '@vitejs/plugin-react-swc' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | test: { 9 | environment:'happy-dom' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), vanillaExtractPlugin()], 8 | }) 9 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/src/Components/Otro.jsx: -------------------------------------------------------------------------------- 1 | import { useCatImage } from '../hooks/useCatImage.js' 2 | 3 | export function Otro () { 4 | const { imageUrl } = useCatImage({ fact: 'cat' }) 5 | console.log(imageUrl) 6 | 7 | return ( 8 | <> 9 | {imageUrl && } 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from '../components/Link' 2 | 3 | export default function HomePage () { 4 | return ( 5 | <> 6 |

Home

7 |

Esta es una página de ejemplo para crear un React Router desde cero

8 | Ir a Sobre nosotros 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /projects/10-crud-redux/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | 7 | // path tremor node_modules 8 | "./node_modules/@tremor/**/*.{js,ts,jsx,tsx}", 9 | ], 10 | theme: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | }; 15 | -------------------------------------------------------------------------------- /projects/10-crud-redux/src/hooks/store.ts: -------------------------------------------------------------------------------- 1 | import type { TypedUseSelectorHook } from "react-redux"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import type { AppDispatch, RootState } from "../store"; 4 | 5 | export const useAppSelector: TypedUseSelectorHook = useSelector; 6 | export const useAppDispatch: () => AppDispatch = useDispatch; 7 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import './Footer.css' 2 | 3 | export function Footer () { 4 | // const { filters } = useFilters() 5 | 6 | return ( 7 |
8 |

Prueba técnica de React ⚛️ - @midudev

9 |
Shopping Cart con useContext & useReducer
10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/hooks/useCart.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { CartContext } from '../context/cart.jsx' 3 | 4 | export const useCart = () => { 5 | const context = useContext(CartContext) 6 | 7 | if (context === undefined) { 8 | throw new Error('useCart must be used within a CartProvider') 9 | } 10 | 11 | return context 12 | } 13 | -------------------------------------------------------------------------------- /projects/10-crud-redux/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import App from "./App"; 3 | import "./index.css"; 4 | 5 | import { Provider } from "react-redux"; 6 | import { store } from "./store"; 7 | 8 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 9 | 10 | 11 | , 12 | ); 13 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { header, link } from './Header.css' 2 | 3 | export const Header = () => { 4 | return ( 5 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/10-crud-redux/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/components/Square.jsx: -------------------------------------------------------------------------------- 1 | export const Square = ({ children, isSelected, updateBoard, index }) => { 2 | const className = `square ${isSelected ? 'is-selected' : ''}` 3 | 4 | const handleClick = () => { 5 | updateBoard(index) 6 | } 7 | 8 | return ( 9 |
10 | {children} 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /projects/03-mouse-follower/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/README.md: -------------------------------------------------------------------------------- 1 | # Prueba técnica para Juniors y Trainees de React en Live Coding. 2 | 3 | APIs: 4 | 5 | - Facts Random: https://catfact.ninja/fact 6 | - Imagen random: https://cataas.com/cat/says/hello 7 | 8 | - Recupera un hecho aleatorio de gatos de la primera API 9 | - Recuperar la primera palabra del hecho 10 | - Muestra una imagen de un gato con la primera palabra. -------------------------------------------------------------------------------- /projects/06-shopping-cart/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/logic/storage/index.js: -------------------------------------------------------------------------------- 1 | export const saveGameToStorage = ({ board, turn }) => { 2 | // guardar aqui partida 3 | window.localStorage.setItem('board', JSON.stringify(board)) 4 | window.localStorage.setItem('turn', turn) 5 | } 6 | 7 | export const resetGameStorage = () => { 8 | window.localStorage.removeItem('board') 9 | window.localStorage.removeItem('turn') 10 | } 11 | -------------------------------------------------------------------------------- /projects/10-crud-redux/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { ListOfUsers } from "./components/ListOfUsers"; 3 | import { CreateNewUser } from './components/CreateNewUser'; 4 | import { Toaster } from 'sonner' 5 | 6 | function App() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { TODO_FILTERS } from './consts' 2 | 3 | export interface Todo { 4 | id: string 5 | title: string 6 | completed: boolean 7 | } 8 | 9 | export type TodoId = Pick 10 | export type TodoTitle = Pick 11 | 12 | export type FilterValue = typeof TODO_FILTERS[keyof typeof TODO_FILTERS] 13 | 14 | export type TodoList = Todo[] 15 | -------------------------------------------------------------------------------- /projects/07-midu-router/src/pages/404.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from '../components/Link' 2 | 3 | export default function Page404 () { 4 | return ( 5 | <> 6 |
7 |

This is NOT fine

8 | Gif del perro de This is Fine quemándose vivo 9 |
10 | Volver a la Home 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | 27 | package-lock.json -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/components/Header.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@vanilla-extract/css' 2 | 3 | export const header = style({ 4 | alignItems: 'center', 5 | borderBottom: '1px solid #eee', 6 | display: 'flex', 7 | gap: '16px', 8 | padding: '12px 32px' 9 | }) 10 | 11 | export const link = style({ 12 | color: '#374151', 13 | fontSize: '18px', 14 | margin: 0, 15 | textDecoration: 'none' 16 | }) 17 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | font-synthesis: none; 7 | text-rendering: optimizeLegibility; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-text-size-adjust: 100%; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/03-mouse-follower/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/10-crud-redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | import { QueryClientProvider, QueryClient } from '@tanstack/react-query' 5 | 6 | const queryClient = new QueryClient() 7 | 8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 9 | 10 | 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JavaScript Quiz 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | /test-results/ 26 | /playwright-report/ 27 | /playwright/.cache/ 28 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/components/Footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | position: fixed; 3 | left: 16px; 4 | bottom: 16px; 5 | text-align: left; 6 | background: rgba(0, 0, 0, .7); 7 | padding: 8px 24px; 8 | border-radius: 32px; 9 | opacity: .95; 10 | backdrop-filter: blur(8px); 11 | } 12 | 13 | .footer span { 14 | font-size: 14px; 15 | color: #09f; 16 | opacity: .8; 17 | } 18 | 19 | .footer h4, .footer h5 { 20 | margin: 0; 21 | display: flex; 22 | } -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/src/hooks/useCatFact.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { getRandomFact } from '../services/facts.js' 3 | 4 | export function useCatFact () { 5 | const [fact, setFact] = useState() 6 | 7 | const refreshFact = () => { 8 | getRandomFact().then(newFact => setFact(newFact)) 9 | } 10 | 11 | // para recuperar la cita al cargar la página 12 | useEffect(refreshFact, []) 13 | 14 | return { fact, refreshFact } 15 | } 16 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hacker News - Prueba Técnica USA de Frontend 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/components/Copyright.css: -------------------------------------------------------------------------------- 1 | .copyright { 2 | filter: invert(1); 3 | border: 1px solid white; 4 | color: white; 5 | position: fixed; 6 | left: 16px; 7 | bottom: 16px; 8 | text-align: left; 9 | padding: 8px 24px; 10 | border-radius: 32px; 11 | opacity: .95; 12 | } 13 | 14 | .copyright span { 15 | font-size: 14px; 16 | color: #09f; 17 | opacity: .8; 18 | } 19 | 20 | .copyright h4, .copyright h5 { 21 | margin: 0; 22 | display: flex; 23 | } -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | margin: 0 auto; 3 | padding: 2rem; 4 | text-align: center; 5 | width: 100%; 6 | } 7 | 8 | .table--showColors tr:nth-child(odd) { 9 | background: #333; 10 | } 11 | 12 | .table--showColors tr:nth-child(even) { 13 | background: #555; 14 | } 15 | 16 | header { 17 | display: flex; 18 | gap: 4px; 19 | margin-bottom: 48px; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .pointer { 25 | cursor: crosshair; 26 | } -------------------------------------------------------------------------------- /projects/07-midu-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | midu-router demo 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/10-crud-redux/src/hooks/useUserActions.ts: -------------------------------------------------------------------------------- 1 | import { User, UserId, addNewUser, deleteUserById } from "../store/users/slice"; 2 | import { useAppDispatch } from "./store"; 3 | 4 | export const useUserActions = () => { 5 | const dispatch = useAppDispatch(); 6 | 7 | const addUser = ({ name, email, github }: User) => { 8 | dispatch(addNewUser({ name, email, github })) 9 | } 10 | 11 | const removeUser = (id: UserId) => { 12 | dispatch(deleteUserById(id)); 13 | }; 14 | 15 | return { addUser, removeUser }; 16 | }; 17 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | margin: 0 auto; 3 | padding: 2rem; 4 | text-align: center; 5 | width: 100%; 6 | } 7 | 8 | .table--showColors tr:nth-child(odd) { 9 | background: #333; 10 | } 11 | 12 | .table--showColors tr:nth-child(even) { 13 | background: #555; 14 | } 15 | 16 | header { 17 | display: flex; 18 | gap: 4px; 19 | margin-bottom: 48px; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .pointer { 25 | cursor: crosshair; 26 | } -------------------------------------------------------------------------------- /projects/06-shopping-cart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "06-shopping-cart", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react-swc": "^3.0.0", 19 | "vite": "^4.1.0" 20 | } 21 | } -------------------------------------------------------------------------------- /projects/03-mouse-follower/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "03-mouse-follower", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.26", 17 | "@types/react-dom": "^18.0.9", 18 | "@vitejs/plugin-react-swc": "^3.0.0", 19 | "vite": "^4.0.0" 20 | } 21 | } -------------------------------------------------------------------------------- /projects/01-twitter-follow-card/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01-twitter-follow-card", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.26", 17 | "@types/react-dom": "^18.0.9", 18 | "@vitejs/plugin-react-swc": "^3.0.0", 19 | "vite": "^4.0.0" 20 | } 21 | } -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | import { QueryClientProvider, QueryClient } from '@tanstack/react-query' 5 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 6 | 7 | const queryClient = new QueryClient() 8 | 9 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 10 | 11 | 12 | 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "02-tic-tac-toe", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "canvas-confetti": "1.6.0", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.26", 18 | "@types/react-dom": "^18.0.9", 19 | "@vitejs/plugin-react-swc": "^3.0.0", 20 | "vite": "^4.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/components/CommentLoader.tsx: -------------------------------------------------------------------------------- 1 | import ContentLoader from 'react-content-loader' 2 | 3 | export const CommentLoader = () => ( 4 | 12 | 13 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/Start.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@mui/material' 2 | import { useQuestionsStore } from './store/questions' 3 | 4 | const LIMIT_QUESTIONS = 10 5 | 6 | export const Start = () => { 7 | const fetchQuestions = useQuestionsStore(state => state.fetchQuestions) 8 | 9 | const handleClick = () => { 10 | fetchQuestions(LIMIT_QUESTIONS) 11 | } 12 | 13 | return ( 14 |
15 | 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "05-react-buscador-peliculas", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "just-debounce-it": "3.2.0", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.27", 18 | "@types/react-dom": "^18.0.10", 19 | "@vitejs/plugin-react-swc": "^3.0.0", 20 | "vite": "^4.1.0" 21 | } 22 | } -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/context/filters.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react' 2 | 3 | // Este es el que tenemos que consumir 4 | export const FiltersContext = createContext() 5 | 6 | // Este es el que nos provee de acceso al contexto 7 | export function FiltersProvider ({ children }) { 8 | const [filters, setFilters] = useState({ 9 | category: 'all', 10 | minPrice: 250 11 | }) 12 | 13 | return ( 14 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'plugin:react/recommended', 8 | 'standard-with-typescript' 9 | ], 10 | overrides: [ 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest', 14 | sourceType: 'module', 15 | project: './tsconfig.json' 16 | }, 17 | plugins: [ 18 | 'react' 19 | ], 20 | rules: { 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | 'react/react-in-jsx-scope': 'off', 23 | 'react/prop-types': 'off' 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'vitest' 2 | import { render } from '@testing-library/react' 3 | import userEvent from '@testing-library/user-event' 4 | import App from './App' 5 | 6 | test('My App works as expected', async () => { 7 | const user = userEvent.setup() 8 | const app = render() 9 | 10 | const textareaFrom = app.getByPlaceholderText('Introducir texto') 11 | 12 | await user.type(textareaFrom, 'Hola mundo') 13 | const result = await app.findByDisplayValue(/Hello world/i, {}, { timeout: 2000 }) 14 | 15 | expect(result).toBeTruthy() 16 | }) 17 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { CreateTodo } from './CreateTodo' 2 | 3 | interface Props { 4 | saveTodo: (title: string) => void 5 | } 6 | 7 | export const Header: React.FC = ({ saveTodo }) => { 8 | return ( 9 |
10 |

todo 11 | 14 |

15 | 16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/src/hooks/useFilters.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { FiltersContext } from '../context/filters.jsx' 3 | 4 | export function useFilters () { 5 | const { filters, setFilters } = useContext(FiltersContext) 6 | 7 | const filterProducts = (products) => { 8 | return products.filter(product => { 9 | return ( 10 | product.price >= filters.minPrice && 11 | ( 12 | filters.category === 'all' || 13 | product.category === filters.category 14 | ) 15 | ) 16 | }) 17 | } 18 | 19 | return { filters, filterProducts, setFilters } 20 | } 21 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/hooks/useQuestionsData.ts: -------------------------------------------------------------------------------- 1 | import { useQuestionsStore } from '../store/questions' 2 | 3 | export const useQuestionsData = () => { 4 | const questions = useQuestionsStore(state => state.questions) 5 | 6 | let correct = 0 7 | let incorrect = 0 8 | let unanswered = 0 9 | 10 | questions.forEach(question => { 11 | const { userSelectedAnswer, correctAnswer } = question 12 | if (userSelectedAnswer == null) unanswered++ 13 | else if (userSelectedAnswer === correctAnswer) correct++ 14 | else incorrect++ 15 | }) 16 | 17 | return { correct, incorrect, unanswered } 18 | } 19 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-prueba-tecnica", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@playwright/test": "^1.30.0", 13 | "standard": "^17.0.0", 14 | "vite": "^4.0.0" 15 | }, 16 | "dependencies": { 17 | "@vitejs/plugin-react": "3.0.1", 18 | "react": "18.2.0", 19 | "react-dom": "18.2.0" 20 | }, 21 | "eslintConfig": { 22 | "extends": "./node_modules/standard/eslintrc.json" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/src/services/movies.js: -------------------------------------------------------------------------------- 1 | const API_KEY = '4287ad07' 2 | 3 | export const searchMovies = async ({ search }) => { 4 | if (search === '') return null 5 | 6 | try { 7 | const response = await fetch(`https://www.omdbapi.com/?apikey=${API_KEY}&s=${search}`) 8 | const json = await response.json() 9 | 10 | const movies = json.Search 11 | 12 | return movies?.map(movie => ({ 13 | id: movie.imdbID, 14 | title: movie.Title, 15 | year: movie.Year, 16 | image: movie.Poster 17 | })) 18 | } catch (e) { 19 | throw new Error('Error searching movies') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense, lazy } from 'react' 2 | import { Header } from './components/Header' 3 | import { Route } from 'wouter' 4 | 5 | const TopStoriesPage = lazy(() => import('./pages/TopStories')) 6 | const DetailPage = lazy(() => import('./pages/Detail')) 7 | 8 | export default function App () { 9 | return ( 10 | <> 11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/components/WinnerModal.jsx: -------------------------------------------------------------------------------- 1 | import { Square } from './Square.jsx' 2 | 3 | export function WinnerModal ({ winner, resetGame }) { 4 | if (winner === null) return null 5 | 6 | const winnerText = winner === false ? 'Empate' : 'Ganó:' 7 | 8 | return ( 9 |
10 |
11 |

{winnerText}

12 | 13 |
14 | {winner && {winner}} 15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /projects/06-shopping-cart/README.md: -------------------------------------------------------------------------------- 1 | # Enunciado 2 | 3 | Ecommerce 4 | 5 | - [x] Muestra una lista de productos que vienen de un JSON 6 | - [x] Añade un filtro por categoría 7 | - [x] Añade un filtro por precio 8 | 9 | Haz uso de useContext para evitar pasar props innecesarias. 10 | 11 | Carrito: 12 | 13 | - [x] Haz que se puedan añadir los productos a un carrito. 14 | - [x] Haz que se puedan eliminar los productos del carrito. 15 | - [x] Haz que se puedan modificar la cantidad de productos del carrito. 16 | - [x] Sincroniza los cambios del carrito con la lista de productos. 17 | - [x] Guarda en un localStorage el carrito para que se recupere al recargar la página. (da puntos) 18 | -------------------------------------------------------------------------------- /projects/10-crud-redux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/07-midu-router/README.md: -------------------------------------------------------------------------------- 1 | # Crea un React Router desde cero 2 | 3 | - [x] Instalar el linter 4 | - [x] Crear una forma de hacer MPAs (Multiple Page Application) 5 | - [x] Crea una forma de hacer SPAs (Single Page Applications) 6 | - [x] Poder navegar entre páginas con el botón de atrás 7 | - [x] Crear componente Link para hacerlo declarativo 8 | - [x] Crear componente Router para hacerlo más declarativo 9 | - [x] Soportar ruta por defecto (404) 10 | - [x] Soportar rutas con parámetros 11 | - [x] Componente para hacerlo declarativo 12 | - [x] Lazy Loading de las rutas 13 | - [x] Hacer un i18n con las rutas 14 | - [x] Testing 15 | - [x] Publicar el paquete en NPM 16 | 17 | -------------------------------------------------------------------------------- /projects/09-google-translate-clone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/07-midu-router/lib/Link.js: -------------------------------------------------------------------------------- 1 | import{jsx as _jsx}from"react/jsx-runtime";import{BUTTONS,EVENTS}from"./consts.js";export function navigate(href){window.history.pushState({},"",href);const navigationEvent=new Event(EVENTS.PUSHSTATE);window.dispatchEvent(navigationEvent)}export function Link({target,to,...props}){const handleClick=event=>{const isMainEvent=event.button===BUTTONS.primary;const isModifiedEvent=event.metaKey||event.altKey||event.ctrlKey||event.shiftKey;const isManageableEvent=target===undefined||target==="_self";if(isMainEvent&&isManageableEvent&&!isModifiedEvent){event.preventDefault();navigate(to);window.scrollTo(0,0)}};return _jsx("a",{onClick:handleClick,href:to,target:target,...props})} -------------------------------------------------------------------------------- /projects/11-typescript-prueba-tecnica/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | './node_modules/ts-standard/eslintrc.json' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: './tsconfig.json' }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | '@typescript-eslint/explicit-function-return-type': 'off', 15 | '@typescript-eslint/no-floating-promises': 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aprendiendo-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "workspaces": [ 10 | "projects/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/midudev/aprendiendo-react.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/midudev/aprendiendo-react/issues" 21 | }, 22 | "homepage": "https://github.com/midudev/aprendiendo-react#readme", 23 | "devDependencies": { 24 | "standard": "17.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import { useCatImage } from './hooks/useCatImage.js' 3 | import { useCatFact } from './hooks/useCatFact.js' 4 | 5 | export function App () { 6 | const { fact, refreshFact } = useCatFact() 7 | const { imageUrl } = useCatImage({ fact }) 8 | 9 | const handleClick = async () => { 10 | refreshFact() 11 | } 12 | 13 | return ( 14 |
15 |

App de gatitos

16 | 17 | 18 | 19 | {fact &&

{fact}

} 20 | {imageUrl && {`Image} 21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /projects/04-react-prueba-tecnica/tests/example.spec.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { test, expect } from '@playwright/test' 3 | 4 | const CAT_PREFIX_IMAGE_URL = 'https://cataas.com' 5 | const LOCALHOST_URL = 'http://localhost:5173/' 6 | 7 | test('app shows random fact and image', async ({ page }) => { 8 | await page.goto(LOCALHOST_URL) 9 | 10 | const text = await page.getByRole('paragraph') 11 | const image = await page.getByRole('img') 12 | 13 | const textContent = await text.textContent() 14 | const imageSrc = await image.getAttribute('src') 15 | 16 | await expect(textContent?.length).toBeGreaterThan(0) 17 | await expect(imageSrc?.startsWith(CAT_PREFIX_IMAGE_URL)).toBeTruthy() 18 | }) 19 | -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src", "vite.config.ts"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider, createTheme } from '@mui/material/styles' 2 | import CssBaseline from '@mui/material/CssBaseline' 3 | 4 | import ReactDOM from 'react-dom/client' 5 | import App from './App.tsx' 6 | import './index.css' 7 | 8 | import '@fontsource/roboto/300.css' 9 | import '@fontsource/roboto/400.css' 10 | import '@fontsource/roboto/500.css' 11 | import '@fontsource/roboto/700.css' 12 | 13 | const darkTheme = createTheme({ 14 | palette: { 15 | mode: 'dark' 16 | } 17 | }) 18 | 19 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 20 | 21 | 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": [ 6 | "DOM", 7 | "DOM.Iterable", 8 | "ESNext" 9 | ], 10 | "allowJs": false, 11 | "skipLibCheck": true, 12 | "esModuleInterop": false, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": 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 | "references": [ 27 | { 28 | "path": "./tsconfig.node.json" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@mui/material' 2 | import { useQuestionsData } from './hooks/useQuestionsData' 3 | import { useQuestionsStore } from './store/questions' 4 | 5 | export const Footer = () => { 6 | const { correct, incorrect, unanswered } = useQuestionsData() 7 | const reset = useQuestionsStore(state => state.reset) 8 | 9 | return ( 10 |
11 | {`✅ ${correct} correctas - ❌ ${incorrect} incorrectas - ❓ ${unanswered} sin responder`} 12 |
13 | 16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/components/StoryLoader.tsx: -------------------------------------------------------------------------------- 1 | import ContentLoader from 'react-content-loader' 2 | 3 | export const StoryLoader = () => { 4 | return ( 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/logic/board.js: -------------------------------------------------------------------------------- 1 | import { WINNER_COMBOS } from '../constants.js' 2 | 3 | export const checkWinnerFrom = (boardToCheck) => { 4 | // revisamos todas las combinaciones ganadoras 5 | // para ver si X u O ganó 6 | for (const combo of WINNER_COMBOS) { 7 | const [a, b, c] = combo 8 | if ( 9 | boardToCheck[a] && 10 | boardToCheck[a] === boardToCheck[b] && 11 | boardToCheck[a] === boardToCheck[c] 12 | ) { 13 | return boardToCheck[a] 14 | } 15 | } 16 | // si no hay ganador 17 | return null 18 | } 19 | 20 | export const checkEndGame = (newBoard) => { 21 | // revisamos si hay un empate 22 | // si no hay más espacios vacíos 23 | // en el tablero 24 | return newBoard.every((square) => square !== null) 25 | } 26 | -------------------------------------------------------------------------------- /projects/11b-typescript-prueba-tecnica-with-react-query/src/services/users.ts: -------------------------------------------------------------------------------- 1 | const delay = async (ms: number) => await new Promise(resolve => setTimeout(resolve, ms)) 2 | 3 | export const fetchUsers = async ({ pageParam = 1 }: { pageParam?: number }) => { 4 | await delay(300) 5 | 6 | return await fetch(`https://randomuser.me/api?results=10&seed=midudev&page=${pageParam}`) 7 | .then(async res => { 8 | if (!res.ok) throw new Error('Error en la petición') 9 | return await res.json() 10 | }) 11 | 12 | .then(res => { 13 | const currentPage = Number(res.info.page) 14 | const nextCursor = currentPage > 3 ? undefined : currentPage + 1 15 | 16 | return { 17 | users: res.results, 18 | nextCursor 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /projects/02-tic-tac-toe/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /projects/03-mouse-follower/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /projects/13-javascript-quiz-con-zustand/src/Results.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@mui/material" 2 | import { useQuestionsData } from "./hooks/useQuestionsData" 3 | import { useQuestionsStore } from "./store/questions" 4 | 5 | export const Results = () => { 6 | const { correct, incorrect } = useQuestionsData() 7 | const reset = useQuestionsStore(state => state.reset) 8 | 9 | return ( 10 |
11 |

¡Tus resultados

12 | 13 | 14 |

✅ {correct} correctas

15 |

❌ {incorrect} incorrectas

16 |
17 | 18 |
19 | 22 |
23 |
24 | ) 25 | } -------------------------------------------------------------------------------- /projects/08-todo-app-typescript/src/components/CreateTodo.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | interface Props { 4 | saveTodo: (title: string) => void 5 | } 6 | 7 | export const CreateTodo: React.FC = ({ saveTodo }) => { 8 | const [inputValue, setInputValue] = useState('') 9 | 10 | const handleKeyDown: React.KeyboardEventHandler = (e) => { 11 | if (e.key === 'Enter' && inputValue !== '') { 12 | saveTodo(inputValue) 13 | setInputValue('') 14 | } 15 | } 16 | 17 | return ( 18 | { setInputValue(e.target.value) }} 22 | onKeyDown={handleKeyDown} 23 | placeholder='¿Qué quieres hacer?' 24 | autoFocus 25 | /> 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/README.md: -------------------------------------------------------------------------------- 1 | ## Enunciado 2 | 3 | Crea una aplicación para buscar películas 4 | 5 | API a usar: - https://www.omdbapi.com/ 6 | Consigue la API Key en la propia página web registrando tu email. 7 | 8 | Requerimientos: 9 | 10 | ✅ Necesita mostrar un input para buscar la película y un botón para buscar. 11 | 12 | ✅ Lista las películas y muestra el título, año y poster. 13 | 14 | ✅ Que el formulario funcione 15 | 16 | ✅ Haz que las películas se muestren en un grid responsive. 17 | 18 | ✅ Hacer el fetching de datos a la API 19 | 20 | Primera iteración: 21 | 22 | ✅ Evitar que se haga la misma búsqueda dos veces seguidas. 23 | 24 | ✅ Haz que la búsqueda se haga automáticamente al escribir. 25 | 26 | ✅ Evita que se haga la búsqueda continuamente al escribir (debounce) 27 | -------------------------------------------------------------------------------- /projects/14-hacker-news-prueba-tecnica/src/services/hacker-news.ts: -------------------------------------------------------------------------------- 1 | export const getTopStories = async (page: number, limit: number) => { 2 | const response = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json') 3 | const json = await response.json() 4 | // page starts with 1 5 | const startIndex = (page - 1) * limit 6 | const endIndex = startIndex + limit 7 | const ids = json.slice(startIndex, endIndex) 8 | 9 | return ids 10 | 11 | // junior dev tip: use Promise.all to fetch multiple items in parallel 12 | // return await Promise.all(ids.map((id: number) => getItemInfo(id))) 13 | } 14 | 15 | export const getItemInfo = async (id: number) => { 16 | const response = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`) 17 | return await response.json() 18 | } 19 | -------------------------------------------------------------------------------- /projects/05-react-buscador-peliculas/src/components/Movies.jsx: -------------------------------------------------------------------------------- 1 | function ListOfMovies ({ movies }) { 2 | return ( 3 |
    4 | { 5 | movies.map(movie => ( 6 |
  • 7 |

    {movie.title}

    8 |

    {movie.year}

    9 | {movie.title} 10 |
  • 11 | )) 12 | } 13 |
14 | ) 15 | } 16 | 17 | function NoMoviesResults () { 18 | return ( 19 |

No se encontraron películas para esta búsqueda

20 | ) 21 | } 22 | 23 | export function Movies ({ movies }) { 24 | const hasMovies = movies?.length > 0 25 | 26 | return ( 27 | hasMovies 28 | ? 29 | : 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /projects/12-comments-react-query/src/components/Form.tsx: -------------------------------------------------------------------------------- 1 | export const FormInput = ({ ...props }) => ( 2 |
3 | 4 | 5 |
6 | ) 7 | 8 | export const FormTextArea = ({ ...props }) => ( 9 |