├── part3 ├── Procfile ├── .eslintignore ├── .gitignore ├── README.md ├── package.json ├── .eslintrc.json └── models │ └── person.js ├── part4 └── bloglist │ ├── .eslintignore │ ├── .gitignore │ ├── utils │ ├── logger.js │ └── config.js │ ├── index.js │ ├── controllers │ ├── testing.js │ ├── login.js │ └── users.js │ ├── models │ ├── blog.js │ └── user.js │ ├── .eslintrc.json │ ├── package.json │ ├── app.js │ └── tests │ └── list_helper.js ├── part9 ├── flight-diary │ ├── backend │ │ ├── .eslintignore │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── routes │ │ │ │ └── diaries.ts │ │ │ └── services │ │ │ │ └── diaryService.ts │ │ ├── package.json │ │ ├── .eslintrc │ │ └── data │ │ │ └── entries.ts │ └── frontend │ │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ │ ├── src │ │ ├── index.tsx │ │ └── types.ts │ │ ├── .gitignore │ │ ├── .eslintrc │ │ ├── tsconfig.json │ │ └── package.json ├── TypeScript First steps │ ├── .prettierrc │ ├── tsconfig.json │ ├── .gitignore │ ├── bmiCalculator.ts │ ├── package.json │ ├── .eslintrc │ └── index.ts ├── patientor │ ├── frontend │ │ ├── src │ │ │ ├── constants.ts │ │ │ ├── index.tsx │ │ │ ├── types.ts │ │ │ ├── services │ │ │ │ └── patients.ts │ │ │ └── components │ │ │ │ ├── AddPatientModal │ │ │ │ └── index.tsx │ │ │ │ ├── HealthRatingBar.tsx │ │ │ │ └── PatientInfoPage │ │ │ │ └── index.tsx │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ └── manifest.json │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── README.md │ └── backend │ │ ├── src │ │ ├── routes │ │ │ ├── diagnoses.ts │ │ │ └── patients.ts │ │ ├── services │ │ │ ├── diagnoseService.ts │ │ │ └── patientService.ts │ │ ├── types.ts │ │ ├── index.ts │ │ └── data │ │ │ └── patients.ts │ │ ├── tsconfig.json │ │ ├── .gitignore │ │ ├── .eslintrc │ │ └── package.json └── create-react-app │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── components │ │ ├── Header.tsx │ │ ├── Total.tsx │ │ └── Content.tsx │ ├── index.tsx │ ├── types.ts │ └── App.tsx │ ├── .gitignore │ ├── .eslintrc │ ├── tsconfig.json │ └── package.json ├── .gitignore ├── part7 ├── bloglist │ ├── redux │ │ ├── bloglist-backend │ │ │ ├── .eslintignore │ │ │ ├── .gitignore │ │ │ ├── utils │ │ │ │ ├── logger.js │ │ │ │ └── config.js │ │ │ ├── index.js │ │ │ ├── controllers │ │ │ │ ├── testing.js │ │ │ │ ├── login.js │ │ │ │ └── users.js │ │ │ ├── models │ │ │ │ ├── blog.js │ │ │ │ └── user.js │ │ │ ├── .eslintrc.json │ │ │ ├── package.json │ │ │ ├── app.js │ │ │ └── tests │ │ │ │ └── list_helper.js │ │ └── bloglist-frontend │ │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ └── manifest.json │ │ │ ├── tailwind.config.js │ │ │ ├── src │ │ │ ├── components │ │ │ │ ├── Notification.js │ │ │ │ └── Blog.js │ │ │ ├── services │ │ │ │ ├── users.js │ │ │ │ ├── login.js │ │ │ │ └── blogs.js │ │ │ ├── reducers │ │ │ │ ├── userReducer.js │ │ │ │ ├── usersReducer.js │ │ │ │ ├── blogReducer.js │ │ │ │ └── notificationReducer.js │ │ │ ├── app.css │ │ │ ├── store.js │ │ │ ├── index.js │ │ │ └── tests │ │ │ │ ├── BlogForm.test.js │ │ │ │ └── Blog.test.js │ │ │ ├── .gitignore │ │ │ ├── .eslintrc.json │ │ │ └── package.json │ └── context-react-query │ │ ├── bloglist-backend │ │ ├── .eslintignore │ │ ├── .gitignore │ │ ├── utils │ │ │ ├── logger.js │ │ │ └── config.js │ │ ├── index.js │ │ ├── controllers │ │ │ ├── testing.js │ │ │ ├── login.js │ │ │ └── users.js │ │ ├── models │ │ │ ├── blog.js │ │ │ └── user.js │ │ ├── .eslintrc.json │ │ ├── package.json │ │ ├── app.js │ │ └── tests │ │ │ └── list_helper.js │ │ └── bloglist-frontend │ │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ │ ├── src │ │ ├── services │ │ │ ├── login.js │ │ │ └── blogs.js │ │ ├── app.css │ │ ├── index.js │ │ ├── loginContext.js │ │ ├── NotificationContext.js │ │ ├── tests │ │ │ ├── BlogForm.test.js │ │ │ └── Blog.test.js │ │ └── components │ │ │ └── BlogForm.js │ │ ├── .gitignore │ │ ├── .eslintrc.json │ │ └── package.json ├── country-hook │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ └── package.json ├── ultimate-hooks │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ ├── db.json │ └── package.json └── anecdotes-routed │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── index.js │ └── hooks │ │ └── index.js │ ├── .gitignore │ └── package.json ├── part0 ├── 0.4.png ├── 0.5.png └── 0.6.png ├── part2 ├── countries │ ├── .env │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ └── package.json ├── courseinfo │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ ├── index.js │ │ ├── Course.js │ │ └── App.js │ ├── .gitignore │ └── package.json └── phonebook │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── index.js │ ├── app.css │ └── services │ │ └── persons.js │ ├── .gitignore │ ├── db.json │ └── package.json ├── part1 ├── anecdotes │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ └── package.json ├── courseinfo │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ ├── index.js │ │ └── App.js │ ├── .gitignore │ └── package.json └── unicafe │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── index.js │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── reportWebVitals.js │ └── App.css │ ├── .gitignore │ └── package.json ├── part6 ├── anecdotes-query │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── Notification.js │ │ │ └── AnecdoteForm.js │ │ ├── requests.js │ │ ├── index.js │ │ └── NotificationContext.js │ ├── server.js │ └── package.json ├── anecdotes-redux │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ ├── index.js │ │ ├── components │ │ │ ├── Notification.js │ │ │ ├── Filter.js │ │ │ ├── AnecdoteForm.js │ │ │ └── AnecdoteList.js │ │ ├── reducers │ │ │ ├── filterReducer.js │ │ │ ├── notificationReducer.js │ │ │ └── anecdoteReducer.js │ │ ├── store.js │ │ ├── App.js │ │ └── services │ │ │ └── anecdotes.js │ ├── .gitignore │ ├── package.json │ └── db.json └── unicafe-redux │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── store.js │ ├── index.js │ └── reducer.js │ ├── .gitignore │ └── package.json ├── part5 └── bloglist-frontend │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── cypress │ ├── fixtures │ │ └── example.json │ └── support │ │ └── e2e.js │ ├── src │ ├── index.js │ ├── services │ │ ├── login.js │ │ └── blogs.js │ ├── app.css │ ├── tests │ │ ├── BlogForm.test.js │ │ └── Blog.test.js │ └── components │ │ └── BlogForm.js │ ├── cypress.config.js │ ├── .gitignore │ ├── .eslintrc.json │ └── package.json ├── part8 ├── library-frontend │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── .gitignore │ ├── src │ │ ├── components │ │ │ ├── Authors.js │ │ │ ├── Recommend.js │ │ │ └── Login.js │ │ ├── index.js │ │ └── queries.js │ └── package.json └── library │ ├── .gitignore │ ├── models │ ├── author.js │ ├── user.js │ └── book.js │ └── package.json └── certificate ├── certificate-part5.png ├── certificate-part6.png ├── certificate-part7.png ├── certificate-part8.png └── certificate-part9.png /part3/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js -------------------------------------------------------------------------------- /part3/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .eslintrc.json -------------------------------------------------------------------------------- /part4/bloglist/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .eslintrc.json -------------------------------------------------------------------------------- /part9/flight-diary/backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .env 3 | .expo 4 | node_modules 5 | .idea -------------------------------------------------------------------------------- /part9/flight-diary/backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build -------------------------------------------------------------------------------- /part4/bloglist/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /requests 3 | /build 4 | .env -------------------------------------------------------------------------------- /part9/TypeScript First steps/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true 3 | } 4 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .eslintrc.json -------------------------------------------------------------------------------- /part0/0.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part0/0.4.png -------------------------------------------------------------------------------- /part0/0.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part0/0.5.png -------------------------------------------------------------------------------- /part0/0.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part0/0.6.png -------------------------------------------------------------------------------- /part2/countries/.env: -------------------------------------------------------------------------------- 1 | # .env 2 | 3 | REACT_APP_API_KEY=9357a7f2dceb6f8b6a955b425b798904 -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | .eslintrc.json -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /requests 3 | /build 4 | .env -------------------------------------------------------------------------------- /part9/patientor/frontend/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const apiBaseUrl = "http://localhost:3001/api" 2 | -------------------------------------------------------------------------------- /part1/anecdotes/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part1/courseinfo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part1/unicafe/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part2/countries/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part2/courseinfo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part2/phonebook/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /requests 3 | /build 4 | .env -------------------------------------------------------------------------------- /part6/anecdotes-query/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part6/unicafe-redux/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part7/country-hook/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part8/library-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part9/create-react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part9/patientor/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /certificate/certificate-part5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/certificate/certificate-part5.png -------------------------------------------------------------------------------- /certificate/certificate-part6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/certificate/certificate-part6.png -------------------------------------------------------------------------------- /certificate/certificate-part7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/certificate/certificate-part7.png -------------------------------------------------------------------------------- /certificate/certificate-part8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/certificate/certificate-part8.png -------------------------------------------------------------------------------- /certificate/certificate-part9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/certificate/certificate-part9.png -------------------------------------------------------------------------------- /part1/unicafe/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/unicafe/public/favicon.ico -------------------------------------------------------------------------------- /part1/unicafe/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/unicafe/public/logo192.png -------------------------------------------------------------------------------- /part1/unicafe/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/unicafe/public/logo512.png -------------------------------------------------------------------------------- /part9/flight-diary/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part1/anecdotes/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/anecdotes/public/favicon.ico -------------------------------------------------------------------------------- /part1/anecdotes/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/anecdotes/public/logo192.png -------------------------------------------------------------------------------- /part1/anecdotes/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/anecdotes/public/logo512.png -------------------------------------------------------------------------------- /part1/courseinfo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/courseinfo/public/favicon.ico -------------------------------------------------------------------------------- /part1/courseinfo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/courseinfo/public/logo192.png -------------------------------------------------------------------------------- /part1/courseinfo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part1/courseinfo/public/logo512.png -------------------------------------------------------------------------------- /part2/countries/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/countries/public/favicon.ico -------------------------------------------------------------------------------- /part2/countries/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/countries/public/logo192.png -------------------------------------------------------------------------------- /part2/countries/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/countries/public/logo512.png -------------------------------------------------------------------------------- /part2/courseinfo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/courseinfo/public/favicon.ico -------------------------------------------------------------------------------- /part2/courseinfo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/courseinfo/public/logo192.png -------------------------------------------------------------------------------- /part2/courseinfo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/courseinfo/public/logo512.png -------------------------------------------------------------------------------- /part2/phonebook/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/phonebook/public/favicon.ico -------------------------------------------------------------------------------- /part2/phonebook/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/phonebook/public/logo192.png -------------------------------------------------------------------------------- /part2/phonebook/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part2/phonebook/public/logo512.png -------------------------------------------------------------------------------- /part6/unicafe-redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/unicafe-redux/public/favicon.ico -------------------------------------------------------------------------------- /part6/unicafe-redux/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/unicafe-redux/public/logo192.png -------------------------------------------------------------------------------- /part6/unicafe-redux/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/unicafe-redux/public/logo512.png -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part7/country-hook/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/country-hook/public/favicon.ico -------------------------------------------------------------------------------- /part7/country-hook/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/country-hook/public/logo192.png -------------------------------------------------------------------------------- /part7/country-hook/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/country-hook/public/logo512.png -------------------------------------------------------------------------------- /part6/anecdotes-query/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-query/public/favicon.ico -------------------------------------------------------------------------------- /part6/anecdotes-query/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-query/public/logo192.png -------------------------------------------------------------------------------- /part6/anecdotes-query/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-query/public/logo512.png -------------------------------------------------------------------------------- /part6/anecdotes-redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-redux/public/favicon.ico -------------------------------------------------------------------------------- /part6/anecdotes-redux/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-redux/public/logo192.png -------------------------------------------------------------------------------- /part6/anecdotes-redux/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part6/anecdotes-redux/public/logo512.png -------------------------------------------------------------------------------- /part7/anecdotes-routed/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/anecdotes-routed/public/favicon.ico -------------------------------------------------------------------------------- /part7/anecdotes-routed/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/anecdotes-routed/public/logo192.png -------------------------------------------------------------------------------- /part7/anecdotes-routed/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/anecdotes-routed/public/logo512.png -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/ultimate-hooks/public/favicon.ico -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/ultimate-hooks/public/logo192.png -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/ultimate-hooks/public/logo512.png -------------------------------------------------------------------------------- /part8/library-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part8/library-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part8/library-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part8/library-frontend/public/logo192.png -------------------------------------------------------------------------------- /part8/library-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part8/library-frontend/public/logo512.png -------------------------------------------------------------------------------- /part9/create-react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/create-react-app/public/favicon.ico -------------------------------------------------------------------------------- /part9/create-react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/create-react-app/public/logo192.png -------------------------------------------------------------------------------- /part9/create-react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/create-react-app/public/logo512.png -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part5/bloglist-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part5/bloglist-frontend/public/logo192.png -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part5/bloglist-frontend/public/logo512.png -------------------------------------------------------------------------------- /part9/patientor/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/patientor/frontend/public/favicon.ico -------------------------------------------------------------------------------- /part9/patientor/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/patientor/frontend/public/logo192.png -------------------------------------------------------------------------------- /part9/patientor/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/patientor/frontend/public/logo512.png -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/flight-diary/frontend/public/favicon.ico -------------------------------------------------------------------------------- /part9/flight-diary/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/flight-diary/frontend/public/logo192.png -------------------------------------------------------------------------------- /part9/flight-diary/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part9/flight-diary/frontend/public/logo512.png -------------------------------------------------------------------------------- /part3/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /requests 6 | .env -------------------------------------------------------------------------------- /part9/create-react-app/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | const Header = ({ name }: { name: string }) => { 2 | return

{name}

