diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28f1ba7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7760d1 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Cypress End-to-End Testing - Getting Started Course Resources + +This repository contains the [slides](/slides) and [code snapshots](/code) for our [Cypress End-to-End Testing - Getting Started Course](https://acad.link/cypress-e2e). + +The [attachments](/attachments) folder contains lecture-specific attachments (i.e., course lectures are linking to those attachments). + +## Using the Code Snapshots + +The code snapshots are there to help you debug your code and find possible errors in your projects. + +If you can't reproduce the results shown in the course videos, take a look at the provided snapshots and compare that code to yours to detect possible error sources. You can also replace your code with ours (step-by-step and only temporarily) to narrow down the issue. + +The folders in the [/code directory](/code) map to the different course sections. Inside the section folders, you find multiple folders (i.e., multiple snapshots per section). \ No newline at end of file diff --git a/attachments/01-getting-started-starting-project.zip b/attachments/01-getting-started-starting-project.zip new file mode 100644 index 0000000..0d29cb8 Binary files /dev/null and b/attachments/01-getting-started-starting-project.zip differ diff --git a/attachments/02-basics-starting-project.zip b/attachments/02-basics-starting-project.zip new file mode 100644 index 0000000..10b78bb Binary files /dev/null and b/attachments/02-basics-starting-project.zip differ diff --git a/attachments/03-diving-deeper-starting-project.zip b/attachments/03-diving-deeper-starting-project.zip new file mode 100644 index 0000000..56af85e Binary files /dev/null and b/attachments/03-diving-deeper-starting-project.zip differ diff --git a/attachments/04-config-starting-project.zip b/attachments/04-config-starting-project.zip new file mode 100644 index 0000000..505c0af Binary files /dev/null and b/attachments/04-config-starting-project.zip differ diff --git a/attachments/05-stubs-starting-project.zip b/attachments/05-stubs-starting-project.zip new file mode 100644 index 0000000..1e6aadf Binary files /dev/null and b/attachments/05-stubs-starting-project.zip differ diff --git a/attachments/06-network-auth-starting-project.zip b/attachments/06-network-auth-starting-project.zip new file mode 100644 index 0000000..5748590 Binary files /dev/null and b/attachments/06-network-auth-starting-project.zip differ diff --git a/code/01 Getting Started/01 Starting Project/index.html b/code/01 Getting Started/01 Starting Project/index.html new file mode 100644 index 0000000..79c4701 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/code/01 Getting Started/01 Starting Project/package.json b/code/01 Getting Started/01 Starting Project/package.json new file mode 100644 index 0000000..f8364db --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/package.json @@ -0,0 +1,22 @@ +{ + "name": "getting-started", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-icons": "^4.7.1" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react": "^3.1.0", + "vite": "^4.1.0" + } +} diff --git a/code/01 Getting Started/01 Starting Project/public/vite.svg b/code/01 Getting Started/01 Starting Project/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/01 Getting Started/01 Starting Project/src/App.jsx b/code/01 Getting Started/01 Starting Project/src/App.jsx new file mode 100644 index 0000000..b408f71 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/App.jsx @@ -0,0 +1,15 @@ +import CourseGoals from './components/CourseGoals'; +import Header from './components/Header'; + +function App() { + return ( + <> +
+
+ +
+ + ); +} + +export default App; diff --git a/code/01 Getting Started/01 Starting Project/src/assets/react.svg b/code/01 Getting Started/01 Starting Project/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.jsx b/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.jsx new file mode 100644 index 0000000..8a3b750 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.jsx @@ -0,0 +1,12 @@ +import classes from './CourseGoal.module.css'; + +function CourseGoal({ icon, text }) { + return ( +
  • + {icon} + {text} +
  • + ); +} + +export default CourseGoal; diff --git a/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.module.css b/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.module.css new file mode 100644 index 0000000..3d1d0ef --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/components/CourseGoal.module.css @@ -0,0 +1,26 @@ +.goal { + margin: 2rem 0; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; + background-color: var(--gray-1000); + border-radius: 6px; + border: 1px solid var(--indigo-300); + text-align: center; + color: var(--indigo-200); + position: relative; +} + +.icon { + background-color: var(--indigo-500); + width: 3rem; + height: 3rem; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: -1.55rem; +} diff --git a/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.jsx b/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.jsx new file mode 100644 index 0000000..952e9fe --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.jsx @@ -0,0 +1,50 @@ +import { + GrInstall, + GrEdit, + GrTerminal, + GrResources, + GrUserExpert, + GrKey, +} from 'react-icons/gr'; + +import CourseGoal from './CourseGoal'; +import classes from './CourseGoals.module.css'; + +const GOALS = [ + { + icon: , + text: 'Learn how to install & start Cypress', + }, + { + icon: , + text: 'Learn how to write tests with Cypress', + }, + { + icon: , + text: 'Understand the core Cypress features & commands', + }, + { + icon: , + text: 'Customize & configure Cypress for your requirements', + }, + { + icon: , + text: 'Learn how to write good tests & follow best practices', + }, + { + icon: , + text: 'Dive into more complex problems - e.g., user authentication testing', + }, +]; + +function CourseGoals() { + return ( +
      + {GOALS.map((goal) => ( + + ))} +
    + ); +} + +export default CourseGoals; diff --git a/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.module.css b/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.module.css new file mode 100644 index 0000000..d01cc44 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/components/CourseGoals.module.css @@ -0,0 +1,7 @@ +.goals { + max-width: 60rem; + margin: 3rem auto; + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); +} \ No newline at end of file diff --git a/code/01 Getting Started/01 Starting Project/src/components/Header.jsx b/code/01 Getting Started/01 Starting Project/src/components/Header.jsx new file mode 100644 index 0000000..42ca0de --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/components/Header.jsx @@ -0,0 +1,10 @@ +function Header() { + return ( +
    + +

    Getting Started with Cypress

    +
    + ); +} + +export default Header; diff --git a/code/01 Getting Started/01 Starting Project/src/index.css b/code/01 Getting Started/01 Starting Project/src/index.css new file mode 100644 index 0000000..cdc787a --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/index.css @@ -0,0 +1,70 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + --gray-100: #f9f9f9; + --gray-200: #d8d8d8; + --gray-300: #c4c1c1; + --gray-400: #aeadad; + --gray-500: #818080; + --gray-600: #6c6c6c; + --gray-700: #5c5b5b; + --gray-800: #403f3f; + --gray-900: #2c2b2b; + --gray-1000: #1a1a1a; + + --indigo-100: #e8e9ff; + --indigo-200: #c7c9ff; + --indigo-300: #a6a9ff; + --indigo-400: #858aff; + --indigo-500: #646cff; + --indigo-600: #535bf2; + --indigo-700: #424ae6; + --indigo-800: #3239da; + --indigo-900: #2228ce; + + --pink-100: #ffe8f0; + --pink-200: #ffcfe3; + --pink-300: #ffb5d6; + --pink-400: #ff9cc9; + --pink-500: #ff82bc; + --pink-600: #f26ba2; + --pink-700: #e65f88; + --pink-800: #da537e; + --pink-900: #ce4764; + + color-scheme: light dark; + color: var(--gray-100); + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; + height: 100vh; + background: linear-gradient(180deg, var(--gray-1000), var(--gray-900)); +} + +h1 { + margin: 6rem 0; + font-size: 3.2em; + line-height: 1.1; + background: linear-gradient(90deg, var(--indigo-600), var(--pink-800)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + text-align: center; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/01 Getting Started/01 Starting Project/src/main.jsx b/code/01 Getting Started/01 Starting Project/src/main.jsx new file mode 100644 index 0000000..5cc5991 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/code/01 Getting Started/01 Starting Project/vite.config.js b/code/01 Getting Started/01 Starting Project/vite.config.js new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/code/01 Getting Started/01 Starting Project/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/code/01 Getting Started/02 Finished First Test/cypress.config.js b/code/01 Getting Started/02 Finished First Test/cypress.config.js new file mode 100644 index 0000000..17161e3 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/cypress.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/code/01 Getting Started/02 Finished First Test/cypress/e2e/first-test.cy.js b/code/01 Getting Started/02 Finished First Test/cypress/e2e/first-test.cy.js new file mode 100644 index 0000000..ba9b4d6 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/cypress/e2e/first-test.cy.js @@ -0,0 +1,6 @@ +describe('template spec', () => { + it('passes', () => { + cy.visit('http://localhost:5173/'); + cy.get('li').should('have.length', 6); + }); +}); diff --git a/code/01 Getting Started/02 Finished First Test/cypress/fixtures/example.json b/code/01 Getting Started/02 Finished First Test/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/code/01 Getting Started/02 Finished First Test/cypress/support/commands.js b/code/01 Getting Started/02 Finished First Test/cypress/support/commands.js new file mode 100644 index 0000000..66ea16e --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file diff --git a/code/01 Getting Started/02 Finished First Test/cypress/support/e2e.js b/code/01 Getting Started/02 Finished First Test/cypress/support/e2e.js new file mode 100644 index 0000000..0e7290a --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/cypress/support/e2e.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/code/01 Getting Started/02 Finished First Test/index.html b/code/01 Getting Started/02 Finished First Test/index.html new file mode 100644 index 0000000..79c4701 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
    + + + diff --git a/code/01 Getting Started/02 Finished First Test/package.json b/code/01 Getting Started/02 Finished First Test/package.json new file mode 100644 index 0000000..8a46db2 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/package.json @@ -0,0 +1,23 @@ +{ + "name": "getting-started", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "cypress": "^12.5.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-icons": "^4.7.1" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react": "^3.1.0", + "vite": "^4.1.0" + } +} diff --git a/code/01 Getting Started/02 Finished First Test/public/vite.svg b/code/01 Getting Started/02 Finished First Test/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/01 Getting Started/02 Finished First Test/src/App.jsx b/code/01 Getting Started/02 Finished First Test/src/App.jsx new file mode 100644 index 0000000..b408f71 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/App.jsx @@ -0,0 +1,15 @@ +import CourseGoals from './components/CourseGoals'; +import Header from './components/Header'; + +function App() { + return ( + <> +
    +
    + +
    + + ); +} + +export default App; diff --git a/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.jsx b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.jsx new file mode 100644 index 0000000..8a3b750 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.jsx @@ -0,0 +1,12 @@ +import classes from './CourseGoal.module.css'; + +function CourseGoal({ icon, text }) { + return ( +
  • + {icon} + {text} +
  • + ); +} + +export default CourseGoal; diff --git a/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.module.css b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.module.css new file mode 100644 index 0000000..3d1d0ef --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoal.module.css @@ -0,0 +1,26 @@ +.goal { + margin: 2rem 0; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; + background-color: var(--gray-1000); + border-radius: 6px; + border: 1px solid var(--indigo-300); + text-align: center; + color: var(--indigo-200); + position: relative; +} + +.icon { + background-color: var(--indigo-500); + width: 3rem; + height: 3rem; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: -1.55rem; +} diff --git a/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.jsx b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.jsx new file mode 100644 index 0000000..952e9fe --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.jsx @@ -0,0 +1,50 @@ +import { + GrInstall, + GrEdit, + GrTerminal, + GrResources, + GrUserExpert, + GrKey, +} from 'react-icons/gr'; + +import CourseGoal from './CourseGoal'; +import classes from './CourseGoals.module.css'; + +const GOALS = [ + { + icon: , + text: 'Learn how to install & start Cypress', + }, + { + icon: , + text: 'Learn how to write tests with Cypress', + }, + { + icon: , + text: 'Understand the core Cypress features & commands', + }, + { + icon: , + text: 'Customize & configure Cypress for your requirements', + }, + { + icon: , + text: 'Learn how to write good tests & follow best practices', + }, + { + icon: , + text: 'Dive into more complex problems - e.g., user authentication testing', + }, +]; + +function CourseGoals() { + return ( +
      + {GOALS.map((goal) => ( + + ))} +
    + ); +} + +export default CourseGoals; diff --git a/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.module.css b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.module.css new file mode 100644 index 0000000..d01cc44 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/components/CourseGoals.module.css @@ -0,0 +1,7 @@ +.goals { + max-width: 60rem; + margin: 3rem auto; + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); +} \ No newline at end of file diff --git a/code/01 Getting Started/02 Finished First Test/src/components/Header.jsx b/code/01 Getting Started/02 Finished First Test/src/components/Header.jsx new file mode 100644 index 0000000..42ca0de --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/components/Header.jsx @@ -0,0 +1,10 @@ +function Header() { + return ( +
    + +

    Getting Started with Cypress

    +
    + ); +} + +export default Header; diff --git a/code/01 Getting Started/02 Finished First Test/src/index.css b/code/01 Getting Started/02 Finished First Test/src/index.css new file mode 100644 index 0000000..cdc787a --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/index.css @@ -0,0 +1,70 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + --gray-100: #f9f9f9; + --gray-200: #d8d8d8; + --gray-300: #c4c1c1; + --gray-400: #aeadad; + --gray-500: #818080; + --gray-600: #6c6c6c; + --gray-700: #5c5b5b; + --gray-800: #403f3f; + --gray-900: #2c2b2b; + --gray-1000: #1a1a1a; + + --indigo-100: #e8e9ff; + --indigo-200: #c7c9ff; + --indigo-300: #a6a9ff; + --indigo-400: #858aff; + --indigo-500: #646cff; + --indigo-600: #535bf2; + --indigo-700: #424ae6; + --indigo-800: #3239da; + --indigo-900: #2228ce; + + --pink-100: #ffe8f0; + --pink-200: #ffcfe3; + --pink-300: #ffb5d6; + --pink-400: #ff9cc9; + --pink-500: #ff82bc; + --pink-600: #f26ba2; + --pink-700: #e65f88; + --pink-800: #da537e; + --pink-900: #ce4764; + + color-scheme: light dark; + color: var(--gray-100); + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; + height: 100vh; + background: linear-gradient(180deg, var(--gray-1000), var(--gray-900)); +} + +h1 { + margin: 6rem 0; + font-size: 3.2em; + line-height: 1.1; + background: linear-gradient(90deg, var(--indigo-600), var(--pink-800)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + text-align: center; +} + +ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/01 Getting Started/02 Finished First Test/src/main.jsx b/code/01 Getting Started/02 Finished First Test/src/main.jsx new file mode 100644 index 0000000..5cc5991 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/code/01 Getting Started/02 Finished First Test/vite.config.js b/code/01 Getting Started/02 Finished First Test/vite.config.js new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/code/01 Getting Started/02 Finished First Test/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/code/02 Basics/01 Starting Project/index.html b/code/02 Basics/01 Starting Project/index.html new file mode 100644 index 0000000..79c4701 --- /dev/null +++ b/code/02 Basics/01 Starting Project/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
    + + + diff --git a/code/02 Basics/01 Starting Project/package.json b/code/02 Basics/01 Starting Project/package.json new file mode 100644 index 0000000..6a0a0d9 --- /dev/null +++ b/code/02 Basics/01 Starting Project/package.json @@ -0,0 +1,21 @@ +{ + "name": "cypress-basics", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react": "^3.1.0", + "vite": "^4.1.0" + } +} diff --git a/code/02 Basics/01 Starting Project/public/vite.svg b/code/02 Basics/01 Starting Project/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/code/02 Basics/01 Starting Project/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/02 Basics/01 Starting Project/src/App.jsx b/code/02 Basics/01 Starting Project/src/App.jsx new file mode 100644 index 0000000..43955a9 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/App.jsx @@ -0,0 +1,65 @@ +import { useState } from 'react'; +import Header from './components/Header'; +import Modal from './components/Modal'; + +import NewTask from './components/NewTask'; +import TaskControl from './components/TaskControl'; +import TaskList from './components/TaskList'; + +function App() { + const [isAddingTask, setIsAddingTask] = useState(false); + const [tasks, setTasks] = useState([]); + const [appliedFilter, setAppliedFilter] = useState('all'); + + const displayedTasks = tasks.filter((task) => { + if (appliedFilter === 'all') { + return true; + } + return task.category === appliedFilter; + }); + + function startAddTaskHandler() { + setIsAddingTask(true); + } + + function cancelAddTaskHandler() { + setIsAddingTask(false); + } + + function addTaskHandler(taskData) { + setTasks((prevTasks) => { + return [ + ...prevTasks, + { + id: Math.random().toString(), + ...taskData, + }, + ]; + }); + setIsAddingTask(false); + } + + function setFilterHandler(category) { + setAppliedFilter(category); + } + + return ( + <> + {isAddingTask && ( + + + + )} +
    +
    + + +
    + + ); +} + +export default App; diff --git a/code/02 Basics/01 Starting Project/src/assets/logo.png b/code/02 Basics/01 Starting Project/src/assets/logo.png new file mode 100644 index 0000000..2a8d015 Binary files /dev/null and b/code/02 Basics/01 Starting Project/src/assets/logo.png differ diff --git a/code/02 Basics/01 Starting Project/src/components/Filter.jsx b/code/02 Basics/01 Starting Project/src/components/Filter.jsx new file mode 100644 index 0000000..b0510e6 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/Filter.jsx @@ -0,0 +1,17 @@ +function Filter({ onFilterChange }) { + function filterChangeHandler(event) { + onFilterChange(event.target.value); + } + + return ( + + ); +} + +export default Filter; diff --git a/code/02 Basics/01 Starting Project/src/components/Header.css b/code/02 Basics/01 Starting Project/src/components/Header.css new file mode 100644 index 0000000..2ba4a37 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/Header.css @@ -0,0 +1,12 @@ +.main-header { + margin: 3rem auto; + text-align: center; + color: var(--color-gray-400); +} + +.main-header img { + width: 7rem; + height: 7rem; + object-fit: contain; + transform: rotateZ(10deg); +} \ No newline at end of file diff --git a/code/02 Basics/01 Starting Project/src/components/Header.jsx b/code/02 Basics/01 Starting Project/src/components/Header.jsx new file mode 100644 index 0000000..f2165e1 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/Header.jsx @@ -0,0 +1,13 @@ +import './Header.css'; +import logo from '../assets/logo.png'; + +function Header() { + return ( +
    + A list +

    React Tasks

    +
    + ); +} + +export default Header; diff --git a/code/02 Basics/01 Starting Project/src/components/Modal.css b/code/02 Basics/01 Starting Project/src/components/Modal.css new file mode 100644 index 0000000..4c57acd --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/Modal.css @@ -0,0 +1,24 @@ +.backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 1; +} + +.modal { + position: fixed; + top: 50%; + left: 50%; + margin: 0; + padding: 2rem; + transform: translate(-50%, -50%); + width: 40rem; + background-color: var(--color-gray-800); + border: none; + border-radius: 4px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + z-index: 10; +} diff --git a/code/02 Basics/01 Starting Project/src/components/Modal.jsx b/code/02 Basics/01 Starting Project/src/components/Modal.jsx new file mode 100644 index 0000000..7347c66 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/Modal.jsx @@ -0,0 +1,14 @@ +import './Modal.css'; + +function Modal({ children, onClose }) { + return ( + <> +
    + + {children} + + + ); +} + +export default Modal; diff --git a/code/02 Basics/01 Starting Project/src/components/NewTask.css b/code/02 Basics/01 Starting Project/src/components/NewTask.css new file mode 100644 index 0000000..e9a37e7 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/NewTask.css @@ -0,0 +1,62 @@ +#new-task-form label { + display: block; + margin-bottom: 0.5rem; + font-weight: bold; +} + +#new-task-form input, +#new-task-form textarea { + font: inherit; + width: 100%; + padding: 0.5rem; + border: 1px solid var(--color-gray-300); + background-color: var(--color-gray-400); + color: var(--color-gray-900); + border-radius: 4px; +} + +#new-task-form select { + font: inherit; + width: 100%; + padding: 0.5rem; + border: 1px solid var(--color-gray-300); + background-color: var(--color-gray-400); + color: var(--color-gray-900); + border-radius: 4px; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 1rem; +} + +#new-task-form button { + padding: 0.75rem 1.5rem; + border: none; + background-color: var(--color-primary-500); + color: var(--color-gray-100); + border-radius: 4px; + cursor: pointer; +} + +#new-task-form button:hover { + background-color: var(--color-primary-600); +} + +#new-task-form button[type="button"] { + background-color: transparent; + color: var(--color-gray-200); +} + +#new-task-form button[type="button"]:hover { + background-color: var(--color-gray-700); +} + + + +.error-message { + color: var(--color-primary-300); + font-weight: bold; + margin-bottom: 0.5rem; +} diff --git a/code/02 Basics/01 Starting Project/src/components/NewTask.jsx b/code/02 Basics/01 Starting Project/src/components/NewTask.jsx new file mode 100644 index 0000000..17f1540 --- /dev/null +++ b/code/02 Basics/01 Starting Project/src/components/NewTask.jsx @@ -0,0 +1,67 @@ +import { useRef, useState } from 'react'; + +import './NewTask.css'; + +function NewTask({ onAddTask, onCancel }) { + const titleRef = useRef(); + const summaryRef = useRef(); + const categoryRef = useRef(); + + const [formInvalid, setFormInvalid] = useState(false); + + function submitHandler(event) { + event.preventDefault(); + + const enteredTitle = titleRef.current.value; + const enteredSummary = summaryRef.current.value; + const chosenCategory = categoryRef.current.value; + + if ( + enteredTitle.trim().length === 0 || + enteredSummary.trim().length === 0 + ) { + setFormInvalid(true); + return; + } + + const taskData = { + title: enteredTitle, + summary: enteredSummary, + category: chosenCategory, + }; + onAddTask(taskData); + } + + return ( +
    +

    + + +

    +

    + +