3 | } 4 | 5 | export default Header 6 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/redux/bloglist-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/redux/bloglist-frontend/public/logo192.png -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/redux/bloglist-frontend/public/logo512.png -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/context-react-query/bloglist-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/context-react-query/bloglist-frontend/public/logo192.png -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Juanescacha/FullStackOpen/HEAD/part7/bloglist/context-react-query/bloglist-frontend/public/logo512.png -------------------------------------------------------------------------------- /part7/ultimate-hooks/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | 5 | ReactDOM.createRoot(document.getElementById('root')).render() -------------------------------------------------------------------------------- /part1/anecdotes/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render() 7 | -------------------------------------------------------------------------------- /part1/unicafe/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render() 7 | -------------------------------------------------------------------------------- /part2/countries/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render() 7 | -------------------------------------------------------------------------------- /part7/country-hook/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import App from "./App" 4 | 5 | ReactDOM.createRoot(document.getElementById("root")).render() 6 | -------------------------------------------------------------------------------- /part9/create-react-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client" 2 | import App from "./App" 3 | 4 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 5 | 6 | ) 7 | -------------------------------------------------------------------------------- /part1/courseinfo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render() 7 | -------------------------------------------------------------------------------- /part2/courseinfo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import App from "./App"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render(); 7 | -------------------------------------------------------------------------------- /part2/phonebook/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import App from "./App"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render(); 7 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client" 2 | import App from "./App" 3 | 4 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) 5 | root.render() 6 | -------------------------------------------------------------------------------- /part6/unicafe-redux/src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit" 2 | import reducer from "./reducer" 3 | 4 | export const store = configureStore({ 5 | reducer: reducer, 6 | }) 7 | 8 | export default store 9 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import App from "./App" 4 | 5 | ReactDOM.createRoot(document.getElementById("root")).render() 6 | 7 | // comment test 8 | -------------------------------------------------------------------------------- /part4/bloglist/utils/logger.js: -------------------------------------------------------------------------------- 1 | const info = (...params) => { 2 | console.log(...params) 3 | } 4 | 5 | const error = (...params) => { 6 | console.error(...params) 7 | } 8 | 9 | module.exports = { 10 | info, 11 | error, 12 | } 13 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* * @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,vue,js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /part9/patientor/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 7 | 8 | ) 9 | -------------------------------------------------------------------------------- /part3/README.md: -------------------------------------------------------------------------------- 1 | # Phonebook 2 | 3 | Single page app deployed on Heroku 4 | Both Front-end and Back-end in server 5 | 6 | [Phonebook App](https://juanescacha-phonebook.herokuapp.com/) 7 | 8 | [BackEnd](https://juanescacha-phonebook.herokuapp.com/api/persons) 9 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/utils/logger.js: -------------------------------------------------------------------------------- 1 | const info = (...params) => { 2 | console.log(...params) 3 | } 4 | 5 | const error = (...params) => { 6 | console.error(...params) 7 | } 8 | 9 | module.exports = { 10 | info, 11 | error, 12 | } 13 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | setupNodeEvents(on, config) { 6 | // implement node event listeners here 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/utils/logger.js: -------------------------------------------------------------------------------- 1 | const info = (...params) => { 2 | console.log(...params) 3 | } 4 | 5 | const error = (...params) => { 6 | console.error(...params) 7 | } 8 | 9 | module.exports = { 10 | info, 11 | error, 12 | } 13 | -------------------------------------------------------------------------------- /part1/unicafe/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom" 6 | -------------------------------------------------------------------------------- /part1/unicafe/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react" 2 | import App from "./App" 3 | 4 | test("renders learn react link", () => { 5 | render() 6 | const linkElement = screen.getByText(/learn react/i) 7 | expect(linkElement).toBeInTheDocument() 8 | }) 9 | -------------------------------------------------------------------------------- /part4/bloglist/utils/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | 3 | const PORT = process.env.PORT 4 | const MONGODB_URI = 5 | process.env.NODE_ENV === "test" 6 | ? process.env.TEST_MONGODB_URI 7 | : process.env.MONGODB_URI 8 | 9 | module.exports = { 10 | MONGODB_URI, 11 | PORT, 12 | } 13 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/utils/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | 3 | const PORT = process.env.PORT 4 | const MONGODB_URI = 5 | process.env.NODE_ENV === "test" 6 | ? process.env.TEST_MONGODB_URI 7 | : process.env.MONGODB_URI 8 | 9 | module.exports = { 10 | MONGODB_URI, 11 | PORT, 12 | } 13 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux" 2 | 3 | const Notification = () => { 4 | const message = useSelector(state => state.notification) 5 | return message[0] &&
{message[0]}
6 | } 7 | 8 | export default Notification 9 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import { BrowserRouter as Router } from "react-router-dom" 4 | 5 | import App from "./App" 6 | 7 | ReactDOM.createRoot(document.getElementById("root")).render( 8 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/routes/diagnoses.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | 3 | import diagnoseService from "../services/diagnoseService" 4 | 5 | const router = express.Router() 6 | 7 | router.get("/", (_req, res) => { 8 | res.json(diagnoseService.getEntries()).status(200) 9 | }) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/services/diagnoseService.ts: -------------------------------------------------------------------------------- 1 | import diagnosesData from "../data/diagnoses" 2 | import { Diagnose } from "../types" 3 | 4 | const diagnoses: Diagnose[] = diagnosesData 5 | 6 | const getEntries = (): Diagnose[] => { 7 | return diagnoses 8 | } 9 | 10 | export default { 11 | getEntries, 12 | } 13 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/services/login.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios") 2 | const baseUrl = "/api/login" 3 | 4 | const login = async credentials => { 5 | const response = await axios.post(baseUrl, credentials) 6 | return response.data 7 | } 8 | 9 | const loginService = { login } 10 | 11 | export default loginService 12 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/utils/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | 3 | const PORT = process.env.PORT 4 | const MONGODB_URI = 5 | process.env.NODE_ENV === "test" 6 | ? process.env.TEST_MONGODB_URI 7 | : process.env.MONGODB_URI 8 | 9 | module.exports = { 10 | MONGODB_URI, 11 | PORT, 12 | } 13 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/services/users.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | const baseUrl = "/api/users" 3 | 4 | const getAll = () => { 5 | const request = axios.get(baseUrl) 6 | return request.then(response => response.data) 7 | } 8 | 9 | const usersService = { getAll } 10 | 11 | export default usersService 12 | -------------------------------------------------------------------------------- /part4/bloglist/index.js: -------------------------------------------------------------------------------- 1 | const app = require("./app") 2 | const http = require("http") 3 | const config = require("./utils/config") 4 | const logger = require("./utils/logger") 5 | 6 | const server = http.createServer(app) 7 | 8 | server.listen(config.PORT, () => { 9 | logger.info(`Server running on port ${config.PORT}`) 10 | }) 11 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/services/login.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios") 2 | const baseUrl = "/api/login" 3 | 4 | const login = async credentials => { 5 | const response = await axios.post(baseUrl, credentials) 6 | return response.data 7 | } 8 | 9 | const loginService = { login } 10 | 11 | export default loginService 12 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import { Provider } from "react-redux" 4 | import App from "./App" 5 | import store from "./store" 6 | 7 | ReactDOM.createRoot(document.getElementById("root")).render( 8 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /part9/patientor/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "outDir": "./build/", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "esModuleInterop": true, 12 | } 13 | } -------------------------------------------------------------------------------- /part9/create-react-app/src/components/Total.tsx: -------------------------------------------------------------------------------- 1 | import { CoursePart } from "../types" 2 | 3 | const Total = ({ courseParts }: { courseParts: CoursePart[] }) => { 4 | return ( 5 |

6 | Number of exercises{" "} 7 | {courseParts.reduce((carry, part) => carry + part.exerciseCount, 0)} 8 |

9 | ) 10 | } 11 | 12 | export default Total 13 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/services/login.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios") 2 | const baseUrl = "/api/login" 3 | 4 | const login = async credentials => { 5 | const response = await axios.post(baseUrl, credentials) 6 | return response.data 7 | } 8 | 9 | const loginService = { login } 10 | 11 | export default loginService 12 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/index.js: -------------------------------------------------------------------------------- 1 | const app = require("./app") 2 | const http = require("http") 3 | const config = require("./utils/config") 4 | const logger = require("./utils/logger") 5 | 6 | const server = http.createServer(app) 7 | 8 | server.listen(config.PORT, () => { 9 | logger.info(`Server running on port ${config.PORT}`) 10 | }) 11 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/index.js: -------------------------------------------------------------------------------- 1 | const app = require("./app") 2 | const http = require("http") 3 | const config = require("./utils/config") 4 | const logger = require("./utils/logger") 5 | 6 | const server = http.createServer(app) 7 | 8 | server.listen(config.PORT, () => { 9 | logger.info(`Server running on port ${config.PORT}`) 10 | }) 11 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/reducers/userReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const userSlice = createSlice({ 4 | name: "user", 5 | initialState: null, 6 | reducers: { 7 | setUser: (state, action) => action.payload, 8 | }, 9 | }) 10 | 11 | export default userSlice.reducer 12 | export const { setUser } = userSlice.actions 13 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/reducers/usersReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const usersSlice = createSlice({ 4 | name: "users", 5 | initialState: [], 6 | reducers: { 7 | setUsers: (state, action) => action.payload, 8 | }, 9 | }) 10 | 11 | export default usersSlice.reducer 12 | export const { setUsers } = usersSlice.actions 13 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "outDir": "./build/", 5 | "module": "commonjs", 6 | "strict": true, 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "esModuleInterop": true, 12 | "resolveJsonModule": true 13 | } 14 | } -------------------------------------------------------------------------------- /part4/bloglist/controllers/testing.js: -------------------------------------------------------------------------------- 1 | const testingRouter = require("express").Router() 2 | const Blog = require("../models/blog") 3 | const User = require("../models/user") 4 | 5 | testingRouter.post("/reset", async (request, response) => { 6 | await Blog.deleteMany({}) 7 | await User.deleteMany({}) 8 | 9 | response.status(204).end() 10 | }) 11 | 12 | module.exports = testingRouter 13 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | export const useField = type => { 4 | const [value, setValue] = useState("") 5 | 6 | const onChange = event => { 7 | setValue(event.target.value) 8 | } 9 | 10 | const onReset = () => { 11 | setValue("") 12 | } 13 | 14 | return { 15 | type, 16 | value, 17 | onChange, 18 | onReset, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux" 2 | 3 | const Notification = () => { 4 | const notification = useSelector(state => state.notification) 5 | 6 | const style = { 7 | border: "solid", 8 | padding: 10, 9 | borderWidth: 1, 10 | } 11 | return notification &&
{notification}
12 | } 13 | 14 | export default Notification 15 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/controllers/testing.js: -------------------------------------------------------------------------------- 1 | const testingRouter = require("express").Router() 2 | const Blog = require("../models/blog") 3 | const User = require("../models/user") 4 | 5 | testingRouter.post("/reset", async (request, response) => { 6 | await Blog.deleteMany({}) 7 | await User.deleteMany({}) 8 | 9 | response.status(204).end() 10 | }) 11 | 12 | module.exports = testingRouter 13 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/app.css: -------------------------------------------------------------------------------- 1 | .success { 2 | color: rgb(0, 133, 51); 3 | background: lightgray; 4 | font-size: 20px; 5 | border-style: solid; 6 | border-radius: 5px; 7 | padding: 10px; 8 | margin-bottom: 10px; 9 | } 10 | 11 | .error { 12 | color: red; 13 | background: lightgray; 14 | font-size: 20px; 15 | border-style: solid; 16 | border-radius: 5px; 17 | padding: 10px; 18 | margin-bottom: 10px; 19 | } 20 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/controllers/testing.js: -------------------------------------------------------------------------------- 1 | const testingRouter = require("express").Router() 2 | const Blog = require("../models/blog") 3 | const User = require("../models/user") 4 | 5 | testingRouter.post("/reset", async (request, response) => { 6 | await Blog.deleteMany({}) 7 | await User.deleteMany({}) 8 | 9 | response.status(204).end() 10 | }) 11 | 12 | module.exports = testingRouter 13 | -------------------------------------------------------------------------------- /part1/unicafe/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", 4 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 5 | "Helvetica Neue", sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /part2/phonebook/src/app.css: -------------------------------------------------------------------------------- 1 | .success { 2 | color: rgb(0, 133, 51); 3 | background: lightgray; 4 | font-size: 20px; 5 | border-style: solid; 6 | border-radius: 5px; 7 | padding: 10px; 8 | margin-bottom: 10px; 9 | } 10 | 11 | .error { 12 | color: red; 13 | background: lightgray; 14 | font-size: 20px; 15 | border-style: solid; 16 | border-radius: 5px; 17 | padding: 10px; 18 | margin-bottom: 10px; 19 | } 20 | -------------------------------------------------------------------------------- /part1/anecdotes/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part1/courseinfo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part1/unicafe/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part2/courseinfo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part2/phonebook/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/reducers/filterReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const initialState = "" 4 | 5 | const filterSlice = createSlice({ 6 | name: "filter", 7 | initialState, 8 | reducers: { 9 | filterAnecdotes: (state, action) => { 10 | return action.payload 11 | }, 12 | }, 13 | }) 14 | 15 | export default filterSlice.reducer 16 | export const { filterAnecdotes } = filterSlice.actions 17 | -------------------------------------------------------------------------------- /part8/library/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "noImplicitReturns": true, 5 | "strictNullChecks": true, 6 | "strictPropertyInitialization": true, 7 | "strictBindCallApply": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "esModuleInterop": true, 13 | "declaration": true, 14 | } 15 | } -------------------------------------------------------------------------------- /part1/unicafe/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then( 4 | ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 5 | getCLS(onPerfEntry) 6 | getFID(onPerfEntry) 7 | getFCP(onPerfEntry) 8 | getLCP(onPerfEntry) 9 | getTTFB(onPerfEntry) 10 | } 11 | ) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /part6/anecdotes-query/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part6/unicafe-redux/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part7/country-hook/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part8/library/models/author.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const uniqueValidator = require("mongoose-unique-validator") 4 | 5 | const schema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | unique: true, 10 | minlength: 4, 11 | }, 12 | born: { 13 | type: Number, 14 | }, 15 | }) 16 | 17 | schema.plugin(uniqueValidator) 18 | 19 | module.exports = mongoose.model("Author", schema) 20 | -------------------------------------------------------------------------------- /part2/countries/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part8/library-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/create-react-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/patientor/backend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/patientor/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/app.css: -------------------------------------------------------------------------------- 1 | .success { 2 | color: rgb(0, 133, 51); 3 | background: lightgray; 4 | font-size: 20px; 5 | border-style: solid; 6 | border-radius: 5px; 7 | padding: 10px; 8 | margin-bottom: 10px; 9 | } 10 | 11 | .error { 12 | color: red; 13 | background: lightgray; 14 | font-size: 20px; 15 | border-style: solid; 16 | border-radius: 5px; 17 | padding: 10px; 18 | margin-bottom: 10px; 19 | } 20 | -------------------------------------------------------------------------------- /part6/unicafe-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | 4 | import App from "./App" 5 | 6 | import store from "./store" 7 | 8 | // ReactDOM.createRoot(document.getElementById("root")).render() 9 | 10 | const root = ReactDOM.createRoot(document.getElementById("root")) 11 | 12 | const renderApp = () => { 13 | root.render() 14 | } 15 | 16 | renderApp() 17 | 18 | store.subscribe(renderApp) 19 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part8/library/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const uniqueValidator = require("mongoose-unique-validator") 4 | 5 | const schema = new mongoose.Schema({ 6 | username: { 7 | type: String, 8 | required: true, 9 | unique: true, 10 | minlength: 3, 11 | }, 12 | favoriteGenre: { 13 | type: String, 14 | required: true, 15 | }, 16 | }) 17 | 18 | schema.plugin(uniqueValidator) 19 | 20 | module.exports = mongoose.model("User", schema) 21 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit" 2 | import anecdoteReducer from "./reducers/anecdoteReducer" 3 | import filterReducer from "./reducers/filterReducer" 4 | import notificationReducer from "./reducers/notificationReducer" 5 | 6 | const store = configureStore({ 7 | reducer: { 8 | anecdotes: anecdoteReducer, 9 | filter: filterReducer, 10 | notification: notificationReducer, 11 | }, 12 | }) 13 | 14 | export default store 15 | -------------------------------------------------------------------------------- /part9/patientor/frontend/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Diagnosis { 2 | code: string 3 | name: string 4 | latin?: string 5 | } 6 | 7 | export enum Gender { 8 | Male = "male", 9 | Female = "female", 10 | Other = "other", 11 | } 12 | 13 | export interface Patient { 14 | id: string 15 | name: string 16 | occupation: string 17 | gender: Gender 18 | ssn?: string 19 | dateOfBirth?: string 20 | } 21 | 22 | export type PatientFormValues = Omit 23 | -------------------------------------------------------------------------------- /part6/anecdotes-query/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import { useNotificationValue } from "../NotificationContext" 2 | 3 | const Notification = () => { 4 | const style = { 5 | border: "solid", 6 | padding: 10, 7 | borderWidth: 1, 8 | marginBottom: 5, 9 | } 10 | const notification = useNotificationValue() 11 | console.log("Notification: ", notification) 12 | 13 | return notification &&
{notification}
14 | } 15 | 16 | export default Notification 17 | -------------------------------------------------------------------------------- /part6/anecdotes-query/src/requests.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const baseUrl = "http://localhost:3001" 4 | 5 | export const getAnecdotes = () => 6 | axios.get(`${baseUrl}/anecdotes`).then(res => res.data) 7 | 8 | export const createAnecdote = content => 9 | axios.post(`${baseUrl}/anecdotes`, content).then(res => res.data) 10 | 11 | export const updateAnecdote = anecdote => 12 | axios 13 | .put(`${baseUrl}/anecdotes/${anecdote.id}`, anecdote) 14 | .then(res => res.data) 15 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "notes": [ 3 | { 4 | "id": 1, 5 | "content": "custom hooks are awesome" 6 | }, 7 | { 8 | "id": 2, 9 | "content": "best feature ever <3" 10 | } 11 | ], 12 | "persons": [ 13 | { 14 | "id": 1, 15 | "name": "Matti Luukkainen", 16 | "number": "040-12344565" 17 | }, 18 | { 19 | "id": 2, 20 | "name": "Tarja Halonen", 21 | "number": "040-2223334" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /part2/phonebook/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "persons": [ 3 | { 4 | "name": "Arto Hellas", 5 | "number": "040-123456", 6 | "id": 1 7 | }, 8 | { 9 | "name": "Ada Lovelace", 10 | "number": "39-44-5323523", 11 | "id": 2 12 | }, 13 | { 14 | "name": "Dan Abramov", 15 | "number": "12-43-234345", 16 | "id": 3 17 | }, 18 | { 19 | "name": "Mary Poppendieck", 20 | "number": "39-23-6423122", 21 | "id": 4 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /part9/flight-diary/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import cors from "cors" 2 | import express from "express" 3 | import diaryRouter from "./routes/diaries" 4 | 5 | const app = express() 6 | app.use(cors()) 7 | app.use(express.json()) 8 | 9 | const PORT = 3001 10 | 11 | app.get("/ping", (_req, res) => { 12 | console.log("someone pinged here") 13 | res.send("pong") 14 | }) 15 | 16 | app.use("/api/diaries", diaryRouter) 17 | 18 | app.listen(PORT, () => { 19 | console.log(`Server running on port ${PORT}`) 20 | }) 21 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .success { 6 | color: rgb(0, 133, 51); 7 | background: lightgray; 8 | font-size: 20px; 9 | border-style: solid; 10 | border-radius: 5px; 11 | padding: 10px; 12 | margin-bottom: 10px; 13 | } 14 | 15 | .error { 16 | color: red; 17 | background: lightgray; 18 | font-size: 20px; 19 | border-style: solid; 20 | border-radius: 5px; 21 | padding: 10px; 22 | margin-bottom: 10px; 23 | } 24 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/reducers/blogReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const initialState = [] 4 | 5 | const blogSlice = createSlice({ 6 | name: "blogs", 7 | initialState, 8 | reducers: { 9 | setBlogs: (state, action) => { 10 | return action.payload 11 | }, 12 | addBlog: (state, action) => { 13 | return [...state, action.payload] 14 | }, 15 | }, 16 | }) 17 | 18 | export const { setBlogs, addBlog } = blogSlice.actions 19 | export default blogSlice.reducer 20 | -------------------------------------------------------------------------------- /part6/unicafe-redux/src/reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | good: 0, 3 | ok: 0, 4 | bad: 0, 5 | } 6 | 7 | const counterReducer = (state = initialState, action) => { 8 | switch (action.type) { 9 | case "GOOD": 10 | return { ...state, good: state.good + 1 } 11 | case "OK": 12 | return { ...state, ok: state.ok + 1 } 13 | case "BAD": 14 | return { ...state, bad: state.bad + 1 } 15 | case "ZERO": 16 | return { ...initialState } 17 | default: 18 | return { ...state } 19 | } 20 | } 21 | 22 | export default counterReducer 23 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit" 2 | import notificationReducer from "./reducers/notificationReducer" 3 | import blogReducer from "./reducers/blogReducer" 4 | import userReducer from "./reducers/userReducer" 5 | import usersReducer from "./reducers/usersReducer" 6 | 7 | const store = configureStore({ 8 | reducer: { 9 | notification: notificationReducer, 10 | blogs: blogReducer, 11 | user: userReducer, 12 | users: usersReducer, 13 | }, 14 | }) 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-interface */ 2 | 3 | export interface Diagnose { 4 | code: string 5 | name: string 6 | latin?: string 7 | } 8 | 9 | export interface Patient { 10 | id: string 11 | name: string 12 | ssn: string 13 | occupation: string 14 | gender: string 15 | dateOfBirth: string 16 | entries: Entry[] 17 | } 18 | 19 | export interface Entry {} 20 | 21 | export type NewPatient = Omit 22 | 23 | export type NonSensitivePatient = Omit 24 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/components/Filter.js: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux" 2 | import { filterAnecdotes } from "../reducers/filterReducer" 3 | 4 | const Filter = () => { 5 | const dispatch = useDispatch() 6 | 7 | const handleChange = event => { 8 | const text = event.target.value 9 | dispatch(filterAnecdotes(text)) 10 | } 11 | const style = { 12 | marginBottom: 10, 13 | } 14 | 15 | return ( 16 |
17 | filter 18 |
19 | ) 20 | } 21 | 22 | export default Filter 23 | -------------------------------------------------------------------------------- /part8/library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@apollo/server": "^4.4.0", 14 | "dotenv": "^16.0.3", 15 | "graphql": "^16.6.0", 16 | "mongoose": "^6.0.0", 17 | "mongoose-unique-validator": "^3.1.0" 18 | }, 19 | "devDependencies": { 20 | "jsonwebtoken": "^9.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /part6/anecdotes-query/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import { QueryClient, QueryClientProvider } from "react-query" 4 | import { NotificationContextProvider } from "./NotificationContext" 5 | 6 | import App from "./App" 7 | 8 | const queryClient = new QueryClient() 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /part8/library/models/book.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const uniqueValidator = require("mongoose-unique-validator") 4 | 5 | const schema = new mongoose.Schema({ 6 | title: { 7 | type: String, 8 | required: true, 9 | unique: true, 10 | minlength: 5, 11 | }, 12 | published: { 13 | type: Number, 14 | }, 15 | author: { 16 | type: mongoose.Schema.Types.ObjectId, 17 | ref: "Author", 18 | }, 19 | genres: [{ type: String }], 20 | }) 21 | 22 | schema.plugin(uniqueValidator) 23 | 24 | module.exports = mongoose.model("Book", schema) 25 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/src/types.ts: -------------------------------------------------------------------------------- 1 | export enum Weather { 2 | Sunny = "sunny", 3 | Rainy = "rainy", 4 | Cloudy = "cloudy", 5 | Stormy = "stormy", 6 | Windy = "windy", 7 | } 8 | 9 | export enum Visibility { 10 | Great = "great", 11 | Good = "good", 12 | Ok = "ok", 13 | Poor = "poor", 14 | } 15 | 16 | export interface DiaryEntry { 17 | id: number 18 | date: string 19 | weather: Weather 20 | visibility: Visibility 21 | comment: string 22 | } 23 | 24 | export type NewDiaryEntry = Omit 25 | 26 | export type NonSensitiveDiaryEntry = Omit 27 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/src/types.ts: -------------------------------------------------------------------------------- 1 | export enum Weather { 2 | Sunny = "sunny", 3 | Rainy = "rainy", 4 | Cloudy = "cloudy", 5 | Stormy = "stormy", 6 | Windy = "windy", 7 | } 8 | 9 | export enum Visibility { 10 | Great = "great", 11 | Good = "good", 12 | Ok = "ok", 13 | Poor = "poor", 14 | } 15 | 16 | export interface DiaryEntry { 17 | id: number 18 | date: string 19 | weather: Weather 20 | visibility: Visibility 21 | comment: string 22 | } 23 | 24 | export type NewDiaryEntry = Omit 25 | 26 | export type NonSensitiveDiaryEntry = Omit 27 | -------------------------------------------------------------------------------- /part9/create-react-app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "plugins": [ 13 | "react", 14 | "@typescript-eslint" 15 | ], 16 | "settings": { 17 | "react": { 18 | "pragma": "React", 19 | "version": "detect" 20 | } 21 | }, 22 | "rules": { 23 | "@typescript-eslint/explicit-function-return-type": 0, 24 | "@typescript-eslint/explicit-module-boundary-types": 0, 25 | "react/react-in-jsx-scope": 0 26 | } 27 | } -------------------------------------------------------------------------------- /part9/flight-diary/frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "plugins": [ 13 | "react", 14 | "@typescript-eslint" 15 | ], 16 | "settings": { 17 | "react": { 18 | "pragma": "React", 19 | "version": "detect" 20 | } 21 | }, 22 | "rules": { 23 | "@typescript-eslint/explicit-function-return-type": 0, 24 | "@typescript-eslint/explicit-module-boundary-types": 0, 25 | "react/react-in-jsx-scope": 0 26 | } 27 | } -------------------------------------------------------------------------------- /part1/unicafe/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/patientor/frontend/src/services/patients.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import { Patient, PatientFormValues } from "../types" 3 | 4 | import { apiBaseUrl } from "../constants" 5 | 6 | const getAll = async () => { 7 | const { data } = await axios.get(`${apiBaseUrl}/patients`) 8 | 9 | return data 10 | } 11 | 12 | const create = async (object: PatientFormValues) => { 13 | const { data } = await axios.post(`${apiBaseUrl}/patients`, object) 14 | 15 | return data 16 | } 17 | 18 | // eslint-disable-next-line import/no-anonymous-default-export 19 | export default { 20 | getAll, 21 | create, 22 | } 23 | -------------------------------------------------------------------------------- /part9/patientor/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } -------------------------------------------------------------------------------- /part1/anecdotes/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part1/courseinfo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part2/countries/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part2/courseinfo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part2/phonebook/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part6/unicafe-redux/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part7/country-hook/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part6/anecdotes-query/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part8/library-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/create-react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/patientor/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import cors from "cors" 3 | 4 | import diagnoseRouter from "./routes/diagnoses" 5 | import patientRouter from "./routes/patients" 6 | 7 | const corsMiddleware = cors() 8 | const app = express() 9 | app.use(express.json()) 10 | app.use(corsMiddleware) 11 | 12 | const PORT = 3001 13 | 14 | app.get("/api/ping", (_req, res) => { 15 | console.log("someone pinged here") 16 | res.send("pong") 17 | }) 18 | 19 | app.use("/api/diagnoses", diagnoseRouter) 20 | app.use("/api/patients", patientRouter) 21 | 22 | app.listen(PORT, () => { 23 | console.log(`Server running on port ${PORT}`) 24 | }) 25 | -------------------------------------------------------------------------------- /part3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonebook-backend", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "lint": "eslint ." 11 | }, 12 | "author": "Juan Esteban Camargo", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "eslint": "^8.20.0", 16 | "nodemon": "^2.0.19" 17 | }, 18 | "dependencies": { 19 | "cors": "^2.8.5", 20 | "dotenv": "^16.0.1", 21 | "express": "^4.18.1", 22 | "mongoose": "^6.4.7", 23 | "morgan": "^1.10.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part4/bloglist/models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const blogSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: { type: String, required: true }, 6 | url: { type: String, required: true }, 7 | likes: { type: Number, default: 0 }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | }) 13 | 14 | blogSchema.set("toJSON", { 15 | transform: (document, returnedObject) => { 16 | returnedObject.id = returnedObject._id.toString() 17 | delete returnedObject._id 18 | delete returnedObject.__v 19 | }, 20 | }) 21 | 22 | module.exports = mongoose.model("Blog", blogSchema) 23 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /part9/create-react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /part2/courseinfo/src/Course.js: -------------------------------------------------------------------------------- 1 | const Header = ({ course }) =>

{course.name}

; 2 | 3 | const Total = ({ parts }) => { 4 | let sum = 0; 5 | sum = parts.reduce((a, b) => a + b.exercises, 0); 6 | return Total of {sum} exercises; 7 | }; 8 | 9 | const Part = ({ part }) => ( 10 |

11 | {part.name} {part.exercises} 12 |

13 | ); 14 | 15 | const Content = ({ parts }) => 16 | parts.map((part) => ); 17 | 18 | const Course = ({ course }) => ( 19 | <> 20 |
21 | 22 | 23 | 24 | ); 25 | 26 | export default Course; 27 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const blogSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: { type: String, required: true }, 6 | url: { type: String, required: true }, 7 | likes: { type: Number, default: 0 }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | }) 13 | 14 | blogSchema.set("toJSON", { 15 | transform: (document, returnedObject) => { 16 | returnedObject.id = returnedObject._id.toString() 17 | delete returnedObject._id 18 | delete returnedObject.__v 19 | }, 20 | }) 21 | 22 | module.exports = mongoose.model("Blog", blogSchema) 23 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import App from "./App" 4 | 5 | import { QueryClient, QueryClientProvider } from "react-query" 6 | import { NotificationContextProvider } from "./NotificationContext" 7 | import { LoginContextProvider } from "./loginContext" 8 | 9 | const queryClient = new QueryClient() 10 | 11 | ReactDOM.createRoot(document.getElementById("root")).render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | -------------------------------------------------------------------------------- /part4/bloglist/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { type: String, required: true, minLength: 3 }, 5 | name: { type: String, required: true }, 6 | passwordHash: { type: String, require: true, minLength: 3 }, 7 | blogs: [ 8 | { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "Blog", 11 | }, 12 | ], 13 | }) 14 | 15 | userSchema.set("toJSON", { 16 | transform: (document, returnedObject) => { 17 | returnedObject.id = returnedObject._id.toString() 18 | delete returnedObject._id 19 | delete returnedObject.__v 20 | delete returnedObject.passwordHash 21 | }, 22 | }) 23 | 24 | module.exports = mongoose.model("User", userSchema) 25 | -------------------------------------------------------------------------------- /part1/unicafe/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part3/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "linux" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "double" 23 | ], 24 | "semi": [ 25 | "error", 26 | "never" 27 | ], 28 | "no-trailing-spaces": "error", 29 | "object-curly-spacing": [ 30 | "error", 31 | "always" 32 | ], 33 | "arrow-spacing": [ 34 | "error", 35 | { 36 | "before": true, 37 | "after": true 38 | } 39 | ], 40 | "no-console": 0 41 | } 42 | } -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const blogSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: { type: String, required: true }, 6 | url: { type: String, required: true }, 7 | likes: { type: Number, default: 0 }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "User", 11 | }, 12 | comments: { type: Array, default: [] }, 13 | }) 14 | 15 | blogSchema.set("toJSON", { 16 | transform: (document, returnedObject) => { 17 | returnedObject.id = returnedObject._id.toString() 18 | delete returnedObject._id 19 | delete returnedObject.__v 20 | }, 21 | }) 22 | 23 | module.exports = mongoose.model("Blog", blogSchema) 24 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { type: String, required: true, minLength: 3 }, 5 | name: { type: String, required: true }, 6 | passwordHash: { type: String, require: true, minLength: 3 }, 7 | blogs: [ 8 | { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "Blog", 11 | }, 12 | ], 13 | }) 14 | 15 | userSchema.set("toJSON", { 16 | transform: (document, returnedObject) => { 17 | returnedObject.id = returnedObject._id.toString() 18 | delete returnedObject._id 19 | delete returnedObject.__v 20 | delete returnedObject.passwordHash 21 | }, 22 | }) 23 | 24 | module.exports = mongoose.model("User", userSchema) 25 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ilaris-diaries", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "tsc": "tsc", 8 | "dev": "ts-node-dev src/index.ts", 9 | "lint": "eslint --ext .ts .", 10 | "start": "node build/index.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@types/cors": "^2.8.17", 17 | "@types/express": "^4.17.17", 18 | "@typescript-eslint/eslint-plugin": "^5.51.0", 19 | "@typescript-eslint/parser": "^5.51.0", 20 | "eslint": "^8.33.0", 21 | "ts-node-dev": "^2.0.0", 22 | "typescript": "^4.9.5" 23 | }, 24 | "dependencies": { 25 | "cors": "^2.8.5", 26 | "express": "^4.18.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom/client" 3 | import App, { Users, User, BlogView } from "./App" 4 | 5 | import store from "./store" 6 | import { Provider } from "react-redux" 7 | 8 | import { createBrowserRouter, RouterProvider } from "react-router-dom" 9 | 10 | const router = createBrowserRouter([ 11 | { path: "/", element: }, 12 | { 13 | path: "/users", 14 | element: , 15 | }, 16 | { path: "/users/:id", element: }, 17 | { path: "/blogs/:id", element: }, 18 | ]) 19 | 20 | ReactDOM.createRoot(document.getElementById("root")).render( 21 | 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { type: String, required: true, minLength: 3 }, 5 | name: { type: String, required: true }, 6 | passwordHash: { type: String, require: true, minLength: 3 }, 7 | blogs: [ 8 | { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: "Blog", 11 | }, 12 | ], 13 | }) 14 | 15 | userSchema.set("toJSON", { 16 | transform: (document, returnedObject) => { 17 | returnedObject.id = returnedObject._id.toString() 18 | delete returnedObject._id 19 | delete returnedObject.__v 20 | delete returnedObject.passwordHash 21 | }, 22 | }) 23 | 24 | module.exports = mongoose.model("User", userSchema) 25 | -------------------------------------------------------------------------------- /part4/bloglist/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": "latest" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab" 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "double" 24 | ], 25 | "semi": [ 26 | "error", 27 | "never" 28 | ], 29 | "no-trailing-spaces": "error", 30 | "object-curly-spacing": [ 31 | "error", 32 | "always" 33 | ], 34 | "arrow-spacing": [ 35 | "error", 36 | { 37 | "before": true, 38 | "after": true 39 | } 40 | ], 41 | "no-console": 0 42 | } 43 | } -------------------------------------------------------------------------------- /part6/anecdotes-query/server.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require("json-server") 2 | const server = jsonServer.create() 3 | const router = jsonServer.router("db.json") 4 | const middlewares = jsonServer.defaults() 5 | 6 | const validator = (request, response, next) => { 7 | console.log() 8 | 9 | const { content } = request.body 10 | 11 | if (request.method === "POST" && (!content || content.length < 5)) { 12 | return response.status(400).json({ 13 | error: "too short anecdote, must have length 5 or more", 14 | }) 15 | } else { 16 | next() 17 | } 18 | } 19 | 20 | server.use(middlewares) 21 | server.use(jsonServer.bodyParser) 22 | server.use(validator) 23 | server.use(router) 24 | 25 | server.listen(3001, () => { 26 | console.log("JSON Server is running") 27 | }) 28 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": "latest" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab" 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "double" 24 | ], 25 | "semi": [ 26 | "error", 27 | "never" 28 | ], 29 | "no-trailing-spaces": "error", 30 | "object-curly-spacing": [ 31 | "error", 32 | "always" 33 | ], 34 | "arrow-spacing": [ 35 | "error", 36 | { 37 | "before": true, 38 | "after": true 39 | } 40 | ], 41 | "no-console": 0 42 | } 43 | } -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | 3 | // Redux 4 | 5 | import { useDispatch } from "react-redux" 6 | import { initializeAnecdotes } from "./reducers/anecdoteReducer" 7 | 8 | // Componentes 9 | 10 | import AnecdoteForm from "./components/AnecdoteForm" 11 | import AnecdoteList from "./components/AnecdoteList" 12 | import Filter from "./components/Filter" 13 | import Notification from "./components/Notification" 14 | 15 | const App = () => { 16 | const dispatch = useDispatch() 17 | useEffect(() => { 18 | dispatch(initializeAnecdotes()) 19 | }, [dispatch]) 20 | 21 | return ( 22 | <> 23 |

Anecdotes

24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export default App 33 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": "latest" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab" 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "double" 24 | ], 25 | "semi": [ 26 | "error", 27 | "never" 28 | ], 29 | "no-trailing-spaces": "error", 30 | "object-curly-spacing": [ 31 | "error", 32 | "always" 33 | ], 34 | "arrow-spacing": [ 35 | "error", 36 | { 37 | "before": true, 38 | "after": true 39 | } 40 | ], 41 | "no-console": 0 42 | } 43 | } -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/reducers/notificationReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const initialState = "" 4 | 5 | const notificationSlice = createSlice({ 6 | name: "notification", 7 | initialState, 8 | reducers: { 9 | setNotification: (state, action) => { 10 | return action.payload 11 | }, 12 | removeNotification: (state, action) => { 13 | return initialState 14 | }, 15 | }, 16 | }) 17 | 18 | export const setNotificationTime = (notification, time) => { 19 | return async dispatch => { 20 | dispatch(setNotification(notification)) 21 | setTimeout(() => { 22 | dispatch(removeNotification()) 23 | }, time * 1000) 24 | } 25 | } 26 | 27 | export default notificationSlice.reducer 28 | export const { setNotification, removeNotification } = notificationSlice.actions 29 | -------------------------------------------------------------------------------- /part9/create-react-app/src/types.ts: -------------------------------------------------------------------------------- 1 | interface CoursePartBase { 2 | name: string 3 | exerciseCount: number 4 | } 5 | 6 | interface CoursePartDescription extends CoursePartBase { 7 | description: string 8 | } 9 | 10 | interface CoursePartBasic extends CoursePartDescription { 11 | kind: "basic" 12 | } 13 | 14 | interface CoursePartGroup extends CoursePartBase { 15 | groupProjectCount: number 16 | kind: "group" 17 | } 18 | 19 | interface CoursePartBackground extends CoursePartDescription { 20 | backgroundMaterial: string 21 | kind: "background" 22 | } 23 | 24 | interface CoursePartSpecial extends CoursePartDescription { 25 | requirements: string[] 26 | kind: "special" 27 | } 28 | 29 | export type CoursePart = 30 | | CoursePartBasic 31 | | CoursePartGroup 32 | | CoursePartBackground 33 | | CoursePartSpecial 34 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/services/anecdotes.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const baseUrl = "http://localhost:3001/anecdotes" 4 | 5 | const getAll = async () => { 6 | const response = await axios.get(baseUrl) 7 | return response.data 8 | } 9 | 10 | const createNew = async content => { 11 | const object = { content, votes: 0 } 12 | const response = await axios.post(baseUrl, object) 13 | return response.data 14 | } 15 | 16 | const getOne = async id => { 17 | const response = await axios.get(`${baseUrl}/${id}`) 18 | return response.data 19 | } 20 | 21 | const update = async (id, newObject) => { 22 | const response = await axios.put(`${baseUrl}/${id}`, newObject) 23 | return response.data 24 | } 25 | 26 | const AnecdoteService = { getAll, createNew, getOne, update } 27 | 28 | export default AnecdoteService 29 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/reducers/notificationReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | 3 | const initialState = ["", "success"] 4 | 5 | const notificationSlice = createSlice({ 6 | name: "notification", 7 | initialState, 8 | reducers: { 9 | setNotification: (state, action) => { 10 | return action.payload 11 | }, 12 | clearNotification: () => { 13 | return initialState 14 | }, 15 | }, 16 | }) 17 | 18 | export const setNotificationTimeout = (message, type, timeout) => { 19 | return async dispatch => { 20 | dispatch(setNotification([message, type])) 21 | setTimeout(() => { 22 | dispatch(clearNotification()) 23 | }, timeout) 24 | } 25 | } 26 | 27 | export const { setNotification, clearNotification } = notificationSlice.actions 28 | export default notificationSlice.reducer 29 | -------------------------------------------------------------------------------- /part2/phonebook/src/services/persons.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const baseUrl = "/api/persons" 4 | 5 | const getAll = () => { 6 | const request = axios.get(baseUrl) 7 | return request.then(response => response.data) 8 | } 9 | 10 | const create = newObject => { 11 | const request = axios.post(baseUrl, newObject) 12 | return request.then(response => response.data) 13 | } 14 | 15 | const update = (id, newObject) => { 16 | const request = axios.put(`${baseUrl}/${id}`, newObject) 17 | return request.then(response => response.data) 18 | } 19 | 20 | const eliminate = id => { 21 | const request = axios.delete(`${baseUrl}/${id}`) 22 | return request.then(response => response.data) 23 | } 24 | 25 | const personService = { 26 | getAll, 27 | create, 28 | update, 29 | eliminate, 30 | } 31 | 32 | export default personService 33 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Authors.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@apollo/client" 2 | import { ALL_AUTHORS } from "../queries" 3 | 4 | const Authors = ({ show }) => { 5 | const result = useQuery(ALL_AUTHORS) 6 | 7 | if (!show) { 8 | return null 9 | } 10 | 11 | if (result.loading) { 12 | return
loading...
13 | } 14 | 15 | const authors = result.data.allAuthors 16 | 17 | return ( 18 |
19 |

authors

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {authors.map(a => ( 28 | 29 | 30 | 31 | 32 | 33 | ))} 34 | 35 |
bornbooks
{a.name}{a.born}{a.bookCount}
36 |
37 | ) 38 | } 39 | 40 | export default Authors 41 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/bmiCalculator.ts: -------------------------------------------------------------------------------- 1 | export const calculateBmi = (height: number, weight: number): string => { 2 | // BMI Calculator 3 | // weight -> kg 4 | // height -> cm 5 | const BMI = weight / (height / 100) ** 2; 6 | // let phrase = "Underweight (unhealthy weight)" ? BMI < 18.5 : "Normal (healthy weight)" ? BMI >= 18.5 && BMI < 25 : "Overweight (unhealthy weight)" ? BMI >= 25 && BMI < 30 : "Obese (unhealthy weight)" 7 | const phrase = 8 | BMI < 18.5 9 | ? "Underweight (unhealthy weight)" 10 | : BMI >= 18.5 && BMI < 22.9 11 | ? "Normal (healthy weight)" 12 | : BMI >= 22.9 && BMI < 24.9 13 | ? "Overweight (at risk)" 14 | : "Overweight (moderately obese)"; 15 | 16 | return phrase; 17 | }; 18 | 19 | // const h = Number(process.argv[2]); 20 | // const w = Number(process.argv[3]); 21 | // console.log(calculateBmi(h, w)); 22 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-first-steps", 3 | "version": "1.0.0", 4 | "description": "Project for the exercises of part 9, about the First steps with TypeScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node index.ts", 8 | "dev": "ts-node-dev index.ts", 9 | "ts-node": "ts-node", 10 | "calculateBmi": "ts-node bmiCalculator.ts", 11 | "calculateExercises": "ts-node exerciseCalculator.ts", 12 | "lint": "eslint --ext .ts ." 13 | }, 14 | "author": "Juan Esteban Camargo", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@types/express": "^4.17.17", 18 | "@typescript-eslint/eslint-plugin": "^5.59.8", 19 | "@typescript-eslint/parser": "^5.59.8", 20 | "eslint": "^8.41.0", 21 | "ts-node": "^10.9.1", 22 | "ts-node-dev": "^2.0.0" 23 | }, 24 | "dependencies": { 25 | "express": "^4.18.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/components/AnecdoteForm.js: -------------------------------------------------------------------------------- 1 | // redux 2 | import { useDispatch } from "react-redux" 3 | import { newAnecdote } from "../reducers/anecdoteReducer" 4 | import { setNotificationTime } from "../reducers/notificationReducer" 5 | 6 | const AnecdoteForm = () => { 7 | const dispatch = useDispatch() 8 | 9 | const createAnecdote = async event => { 10 | event.preventDefault() 11 | const content = event.target.anecdote.value 12 | event.target.anecdote.value = "" 13 | dispatch( 14 | setNotificationTime(`you created the Anecdote: '${content}'`, 5) 15 | ) 16 | dispatch(newAnecdote(content)) 17 | } 18 | 19 | return ( 20 | <> 21 |

create new

22 |
23 |
24 | 25 |
26 | 27 |
28 | 29 | ) 30 | } 31 | 32 | export default AnecdoteForm 33 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/services/patientService.ts: -------------------------------------------------------------------------------- 1 | import patientsData from "../data/patients" 2 | import { NewPatient, NonSensitivePatient, Patient } from "../types" 3 | import { v1 as uuid } from "uuid" 4 | 5 | const patients: NonSensitivePatient[] = patientsData 6 | 7 | const getEntries = (): NonSensitivePatient[] => { 8 | return patients.map(({ id, name, dateOfBirth, gender, occupation }) => ({ 9 | id, 10 | name, 11 | dateOfBirth, 12 | gender, 13 | occupation, 14 | })) 15 | } 16 | 17 | const addPatient = (entry: NewPatient): Patient => { 18 | const newPatient = { 19 | id: uuid(), 20 | ...entry, 21 | } 22 | 23 | patients.push(newPatient) 24 | return newPatient 25 | } 26 | 27 | const getPatient = (id: string): NonSensitivePatient | undefined => { 28 | return patients.find(patient => patient.id === id) 29 | } 30 | 31 | export default { 32 | getEntries, 33 | getPatient, 34 | addPatient, 35 | } 36 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 6 | ], 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "env": { 11 | "node": true, 12 | "es6": true 13 | }, 14 | "rules": { 15 | "@typescript-eslint/semi": [ 16 | "error" 17 | ], 18 | "@typescript-eslint/explicit-function-return-type": "off", 19 | "@typescript-eslint/explicit-module-boundary-types": "off", 20 | "@typescript-eslint/restrict-template-expressions": "off", 21 | "@typescript-eslint/restrict-plus-operands": "off", 22 | "@typescript-eslint/no-unused-vars": [ 23 | "error", 24 | { 25 | "argsIgnorePattern": "^_" 26 | } 27 | ], 28 | "no-case-declarations": "off" 29 | }, 30 | "parser": "@typescript-eslint/parser", 31 | "parserOptions": { 32 | "project": "./tsconfig.json" 33 | } 34 | } -------------------------------------------------------------------------------- /part9/patientor/frontend/src/components/AddPatientModal/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dialog, 3 | DialogTitle, 4 | DialogContent, 5 | Divider, 6 | Alert, 7 | } from "@mui/material" 8 | 9 | import AddPatientForm from "./AddPatientForm" 10 | import { PatientFormValues } from "../../types" 11 | 12 | interface Props { 13 | modalOpen: boolean 14 | onClose: () => void 15 | onSubmit: (values: PatientFormValues) => void 16 | error?: string 17 | } 18 | 19 | const AddPatientModal = ({ modalOpen, onClose, onSubmit, error }: Props) => ( 20 | onClose()}> 24 | Add a new patient 25 | 26 | 27 | {error && {error}} 28 | 32 | 33 | 34 | ) 35 | 36 | export default AddPatientModal 37 | -------------------------------------------------------------------------------- /part8/library-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloClient, 3 | ApolloProvider, 4 | InMemoryCache, 5 | createHttpLink, 6 | } from "@apollo/client" 7 | import { setContext } from "@apollo/client/link/context" 8 | import React from "react" 9 | import ReactDOM from "react-dom/client" 10 | import App from "./App" 11 | 12 | const authLink = setContext((_, { headers }) => { 13 | const token = localStorage.getItem("library-user-token") 14 | return { 15 | headers: { 16 | ...headers, 17 | authorization: token ? `bearer ${token}` : null, 18 | }, 19 | } 20 | }) 21 | 22 | const httpLink = createHttpLink({ 23 | uri: "http://localhost:4000", 24 | }) 25 | 26 | const client = new ApolloClient({ 27 | cache: new InMemoryCache(), 28 | // uri: "http://localhost:4000", 29 | link: authLink.concat(httpLink), 30 | }) 31 | 32 | ReactDOM.createRoot(document.getElementById("root")).render( 33 | 34 | 35 | 36 | ) 37 | -------------------------------------------------------------------------------- /part1/unicafe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unicafe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/src/routes/diaries.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | 3 | import diaryService from "../services/diaryService" 4 | 5 | import toNewDiaryEntry from "../utils" 6 | 7 | const router = express.Router() 8 | 9 | router.get("/", (_req, res) => { 10 | res.send(diaryService.getEntries()) 11 | }) 12 | 13 | router.get("/:id", (req, res) => { 14 | const diary = diaryService.findById(Number(req.params.id)) 15 | 16 | if (diary) { 17 | res.send(diary) 18 | } else { 19 | res.sendStatus(404) 20 | } 21 | }) 22 | 23 | router.post("/", (req, res) => { 24 | try { 25 | const newDiaryEntry = toNewDiaryEntry(req.body) 26 | const addedEntry = diaryService.addDiary(newDiaryEntry) 27 | res.json(addedEntry) 28 | } catch (error: unknown) { 29 | let errorMessage = "Something went wrong." 30 | if (error instanceof Error) { 31 | errorMessage += " Error: " + error.message 32 | } 33 | res.status(400).send(errorMessage) 34 | } 35 | }) 36 | 37 | export default router 38 | -------------------------------------------------------------------------------- /part1/anecdotes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part1/courseinfo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "courseinfo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part2/courseinfo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "courseinfo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part4/bloglist/controllers/login.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken") 2 | const bcrypt = require("bcrypt") 3 | const loginRouter = require("express").Router() 4 | const User = require("../models/user") 5 | 6 | loginRouter.post("/", async (request, response) => { 7 | const { username, password } = request.body 8 | 9 | const user = await User.findOne({ username }) 10 | 11 | const passwordCorrect = 12 | user === null 13 | ? false 14 | : await bcrypt.compare(password, user.passwordHash) 15 | 16 | if (!(user && passwordCorrect)) { 17 | return response.status(401).json({ 18 | error: "Invalid username or password", 19 | }) 20 | } 21 | 22 | const userForToken = { 23 | username: user.username, 24 | id: user._id, 25 | } 26 | 27 | const token = jwt.sign(userForToken, process.env.SECRET) 28 | 29 | response.status(200).send({ 30 | token: token, 31 | username: user.username, 32 | name: user.name, 33 | id: user._id, 34 | }) 35 | }) 36 | 37 | module.exports = loginRouter 38 | -------------------------------------------------------------------------------- /part4/bloglist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloglist", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon", 9 | "lint": "eslint .", 10 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand --forceExit", 11 | "start:test": "cross-env NODE_ENV=test node index.js" 12 | }, 13 | "author": "Juan Esteban Camargo", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "eslint": "^8.20.0", 17 | "jest": "^28.1.3", 18 | "nodemon": "^2.0.19", 19 | "supertest": "^6.2.4" 20 | }, 21 | "dependencies": { 22 | "bcrypt": "^5.0.1", 23 | "cors": "^2.8.5", 24 | "cross-env": "^7.0.3", 25 | "dotenv": "^16.0.1", 26 | "express": "^4.18.1", 27 | "express-async-errors": "^3.1.1", 28 | "jsonwebtoken": "^8.5.1", 29 | "lodash": "^4.17.21", 30 | "mongoose": "^6.5.0" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/loginContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useReducer, useContext } from "react" 2 | 3 | const loginReducer = (state, action) => { 4 | switch (action.type) { 5 | case "SET": 6 | return action.data 7 | case "REMOVE": 8 | return false 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | const LoginContext = createContext() 15 | 16 | export const LoginContextProvider = ({ children }) => { 17 | const [login, loginDispatch] = useReducer(loginReducer, false) 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | // helpers 27 | 28 | export const useLoginValue = () => { 29 | const loginAndDispatch = useContext(LoginContext) 30 | return loginAndDispatch[0] 31 | } 32 | 33 | export const useLoginDispatch = () => { 34 | const loginAndDispatch = useContext(LoginContext) 35 | return loginAndDispatch[1] 36 | } 37 | 38 | export default LoginContext 39 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/controllers/login.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken") 2 | const bcrypt = require("bcrypt") 3 | const loginRouter = require("express").Router() 4 | const User = require("../models/user") 5 | 6 | loginRouter.post("/", async (request, response) => { 7 | const { username, password } = request.body 8 | 9 | const user = await User.findOne({ username }) 10 | 11 | const passwordCorrect = 12 | user === null 13 | ? false 14 | : await bcrypt.compare(password, user.passwordHash) 15 | 16 | if (!(user && passwordCorrect)) { 17 | return response.status(401).json({ 18 | error: "Invalid username or password", 19 | }) 20 | } 21 | 22 | const userForToken = { 23 | username: user.username, 24 | id: user._id, 25 | } 26 | 27 | const token = jwt.sign(userForToken, process.env.SECRET) 28 | 29 | response.status(200).send({ 30 | token: token, 31 | username: user.username, 32 | name: user.name, 33 | id: user._id, 34 | }) 35 | }) 36 | 37 | module.exports = loginRouter 38 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/src/services/diaryService.ts: -------------------------------------------------------------------------------- 1 | import diaryData from "../../data/entries" 2 | 3 | import { NonSensitiveDiaryEntry, DiaryEntry, NewDiaryEntry } from "../types" 4 | 5 | const diaries: DiaryEntry[] = diaryData 6 | 7 | const getEntries = (): DiaryEntry[] => { 8 | return diaries 9 | } 10 | 11 | const getNonSensitiveEntries = (): NonSensitiveDiaryEntry[] => { 12 | return diaries.map(({ id, date, weather, visibility }) => ({ 13 | id, 14 | date, 15 | weather, 16 | visibility, 17 | })) 18 | } 19 | 20 | const findById = (id: number): DiaryEntry | undefined => { 21 | const entry = diaries.find(d => d.id === id) 22 | return entry 23 | } 24 | 25 | const addDiary = (entry: NewDiaryEntry): DiaryEntry => { 26 | const newDiaryEntry = { 27 | id: Math.max(...diaries.map(d => d.id)) + 1, 28 | ...entry, 29 | } 30 | 31 | diaries.push(newDiaryEntry) 32 | return newDiaryEntry 33 | } 34 | 35 | export default { 36 | getEntries, 37 | addDiary, 38 | getNonSensitiveEntries, 39 | findById, 40 | } 41 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloglist", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon", 9 | "lint": "eslint .", 10 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand --forceExit", 11 | "start:test": "cross-env NODE_ENV=test node index.js" 12 | }, 13 | "author": "Juan Esteban Camargo", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "eslint": "^8.20.0", 17 | "jest": "^28.1.3", 18 | "nodemon": "^2.0.19", 19 | "supertest": "^6.2.4" 20 | }, 21 | "dependencies": { 22 | "bcrypt": "^5.0.1", 23 | "cors": "^2.8.5", 24 | "cross-env": "^7.0.3", 25 | "dotenv": "^16.0.1", 26 | "express": "^4.18.1", 27 | "express-async-errors": "^3.1.1", 28 | "jsonwebtoken": "^8.5.1", 29 | "lodash": "^4.17.21", 30 | "mongoose": "^6.5.0" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part9/patientor/backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 6 | ], 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "es6": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | // "@typescript-eslint/semi": [ 17 | // "error" 18 | // ], 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/restrict-template-expressions": "off", 22 | "@typescript-eslint/restrict-plus-operands": "off", 23 | "@typescript-eslint/no-unsafe-member-access": "off", 24 | "@typescript-eslint/no-unused-vars": [ 25 | "error", 26 | { 27 | "argsIgnorePattern": "^_" 28 | } 29 | ], 30 | "no-case-declarations": "off" 31 | }, 32 | "parser": "@typescript-eslint/parser", 33 | "parserOptions": { 34 | "project": "./tsconfig.json" 35 | } 36 | } -------------------------------------------------------------------------------- /part2/countries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "countries", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^0.27.2", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 6 | ], 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "es6": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | // "@typescript-eslint/semi": [ 17 | // "error" 18 | // ], 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/restrict-template-expressions": "off", 22 | "@typescript-eslint/restrict-plus-operands": "off", 23 | "@typescript-eslint/no-unsafe-member-access": "off", 24 | "@typescript-eslint/no-unused-vars": [ 25 | "error", 26 | { 27 | "argsIgnorePattern": "^_" 28 | } 29 | ], 30 | "no-case-declarations": "off" 31 | }, 32 | "parser": "@typescript-eslint/parser", 33 | "parserOptions": { 34 | "project": "./tsconfig.json" 35 | } 36 | } -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/controllers/login.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken") 2 | const bcrypt = require("bcrypt") 3 | const loginRouter = require("express").Router() 4 | const User = require("../models/user") 5 | 6 | loginRouter.post("/", async (request, response) => { 7 | const { username, password } = request.body 8 | 9 | const user = await User.findOne({ username }) 10 | 11 | const passwordCorrect = 12 | user === null 13 | ? false 14 | : await bcrypt.compare(password, user.passwordHash) 15 | 16 | if (!(user && passwordCorrect)) { 17 | return response.status(401).json({ 18 | error: "Invalid username or password", 19 | }) 20 | } 21 | 22 | const userForToken = { 23 | username: user.username, 24 | id: user._id, 25 | } 26 | 27 | const token = jwt.sign(userForToken, process.env.SECRET) 28 | 29 | response.status(200).send({ 30 | token: token, 31 | username: user.username, 32 | name: user.name, 33 | id: user._id, 34 | }) 35 | }) 36 | 37 | module.exports = loginRouter 38 | -------------------------------------------------------------------------------- /part7/country-hook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "country-hook", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^1.3.2", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloglist", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon", 9 | "lint": "eslint .", 10 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand --forceExit", 11 | "start:test": "cross-env NODE_ENV=test node index.js" 12 | }, 13 | "author": "Juan Esteban Camargo", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "eslint": "^8.20.0", 17 | "jest": "^28.1.3", 18 | "nodemon": "^2.0.19", 19 | "supertest": "^6.2.4" 20 | }, 21 | "dependencies": { 22 | "bcrypt": "^5.0.1", 23 | "cors": "^2.8.5", 24 | "cross-env": "^7.0.3", 25 | "dotenv": "^16.0.1", 26 | "express": "^4.18.1", 27 | "express-async-errors": "^3.1.1", 28 | "jsonwebtoken": "^8.5.1", 29 | "lodash": "^4.17.21", 30 | "mongoose": "^6.5.0" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part4/bloglist/controllers/users.js: -------------------------------------------------------------------------------- 1 | const usersRouter = require("express").Router() 2 | const User = require("../models/user") 3 | const bcrypt = require("bcrypt") 4 | 5 | usersRouter.get("/", async (request, response) => { 6 | const users = await User.find({}).populate("blogs", { 7 | title: 1, 8 | author: 1, 9 | url: 1, 10 | likes: 1, 11 | }) 12 | response.json(users) 13 | }) 14 | 15 | usersRouter.post("/", async (request, response) => { 16 | const { username, name, password } = request.body 17 | 18 | const existingUser = await User.findOne({ username }) 19 | 20 | if (existingUser) { 21 | return response.status(400).json({ error: "Username must be unique" }) 22 | } 23 | 24 | const saltRounds = 10 25 | const passwordHash = await bcrypt.hash(password, saltRounds) 26 | 27 | const user = new User({ 28 | username: username, 29 | name: name, 30 | passwordHash: passwordHash, 31 | }) 32 | 33 | const savedUser = await user.save() 34 | response.status(201).json(savedUser) 35 | }) 36 | 37 | module.exports = usersRouter 38 | -------------------------------------------------------------------------------- /part7/anecdotes-routed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routed-anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-router-dom": "^6.8.1", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/controllers/users.js: -------------------------------------------------------------------------------- 1 | const usersRouter = require("express").Router() 2 | const User = require("../models/user") 3 | const bcrypt = require("bcrypt") 4 | 5 | usersRouter.get("/", async (request, response) => { 6 | const users = await User.find({}).populate("blogs", { 7 | title: 1, 8 | author: 1, 9 | url: 1, 10 | likes: 1, 11 | }) 12 | response.json(users) 13 | }) 14 | 15 | usersRouter.post("/", async (request, response) => { 16 | const { username, name, password } = request.body 17 | 18 | const existingUser = await User.findOne({ username }) 19 | 20 | if (existingUser) { 21 | return response.status(400).json({ error: "Username must be unique" }) 22 | } 23 | 24 | const saltRounds = 10 25 | const passwordHash = await bcrypt.hash(password, saltRounds) 26 | 27 | const user = new User({ 28 | username: username, 29 | name: name, 30 | passwordHash: passwordHash, 31 | }) 32 | 33 | const savedUser = await user.save() 34 | response.status(201).json(savedUser) 35 | }) 36 | 37 | module.exports = usersRouter 38 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/routes/patients.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | 3 | import patientService from "../services/patientService" 4 | import { toNewPatient } from "../utils" 5 | 6 | const router = express.Router() 7 | 8 | router.get("/", (_req, res) => { 9 | res.json(patientService.getEntries()).status(200) 10 | }) 11 | 12 | router.post("/", (req, res) => { 13 | try { 14 | const newPatient = toNewPatient(req.body) 15 | 16 | const addedPatient = patientService.addPatient(newPatient) 17 | 18 | res.json(addedPatient) 19 | } catch (error: unknown) { 20 | let errorMessage = "Something went wrong" 21 | if (error instanceof Error) { 22 | errorMessage += " Error: " + error.message 23 | } 24 | res.status(400).send(errorMessage) 25 | } 26 | }) 27 | 28 | router.get("/:id", (req, res) => { 29 | const id = req.params.id 30 | const patient = patientService.getPatient(id) 31 | if (patient) { 32 | res.json(patient).status(200) 33 | } else { 34 | res.status(404).send("Patient not found") 35 | } 36 | }) 37 | 38 | export default router 39 | -------------------------------------------------------------------------------- /part9/flight-diary/backend/data/entries.ts: -------------------------------------------------------------------------------- 1 | import { DiaryEntry } from "../src/types" 2 | import toNewDiaryEntry from "../src/utils" 3 | 4 | const data = [ 5 | { 6 | id: 1, 7 | date: "2017-01-01", 8 | weather: "rainy", 9 | visibility: "poor", 10 | comment: "Pretty scary flight, I'm glad I'm alive", 11 | }, 12 | { 13 | id: 2, 14 | date: "2017-04-01", 15 | weather: "sunny", 16 | visibility: "good", 17 | comment: "Everything went better than expected, I'm learning much", 18 | }, 19 | { 20 | id: 3, 21 | date: "2017-04-15", 22 | weather: "windy", 23 | visibility: "good", 24 | comment: "I'm getting pretty confident although I hit a flock of birds", 25 | }, 26 | { 27 | id: 4, 28 | date: "2017-05-11", 29 | weather: "cloudy", 30 | visibility: "good", 31 | comment: "I almost failed the landing but I survived", 32 | }, 33 | ] 34 | 35 | const diaryEntries: DiaryEntry[] = data.map(obj => { 36 | const object = toNewDiaryEntry(obj) as DiaryEntry 37 | object.id = obj.id 38 | return object 39 | }) 40 | 41 | export default diaryEntries 42 | -------------------------------------------------------------------------------- /part9/patientor/frontend/src/components/HealthRatingBar.tsx: -------------------------------------------------------------------------------- 1 | import { Rating } from "@mui/material" 2 | import { Favorite } from "@mui/icons-material" 3 | 4 | import { styled } from "@mui/material/styles" 5 | 6 | type BarProps = { 7 | rating: number 8 | showText: boolean 9 | } 10 | 11 | const StyledRating = styled(Rating)({ 12 | iconFilled: { 13 | color: "#ff6d75", 14 | }, 15 | iconHover: { 16 | color: "#ff3d47", 17 | }, 18 | }) 19 | 20 | const HEALTHBAR_TEXTS = [ 21 | "The patient is in great shape", 22 | "The patient has a low risk of getting sick", 23 | "The patient has a high risk of getting sick", 24 | "The patient has a diagnosed condition", 25 | ] 26 | 27 | const HealthRatingBar = ({ rating, showText }: BarProps) => { 28 | return ( 29 |
30 | } 35 | /> 36 | 37 | {showText ?

{HEALTHBAR_TEXTS[rating]}

: null} 38 |
39 | ) 40 | } 41 | 42 | export default HealthRatingBar 43 | -------------------------------------------------------------------------------- /part9/patientor/frontend/src/components/PatientInfoPage/index.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom" 2 | import axios from "axios" 3 | import { useEffect, useState } from "react" 4 | import { Patient } from "../../types" 5 | 6 | const PatientInfoPage = () => { 7 | const { id } = useParams() 8 | const [patient, setPatient] = useState() 9 | 10 | useEffect(() => { 11 | const fetchPatient = async () => { 12 | try { 13 | const patient = await axios.get( 14 | `http://localhost:3001/api/patients/${id}` 15 | ) 16 | setPatient(patient.data) 17 | } catch (error) { 18 | console.error("Error fetching patient data:" + error) 19 | } 20 | } 21 | 22 | fetchPatient() 23 | }, [id]) 24 | 25 | if (patient === undefined) { 26 | return

Something went wrong...

27 | } 28 | 29 | return ( 30 |
31 |

{patient.name}

32 |

ssh: {patient.ssn}

33 |

occupation: {patient.occupation}

34 |

gender: {patient.gender}

35 |
36 | ) 37 | } 38 | 39 | export default PatientInfoPage 40 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/controllers/users.js: -------------------------------------------------------------------------------- 1 | const usersRouter = require("express").Router() 2 | const User = require("../models/user") 3 | const bcrypt = require("bcrypt") 4 | 5 | usersRouter.get("/", async (request, response) => { 6 | const users = await User.find({}).populate("blogs", { 7 | title: 1, 8 | author: 1, 9 | url: 1, 10 | likes: 1, 11 | }) 12 | response.json(users) 13 | }) 14 | 15 | usersRouter.post("/", async (request, response) => { 16 | const { username, name, password } = request.body 17 | 18 | const existingUser = await User.findOne({ username }) 19 | 20 | if (existingUser) { 21 | return response.status(400).json({ error: "Username must be unique" }) 22 | } 23 | 24 | const saltRounds = 10 25 | const passwordHash = await bcrypt.hash(password, saltRounds) 26 | 27 | const user = new User({ 28 | username: username, 29 | name: name, 30 | passwordHash: passwordHash, 31 | }) 32 | 33 | const savedUser = await user.save() 34 | response.status(201).json(savedUser) 35 | }) 36 | 37 | module.exports = usersRouter 38 | -------------------------------------------------------------------------------- /part6/anecdotes-query/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "query-anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^1.2.6", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-query": "^3.39.3", 13 | "react-scripts": "5.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "server": "node server" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "json-server": "^0.17.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Recommend.js: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@apollo/client" 2 | import { ALL_BOOKS, ME } from "../queries" 3 | 4 | const Books = ({ show }) => { 5 | const result = useQuery(ALL_BOOKS) 6 | const me = useQuery(ME) 7 | 8 | if (!show) { 9 | return null 10 | } 11 | 12 | if (result.loading) { 13 | return
loading...
14 | } 15 | 16 | const books = result.data.allBooks 17 | 18 | const filter = me.data.me.favoriteGenre 19 | 20 | const filteredBooks = books.filter(book => book.genres.includes(filter)) 21 | 22 | return ( 23 |
24 |

books

25 | in your favorite genre {filter} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {filteredBooks.map(book => ( 34 | 35 | 36 | 37 | 38 | 39 | ))} 40 | 41 |
authorpublished
{book.title}{book.author.name}{book.published}
42 |
43 | ) 44 | } 45 | 46 | export default Books 47 | -------------------------------------------------------------------------------- /part8/library-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.7.9", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "graphql": "^16.6.0", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "5.0.1", 14 | "react-select": "^5.7.0", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /part3/models/person.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const url = process.env.MONGODB_URI 4 | 5 | console.log(`connecting to ${url}`) 6 | 7 | mongoose 8 | .connect(url) 9 | .then(result => { 10 | console.log("connected to MongoDB", result) 11 | }) 12 | .catch(error => { 13 | console.log("error connecting to MongoDB:", error.message) 14 | }) 15 | 16 | const validator = num => { 17 | return /^\d{2,3}-\d+$/.test(num) 18 | } 19 | 20 | const numValidatorError = [ 21 | validator, 22 | "Error, please enter the phone number with the format xxx-xxxx", 23 | ] 24 | 25 | const personSchema = new mongoose.Schema({ 26 | name: { 27 | type: String, 28 | minLength: 3, 29 | required: true, 30 | }, 31 | number: { 32 | type: String, 33 | minLength: 8, 34 | required: true, 35 | validate: numValidatorError, 36 | }, 37 | }) 38 | 39 | personSchema.set("toJSON", { 40 | transform: (document, returnedObject) => { 41 | returnedObject.id = returnedObject._id.toString() 42 | delete returnedObject._id 43 | delete returnedObject.__v 44 | }, 45 | }) 46 | 47 | module.exports = mongoose.model("Person", personSchema) 48 | -------------------------------------------------------------------------------- /part6/anecdotes-query/src/NotificationContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useReducer, useContext } from "react" 2 | 3 | const notificationReducer = (state, action) => { 4 | switch (action.type) { 5 | case "SHOW": 6 | return action.data 7 | case "HIDE": 8 | return "" 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | const NotificationContext = createContext() 15 | 16 | export const NotificationContextProvider = ({ children }) => { 17 | const [notification, notificationDispatch] = useReducer( 18 | notificationReducer, 19 | "" 20 | ) 21 | 22 | return ( 23 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | // helpers 32 | 33 | export const useNotificationValue = () => { 34 | const notificationAndDispatch = useContext(NotificationContext) 35 | return notificationAndDispatch[0] 36 | } 37 | 38 | export const useNotificationDispatch = () => { 39 | const notificationAndDispatch = useContext(NotificationContext) 40 | return notificationAndDispatch[1] 41 | } 42 | 43 | export default NotificationContext 44 | -------------------------------------------------------------------------------- /part9/create-react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.38", 11 | "@types/react": "^18.2.14", 12 | "@types/react-dom": "^18.2.6", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-scripts": "5.0.1", 16 | "typescript": "^4.9.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject", 24 | "lint": "eslint './src/**/*.{ts,tsx}'" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/services/blogs.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | const baseUrl = "/api/blogs" 3 | 4 | let token = null 5 | 6 | const setToken = newToken => { 7 | token = `bearer ${newToken}` 8 | } 9 | 10 | const getToken = () => { 11 | return token 12 | } 13 | 14 | const getAll = () => { 15 | const request = axios.get(baseUrl) 16 | return request.then(response => response.data) 17 | } 18 | 19 | const create = async newObject => { 20 | const config = { headers: { Authorization: token } } 21 | 22 | const response = await axios.post(baseUrl, newObject, config) 23 | return response.data 24 | } 25 | 26 | const addLike = async (newObject, id) => { 27 | const config = { headers: { Authorization: token } } 28 | 29 | const response = await axios.put(`${baseUrl}/${id}`, newObject, config) 30 | return response.data 31 | } 32 | 33 | const remove = async id => { 34 | const config = { headers: { Authorization: token } } 35 | 36 | const response = await axios.delete(`${baseUrl}/${id}`, config) 37 | return response.data 38 | } 39 | 40 | const blogService = { setToken, getAll, create, getToken, addLike, remove } 41 | 42 | export default blogService 43 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ultimate-hooks", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^1.3.2", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject", 20 | "server": "json-server --port=3005 --watch db.json" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "json-server": "^0.17.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.2", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "axios": "^1.3.2", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-redux": "^8.0.5", 14 | "react-scripts": "5.0.1", 15 | "redux": "^4.2.0", 16 | "web-vitals": "^2.1.4" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "server": "json-server --watch db.json --port 3001" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "devDependencies": { 44 | "json-server": "^0.17.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest/globals": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react/recommended" 11 | ], 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": 2018, 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "react", 21 | "jest" 22 | ], 23 | "rules": { 24 | "no-unused-vars": "warn", 25 | "indent": [ 26 | "error", 27 | "tab" 28 | ], 29 | "linebreak-style": [ 30 | "error", 31 | "unix" 32 | ], 33 | "quotes": [ 34 | "error", 35 | "double" 36 | ], 37 | "semi": [ 38 | "error", 39 | "never" 40 | ], 41 | "eqeqeq": "error", 42 | "no-trailing-spaces": "off", 43 | "object-curly-spacing": [ 44 | "error", 45 | "always" 46 | ], 47 | "arrow-spacing": [ 48 | "error", 49 | { 50 | "before": true, 51 | "after": true 52 | } 53 | ], 54 | "no-console": 0, 55 | "react/prop-types": 0, 56 | "react/react-in-jsx-scope": "off" 57 | }, 58 | "settings": { 59 | "react": { 60 | "version": "detect" 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /part2/phonebook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonebook", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^0.27.2", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject", 20 | "server": "json-server -p3001 --watch db.json" 21 | }, 22 | "proxy": "http://localhost:3001", 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "json-server": "^0.17.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest/globals": true, 6 | "node": true, 7 | "cypress/globals": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:react/recommended" 12 | ], 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "ecmaVersion": 2018, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "react", 22 | "jest", 23 | "cypress/globals" 24 | ], 25 | "rules": { 26 | "indent": [ 27 | "error", 28 | "tab" 29 | ], 30 | "linebreak-style": [ 31 | "error", 32 | "unix" 33 | ], 34 | "quotes": [ 35 | "error", 36 | "double" 37 | ], 38 | "semi": [ 39 | "error", 40 | "never" 41 | ], 42 | "eqeqeq": "error", 43 | "no-trailing-spaces": "error", 44 | "object-curly-spacing": [ 45 | "error", 46 | "always" 47 | ], 48 | "arrow-spacing": [ 49 | "error", 50 | { 51 | "before": true, 52 | "after": true 53 | } 54 | ], 55 | "no-console": 0, 56 | "react/prop-types": 0, 57 | "react/react-in-jsx-scope": "off" 58 | }, 59 | "settings": { 60 | "react": { 61 | "version": "detect" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /part6/unicafe-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unicafe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.2", 7 | "@testing-library/jest-dom": "^5.16.4", 8 | "@testing-library/react": "^13.3.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-redux": "^8.0.5", 13 | "react-scripts": "5.0.1", 14 | "redux": "^4.2.1", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@redux-devtools/core": "^3.13.1", 43 | "deep-freeze": "^0.0.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.1.1", 8 | "@testing-library/user-event": "^14.1.1", 9 | "axios": "^0.27.2", 10 | "prop-types": "^15.8.1", 11 | "react": "^18.1.0", 12 | "react-dom": "^18.1.0", 13 | "react-scripts": "5.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "cypress:open": "cypress open" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "proxy": "http://localhost:3003", 42 | "devDependencies": { 43 | "cypress": "^12.5.1", 44 | "eslint-plugin-cypress": "^2.12.1", 45 | "eslint-plugin-jest": "^26.8.7" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest/globals": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react/recommended" 11 | ], 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": 2018, 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "react", 21 | "jest" 22 | ], 23 | "rules": { 24 | "indent": [ 25 | "error", 26 | "tab", 27 | { 28 | "SwitchCase": 1 29 | } 30 | ], 31 | "linebreak-style": [ 32 | "error", 33 | "unix" 34 | ], 35 | "quotes": [ 36 | "error", 37 | "double" 38 | ], 39 | "semi": [ 40 | "error", 41 | "never" 42 | ], 43 | "eqeqeq": "error", 44 | "no-trailing-spaces": "error", 45 | "object-curly-spacing": [ 46 | "error", 47 | "always" 48 | ], 49 | "arrow-spacing": [ 50 | "error", 51 | { 52 | "before": true, 53 | "after": true 54 | } 55 | ], 56 | "no-console": 0, 57 | "react/prop-types": 0, 58 | "react/react-in-jsx-scope": "off" 59 | }, 60 | "settings": { 61 | "react": { 62 | "version": "detect" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/NotificationContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useReducer, useContext } from "react" 2 | 3 | const notificationReducer = (state, action) => { 4 | switch (action.type) { 5 | case "SHOW": 6 | return [action.data[0], action.data[1]] 7 | case "HIDE": 8 | return ["", true] 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | const NotificationContext = createContext() 15 | 16 | export const NotificationContextProvider = ({ children }) => { 17 | const [notification, notificationDispatch] = useReducer( 18 | notificationReducer, 19 | ["", true] 20 | ) 21 | 22 | return ( 23 | 26 | {children} 27 | 28 | ) 29 | } 30 | 31 | // helpers 32 | 33 | export const useNotificationValue = () => { 34 | const notificationAndDispatch = useContext(NotificationContext) 35 | return notificationAndDispatch[0] 36 | } 37 | 38 | export const useNotificationDispatch = () => { 39 | const notificationAndDispatch = useContext(NotificationContext) 40 | return notificationAndDispatch[1] 41 | } 42 | 43 | export default NotificationContext 44 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "anecdotes": [ 3 | { 4 | "content": "If it hurts, do it more often", 5 | "id": "47145", 6 | "votes": 4 7 | }, 8 | { 9 | "content": "Adding manpower to a late software project makes it later!", 10 | "id": "21149", 11 | "votes": 0 12 | }, 13 | { 14 | "content": "The first 90 percent of the code accounts for the first 10 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.", 15 | "id": "69581", 16 | "votes": 0 17 | }, 18 | { 19 | "content": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", 20 | "id": "36975", 21 | "votes": 0 22 | }, 23 | { 24 | "content": "Premature optimization is the root of all evil.", 25 | "id": "25170", 26 | "votes": 0 27 | }, 28 | { 29 | "content": "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.", 30 | "id": "98312", 31 | "votes": 0 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/services/blogs.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | const baseUrl = "/api/blogs" 3 | 4 | let token = null 5 | 6 | const setToken = newToken => { 7 | token = `bearer ${newToken}` 8 | } 9 | 10 | const getToken = () => { 11 | return token 12 | } 13 | 14 | const getAll = () => { 15 | const request = axios.get(baseUrl) 16 | return request.then(response => response.data) 17 | } 18 | 19 | const create = async newObject => { 20 | const config = { headers: { Authorization: token } } 21 | 22 | const response = await axios.post(baseUrl, newObject, config) 23 | return response.data 24 | } 25 | 26 | const addLike = async newObject => { 27 | const config = { headers: { Authorization: token } } 28 | 29 | const response = await axios.put( 30 | `${baseUrl}/${newObject.id}`, 31 | newObject, 32 | config 33 | ) 34 | return response.data 35 | } 36 | 37 | const remove = async id => { 38 | const config = { headers: { Authorization: token } } 39 | 40 | const response = await axios.delete(`${baseUrl}/${id}`, config) 41 | return response.data 42 | } 43 | 44 | const blogService = { setToken, getAll, create, getToken, addLike, remove } 45 | 46 | export default blogService 47 | -------------------------------------------------------------------------------- /part9/flight-diary/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.38", 11 | "@types/react": "^18.2.14", 12 | "@types/react-dom": "^18.2.6", 13 | "axios": "^1.4.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-scripts": "5.0.1", 17 | "typescript": "^4.9.5", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject", 25 | "lint": "eslint './src/**/*.{ts,tsx}'" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } -------------------------------------------------------------------------------- /part2/courseinfo/src/App.js: -------------------------------------------------------------------------------- 1 | import Course from "./Course"; 2 | 3 | const Courses = ({ courses }) => 4 | courses.map((course) => ); 5 | 6 | const App = () => { 7 | const courses = [ 8 | { 9 | name: "Half Stack application development", 10 | id: 1, 11 | parts: [ 12 | { 13 | name: "Fundamentals of React", 14 | exercises: 10, 15 | id: 1, 16 | }, 17 | { 18 | name: "Using props to pass data", 19 | exercises: 7, 20 | id: 2, 21 | }, 22 | { 23 | name: "State of a component", 24 | exercises: 14, 25 | id: 3, 26 | }, 27 | { 28 | name: "Redux", 29 | exercises: 11, 30 | id: 4, 31 | }, 32 | ], 33 | }, 34 | { 35 | name: "Node.js", 36 | id: 2, 37 | parts: [ 38 | { 39 | name: "Routing", 40 | exercises: 3, 41 | id: 1, 42 | }, 43 | { 44 | name: "Middlewares", 45 | exercises: 7, 46 | id: 2, 47 | }, 48 | ], 49 | }, 50 | ]; 51 | 52 | return ; 53 | }; 54 | 55 | export default App; 56 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/tests/BlogForm.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, fireEvent } from "@testing-library/react" 4 | import BlogForm from "../components/BlogForm" 5 | 6 | describe("", () => { 7 | test("test para nuevo BlogForm", () => { 8 | const addBlog = jest.fn() 9 | 10 | const component = render() 11 | 12 | const titulo = component.container.querySelector("#title") 13 | const autor = component.container.querySelector("#author") 14 | const url = component.container.querySelector("#url") 15 | 16 | const form = component.container.querySelector("form") 17 | 18 | fireEvent.change(titulo, { 19 | target: { value: "testiando blogForm" }, 20 | }) 21 | 22 | fireEvent.change(autor, { 23 | target: { value: "Juanescacha" }, 24 | }) 25 | 26 | fireEvent.change(url, { 27 | target: { value: "www.test.com" }, 28 | }) 29 | 30 | fireEvent.submit(form) 31 | 32 | expect(addBlog.mock.calls).toHaveLength(1) 33 | expect(addBlog.mock.calls[0][0].title).toBe("testiando blogForm") 34 | expect(addBlog.mock.calls[0][0].author).toBe("Juanescacha") 35 | expect(addBlog.mock.calls[0][0].url).toBe("www.test.com") 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.1.1", 8 | "@testing-library/user-event": "^14.1.1", 9 | "axios": "^0.27.2", 10 | "prop-types": "^15.8.1", 11 | "react": "^18.1.0", 12 | "react-dom": "^18.1.0", 13 | "react-query": "^3.39.3", 14 | "react-scripts": "5.0.1", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject", 22 | "cypress:open": "cypress open" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "proxy": "http://localhost:3003", 43 | "devDependencies": { 44 | "cypress": "^12.5.1", 45 | "eslint-plugin-cypress": "^2.12.1", 46 | "eslint-plugin-jest": "^26.8.7" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /part1/courseinfo/src/App.js: -------------------------------------------------------------------------------- 1 | const Header = props => { 2 | return

{props.course.name}

3 | } 4 | 5 | const Part = props => { 6 | return ( 7 |

8 | {props.part.name} {props.part.exercises} 9 |

10 | ) 11 | } 12 | 13 | const Content = props => { 14 | return ( 15 |
16 | 17 | 18 | 19 |
20 | ) 21 | } 22 | 23 | const Total = props => { 24 | return ( 25 |

26 | Number of exercises{" "} 27 | {props.course.parts[0].exercises + 28 | props.course.parts[1].exercises + 29 | props.course.parts[2].exercises} 30 |

31 | ) 32 | } 33 | 34 | const App = () => { 35 | const course = { 36 | name: "Half Stack application development", 37 | parts: [ 38 | { 39 | name: "Fundamentals of React", 40 | exercises: 10, 41 | }, 42 | { 43 | name: "Using props to pass data", 44 | exercises: 7, 45 | }, 46 | { 47 | name: "State of a component", 48 | exercises: 14, 49 | }, 50 | ], 51 | } 52 | 53 | return ( 54 |
55 |
56 | 57 | 58 |
59 | ) 60 | } 61 | 62 | export default App 63 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/tests/BlogForm.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, fireEvent } from "@testing-library/react" 4 | import BlogForm from "../components/BlogForm" 5 | 6 | describe("", () => { 7 | test("test para nuevo BlogForm", () => { 8 | const addBlog = jest.fn() 9 | 10 | const component = render() 11 | 12 | const titulo = component.container.querySelector("#title") 13 | const autor = component.container.querySelector("#author") 14 | const url = component.container.querySelector("#url") 15 | 16 | const form = component.container.querySelector("form") 17 | 18 | fireEvent.change(titulo, { 19 | target: { value: "testiando blogForm" }, 20 | }) 21 | 22 | fireEvent.change(autor, { 23 | target: { value: "Juanescacha" }, 24 | }) 25 | 26 | fireEvent.change(url, { 27 | target: { value: "www.test.com" }, 28 | }) 29 | 30 | fireEvent.submit(form) 31 | 32 | expect(addBlog.mock.calls).toHaveLength(1) 33 | expect(addBlog.mock.calls[0][0].title).toBe("testiando blogForm") 34 | expect(addBlog.mock.calls[0][0].author).toBe("Juanescacha") 35 | expect(addBlog.mock.calls[0][0].url).toBe("www.test.com") 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /part9/patientor/backend/src/data/patients.ts: -------------------------------------------------------------------------------- 1 | // Cspell: disable 2 | 3 | const data = [ 4 | { 5 | id: "d2773336-f723-11e9-8f0b-362b9e155667", 6 | name: "John McClane", 7 | dateOfBirth: "1986-07-09", 8 | ssn: "090786-122X", 9 | gender: "male", 10 | occupation: "New york city cop", 11 | entries: [], 12 | }, 13 | { 14 | id: "d2773598-f723-11e9-8f0b-362b9e155667", 15 | name: "Martin Riggs", 16 | dateOfBirth: "1979-01-30", 17 | ssn: "300179-77A", 18 | gender: "male", 19 | occupation: "Cop", 20 | entries: [], 21 | }, 22 | { 23 | id: "d27736ec-f723-11e9-8f0b-362b9e155667", 24 | name: "Hans Gruber", 25 | dateOfBirth: "1970-04-25", 26 | ssn: "250470-555L", 27 | gender: "other", 28 | occupation: "Technician", 29 | entries: [], 30 | }, 31 | { 32 | id: "d2773822-f723-11e9-8f0b-362b9e155667", 33 | name: "Dana Scully", 34 | dateOfBirth: "1974-01-05", 35 | ssn: "050174-432N", 36 | gender: "female", 37 | occupation: "Forensic Pathologist", 38 | entries: [], 39 | }, 40 | { 41 | id: "d2773c6e-f723-11e9-8f0b-362b9e155667", 42 | name: "Matti Luukkainen", 43 | dateOfBirth: "1971-04-09", 44 | ssn: "090471-8890", 45 | gender: "male", 46 | occupation: "Digital evangelist", 47 | entries: [], 48 | }, 49 | ] 50 | 51 | export default data 52 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/tests/BlogForm.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, fireEvent } from "@testing-library/react" 4 | import BlogForm from "../components/BlogForm" 5 | 6 | describe("", () => { 7 | test("test para nuevo BlogForm", () => { 8 | const addBlog = jest.fn() 9 | 10 | const component = render() 11 | 12 | const titulo = component.container.querySelector("#title") 13 | const autor = component.container.querySelector("#author") 14 | const url = component.container.querySelector("#url") 15 | 16 | const form = component.container.querySelector("form") 17 | 18 | fireEvent.change(titulo, { 19 | target: { value: "testiando blogForm" }, 20 | }) 21 | 22 | fireEvent.change(autor, { 23 | target: { value: "Juanescacha" }, 24 | }) 25 | 26 | fireEvent.change(url, { 27 | target: { value: "www.test.com" }, 28 | }) 29 | 30 | fireEvent.submit(form) 31 | 32 | expect(addBlog.mock.calls).toHaveLength(1) 33 | expect(addBlog.mock.calls[0][0].title).toBe("testiando blogForm") 34 | expect(addBlog.mock.calls[0][0].author).toBe("Juanescacha") 35 | expect(addBlog.mock.calls[0][0].url).toBe("www.test.com") 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /part4/bloglist/app.js: -------------------------------------------------------------------------------- 1 | const config = require("./utils/config") 2 | const express = require("express") 3 | require("express-async-errors") 4 | const app = express() 5 | const cors = require("cors") 6 | const blogsRouter = require("./controllers/blogs") 7 | const usersRouter = require("./controllers/users") 8 | const middleware = require("./utils/middleware") 9 | const loginRouter = require("./controllers/login") 10 | const testingRouter = require("./controllers/testing") 11 | const logger = require("./utils/logger") 12 | const mongoose = require("mongoose") 13 | 14 | mongoose 15 | .connect(config.MONGODB_URI) 16 | .then(() => { 17 | logger.info("connected to MongoDB") 18 | }) 19 | .catch(error => { 20 | logger.error("error connecting to MongoDB:", error.message) 21 | }) 22 | 23 | app.use(cors()) 24 | app.use(express.static("build")) 25 | app.use(express.json()) 26 | 27 | app.use(middleware.requestLogger) 28 | app.use(middleware.tokenExtractor) 29 | 30 | app.use("/api/blogs", middleware.userExtractor, blogsRouter) 31 | app.use("/api/users", usersRouter) 32 | app.use("/api/login", loginRouter) 33 | 34 | if (process.env.NODE_ENV === "test") { 35 | app.use("/api/testing", testingRouter) 36 | } 37 | 38 | app.use(middleware.unknowEndpoint) 39 | app.use(middleware.errorHandler) 40 | 41 | module.exports = app 42 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/BlogForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const NoteForm = ({ createBlog }) => { 4 | const [title, setTitle] = useState("") 5 | const [author, setAuthor] = useState("") 6 | const [url, setUrl] = useState("") 7 | 8 | const addBlog = event => { 9 | event.preventDefault() 10 | const blog = { title, author, url } 11 | createBlog(blog) 12 | 13 | setAuthor("") 14 | setTitle("") 15 | setUrl("") 16 | } 17 | 18 | return ( 19 | <> 20 |

create new

21 |
22 | title:{" "} 23 | setTitle(target.value)} 29 | /> 30 |
31 | author:{" "} 32 | setAuthor(target.value)} 38 | /> 39 |
40 | url:{" "} 41 | setUrl(target.value)} 47 | /> 48 |
49 | 52 |
53 | 54 | ) 55 | } 56 | 57 | export default NoteForm 58 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/app.js: -------------------------------------------------------------------------------- 1 | const config = require("./utils/config") 2 | const express = require("express") 3 | require("express-async-errors") 4 | const app = express() 5 | const cors = require("cors") 6 | const blogsRouter = require("./controllers/blogs") 7 | const usersRouter = require("./controllers/users") 8 | const middleware = require("./utils/middleware") 9 | const loginRouter = require("./controllers/login") 10 | const testingRouter = require("./controllers/testing") 11 | const logger = require("./utils/logger") 12 | const mongoose = require("mongoose") 13 | 14 | mongoose 15 | .connect(config.MONGODB_URI) 16 | .then(() => { 17 | logger.info("connected to MongoDB") 18 | }) 19 | .catch(error => { 20 | logger.error("error connecting to MongoDB:", error.message) 21 | }) 22 | 23 | app.use(cors()) 24 | app.use(express.static("build")) 25 | app.use(express.json()) 26 | 27 | app.use(middleware.requestLogger) 28 | app.use(middleware.tokenExtractor) 29 | 30 | app.use("/api/blogs", middleware.userExtractor, blogsRouter) 31 | app.use("/api/users", usersRouter) 32 | app.use("/api/login", loginRouter) 33 | 34 | if (process.env.NODE_ENV === "test") { 35 | app.use("/api/testing", testingRouter) 36 | } 37 | 38 | app.use(middleware.unknowEndpoint) 39 | app.use(middleware.errorHandler) 40 | 41 | module.exports = app 42 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/app.js: -------------------------------------------------------------------------------- 1 | const config = require("./utils/config") 2 | const express = require("express") 3 | require("express-async-errors") 4 | const app = express() 5 | const cors = require("cors") 6 | const blogsRouter = require("./controllers/blogs") 7 | const usersRouter = require("./controllers/users") 8 | const middleware = require("./utils/middleware") 9 | const loginRouter = require("./controllers/login") 10 | const testingRouter = require("./controllers/testing") 11 | const logger = require("./utils/logger") 12 | const mongoose = require("mongoose") 13 | 14 | mongoose 15 | .connect(config.MONGODB_URI) 16 | .then(() => { 17 | logger.info("connected to MongoDB") 18 | }) 19 | .catch(error => { 20 | logger.error("error connecting to MongoDB:", error.message) 21 | }) 22 | 23 | app.use(cors()) 24 | app.use(express.static("build")) 25 | app.use(express.json()) 26 | 27 | app.use(middleware.requestLogger) 28 | app.use(middleware.tokenExtractor) 29 | 30 | app.use("/api/blogs", middleware.userExtractor, blogsRouter) 31 | app.use("/api/users", usersRouter) 32 | app.use("/api/login", loginRouter) 33 | 34 | if (process.env.NODE_ENV === "test") { 35 | app.use("/api/testing", testingRouter) 36 | } 37 | 38 | app.use(middleware.unknowEndpoint) 39 | app.use(middleware.errorHandler) 40 | 41 | module.exports = app 42 | -------------------------------------------------------------------------------- /part9/patientor/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "tsc": "tsc", 8 | "dev": "ts-node-dev src/index.ts", 9 | "lint": "eslint --ext .ts .", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Juan Esteban Camargo", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/aria-query": "^5.0.4", 16 | "@types/babel__core": "^7.20.5", 17 | "@types/babel__generator": "^7.6.8", 18 | "@types/babel__template": "^7.4.4", 19 | "@types/babel__traverse": "^7.20.6", 20 | "@types/bonjour": "^3.5.13", 21 | "@types/cors": "^2.8.13", 22 | "@types/estree": "^1.0.6", 23 | "@types/express": "^4.17.17", 24 | "@types/graceful-fs": "^4.1.9", 25 | "@types/node": "^22.7.4", 26 | "@types/prettier": "^3.0.0", 27 | "@types/testing-library__jest-dom": "^6.0.0", 28 | "@types/trusted-types": "^2.0.7", 29 | "@types/uuid": "^9.0.2", 30 | "@typescript-eslint/eslint-plugin": "^5.59.8", 31 | "@typescript-eslint/parser": "^5.59.8", 32 | "eslint": "^8.41.0", 33 | "ts-node-dev": "^2.0.0", 34 | "typescript": "^5.0.4" 35 | }, 36 | "dependencies": { 37 | "cors": "^2.8.5", 38 | "express": "^4.18.2", 39 | "uuid": "^9.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /part8/library-frontend/src/queries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client" 2 | 3 | export const ALL_AUTHORS = gql` 4 | query { 5 | allAuthors { 6 | name 7 | born 8 | bookCount 9 | } 10 | } 11 | ` 12 | 13 | export const ALL_BOOKS = gql` 14 | query AllBooks($genre: String) { 15 | allBooks(genre: $genre) { 16 | title 17 | author { 18 | name 19 | } 20 | published 21 | genres 22 | } 23 | } 24 | ` 25 | 26 | export const ADD_BOOK = gql` 27 | mutation addBook( 28 | $title: String! 29 | $author: String! 30 | $published: Int! 31 | $genres: [String!]! 32 | ) { 33 | addBook( 34 | title: $title 35 | author: $author 36 | published: $published 37 | genres: $genres 38 | ) { 39 | title 40 | author { 41 | name 42 | } 43 | published 44 | genres 45 | } 46 | } 47 | ` 48 | 49 | export const EDIT_AUTHOR = gql` 50 | mutation EditAuthor($name: String!, $setBornTo: Int!) { 51 | editAuthor(name: $name, setBornTo: $setBornTo) { 52 | born 53 | name 54 | } 55 | } 56 | ` 57 | 58 | export const LOGIN = gql` 59 | mutation Login($username: String!, $password: String!) { 60 | login(username: $username, password: $password) { 61 | value 62 | } 63 | } 64 | ` 65 | 66 | export const ME = gql` 67 | query { 68 | me { 69 | username 70 | favoriteGenre 71 | id 72 | } 73 | } 74 | ` 75 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/components/BlogForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const NoteForm = ({ createBlog }) => { 4 | const [title, setTitle] = useState("") 5 | const [author, setAuthor] = useState("") 6 | const [url, setUrl] = useState("") 7 | 8 | const addBlog = event => { 9 | event.preventDefault() 10 | const blog = { title, author, url } 11 | createBlog(blog) 12 | 13 | setAuthor("") 14 | setTitle("") 15 | setUrl("") 16 | } 17 | 18 | return ( 19 | <> 20 |

create new

21 |
22 | title:{" "} 23 | setTitle(target.value)} 29 | /> 30 |
31 | author:{" "} 32 | setAuthor(target.value)} 38 | /> 39 |
40 | url:{" "} 41 | setUrl(target.value)} 47 | /> 48 |
49 | 52 |
53 | 54 | ) 55 | } 56 | 57 | export default NoteForm 58 | -------------------------------------------------------------------------------- /part9/patientor/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-new", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.5", 7 | "@emotion/styled": "^11.10.5", 8 | "@mui/icons-material": "^5.11.0", 9 | "@mui/material": "^5.11.8", 10 | "@testing-library/jest-dom": "^5.16.5", 11 | "@testing-library/react": "^13.4.0", 12 | "@testing-library/user-event": "^13.5.0", 13 | "@types/jest": "^27.5.2", 14 | "@types/node": "^16.18.12", 15 | "@types/react": "^18.0.28", 16 | "@types/react-dom": "^18.0.10", 17 | "axios": "^1.3.2", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-router-dom": "^6.8.1", 21 | "react-scripts": "5.0.1", 22 | "typescript": "^4.9.5", 23 | "web-vitals": "^2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@types/axios": "^0.14.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/components/AnecdoteList.js: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux" 2 | import { voteAnecdote } from "../reducers/anecdoteReducer" 3 | import { 4 | setNotificationTime, 5 | removeNotification, 6 | } from "../reducers/notificationReducer" 7 | 8 | const AnecdoteList = () => { 9 | const anecdotes = useSelector(state => state.anecdotes) 10 | const filter = useSelector(state => state.filter) 11 | const dispatch = useDispatch() 12 | 13 | const vote = id => { 14 | dispatch(voteAnecdote(id)) 15 | dispatch( 16 | setNotificationTime( 17 | `you voted '${anecdotes.find(a => a.id === id).content}'`, 18 | 5 19 | ) 20 | ) 21 | setTimeout(() => { 22 | dispatch(removeNotification()) 23 | }, 5 * 1000) 24 | } 25 | return ( 26 | <> 27 | {anecdotes 28 | .filter(anecdote => { 29 | return filter === "" 30 | ? true 31 | : anecdote.content.includes(filter) 32 | }) 33 | .sort((x, y) => { 34 | if (x.votes > y.votes) return -1 35 | else return 1 36 | }) 37 | .map(anecdote => ( 38 |
39 |
{anecdote.content}
40 |
41 | has {anecdote.votes} 42 | 45 |
46 |
47 | ))} 48 | 49 | ) 50 | } 51 | 52 | export default AnecdoteList 53 | -------------------------------------------------------------------------------- /part9/TypeScript First steps/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { calculateBmi } from "./bmiCalculator"; 3 | import { calculateExercises } from "./exerciseCalculator"; 4 | 5 | const app = express(); 6 | 7 | app.use(express.json()); 8 | 9 | app.get("/hello", (_req, res) => { 10 | res.send("Hello Full Stack!"); 11 | }); 12 | 13 | app.get("/bmi", (req, res) => { 14 | const height = Number(req.query.height); 15 | const weight = Number(req.query.weight); 16 | const bmi = calculateBmi(height, weight); 17 | 18 | res.send({ 19 | weight, 20 | height, 21 | bmi, 22 | }); 23 | }); 24 | 25 | app.post("/exercises", (req, res) => { 26 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 27 | const { daily_exercises, target } = req.body; 28 | 29 | if (!daily_exercises || !target) { 30 | return res.status(400).json({ 31 | error: "parameters missing", 32 | }); 33 | } 34 | 35 | if (isNaN(Number(target)) || !Array.isArray(daily_exercises)) { 36 | return res.status(400).json({ 37 | error: "malformatted parameters", 38 | }); 39 | } 40 | 41 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument 42 | const exercise = calculateExercises(daily_exercises, target); 43 | 44 | return res.status(200).json(exercise); 45 | }); 46 | 47 | app.listen(3000, () => { 48 | console.log("Server is listening on port 3000"); 49 | }); 50 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.2", 7 | "@testing-library/jest-dom": "^5.16.4", 8 | "@testing-library/react": "^13.1.1", 9 | "@testing-library/user-event": "^14.1.1", 10 | "axios": "^0.27.2", 11 | "prop-types": "^15.8.1", 12 | "react": "^18.1.0", 13 | "react-dom": "^18.1.0", 14 | "react-redux": "^8.0.5", 15 | "react-router-dom": "^6.8.1", 16 | "react-scripts": "5.0.1", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject", 24 | "cypress:open": "cypress open" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "proxy": "http://localhost:3003", 45 | "devDependencies": { 46 | "cypress": "^12.5.1", 47 | "eslint": "^8.34.0", 48 | "eslint-plugin-cypress": "^2.12.1", 49 | "eslint-plugin-jest": "^26.8.7", 50 | "prettier": "^2.8.4", 51 | "prettier-plugin-tailwindcss": "^0.2.3", 52 | "tailwindcss": "^3.2.7" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/services/blogs.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | const baseUrl = "/api/blogs" 3 | 4 | let token = null 5 | 6 | const setToken = newToken => { 7 | token = `bearer ${newToken}` 8 | } 9 | 10 | const getToken = () => { 11 | return token 12 | } 13 | 14 | const getAll = () => { 15 | const request = axios.get(baseUrl) 16 | return request.then(response => response.data) 17 | } 18 | 19 | const create = async newObject => { 20 | const config = { headers: { Authorization: token } } 21 | 22 | const response = await axios.post(baseUrl, newObject, config) 23 | return response.data 24 | } 25 | 26 | const addLike = async (newObject, id) => { 27 | const config = { headers: { Authorization: token } } 28 | 29 | const response = await axios.put(`${baseUrl}/${id}`, newObject, config) 30 | return response.data 31 | } 32 | 33 | const addComment = async (comment, id) => { 34 | const config = { headers: { Authorization: token } } 35 | 36 | const response = await axios.put( 37 | `${baseUrl}/${id}/comments`, 38 | { comment }, 39 | config 40 | ) 41 | return response.data 42 | } 43 | 44 | const remove = async id => { 45 | const config = { headers: { Authorization: token } } 46 | 47 | const response = await axios.delete(`${baseUrl}/${id}`, config) 48 | return response.data 49 | } 50 | 51 | const blogService = { 52 | setToken, 53 | getAll, 54 | create, 55 | getToken, 56 | addLike, 57 | remove, 58 | addComment, 59 | } 60 | 61 | export default blogService 62 | -------------------------------------------------------------------------------- /part4/bloglist/tests/list_helper.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash") 2 | 3 | const dummy = blogs => { 4 | if (blogs) { 5 | return 1 6 | } 7 | } 8 | 9 | const totalLikes = blogs => { 10 | const sumLikes = (sum, blog) => sum + blog.likes 11 | return blogs.reduce(sumLikes, 0) 12 | } 13 | 14 | const favoriteBlog = blogs => { 15 | const mayor = (counter, blog) => { 16 | return blog.likes > counter.likes ? blog : counter 17 | } 18 | return blogs.reduce(mayor, { likes: 0 }) 19 | } 20 | 21 | const mostBlogs = blogs => { 22 | if (blogs.length === 0) return { author: "", blogs: 0 } 23 | 24 | const groupedBlogs = _.groupBy(blogs, "author") 25 | const blogsAuthor = _.mapValues(groupedBlogs, "length") 26 | const toArray = Object.entries(blogsAuthor) 27 | const mayorPair = toArray.reduce((a, b) => (a[1] > b[1] ? a : b)) 28 | 29 | return { author: mayorPair[0], blogs: mayorPair[1] } 30 | } 31 | 32 | const mostLikes = blogs => { 33 | if (blogs.length === 0) return { author: "", likes: 0 } 34 | 35 | const likes = blogs => { 36 | return blogs.reduce((sum, blog) => sum + blog.likes, 0) 37 | } 38 | 39 | const blogsAgrupados = _.groupBy(blogs, "author") 40 | const blogsLikes = _.mapValues(blogsAgrupados, likes) 41 | const objToArreglo = Object.entries(blogsLikes) 42 | const mayorPair = objToArreglo.reduce((a, b) => (a[1] > b[1] ? a : b)) 43 | 44 | return { author: mayorPair[0], likes: mayorPair[1] } 45 | } 46 | 47 | module.exports = { 48 | dummy, 49 | totalLikes, 50 | favoriteBlog, 51 | mostBlogs, 52 | mostLikes, 53 | } 54 | -------------------------------------------------------------------------------- /part6/anecdotes-redux/src/reducers/anecdoteReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit" 2 | //servicio 3 | import anecdoteService from "../services/anecdotes" 4 | 5 | const initialState = [] 6 | 7 | const anecdoteSlice = createSlice({ 8 | name: "anecdotes", 9 | initialState, 10 | reducers: { 11 | addVote: (state, action) => { 12 | const id = action.payload 13 | const anecdoteIndex = state.findIndex(a => a.id === id) 14 | state[anecdoteIndex].votes += 1 15 | }, 16 | addAnecdote: (state, action) => { 17 | state.push(action.payload) 18 | }, 19 | setAnecdotes: (state, action) => { 20 | return action.payload 21 | }, 22 | }, 23 | }) 24 | 25 | export const initializeAnecdotes = () => { 26 | return async dispatch => { 27 | const anecdotes = await anecdoteService.getAll() 28 | dispatch(setAnecdotes(anecdotes)) 29 | } 30 | } 31 | 32 | export const newAnecdote = content => { 33 | return async dispatch => { 34 | const newAnecdote = await anecdoteService.createNew(content) 35 | dispatch(addAnecdote(newAnecdote)) 36 | } 37 | } 38 | 39 | export const voteAnecdote = id => { 40 | return async dispatch => { 41 | const anecdote = await anecdoteService.getOne(id) 42 | const updatedAnecdote = { 43 | ...anecdote, 44 | votes: anecdote.votes + 1, 45 | } 46 | await anecdoteService.update(id, updatedAnecdote) 47 | dispatch(addVote(id)) 48 | } 49 | } 50 | 51 | export const { addVote, addAnecdote, appendAnecdotes, setAnecdotes } = 52 | anecdoteSlice.actions 53 | export default anecdoteSlice.reducer 54 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-backend/tests/list_helper.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash") 2 | 3 | const dummy = blogs => { 4 | if (blogs) { 5 | return 1 6 | } 7 | } 8 | 9 | const totalLikes = blogs => { 10 | const sumLikes = (sum, blog) => sum + blog.likes 11 | return blogs.reduce(sumLikes, 0) 12 | } 13 | 14 | const favoriteBlog = blogs => { 15 | const mayor = (counter, blog) => { 16 | return blog.likes > counter.likes ? blog : counter 17 | } 18 | return blogs.reduce(mayor, { likes: 0 }) 19 | } 20 | 21 | const mostBlogs = blogs => { 22 | if (blogs.length === 0) return { author: "", blogs: 0 } 23 | 24 | const groupedBlogs = _.groupBy(blogs, "author") 25 | const blogsAuthor = _.mapValues(groupedBlogs, "length") 26 | const toArray = Object.entries(blogsAuthor) 27 | const mayorPair = toArray.reduce((a, b) => (a[1] > b[1] ? a : b)) 28 | 29 | return { author: mayorPair[0], blogs: mayorPair[1] } 30 | } 31 | 32 | const mostLikes = blogs => { 33 | if (blogs.length === 0) return { author: "", likes: 0 } 34 | 35 | const likes = blogs => { 36 | return blogs.reduce((sum, blog) => sum + blog.likes, 0) 37 | } 38 | 39 | const blogsAgrupados = _.groupBy(blogs, "author") 40 | const blogsLikes = _.mapValues(blogsAgrupados, likes) 41 | const objToArreglo = Object.entries(blogsLikes) 42 | const mayorPair = objToArreglo.reduce((a, b) => (a[1] > b[1] ? a : b)) 43 | 44 | return { author: mayorPair[0], likes: mayorPair[1] } 45 | } 46 | 47 | module.exports = { 48 | dummy, 49 | totalLikes, 50 | favoriteBlog, 51 | mostBlogs, 52 | mostLikes, 53 | } 54 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-backend/tests/list_helper.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash") 2 | 3 | const dummy = blogs => { 4 | if (blogs) { 5 | return 1 6 | } 7 | } 8 | 9 | const totalLikes = blogs => { 10 | const sumLikes = (sum, blog) => sum + blog.likes 11 | return blogs.reduce(sumLikes, 0) 12 | } 13 | 14 | const favoriteBlog = blogs => { 15 | const mayor = (counter, blog) => { 16 | return blog.likes > counter.likes ? blog : counter 17 | } 18 | return blogs.reduce(mayor, { likes: 0 }) 19 | } 20 | 21 | const mostBlogs = blogs => { 22 | if (blogs.length === 0) return { author: "", blogs: 0 } 23 | 24 | const groupedBlogs = _.groupBy(blogs, "author") 25 | const blogsAuthor = _.mapValues(groupedBlogs, "length") 26 | const toArray = Object.entries(blogsAuthor) 27 | const mayorPair = toArray.reduce((a, b) => (a[1] > b[1] ? a : b)) 28 | 29 | return { author: mayorPair[0], blogs: mayorPair[1] } 30 | } 31 | 32 | const mostLikes = blogs => { 33 | if (blogs.length === 0) return { author: "", likes: 0 } 34 | 35 | const likes = blogs => { 36 | return blogs.reduce((sum, blog) => sum + blog.likes, 0) 37 | } 38 | 39 | const blogsAgrupados = _.groupBy(blogs, "author") 40 | const blogsLikes = _.mapValues(blogsAgrupados, likes) 41 | const objToArreglo = Object.entries(blogsLikes) 42 | const mayorPair = objToArreglo.reduce((a, b) => (a[1] > b[1] ? a : b)) 43 | 44 | return { author: mayorPair[0], likes: mayorPair[1] } 45 | } 46 | 47 | module.exports = { 48 | dummy, 49 | totalLikes, 50 | favoriteBlog, 51 | mostBlogs, 52 | mostLikes, 53 | } 54 | -------------------------------------------------------------------------------- /part9/patientor/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Patientor - frontend 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm install` 10 | 11 | Install the project dependencies. 12 | 13 | ### `npm start` 14 | 15 | Runs the app in the development mode.
16 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 17 | 18 | The page will reload if you make edits.
19 | You will also see any lint errors in the console. 20 | 21 | ### `npm test` 22 | 23 | Launches the test runner in the interactive watch mode.
24 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 25 | 26 | ### `npm build` 27 | 28 | Builds the app for production to the `build` folder.
29 | It correctly bundles React in production mode and optimizes the build for the best performance. 30 | 31 | The build is minified and the filenames include the hashes.
32 | Your app is ready to be deployed! 33 | 34 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 35 | 36 | ## Learn More 37 | 38 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 39 | 40 | To learn React, check out the [React documentation](https://reactjs.org/). 41 | 42 | [React TypeScript cheetseat](https://react-typescript-cheatsheet.netlify.app/) 43 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/tests/Blog.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, screen } from "@testing-library/react" 4 | import userEvent from "@testing-library/user-event" 5 | import Blog from "../components/Blog" 6 | 7 | describe("", () => { 8 | const blog = { 9 | title: "titulo", 10 | author: "Autor", 11 | url: "http://url.com", 12 | likes: "10", 13 | user: { 14 | id: "1234", 15 | }, 16 | } 17 | const user = { 18 | id: "1234", 19 | } 20 | 21 | let container 22 | let onClick = jest.fn() 23 | 24 | beforeEach(() => { 25 | container = render( 26 | 27 | ).container 28 | }) 29 | 30 | test("al inicio el componente hijo no se muestra", () => { 31 | const div = container.querySelector(".hidden") 32 | expect(div).toHaveStyle("display: none") 33 | }) 34 | 35 | test("despues de dar click, los componentes hijos se muestran", async () => { 36 | const usuario = userEvent.setup() 37 | const boton = screen.getByText("view") 38 | await usuario.click(boton) 39 | 40 | const div = container.querySelector(".hidden") 41 | expect(div).not.toHaveStyle("display: none") 42 | }) 43 | 44 | test("despues de dar dos clicksboton de like, el controlador del evento se llama 2 veces", async () => { 45 | const usuario = userEvent.setup() 46 | const botonLike = screen.getByText("like") 47 | await usuario.click(botonLike) 48 | await usuario.click(botonLike) 49 | expect(onClick).toHaveBeenCalledTimes(2) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/components/Blog.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | const Blog = ({ blog /* updateLike, removeBlog, user */ }) => { 4 | const [visible /* setVisible */] = useState(false) 5 | 6 | // const showWhenVisible = { display: visible ? "" : "none" } 7 | const hideWhenVisible = { display: visible ? "none" : "" } 8 | 9 | // const toggleVisibility = () => { 10 | // setVisible(!visible) 11 | // } 12 | 13 | /* const handleLike = () => { 14 | const blogObj = { 15 | title: blog.title, 16 | author: blog.author, 17 | url: blog.url, 18 | likes: blog.likes + 1, 19 | } 20 | updateLike(blogObj, blog.id) 21 | } */ 22 | 23 | /* const handleRemove = () => { 24 | if ( 25 | window.confirm("Are you sure you want to remove this blog?") === 26 | true 27 | ) { 28 | removeBlog(blog.id) 29 | } 30 | } */ 31 | 32 | // const showDelete = blog.user.id === user.id ? true : false 33 | 34 | return ( 35 |
36 | {blog.title} {blog.author} 37 |
38 | //
39 | // {blog.title} {blog.author} 40 | // 41 | //
42 | // {blog.url} 43 | //
44 | // likes {blog.likes}{" "} 45 | // 46 | //
47 | // {blog.author} 48 | //
49 | // {showDelete && ( 50 | // 51 | // )} 52 | //
53 | ) 54 | } 55 | 56 | export default Blog 57 | -------------------------------------------------------------------------------- /part6/anecdotes-query/src/components/AnecdoteForm.js: -------------------------------------------------------------------------------- 1 | import { useMutation, useQueryClient } from "react-query" 2 | import { createAnecdote } from "../requests" 3 | import { useNotificationDispatch } from "../NotificationContext" 4 | 5 | const AnecdoteForm = () => { 6 | const queryClient = useQueryClient() 7 | const notificationDispatch = useNotificationDispatch() 8 | 9 | const newAnecdoteMutation = useMutation(createAnecdote, { 10 | onSuccess: () => { 11 | queryClient.invalidateQueries("anecdotes") 12 | }, 13 | }) 14 | 15 | const onCreate = event => { 16 | event.preventDefault() 17 | const content = event.target.anecdote.value 18 | event.target.anecdote.value = "" 19 | newAnecdoteMutation.mutate( 20 | { content, votes: 0 }, 21 | { 22 | onSuccess: () => { 23 | notificationDispatch({ 24 | type: "SHOW", 25 | data: `you created: '${content}'`, 26 | }) 27 | setTimeout(() => { 28 | notificationDispatch({ type: "HIDE" }) 29 | }, 5000) 30 | }, 31 | onError: () => { 32 | notificationDispatch({ 33 | type: "SHOW", 34 | data: `an error occurred while creating '${content}', use at least 5 characters for`, 35 | }) 36 | setTimeout(() => { 37 | notificationDispatch({ type: "HIDE" }) 38 | }, 5000) 39 | }, 40 | } 41 | ) 42 | } 43 | 44 | return ( 45 |
46 |

create new

47 |
48 | 49 | 50 |
51 |
52 | ) 53 | } 54 | 55 | export default AnecdoteForm 56 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Login.js: -------------------------------------------------------------------------------- 1 | import { useMutation } from "@apollo/client" 2 | import { useState } from "react" 3 | import { LOGIN } from "../queries" 4 | 5 | const Login = ({ show, setPage }) => { 6 | const [username, setUsername] = useState("") 7 | const [password, setPassword] = useState("") 8 | const [login] = useMutation(LOGIN, { 9 | onError: error => { 10 | console.log(error.graphQLErrors[0].message) 11 | }, 12 | }) 13 | 14 | if (!show) { 15 | return null 16 | } 17 | 18 | const submit = async event => { 19 | event.preventDefault() 20 | 21 | try { 22 | const result = await login({ 23 | variables: { username, password }, 24 | }) 25 | 26 | if (result.data.login !== null) { 27 | const token = result.data.login.value 28 | localStorage.setItem("library-user-token", token) 29 | setPage("authors") 30 | } else { 31 | console.log("Wrong credentials") 32 | } 33 | } catch (error) { 34 | console.log(error) 35 | } 36 | setUsername("") 37 | setPassword("") 38 | } 39 | 40 | return ( 41 |
42 |
43 |
44 | username 45 | setUsername(target.value)} 48 | /> 49 |
50 |
51 | password 52 | setPassword(target.value)} 56 | /> 57 |
58 | 59 |
60 |
61 | ) 62 | } 63 | 64 | export default Login 65 | -------------------------------------------------------------------------------- /part7/bloglist/redux/bloglist-frontend/src/tests/Blog.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, screen } from "@testing-library/react" 4 | import userEvent from "@testing-library/user-event" 5 | import Blog from "../components/Blog" 6 | 7 | describe("", () => { 8 | const blog = { 9 | title: "titulo", 10 | author: "Autor", 11 | url: "http://url.com", 12 | likes: "10", 13 | user: { 14 | id: "1234", 15 | }, 16 | } 17 | const user = { 18 | id: "1234", 19 | } 20 | 21 | let container 22 | let onClick = jest.fn() 23 | 24 | beforeEach(() => { 25 | container = render( 26 | 27 | ).container 28 | }) 29 | 30 | test("al inicio el componente hijo no se muestra", () => { 31 | const div = container.querySelector(".hidden") 32 | expect(div).toHaveStyle("display: none") 33 | }) 34 | 35 | test("despues de dar click, los componentes hijos se muestran", async () => { 36 | const usuario = userEvent.setup() 37 | const boton = screen.getByText("view") 38 | await usuario.click(boton) 39 | 40 | const div = container.querySelector(".hidden") 41 | expect(div).not.toHaveStyle("display: none") 42 | }) 43 | 44 | test("despues de dar dos clicksboton de like, el controlador del evento se llama 2 veces", async () => { 45 | const usuario = userEvent.setup() 46 | const botonLike = screen.getByText("like") 47 | await usuario.click(botonLike) 48 | await usuario.click(botonLike) 49 | expect(onClick).toHaveBeenCalledTimes(2) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /part9/create-react-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import Header from "./components/Header" 2 | import Content from "./components/Content" 3 | import Total from "./components/Total" 4 | 5 | import { CoursePart } from "./types" 6 | 7 | const App = () => { 8 | const courseName = "Half Stack application development" 9 | const courseParts: CoursePart[] = [ 10 | { 11 | name: "Fundamentals", 12 | exerciseCount: 10, 13 | description: "This is an awesome course part", 14 | kind: "basic", 15 | }, 16 | { 17 | name: "Using props to pass data", 18 | exerciseCount: 7, 19 | groupProjectCount: 3, 20 | kind: "group", 21 | }, 22 | { 23 | name: "Basics of type Narrowing", 24 | exerciseCount: 7, 25 | description: "How to go from unknown to string", 26 | kind: "basic", 27 | }, 28 | { 29 | name: "Deeper type usage", 30 | exerciseCount: 14, 31 | description: "Confusing description", 32 | backgroundMaterial: 33 | "https://type-level-typescript.com/template-literal-types", 34 | kind: "background", 35 | }, 36 | { 37 | name: "TypeScript in frontend", 38 | exerciseCount: 10, 39 | description: "a hard part", 40 | kind: "basic", 41 | }, 42 | { 43 | name: "Backend development", 44 | exerciseCount: 21, 45 | description: "Typing the backend", 46 | requirements: ["nodejs", "jest"], 47 | kind: "special", 48 | }, 49 | ] 50 | 51 | return ( 52 |
53 |
54 | 55 | 56 |
57 | ) 58 | } 59 | 60 | export default App 61 | -------------------------------------------------------------------------------- /part9/create-react-app/src/components/Content.tsx: -------------------------------------------------------------------------------- 1 | import { CoursePart } from "../types" 2 | 3 | const assertNever = (value: never): never => { 4 | throw new Error( 5 | `Unhandled discriminated union member: ${JSON.stringify(value)}` 6 | ) 7 | } 8 | 9 | const Part = ({ part }: { part: CoursePart }) => { 10 | switch (part.kind) { 11 | case "basic": 12 | return ( 13 |

14 | 15 | {part.name} {part.exerciseCount} 16 | 17 |
18 | {part.description} 19 |

20 | ) 21 | case "group": 22 | return ( 23 |

24 | 25 | {part.name} {part.exerciseCount} 26 | 27 |
28 | project exercises {part.groupProjectCount} 29 |

30 | ) 31 | case "background": 32 | return ( 33 |

34 | 35 | {part.name} {part.exerciseCount} 36 | 37 |
38 | {part.description} 39 |
40 | {part.backgroundMaterial} 41 |

42 | ) 43 | case "special": 44 | return ( 45 |

46 | 47 | {part.name} {part.exerciseCount} 48 | 49 |
50 | {part.description}
51 | {part.requirements && part.requirements.join(", ")} 52 |

53 | ) 54 | default: 55 | return assertNever(part) 56 | } 57 | } 58 | 59 | const Content = ({ courseParts }: { courseParts: CoursePart[] }) => { 60 | return ( 61 | <> 62 | {courseParts.map((part, index) => ( 63 | 67 | ))} 68 | 69 | ) 70 | } 71 | 72 | export default Content 73 | -------------------------------------------------------------------------------- /part7/bloglist/context-react-query/bloglist-frontend/src/tests/Blog.test.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "@testing-library/jest-dom/extend-expect" 3 | import { render, screen } from "@testing-library/react" 4 | import userEvent from "@testing-library/user-event" 5 | import Blog from "../components/Blog" 6 | 7 | describe("", () => { 8 | const blog = { 9 | title: "titulo", 10 | author: "Autor", 11 | url: "http://url.com", 12 | likes: "10", 13 | user: { 14 | id: "1234", 15 | }, 16 | } 17 | const user = { 18 | id: "1234", 19 | } 20 | 21 | let container 22 | let onClick = jest.fn() 23 | 24 | beforeEach(() => { 25 | container = render( 26 | 27 | ).container 28 | }) 29 | 30 | test("al inicio el componente hijo no se muestra", () => { 31 | const div = container.querySelector(".hidden") 32 | expect(div).toHaveStyle("display: none") 33 | }) 34 | 35 | test("despues de dar click, los componentes hijos se muestran", async () => { 36 | const usuario = userEvent.setup() 37 | const boton = screen.getByText("view") 38 | await usuario.click(boton) 39 | 40 | const div = container.querySelector(".hidden") 41 | expect(div).not.toHaveStyle("display: none") 42 | }) 43 | 44 | test("despues de dar dos clicksboton de like, el controlador del evento se llama 2 veces", async () => { 45 | const usuario = userEvent.setup() 46 | const botonLike = screen.getByText("like") 47 | await usuario.click(botonLike) 48 | await usuario.click(botonLike) 49 | expect(onClick).toHaveBeenCalledTimes(2) 50 | }) 51 | }) 52 | --------------------------------------------------------------------------------