├── part9 ├── bmi │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── .eslintrc │ └── bmiCalculator.ts ├── patientor-backend │ ├── .gitignore │ ├── .eslintignore │ ├── src │ │ ├── services │ │ │ ├── diagnosisService.ts │ │ │ └── patientService.ts │ │ ├── routes │ │ │ ├── diagnoses.ts │ │ │ └── patients.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── .eslintrc ├── patientor-frontend │ ├── .gitignore │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── constants.ts │ │ ├── state │ │ │ ├── index.ts │ │ │ └── state.tsx │ │ ├── utils.ts │ │ ├── index.tsx │ │ ├── components │ │ │ └── HealthRatingBar.tsx │ │ ├── AddEntryModal │ │ │ └── index.tsx │ │ ├── AddPatientModal │ │ │ └── index.tsx │ │ └── PatientPage │ │ │ ├── DiagnosisList.tsx │ │ │ ├── EntryDetails.tsx │ │ │ ├── HealthCheckEntryDetails.tsx │ │ │ ├── HospitalEntryDetails.tsx │ │ │ └── OccupationalHealthcareEntryDetails.tsx │ ├── public │ │ ├── favicon.ico │ │ └── manifest.json │ ├── screenshots │ │ ├── EntryForm.png │ │ ├── PatientList.png │ │ ├── NewPatientForm.png │ │ └── PatientEntries.png │ ├── tsconfig.json │ ├── .eslintrc │ ├── package.json │ └── README.md └── courseinfo-typescript │ ├── src │ ├── react-app-env.d.ts │ ├── index.tsx │ ├── utils.ts │ ├── components │ │ ├── Header.tsx │ │ ├── Total.tsx │ │ └── Content.tsx │ ├── types.ts │ └── App.tsx │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── .gitignore │ ├── .eslintrc │ ├── tsconfig.json │ └── package.json ├── part3 └── phonebook-backend │ ├── .eslintignore │ ├── Procfile │ ├── README.md │ ├── build │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── static │ │ ├── css │ │ │ ├── main.b5860f0e.chunk.css │ │ │ └── main.b5860f0e.chunk.css.map │ │ └── js │ │ │ ├── 2.732f3632.chunk.js.LICENSE.txt │ │ │ └── runtime-main.a212f097.js │ ├── manifest.json │ └── asset-manifest.json │ ├── .gitignore │ ├── .eslintrc.js │ ├── models │ └── person.js │ ├── package.json │ └── mongo.js ├── part4 └── blog-list-backend │ ├── .eslintignore │ ├── .gitignore │ ├── index.js │ ├── utils │ ├── config.js │ └── logger.js │ ├── .env.example │ ├── controllers │ ├── testing.js │ ├── users.js │ └── login.js │ ├── models │ ├── blog.js │ └── user.js │ ├── .eslintrc.js │ ├── package.json │ └── app.js ├── part5 └── bloglist-frontend │ ├── cypress.json │ ├── .eslintignore │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── index.js │ ├── services │ │ ├── login.js │ │ └── blogs.js │ ├── index.css │ ├── utils │ │ └── handleError.js │ └── components │ │ ├── UserInfo.js │ │ ├── Notification.js │ │ ├── Togglable.js │ │ ├── AddBlogForm.test.js │ │ └── LoginForm.js │ ├── cypress │ ├── fixtures │ │ ├── users.json │ │ └── blogs.json │ ├── support │ │ └── index.js │ └── plugins │ │ └── index.js │ ├── .gitignore │ ├── .eslintrc.js │ └── package.json ├── part7 ├── blog-list-backend-extended │ ├── .eslintignore │ ├── .gitignore │ ├── index.js │ ├── utils │ │ ├── config.js │ │ └── logger.js │ ├── .env.example │ ├── controllers │ │ ├── testing.js │ │ ├── users.js │ │ └── login.js │ ├── models │ │ ├── blog.js │ │ └── user.js │ ├── .eslintrc.js │ ├── package.json │ └── app.js ├── blog-list-frontend-extended │ ├── cypress.json │ ├── .eslintignore │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── screenshots │ │ ├── BlogList.png │ │ ├── BlogPage.png │ │ └── UsersList.png │ ├── src │ │ ├── services │ │ │ ├── users.js │ │ │ ├── login.js │ │ │ └── blogs.js │ │ ├── index.js │ │ ├── utils │ │ │ └── handleError.js │ │ ├── components │ │ │ ├── UnauthenticatedRoute.js │ │ │ ├── Comments.js │ │ │ ├── Notification.js │ │ │ ├── User.js │ │ │ ├── Togglable.js │ │ │ ├── BlogList.js │ │ │ ├── Navigation.js │ │ │ └── CommentForm.js │ │ ├── store.js │ │ └── reducers │ │ │ ├── userReducer.js │ │ │ └── notificationReducer.js │ ├── cypress │ │ ├── fixtures │ │ │ ├── users.json │ │ │ └── blogs.json │ │ ├── support │ │ │ └── index.js │ │ └── plugins │ │ │ └── index.js │ ├── .gitignore │ ├── README.md │ └── .eslintrc.js ├── country-hook │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ ├── package.json │ └── .eslintrc.js ├── ultimate-hooks │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ └── index.js │ ├── .gitignore │ ├── db.json │ ├── package.json │ └── .eslintrc.js └── routed-anecdotes │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── components │ │ ├── Notification.js │ │ ├── Footer.js │ │ ├── Menu.js │ │ ├── Anecdote.js │ │ ├── AnecdoteList.js │ │ ├── About.js │ │ └── CreateNew.js │ ├── index.js │ └── hooks │ │ └── index.js │ ├── .gitignore │ ├── package.json │ └── .eslintrc.js ├── part8 ├── library-backend │ ├── .gitignore │ ├── .env.example │ ├── utils │ │ └── config.js │ ├── models │ │ ├── author.js │ │ ├── user.js │ │ └── book.js │ ├── loaders.js │ ├── package.json │ ├── .eslintrc.js │ └── typeDefs.js └── library-frontend │ ├── .eslintignore │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── .gitignore │ ├── src │ ├── components │ │ ├── BookTable.js │ │ ├── Recommendations.js │ │ ├── Authors.js │ │ ├── Books.js │ │ └── BirthYearForm.js │ └── index.js │ ├── package.json │ └── .eslintrc.js ├── part0 ├── 0.4_new_note.png ├── 0.6_new_note_spa.png └── 0.5_single_page_app.png ├── part1 ├── anecdotes │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── .gitignore │ ├── src │ │ └── index.css │ └── package.json ├── courseinfo │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── .gitignore │ ├── src │ │ ├── index.css │ │ └── index.js │ └── package.json └── unicafe │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── .gitignore │ ├── src │ └── index.css │ └── package.json ├── part2 ├── countries │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── .env.example │ ├── src │ │ ├── index.js │ │ ├── components │ │ │ ├── Search.js │ │ │ └── Country.js │ │ └── App.js │ ├── .gitignore │ └── package.json ├── phonebook │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ └── manifest.json │ ├── src │ │ ├── index.js │ │ ├── index.css │ │ ├── components │ │ │ ├── Person.js │ │ │ ├── Search.js │ │ │ ├── Notification.js │ │ │ └── PersonList.js │ │ └── services │ │ │ └── persons.js │ ├── .gitignore │ ├── db.json │ └── package.json └── courseinfo2 │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json │ ├── src │ ├── index.js │ ├── components │ │ ├── Header.js │ │ ├── Part.js │ │ ├── Total.js │ │ ├── Content.js │ │ └── Course.js │ ├── index.css │ └── App.js │ ├── .gitignore │ └── package.json └── part6 ├── redux-anecdotes ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ └── manifest.json ├── src │ ├── index.js │ ├── reducers │ │ ├── filterReducer.js │ │ ├── notificationReducer.js │ │ └── anecdoteReducer.js │ ├── components │ │ ├── Filter.js │ │ ├── Notification.js │ │ ├── AnecdoteForm.js │ │ └── AnecdoteList.js │ ├── services │ │ └── anecdotes.js │ ├── store.js │ └── App.js ├── .gitignore ├── db.json ├── .eslintrc.js └── package.json └── unicafe-redux ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png └── manifest.json ├── .gitignore ├── src ├── reducer.js └── index.js ├── package.json └── .eslintrc.js /part9/bmi/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /part3/phonebook-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /part4/blog-list-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /part3/phonebook-backend/Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/.eslintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /part8/library-backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /part4/blog-list-backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /part8/library-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /part9/patientor-backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /part9/patientor-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /part5/bloglist-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /part9/patientor-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /part9/patientor-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const apiBaseUrl = 'http://localhost:3001/api'; 2 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./reducer"; 2 | export * from "./state"; 3 | -------------------------------------------------------------------------------- /part0/0.4_new_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part0/0.4_new_note.png -------------------------------------------------------------------------------- /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/phonebook/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part3/phonebook-backend/README.md: -------------------------------------------------------------------------------- 1 | # Heroku App for Part 3 2 | 3 | https://phonebook-app-12345.herokuapp.com/ 4 | -------------------------------------------------------------------------------- /part0/0.6_new_note_spa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part0/0.6_new_note_spa.png -------------------------------------------------------------------------------- /part2/courseinfo2/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part6/redux-anecdotes/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 | -------------------------------------------------------------------------------- /part0/0.5_single_page_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part0/0.5_single_page_app.png -------------------------------------------------------------------------------- /part3/phonebook-backend/build/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/routed-anecdotes/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 | -------------------------------------------------------------------------------- /part1/unicafe/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/unicafe/public/favicon.ico -------------------------------------------------------------------------------- /part1/unicafe/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/unicafe/public/logo192.png -------------------------------------------------------------------------------- /part1/unicafe/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/unicafe/public/logo512.png -------------------------------------------------------------------------------- /part9/courseinfo-typescript/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/ruelneuman/full-stack-open/HEAD/part1/anecdotes/public/favicon.ico -------------------------------------------------------------------------------- /part1/anecdotes/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/anecdotes/public/logo192.png -------------------------------------------------------------------------------- /part1/anecdotes/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/anecdotes/public/logo512.png -------------------------------------------------------------------------------- /part1/courseinfo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/courseinfo/public/favicon.ico -------------------------------------------------------------------------------- /part1/courseinfo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/courseinfo/public/logo192.png -------------------------------------------------------------------------------- /part1/courseinfo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part1/courseinfo/public/logo512.png -------------------------------------------------------------------------------- /part2/countries/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/countries/public/favicon.ico -------------------------------------------------------------------------------- /part2/countries/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/countries/public/logo192.png -------------------------------------------------------------------------------- /part2/countries/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/countries/public/logo512.png -------------------------------------------------------------------------------- /part2/phonebook/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/phonebook/public/favicon.ico -------------------------------------------------------------------------------- /part2/phonebook/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/phonebook/public/logo192.png -------------------------------------------------------------------------------- /part2/phonebook/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/phonebook/public/logo512.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /part2/countries/.env.example: -------------------------------------------------------------------------------- 1 | # .env.example 2 | # api key needed from https://openweathermap.org/ 3 | 4 | REACT_APP_OPEN_WEATHER_API_KEY=~ -------------------------------------------------------------------------------- /part2/courseinfo2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/courseinfo2/public/favicon.ico -------------------------------------------------------------------------------- /part2/courseinfo2/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/courseinfo2/public/logo192.png -------------------------------------------------------------------------------- /part2/courseinfo2/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part2/courseinfo2/public/logo512.png -------------------------------------------------------------------------------- /part6/unicafe-redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/unicafe-redux/public/favicon.ico -------------------------------------------------------------------------------- /part6/unicafe-redux/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/unicafe-redux/public/logo192.png -------------------------------------------------------------------------------- /part6/unicafe-redux/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/unicafe-redux/public/logo512.png -------------------------------------------------------------------------------- /part7/country-hook/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/country-hook/public/favicon.ico -------------------------------------------------------------------------------- /part7/country-hook/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/country-hook/public/logo192.png -------------------------------------------------------------------------------- /part7/country-hook/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/country-hook/public/logo512.png -------------------------------------------------------------------------------- /part6/redux-anecdotes/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/redux-anecdotes/public/favicon.ico -------------------------------------------------------------------------------- /part6/redux-anecdotes/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/redux-anecdotes/public/logo192.png -------------------------------------------------------------------------------- /part6/redux-anecdotes/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part6/redux-anecdotes/public/logo512.png -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/ultimate-hooks/public/favicon.ico -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/ultimate-hooks/public/logo192.png -------------------------------------------------------------------------------- /part7/ultimate-hooks/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/ultimate-hooks/public/logo512.png -------------------------------------------------------------------------------- /part8/library-backend/.env.example: -------------------------------------------------------------------------------- 1 | # .env 2 | 3 | MONGODB_URI= 4 | 5 | JWT_SECRET= -------------------------------------------------------------------------------- /part3/phonebook-backend/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part3/phonebook-backend/build/favicon.ico -------------------------------------------------------------------------------- /part3/phonebook-backend/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part3/phonebook-backend/build/logo192.png -------------------------------------------------------------------------------- /part3/phonebook-backend/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part3/phonebook-backend/build/logo512.png -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part5/bloglist-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part5/bloglist-frontend/public/logo192.png -------------------------------------------------------------------------------- /part5/bloglist-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part5/bloglist-frontend/public/logo512.png -------------------------------------------------------------------------------- /part7/routed-anecdotes/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/routed-anecdotes/public/favicon.ico -------------------------------------------------------------------------------- /part7/routed-anecdotes/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/routed-anecdotes/public/logo192.png -------------------------------------------------------------------------------- /part7/routed-anecdotes/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/routed-anecdotes/public/logo512.png -------------------------------------------------------------------------------- /part8/library-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part8/library-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part8/library-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part8/library-frontend/public/logo192.png -------------------------------------------------------------------------------- /part8/library-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part8/library-frontend/public/logo512.png -------------------------------------------------------------------------------- /part9/patientor-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/patientor-frontend/public/favicon.ico -------------------------------------------------------------------------------- /part9/courseinfo-typescript/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/courseinfo-typescript/public/favicon.ico -------------------------------------------------------------------------------- /part9/courseinfo-typescript/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/courseinfo-typescript/public/logo192.png -------------------------------------------------------------------------------- /part9/courseinfo-typescript/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/courseinfo-typescript/public/logo512.png -------------------------------------------------------------------------------- /part9/patientor-frontend/screenshots/EntryForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/patientor-frontend/screenshots/EntryForm.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/public/favicon.ico -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/public/logo192.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/public/logo512.png -------------------------------------------------------------------------------- /part9/patientor-frontend/screenshots/PatientList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/patientor-frontend/screenshots/PatientList.png -------------------------------------------------------------------------------- /part9/patientor-frontend/screenshots/NewPatientForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/patientor-frontend/screenshots/NewPatientForm.png -------------------------------------------------------------------------------- /part9/patientor-frontend/screenshots/PatientEntries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part9/patientor-frontend/screenshots/PatientEntries.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/screenshots/BlogList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/screenshots/BlogList.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/screenshots/BlogPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/screenshots/BlogPage.png -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/screenshots/UsersList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruelneuman/full-stack-open/HEAD/part7/blog-list-frontend-extended/screenshots/UsersList.png -------------------------------------------------------------------------------- /part2/countries/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /part2/courseinfo2/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /part2/phonebook/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /part7/country-hook/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /part7/ultimate-hooks/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); -------------------------------------------------------------------------------- /part2/courseinfo2/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Header = ({ course }) => { 4 | return ( 5 |

{course.name}

6 | ); 7 | } 8 | 9 | export default Header; -------------------------------------------------------------------------------- /part8/library-backend/utils/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const MONGODB_URI = process.env.MONGODB_URI; 4 | const JWT_SECRET = process.env.JWT_SECRET; 5 | 6 | module.exports = { 7 | MONGODB_URI, 8 | JWT_SECRET, 9 | }; -------------------------------------------------------------------------------- /part2/courseinfo2/src/components/Part.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Part = ({ part }) => { 4 | return ( 5 |

6 | {part.name} {part.exercises} 7 |

8 | ); 9 | } 10 | 11 | export default Part; -------------------------------------------------------------------------------- /part9/patientor-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Notification = ({ notification }) => { 4 | return ( 5 |
6 | {notification} 7 |
8 | ); 9 | }; 10 | 11 | export default Notification; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/services/users.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | const baseUrl = '/api/users'; 3 | 4 | const getAll = async () => { 5 | const response = await axios.get(baseUrl); 6 | return response.data; 7 | }; 8 | 9 | export default { getAll }; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function for exhaustive type checking 3 | */ 4 | export const assertNever = (value: never): never => { 5 | throw new Error( 6 | `Unhandled discriminated union member: ${JSON.stringify(value)}` 7 | ); 8 | }; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function for exhaustive type checking 3 | */ 4 | export const assertNever = (value: never): never => { 5 | throw new Error( 6 | `Unhandled discriminated union member: ${JSON.stringify(value)}` 7 | ); 8 | }; -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/services/login.js: -------------------------------------------------------------------------------- 1 | import axios from '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 | export default { login }; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface HeaderProps { 4 | courseName: string; 5 | } 6 | 7 | const Header = ({ courseName }: HeaderProps) => { 8 | return

{courseName}

; 9 | }; 10 | 11 | export default Header; -------------------------------------------------------------------------------- /part9/patientor-backend/src/services/diagnosisService.ts: -------------------------------------------------------------------------------- 1 | import diagnoses from '../../data/diagnoses'; 2 | import { Diagnosis } from "../types"; 3 | 4 | const getDiagnoses = (): Array => { 5 | return diagnoses; 6 | }; 7 | 8 | export default { 9 | getDiagnoses, 10 | }; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/services/login.js: -------------------------------------------------------------------------------- 1 | import axios from '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 | export default { login }; -------------------------------------------------------------------------------- /part4/blog-list-backend/index.js: -------------------------------------------------------------------------------- 1 | const config = require('./utils/config'); 2 | const app = require('./app'); 3 | const http = require('http'); 4 | 5 | const server = http.createServer(app); 6 | 7 | server.listen(config.PORT, () => { 8 | console.log(`Server running on port ${config.PORT}`); 9 | }); -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "John Doe", 4 | "username": "JohnDoe123", 5 | "password": "johnspassword" 6 | }, 7 | { 8 | "name": "Jane Doe", 9 | "username": "JaneDoe123", 10 | "password": "janespassword" 11 | } 12 | ] -------------------------------------------------------------------------------- /part3/phonebook-backend/build/static/css/main.b5860f0e.chunk.css: -------------------------------------------------------------------------------- 1 | .notification{background:#d3d3d3;font-size:20px;border-style:solid;border-radius:5px;padding:10px;margin-bottom:10px}.notification__failure{color:red}.notification__success{color:green} 2 | /*# sourceMappingURL=main.b5860f0e.chunk.css.map */ -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/index.js: -------------------------------------------------------------------------------- 1 | const config = require('./utils/config'); 2 | const app = require('./app'); 3 | const http = require('http'); 4 | 5 | const server = http.createServer(app); 6 | 7 | server.listen(config.PORT, () => { 8 | console.log(`Server running on port ${config.PORT}`); 9 | }); -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "John Doe", 4 | "username": "JohnDoe123", 5 | "password": "johnspassword" 6 | }, 7 | { 8 | "name": "Jane Doe", 9 | "username": "JaneDoe123", 10 | "password": "janespassword" 11 | } 12 | ] -------------------------------------------------------------------------------- /part4/blog-list-backend/utils/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const PORT = process.env.PORT; 4 | 5 | const MONGODB_URI = 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/routed-anecdotes/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { BrowserRouter as Router } from 'react-router-dom'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); -------------------------------------------------------------------------------- /part9/patientor-backend/src/routes/diagnoses.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import diagnosisService from '../services/diagnosisService'; 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/', (_req, res) => { 7 | res.json(diagnosisService.getDiagnoses()); 8 | }); 9 | 10 | export default router; -------------------------------------------------------------------------------- /part2/courseinfo2/src/components/Total.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Total = ({ course: { parts } }) => { 4 | const total = parts.reduce((sum, part) => sum + part.exercises, 0); 5 | return ( 6 |

total of {total} exercises

7 | ); 8 | } 9 | 10 | export default Total; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/utils/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const PORT = process.env.PORT; 4 | 5 | const MONGODB_URI = 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 | -------------------------------------------------------------------------------- /part4/blog-list-backend/.env.example: -------------------------------------------------------------------------------- 1 | # .env.example 2 | 3 | MONGODB_URI='mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]' 4 | PORT=3003 5 | SECRET='secretfortokencreationhere' 6 | 7 | TEST_MONGODB_URI='mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]' -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/index.css: -------------------------------------------------------------------------------- 1 | .notification { 2 | background: lightgrey; 3 | font-size: 20px; 4 | border-style: solid; 5 | border-radius: 5px; 6 | padding: 10px; 7 | margin-bottom: 10px; 8 | } 9 | 10 | .notification--failure { 11 | color: red; 12 | } 13 | 14 | .notification--success { 15 | color: green; 16 | } 17 | -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import store from './store'; 5 | import App from './App'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/.env.example: -------------------------------------------------------------------------------- 1 | # .env.example 2 | 3 | MONGODB_URI='mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]' 4 | PORT=3003 5 | SECRET='secretfortokencreationhere' 6 | 7 | TEST_MONGODB_URI='mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]' -------------------------------------------------------------------------------- /part2/phonebook/src/index.css: -------------------------------------------------------------------------------- 1 | .notification { 2 | background: lightgrey; 3 | font-size: 20px; 4 | border-style: solid; 5 | border-radius: 5px; 6 | padding: 10px; 7 | margin-bottom: 10px; 8 | } 9 | 10 | .notification__failure { 11 | color: red; 12 | } 13 | 14 | .notification__success { 15 | color: green; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /part4/blog-list-backend/utils/logger.js: -------------------------------------------------------------------------------- 1 | const info = (...params) => { 2 | if (process.env.NODE_ENV !== 'test') { 3 | console.log(...params); 4 | } 5 | }; 6 | 7 | const error = (...params) => { 8 | if (process.env.NODE_ENV !== 'test') { 9 | console.error(...params); 10 | } 11 | }; 12 | 13 | module.exports = { 14 | info, 15 | error 16 | }; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/utils/logger.js: -------------------------------------------------------------------------------- 1 | const info = (...params) => { 2 | if (process.env.NODE_ENV !== 'test') { 3 | console.log(...params); 4 | } 5 | }; 6 | 7 | const error = (...params) => { 8 | if (process.env.NODE_ENV !== 'test') { 9 | console.error(...params); 10 | } 11 | }; 12 | 13 | module.exports = { 14 | info, 15 | error 16 | }; -------------------------------------------------------------------------------- /part2/phonebook/src/components/Person.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Person = ({ person, handleDelete }) => { 4 | 5 | return ( 6 |
7 | {person.name} {person.number} 8 | 9 |
10 | ) 11 | } 12 | 13 | export default Person; -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /part2/countries/src/components/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Search = ({ query, handleQuery }) => { 4 | return ( 5 |
6 | 7 | 8 |
9 | ); 10 | } 11 | 12 | export default Search; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'semantic-ui-css/semantic.min.css'; 4 | import App from './App'; 5 | import { reducer, StateProvider } from "./state"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/reducers/filterReducer.js: -------------------------------------------------------------------------------- 1 | const reducer = (state = '', action) => { 2 | switch (action.type) { 3 | case 'FILTER': 4 | return action.payload; 5 | default: 6 | return state; 7 | } 8 | }; 9 | 10 | export const setFilter = (filter) => { 11 | return { 12 | type: 'FILTER', 13 | payload: filter 14 | }; 15 | }; 16 | 17 | export default reducer; -------------------------------------------------------------------------------- /part2/phonebook/src/components/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Search = ({ query, handleQuery }) => { 4 | return ( 5 |
6 |

Search

7 | search: 8 | 12 |
13 | ); 14 | } 15 | 16 | export default Search; -------------------------------------------------------------------------------- /part4/blog-list-backend/controllers/testing.js: -------------------------------------------------------------------------------- 1 | const testingRouter = require('express').Router(); 2 | const Note = require('../models/blog'); 3 | const User = require('../models/user'); 4 | 5 | testingRouter.post('/reset', async (request, response) => { 6 | await Note.deleteMany({}); 7 | await User.deleteMany({}); 8 | 9 | response.status(204).end(); 10 | }); 11 | 12 | module.exports = testingRouter; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/controllers/testing.js: -------------------------------------------------------------------------------- 1 | const testingRouter = require('express').Router(); 2 | const Note = require('../models/blog'); 3 | const User = require('../models/user'); 4 | 5 | testingRouter.post('/reset', async (request, response) => { 6 | await Note.deleteMany({}); 7 | await User.deleteMany({}); 8 | 9 | response.status(204).end(); 10 | }); 11 | 12 | module.exports = testingRouter; -------------------------------------------------------------------------------- /part3/phonebook-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 | # misc 12 | .DS_Store 13 | .env 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /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/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 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /part2/courseinfo2/src/components/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Part from './Part'; 3 | 4 | const Content = ({ course: { parts } }) => { 5 | return ( 6 |
7 | {parts.map(part => 8 | 12 | )} 13 |
14 | ); 15 | } 16 | 17 | export default Content; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export const useField = (name, type = 'text') => { 4 | const [value, setValue] = useState(''); 5 | 6 | const onChange = (event) => { 7 | setValue(event.target.value); 8 | }; 9 | 10 | const reset = () => { 11 | setValue(''); 12 | }; 13 | 14 | const props = { type, name, value, onChange }; 15 | 16 | return [ props, reset ]; 17 | }; -------------------------------------------------------------------------------- /part2/courseinfo2/.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/redux-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part9/bmi/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/anecdotes/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /part1/courseinfo/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /part1/unicafe/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /part2/courseinfo2/src/components/Course.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | import Content from './Content'; 4 | import Total from './Total'; 5 | 6 | const Course = ({ course }) => { 7 | return ( 8 |
9 |
10 | 11 | 12 |
13 | ); 14 | } 15 | 16 | export default Course; -------------------------------------------------------------------------------- /part2/courseinfo2/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /part7/routed-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /part2/phonebook/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../index.css'; 3 | 4 | const Notification = ({ notification }) => { 5 | if (notification === null) { 6 | return null 7 | } 8 | 9 | const className = `notification notification__${notification.type}` 10 | 11 | return ( 12 |
{notification.message}
13 | ); 14 | } 15 | 16 | export default Notification; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/.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 | -------------------------------------------------------------------------------- /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 | .eslintcache -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { BrowserRouter as Router } from 'react-router-dom'; 5 | import store from './store'; 6 | import App from './App'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); -------------------------------------------------------------------------------- /part8/library-backend/models/author.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const schema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | minlength: 4 10 | }, 11 | born: { 12 | type: Number, 13 | }, 14 | }); 15 | 16 | schema.plugin(uniqueValidator); 17 | 18 | module.exports = mongoose.model('Author', schema); -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/.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 | .eslintcache -------------------------------------------------------------------------------- /part8/library-backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const schema = new mongoose.Schema({ 5 | username: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | minlength: 4 10 | }, 11 | favoriteGenre: { 12 | type: String, 13 | } 14 | }); 15 | 16 | schema.plugin(uniqueValidator); 17 | 18 | module.exports = mongoose.model('User', schema); -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => ( 4 | 9 | ); 10 | 11 | export default Footer; -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const Menu = () => { 5 | const padding = { 6 | paddingRight: 5 7 | }; 8 | 9 | return ( 10 |
11 | anecdotes 12 | create new 13 | about 14 |
15 | ); 16 | }; 17 | 18 | export default Menu; -------------------------------------------------------------------------------- /part7/ultimate-hooks/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "notes": [ 3 | { 4 | "content": "custom-hooks are grat", 5 | "id": 1 6 | }, 7 | { 8 | "content": "the best featrure ever <3", 9 | "id": 2 10 | } 11 | ], 12 | "persons": [ 13 | { 14 | "name": "mluukkai", 15 | "number": "040-5483923", 16 | "id": 1 17 | }, 18 | { 19 | "name": "kaltsoon", 20 | "number": "045-2323456", 21 | "id": 2 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/components/Total.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CoursePart } from '../types'; 3 | 4 | interface TotalProps { 5 | courseParts: CoursePart[]; 6 | } 7 | 8 | const Total = ({ courseParts }: TotalProps) => { 9 | return ( 10 |

11 | 12 | Number of exercises{' '} 13 | {courseParts.reduce((carry, part) => carry + part.exerciseCount, 0)} 14 | 15 |

16 | ) 17 | }; 18 | 19 | export default Total; -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/fixtures/blogs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "First Blog", 4 | "author": "Test Author", 5 | "url": "http://www.example.com/blog1", 6 | "likes": 5 7 | }, 8 | { 9 | "title": "Second Blog", 10 | "author": "Test Author", 11 | "url": "http://www.example.com/blog2", 12 | "likes": 1 13 | }, 14 | { 15 | "title": "Third Blog", 16 | "author": "Test Author", 17 | "url": "http://www.example.com/blog3", 18 | "likes": 3 19 | } 20 | ] -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/utils/handleError.js: -------------------------------------------------------------------------------- 1 | const handleError = (error) => { 2 | if (error.response) { 3 | console.error(error.response); 4 | 5 | return error.response.data.error 6 | ? error.response.data.error 7 | : error.message; 8 | } else if (error.request) { 9 | console.error(error.request); 10 | return error.message; 11 | } else { 12 | console.log('Error', error.message); 13 | return error.message; 14 | } 15 | }; 16 | 17 | export default handleError; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/cypress/fixtures/blogs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "First Blog", 4 | "author": "Test Author", 5 | "url": "http://www.example.com/blog1", 6 | "likes": 5 7 | }, 8 | { 9 | "title": "Second Blog", 10 | "author": "Test Author", 11 | "url": "http://www.example.com/blog2", 12 | "likes": 1 13 | }, 14 | { 15 | "title": "Third Blog", 16 | "author": "Test Author", 17 | "url": "http://www.example.com/blog3", 18 | "likes": 3 19 | } 20 | ] -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/utils/handleError.js: -------------------------------------------------------------------------------- 1 | const handleError = (error) => { 2 | if (error.response) { 3 | console.error(error.response); 4 | 5 | return error.response.data.error 6 | ? error.response.data.error 7 | : error.message; 8 | } else if (error.request) { 9 | console.error(error.request); 10 | return error.message; 11 | } else { 12 | console.error('Error', error.message); 13 | return error.message; 14 | } 15 | }; 16 | 17 | export default handleError; -------------------------------------------------------------------------------- /part8/library-backend/loaders.js: -------------------------------------------------------------------------------- 1 | const Book = require('./models/book'); 2 | 3 | const batchBookCounts = async (authorIds) => { 4 | const books = await Book.find({}); 5 | 6 | const bookCounts = books.reduce((bookCounts, book) => { 7 | const authorId = book.author; 8 | 9 | bookCounts[authorId] = (bookCounts[authorId] || 0) + 1; 10 | 11 | return bookCounts; 12 | }, {}); 13 | 14 | return authorIds.map((authorId) => bookCounts[authorId] || 0); 15 | }; 16 | 17 | module.exports = { batchBookCounts }; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/components/Content.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CoursePart } from '../types'; 3 | import Part from './Part'; 4 | 5 | interface ContentProps { 6 | courseParts: CoursePart[]; 7 | } 8 | 9 | const Content = ({ courseParts }: ContentProps) => { 10 | return ( 11 |
12 | {courseParts.map((coursePart) => { 13 | return ( 14 | 15 | ) 16 | })} 17 |
18 | ); 19 | }; 20 | 21 | export default Content; -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/Anecdote.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Anecdote = ({ anecdote, voteAnecdote }) => { 4 | if (!anecdote) { 5 | return null; 6 | } 7 | 8 | return ( 9 |
10 |

{anecdote.content}

11 |

has {anecdote.votes} votes

12 | 15 |

for more info see {anecdote.info}

16 |
17 | ); 18 | }; 19 | 20 | export default Anecdote; -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/AnecdoteList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const AnecdoteList = ({ anecdotes }) => ( 5 |
6 |

Anecdotes

7 |
    8 | {anecdotes.map(anecdote => { 9 | return ( 10 |
  • 11 | 12 | {anecdote.content} 13 | 14 |
  • 15 | ); 16 | })} 17 |
18 |
19 | ); 20 | 21 | export default AnecdoteList; -------------------------------------------------------------------------------- /part2/phonebook/src/components/PersonList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Person from './Person' 3 | 4 | const PersonList = ({ filteredPersons, handleDelete }) => { 5 | 6 | return ( 7 |
8 |

Numbers

9 | {filteredPersons.map(person => 10 | 15 | )} 16 |
17 | ) 18 | } 19 | 20 | export default PersonList; -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/components/Filter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { setFilter } from '../reducers/filterReducer'; 4 | 5 | const Filter = ({ setFilter }) => { 6 | const handleChange = (event) => { 7 | setFilter(event.target.value); 8 | }; 9 | const style = { 10 | marginBottom: 10 11 | }; 12 | 13 | return ( 14 |
15 | filter 16 |
17 | ); 18 | }; 19 | 20 | export default connect( 21 | null, 22 | { setFilter } 23 | )(Filter); -------------------------------------------------------------------------------- /part3/phonebook-backend/build/static/css/main.b5860f0e.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://src/index.css"],"names":[],"mappings":"AAAA,cACI,kBAAqB,CACrB,cAAe,CACf,kBAAmB,CACnB,iBAAkB,CAClB,YAAa,CACb,kBACJ,CAEA,uBACG,SACH,CAEA,uBACI,WACH","file":"main.b5860f0e.chunk.css","sourcesContent":[".notification {\r\n background: lightgrey;\r\n font-size: 20px;\r\n border-style: solid;\r\n border-radius: 5px;\r\n padding: 10px;\r\n margin-bottom: 10px;\r\n}\r\n\r\n.notification__failure {\r\n color: red;\r\n}\r\n\r\n.notification__success {\r\n color: green;\r\n }\r\n\r\n"]} -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | const Notification = ({ notification }) => { 5 | const style = { 6 | border: 'solid', 7 | padding: 10, 8 | borderWidth: 1 9 | }; 10 | 11 | if (!notification) return null; 12 | 13 | return ( 14 |
15 | {notification} 16 |
17 | ); 18 | }; 19 | 20 | const mapStateToProps = (state) => { 21 | return { notification: state.notification }; 22 | }; 23 | 24 | export default connect(mapStateToProps)(Notification); -------------------------------------------------------------------------------- /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 | console.log(action); 9 | switch (action.type) { 10 | case 'GOOD': 11 | return { ...state, good: state.good + 1 }; 12 | case 'OK': 13 | return { ...state, ok: state.ok + 1 }; 14 | case 'BAD': 15 | return { ...state, bad: state.bad + 1 }; 16 | case 'ZERO': 17 | return initialState; 18 | default: 19 | return state; 20 | } 21 | 22 | }; 23 | 24 | export default counterReducer; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/.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": ["react", "@typescript-eslint"], 13 | "settings": { 14 | "react": { 15 | "pragma": "React", 16 | "version": "detect" 17 | } 18 | }, 19 | "rules": { 20 | "@typescript-eslint/explicit-function-return-type": 0, 21 | "@typescript-eslint/explicit-module-boundary-types": 0 22 | } 23 | } -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/UnauthenticatedRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { Route, Redirect } from 'react-router-dom'; 4 | 5 | const UnauthenticatedRoute = ({ component: Component, ...rest }) => { 6 | const isLoggedIn = useSelector((state) => state.authentication.isLoggedIn); 7 | 8 | return ( 9 | ( 10 | isLoggedIn ? 11 | 12 | : 13 | )} /> 14 | ); 15 | }; 16 | 17 | export default UnauthenticatedRoute; -------------------------------------------------------------------------------- /part8/library-backend/models/book.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const schema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | minlength: 2 10 | }, 11 | published: { 12 | type: Number, 13 | }, 14 | author: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | ref: 'Author' 17 | }, 18 | genres: [ 19 | { type: String } 20 | ] 21 | }); 22 | 23 | schema.plugin(uniqueValidator); 24 | 25 | module.exports = mongoose.model('Book', schema); -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/services/anecdotes.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const baseUrl = '/anecdotes'; 4 | 5 | const getAll = async () => { 6 | const response = await axios.get(baseUrl); 7 | return response.data; 8 | }; 9 | 10 | const createNew = async (anecdote) => { 11 | const response = await axios.post(baseUrl, anecdote); 12 | return response.data; 13 | }; 14 | 15 | const update = async (id, newAnecdote) => { 16 | const response = await axios.put(`${baseUrl}/${id}`, newAnecdote); 17 | return response.data; 18 | }; 19 | 20 | export default { 21 | getAll, 22 | createNew, 23 | update, 24 | }; -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/UserInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const UserInfo = ({ user, handleLogout }) => ( 5 |
6 | {user.name} is logged in 7 | 8 |
9 | ); 10 | 11 | UserInfo.propTypes = { 12 | user: PropTypes.shape({ 13 | name: PropTypes.string.isRequired, 14 | token: PropTypes.string.isRequired, 15 | username: PropTypes.string.isRequired, 16 | }).isRequired, 17 | handleLogout: PropTypes.func.isRequired, 18 | }; 19 | 20 | export default UserInfo; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/courseinfo2/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 | -------------------------------------------------------------------------------- /part3/phonebook-backend/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /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/redux-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 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import CommentForm from './CommentForm'; 4 | import List from '@material-ui/core/List'; 5 | import ListItem from '@material-ui/core/ListItem'; 6 | 7 | const Comments = ({ blog }) => { 8 | const comments = blog.comments; 9 | 10 | return ( 11 |
12 |

Comments:

13 | 14 | {comments.map((comment) => {comment})} 15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default Comments; -------------------------------------------------------------------------------- /part7/routed-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 | -------------------------------------------------------------------------------- /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/patientor-backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import diagnosesRouter from './routes/diagnoses'; 4 | import patientsRouter from './routes/patients'; 5 | 6 | const app = express(); 7 | 8 | app.use(express.json()); 9 | 10 | app.use(cors()); 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', diagnosesRouter); 20 | app.use('/api/patients', patientsRouter); 21 | 22 | app.listen(PORT, () => { 23 | console.log(`Server running on port ${PORT}`); 24 | }); -------------------------------------------------------------------------------- /part9/courseinfo-typescript/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/blog-list-frontend-extended/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 | -------------------------------------------------------------------------------- /part4/blog-list-backend/models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const blogSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: String, 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); -------------------------------------------------------------------------------- /part9/courseinfo-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": false, 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 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/README.md: -------------------------------------------------------------------------------- 1 | # blog-list-frontend-extended 2 | 3 | The frontend for a full stack application that was extended from the blog app from parts 4 and 5. It is used to share blog post with friends who can then like or comment on them. Redux is used for state management and React Router for routing. Styling is accomplished with Material-UI. 4 | 5 | ## Screenshots 6 | 7 |

8 | 9 | 10 | 11 |

12 | -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/models/blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const blogSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: String, 6 | url: { type: String, required: true }, 7 | likes: { type: Number, default: 0 }, 8 | comments: [String], 9 | user: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'User', 12 | } 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); -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | 5 | import anecdoteReducer from './reducers/anecdoteReducer'; 6 | import notificationReducer from './reducers/notificationReducer'; 7 | import filterReducer from './reducers/filterReducer'; 8 | 9 | const reducer = combineReducers({ 10 | anecdotes: anecdoteReducer, 11 | notification: notificationReducer, 12 | filter: filterReducer, 13 | }); 14 | 15 | const store = createStore( 16 | reducer, 17 | composeWithDevTools( 18 | applyMiddleware(thunk) 19 | ) 20 | ); 21 | 22 | export default store; -------------------------------------------------------------------------------- /part9/patientor-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | "downlevelIteration": true, 21 | "allowJs": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../index.css'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Notification = ({ notification }) => { 6 | if (notification === null) { 7 | return null; 8 | } 9 | 10 | const className = `notification notification--${notification.type}`; 11 | 12 | return ( 13 |
{notification.message}
14 | ); 15 | }; 16 | 17 | Notification.propTypes = { 18 | notification: PropTypes.shape({ 19 | message: PropTypes.string.isRequired, 20 | type: PropTypes.oneOf([ 21 | 'success', 22 | 'failure', 23 | ]).isRequired, 24 | }), 25 | }; 26 | 27 | export default Notification; -------------------------------------------------------------------------------- /part8/library-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "library-backend", 3 | "version": "1.0.0", 4 | "description": "Backend for library app", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint ." 10 | }, 11 | "author": "Ruel Neuman", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "eslint": "^7.26.0", 15 | "nodemon": "^2.0.7" 16 | }, 17 | "dependencies": { 18 | "apollo-server": "^2.24.0", 19 | "dataloader": "^2.0.0", 20 | "dotenv": "^9.0.2", 21 | "graphql": "^15.5.0", 22 | "jsonwebtoken": "^8.5.1", 23 | "mongoose": "^5.12.9", 24 | "mongoose-unique-validator": "^2.0.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/Notification.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { Alert } from '@material-ui/lab'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | alert: { 8 | margin: theme.spacing(1), 9 | }, 10 | })); 11 | 12 | const Notification = () => { 13 | const classes = useStyles(); 14 | 15 | const notification = useSelector((state) => state.notification); 16 | 17 | if (!notification || !notification.message) { 18 | return null; 19 | } 20 | 21 | return ( 22 | {notification.message} 23 | ); 24 | }; 25 | 26 | export default Notification; -------------------------------------------------------------------------------- /part8/library-frontend/src/components/BookTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BookTable = ({ books }) => { 4 | if (!books || books.length <= 0) { 5 | return
No books found
; 6 | } 7 | 8 | return ( 9 | 10 | 11 | 12 | 13 | 16 | 19 | 20 | {books.map((book) => 21 | 22 | 23 | 24 | 25 | 26 | )} 27 | 28 |
14 | Author 15 | 17 | Published 18 |
{book.title}{book.author.name}{book.published}
29 | ); 30 | }; 31 | 32 | export default BookTable; -------------------------------------------------------------------------------- /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 remove = (object) => { 16 | const request = axios.delete(`${baseUrl}/${object.id}`, object); 17 | return request; 18 | } 19 | 20 | const update = (id, newObject) => { 21 | const request = axios.put(`${baseUrl}/${id}`, newObject); 22 | return request.then((response) => response.data); 23 | } 24 | 25 | export default { getAll, create, remove, update }; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/components/HealthRatingBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Rating } from 'semantic-ui-react'; 3 | 4 | type BarProps = { 5 | rating: number; 6 | showText: boolean; 7 | }; 8 | 9 | const HEALTHBAR_TEXTS = [ 10 | 'The patient is in great shape', 11 | 'The patient has a low risk of getting sick', 12 | 'The patient has a high risk of getting sick', 13 | 'The patient has a diagnosed condition', 14 | ]; 15 | 16 | const HealthRatingBar = ({ rating, showText }: BarProps) => { 17 | return ( 18 |
19 | {} 20 | {showText ?

{HEALTHBAR_TEXTS[rating]}

: null} 21 |
22 | ); 23 | }; 24 | 25 | export default HealthRatingBar; 26 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | 5 | import notificationReducer from './reducers/notificationReducer'; 6 | import blogReducer from './reducers/blogReducer'; 7 | import userReducer from './reducers/userReducer'; 8 | import authenticationReducer from './reducers/authenticationReducer'; 9 | 10 | const reducer = combineReducers({ 11 | notification: notificationReducer, 12 | blogs: blogReducer, 13 | users: userReducer, 14 | authentication: authenticationReducer, 15 | }); 16 | 17 | const store = createStore( 18 | reducer, 19 | composeWithDevTools( 20 | applyMiddleware(thunk) 21 | ) 22 | ); 23 | 24 | export default store; -------------------------------------------------------------------------------- /part8/library-backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es2021': true, 6 | 'jest': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'parserOptions': { 10 | 'ecmaVersion': 12 11 | }, 12 | 'rules': { 13 | 'indent': [ 14 | 'error', 15 | 2 16 | ], 17 | 'linebreak-style': [ 18 | 'error', 19 | 'windows' 20 | ], 21 | 'quotes': [ 22 | 'error', 23 | 'single' 24 | ], 25 | 'semi': [ 26 | 'error', 27 | 'always' 28 | ], 29 | 'eqeqeq': 'error', 30 | 'no-trailing-spaces': 'error', 31 | 'object-curly-spacing': [ 32 | 'error', 'always' 33 | ], 34 | 'arrow-spacing': [ 35 | 'error', { 'before': true, 'after': true } 36 | ] 37 | } 38 | }; -------------------------------------------------------------------------------- /part4/blog-list-backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es2021': true, 6 | 'jest': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'parserOptions': { 10 | 'ecmaVersion': 12 11 | }, 12 | 'rules': { 13 | 'indent': [ 14 | 'error', 15 | 2 16 | ], 17 | 'linebreak-style': [ 18 | 'error', 19 | 'windows' 20 | ], 21 | 'quotes': [ 22 | 'error', 23 | 'single' 24 | ], 25 | 'semi': [ 26 | 'error', 27 | 'always' 28 | ], 29 | 'eqeqeq': 'error', 30 | 'no-trailing-spaces': 'error', 31 | 'object-curly-spacing': [ 32 | 'error', 'always' 33 | ], 34 | 'arrow-spacing': [ 35 | 'error', { 'before': true, 'after': true } 36 | ] 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import AnecdoteForm from './components/AnecdoteForm'; 4 | import AnecdoteList from './components/AnecdoteList'; 5 | import Notification from './components/Notification'; 6 | import Filter from './components/Filter'; 7 | import { initializeAnecdotes } from './reducers/anecdoteReducer'; 8 | 9 | 10 | const App = () => { 11 | const dispatch = useDispatch(); 12 | 13 | useEffect(() => { 14 | dispatch(initializeAnecdotes()); 15 | }, []); 16 | 17 | return ( 18 |
19 |

Anecdotes

20 | 21 | 22 | 23 |

Create New

24 | 25 |
26 | ); 27 | }; 28 | 29 | export default App; -------------------------------------------------------------------------------- /part3/phonebook-backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es2021': true 6 | }, 7 | 'extends': 'eslint:recommended', 8 | 'parserOptions': { 9 | 'ecmaVersion': 12 10 | }, 11 | 'rules': { 12 | 'indent': [ 13 | 'error', 14 | 2 15 | ], 16 | 'linebreak-style': [ 17 | 'error', 18 | 'windows' 19 | ], 20 | 'quotes': [ 21 | 'error', 22 | 'single' 23 | ], 24 | 'semi': [ 25 | 'error', 26 | 'always' 27 | ], 28 | 'eqeqeq': 'error', 29 | 'no-trailing-spaces': 'error', 30 | 'object-curly-spacing': [ 31 | 'error', 'always' 32 | ], 33 | 'arrow-spacing': [ 34 | 'error', { 'before': true, 'after': true } 35 | ], 36 | 'no-console': 0 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'node': true, 4 | 'commonjs': true, 5 | 'es2021': true, 6 | 'jest': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'parserOptions': { 10 | 'ecmaVersion': 12 11 | }, 12 | 'rules': { 13 | 'indent': [ 14 | 'error', 15 | 2 16 | ], 17 | 'linebreak-style': [ 18 | 'error', 19 | 'windows' 20 | ], 21 | 'quotes': [ 22 | 'error', 23 | 'single' 24 | ], 25 | 'semi': [ 26 | 'error', 27 | 'always' 28 | ], 29 | 'eqeqeq': 'error', 30 | 'no-trailing-spaces': 'error', 31 | 'object-curly-spacing': [ 32 | 'error', 'always' 33 | ], 34 | 'arrow-spacing': [ 35 | 'error', { 'before': true, 'after': true } 36 | ] 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/AddEntryModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Segment } from 'semantic-ui-react'; 3 | import AddEntryForm from './AddEntryForm'; 4 | import { NewEntry } from '../types'; 5 | 6 | interface Props { 7 | modalOpen: boolean; 8 | onClose: () => void; 9 | onSubmit: (values: NewEntry) => void; 10 | error?: string; 11 | } 12 | 13 | const AddEntryModal = ({ modalOpen, onClose, onSubmit, error }: Props) => ( 14 | 15 | Add a new entry 16 | 17 | {error && {`Error: ${error}`}} 18 | 19 | 20 | 21 | ); 22 | 23 | export default AddEntryModal; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/AddPatientModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Segment } from 'semantic-ui-react'; 3 | import AddPatientForm, { PatientFormValues } from './AddPatientForm'; 4 | 5 | interface Props { 6 | modalOpen: boolean; 7 | onClose: () => void; 8 | onSubmit: (values: PatientFormValues) => void; 9 | error?: string; 10 | } 11 | 12 | const AddPatientModal = ({ modalOpen, onClose, onSubmit, error }: Props) => ( 13 | 14 | Add a new patient 15 | 16 | {error && {`Error: ${error}`}} 17 | 18 | 19 | 20 | ); 21 | 22 | export default AddPatientModal; 23 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/PatientPage/DiagnosisList.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { v1 as uuidv1 } from 'uuid'; 3 | import { List, Label } from "semantic-ui-react"; 4 | 5 | import { Diagnosis } from "../types"; 6 | import { useStateValue } from "../state"; 7 | 8 | interface DiagnosisListProps { 9 | diagnosisCodes: Array; 10 | } 11 | 12 | const DiagnosisList = ({ diagnosisCodes }: DiagnosisListProps) => { 13 | const [{ diagnoses }] = useStateValue(); 14 | 15 | return ( 16 | 17 | {diagnosisCodes.map((code) => { 18 | return ( 19 | 20 | 21 | {diagnoses[code]?.name} 22 | 23 | ); 24 | })} 25 | 26 | ); 27 | }; 28 | 29 | export default DiagnosisList; 30 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/User.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { useParams } from 'react-router-dom'; 4 | import List from '@material-ui/core/List'; 5 | import ListItem from '@material-ui/core/ListItem'; 6 | 7 | const User = () => { 8 | const id = useParams().id; 9 | 10 | const user = useSelector((state) => { 11 | return state.users.users.find((user) => { 12 | return user.id === id; 13 | }); 14 | }); 15 | 16 | if (!user) { 17 | return null; 18 | } 19 | 20 | return ( 21 |
22 |

{user.name}

23 |

Blogs Added:

24 | 25 | {user.blogs.map((blog) => { 26 | return {blog.title}; 27 | })} 28 | 29 |
30 | ); 31 | }; 32 | 33 | export default User; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | React App 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = () => ( 4 |
5 |

About anecdote app

6 |

According to Wikipedia:

7 | 8 | An anecdote is a brief, revealing account of an individual person or an incident. 9 | Occasionally humorous, anecdotes differ from jokes because their primary purpose is not simply to provoke laughter but to reveal a truth more general than the brief tale itself, 10 | such as to characterize a person by delineating a specific quirk or trait, to communicate an abstract idea about a person, place, or thing through the concrete details of a short narrative. 11 | An anecdote is "a story with a point." 12 | 13 |

Software engineering is full of excellent anecdotes, at this app you can find the best and add more.

14 |
15 | ); 16 | 17 | export default About; -------------------------------------------------------------------------------- /part9/bmi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bmi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "calculateBmi": "ts-node bmiCalculator.ts", 9 | "calculateExercises": "ts-node exerciseCalculator.ts", 10 | "start": "ts-node index.ts", 11 | "dev": "ts-node-dev index.ts", 12 | "lint": "eslint --ext .ts ." 13 | }, 14 | "author": "Ruel Neuman", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@types/express": "^4.17.11", 18 | "@types/node": "^15.6.0", 19 | "@typescript-eslint/eslint-plugin": "^4.25.0", 20 | "@typescript-eslint/parser": "^4.25.0", 21 | "eslint": "^7.27.0", 22 | "ts-node": "^10.0.0", 23 | "ts-node-dev": "^1.1.6", 24 | "typescript": "^4.2.4" 25 | }, 26 | "dependencies": { 27 | "express": "^4.17.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /part4/blog-list-backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const userSchema = new mongoose.Schema({ 5 | username: { 6 | type: String, 7 | minLength: 3, 8 | unique: true, 9 | required: true, 10 | }, 11 | name: String, 12 | passwordHash: { 13 | type: String, 14 | required: true, 15 | }, 16 | blogs: [ 17 | { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'Blog', 20 | } 21 | ], 22 | }); 23 | 24 | userSchema.plugin(uniqueValidator); 25 | 26 | userSchema.set('toJSON', { 27 | transform: (document, returnedObject) => { 28 | returnedObject.id = returnedObject._id.toString(); 29 | delete returnedObject._id; 30 | delete returnedObject.__v; 31 | delete returnedObject.passwordHash; 32 | } 33 | }); 34 | 35 | module.exports = mongoose.model('User', userSchema); -------------------------------------------------------------------------------- /part5/bloglist-frontend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /// 3 | // *********************************************************** 4 | // This example plugins/index.js can be used to load plugins 5 | // 6 | // You can change the location of this file or turn off loading 7 | // the plugins file with the 'pluginsFile' configuration option. 8 | // 9 | // You can read more here: 10 | // https://on.cypress.io/plugins-guide 11 | // *********************************************************** 12 | 13 | // This function is called when a project is opened or re-opened (e.g. due to 14 | // the project's config changing) 15 | 16 | /** 17 | * @type {Cypress.PluginConfig} 18 | */ 19 | // eslint-disable-next-line no-unused-vars 20 | module.exports = (on, config) => { 21 | // `on` is used to hook into various events Cypress emits 22 | // `config` is the resolved Cypress config 23 | }; 24 | -------------------------------------------------------------------------------- /part9/bmi/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 6 | ], 7 | "plugins": ["@typescript-eslint"], 8 | "env": { 9 | "node": true, 10 | "es6": true 11 | }, 12 | "rules": { 13 | "@typescript-eslint/semi": ["error"], 14 | "@typescript-eslint/explicit-function-return-type": "off", 15 | "@typescript-eslint/explicit-module-boundary-types": "off", 16 | "@typescript-eslint/restrict-template-expressions": "off", 17 | "@typescript-eslint/restrict-plus-operands": "off", 18 | "@typescript-eslint/no-unused-vars": [ 19 | "error", 20 | { "argsIgnorePattern": "^_" } 21 | ], 22 | "no-case-declarations": "off" 23 | }, 24 | "parser": "@typescript-eslint/parser", 25 | "parserOptions": { 26 | "project": "./tsconfig.json" 27 | } 28 | } -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const userSchema = new mongoose.Schema({ 5 | username: { 6 | type: String, 7 | minLength: 3, 8 | unique: true, 9 | required: true, 10 | }, 11 | name: String, 12 | passwordHash: { 13 | type: String, 14 | required: true, 15 | }, 16 | blogs: [ 17 | { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'Blog', 20 | } 21 | ], 22 | }); 23 | 24 | userSchema.plugin(uniqueValidator); 25 | 26 | userSchema.set('toJSON', { 27 | transform: (document, returnedObject) => { 28 | returnedObject.id = returnedObject._id.toString(); 29 | delete returnedObject._id; 30 | delete returnedObject.__v; 31 | delete returnedObject.passwordHash; 32 | } 33 | }); 34 | 35 | module.exports = mongoose.model('User', userSchema); -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /// 3 | // *********************************************************** 4 | // This example plugins/index.js can be used to load plugins 5 | // 6 | // You can change the location of this file or turn off loading 7 | // the plugins file with the 'pluginsFile' configuration option. 8 | // 9 | // You can read more here: 10 | // https://on.cypress.io/plugins-guide 11 | // *********************************************************** 12 | 13 | // This function is called when a project is opened or re-opened (e.g. due to 14 | // the project's config changing) 15 | 16 | /** 17 | * @type {Cypress.PluginConfig} 18 | */ 19 | // eslint-disable-next-line no-unused-vars 20 | module.exports = (on, config) => { 21 | // `on` is used to hook into various events Cypress emits 22 | // `config` is the resolved Cypress config 23 | }; 24 | -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface CoursePartBase { 2 | name: string; 3 | exerciseCount: number; 4 | type: string; 5 | } 6 | 7 | export interface CoursePartWithDescription extends CoursePartBase { 8 | description: string; 9 | } 10 | 11 | export interface CourseNormalPart extends CoursePartWithDescription { 12 | type: "normal"; 13 | } 14 | 15 | export interface CourseProjectPart extends CoursePartBase { 16 | type: "groupProject"; 17 | groupProjectCount: number; 18 | } 19 | 20 | export interface CourseSubmissionPart extends CoursePartWithDescription { 21 | type: "submission"; 22 | exerciseSubmissionLink: string; 23 | } 24 | 25 | export interface CourseSpecialPart extends CoursePartWithDescription { 26 | type: "special"; 27 | requirements: string[]; 28 | } 29 | 30 | export type CoursePart = 31 | | CourseNormalPart 32 | | CourseProjectPart 33 | | CourseSubmissionPart 34 | | CourseSpecialPart; -------------------------------------------------------------------------------- /part9/patientor-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patientor-backend", 3 | "version": "1.0.0", 4 | "description": "patientor backend for full stack open", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "ts-node-dev src/index.ts", 8 | "start": "node build/index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "lint": "eslint --ext .ts .", 11 | "tsc": "tsc" 12 | }, 13 | "author": "Ruel Neuman", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@types/cors": "^2.8.10", 17 | "@types/express": "^4.17.12", 18 | "@types/uuid": "^8.3.0", 19 | "@typescript-eslint/eslint-plugin": "^4.25.0", 20 | "@typescript-eslint/parser": "^4.25.0", 21 | "eslint": "^7.27.0", 22 | "ts-node-dev": "^1.1.6", 23 | "typescript": "^4.3.2" 24 | }, 25 | "dependencies": { 26 | "cors": "^2.8.5", 27 | "express": "^4.17.1", 28 | "uuid": "^8.3.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /part6/unicafe-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { createStore } from 'redux'; 4 | import reducer from './reducer'; 5 | 6 | const store = createStore(reducer); 7 | 8 | const App = () => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
good {store.getState().good}
16 |
neutral {store.getState().ok}
17 |
bad {store.getState().bad}
18 |
19 | ); 20 | }; 21 | 22 | const renderApp = () => { 23 | ReactDOM.render(, document.getElementById('root')); 24 | }; 25 | 26 | renderApp(); 27 | store.subscribe(renderApp); -------------------------------------------------------------------------------- /part9/patientor-frontend/src/PatientPage/EntryDetails.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Entry, EntryType } from "../types"; 4 | import { assertNever } from "../utils"; 5 | 6 | import HospitalEntryDetails from "./HospitalEntryDetails"; 7 | import OccupationalHealthcareEntryDetails from "./OccupationalHealthcareEntryDetails"; 8 | import HealthCheckEntryDetails from "./HealthCheckEntryDetails"; 9 | 10 | interface EntryDetailsProps { 11 | entry: Entry; 12 | } 13 | 14 | const EntryDetails = ({ entry }: EntryDetailsProps) => { 15 | switch (entry.type) { 16 | case EntryType.Hospital: 17 | return ; 18 | case EntryType.OccupationalHealthcare: 19 | return ; 20 | case EntryType.HealthCheck: 21 | return ; 22 | default: 23 | return assertNever(entry); 24 | } 25 | }; 26 | 27 | export default EntryDetails; 28 | -------------------------------------------------------------------------------- /part3/phonebook-backend/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.b5860f0e.chunk.css", 4 | "main.js": "/static/js/main.41b0bf33.chunk.js", 5 | "main.js.map": "/static/js/main.41b0bf33.chunk.js.map", 6 | "runtime-main.js": "/static/js/runtime-main.a212f097.js", 7 | "runtime-main.js.map": "/static/js/runtime-main.a212f097.js.map", 8 | "static/js/2.732f3632.chunk.js": "/static/js/2.732f3632.chunk.js", 9 | "static/js/2.732f3632.chunk.js.map": "/static/js/2.732f3632.chunk.js.map", 10 | "index.html": "/index.html", 11 | "static/css/main.b5860f0e.chunk.css.map": "/static/css/main.b5860f0e.chunk.css.map", 12 | "static/js/2.732f3632.chunk.js.LICENSE.txt": "/static/js/2.732f3632.chunk.js.LICENSE.txt" 13 | }, 14 | "entrypoints": [ 15 | "static/js/runtime-main.a212f097.js", 16 | "static/js/2.732f3632.chunk.js", 17 | "static/css/main.b5860f0e.chunk.css", 18 | "static/js/main.41b0bf33.chunk.js" 19 | ] 20 | } -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/reducers/notificationReducer.js: -------------------------------------------------------------------------------- 1 | const reducer = (state = '', action) => { 2 | switch (action.type) { 3 | case 'SHOW_NOTIFICATION': 4 | return action.payload; 5 | case 'HIDE_NOTIFICATION': 6 | return ''; 7 | default: 8 | return state; 9 | } 10 | }; 11 | 12 | let timeoutID = null; 13 | 14 | export const showNotificationWithTimeout = (message, seconds) => { 15 | return async (dispatch) => { 16 | dispatch(showNotification(message)); 17 | 18 | if (timeoutID) { 19 | clearTimeout(timeoutID); 20 | } 21 | 22 | timeoutID = setTimeout(() => { 23 | dispatch(hideNotification()); 24 | }, seconds * 1000); 25 | }; 26 | }; 27 | 28 | export const showNotification = (message) => { 29 | return { 30 | type: 'SHOW_NOTIFICATION', 31 | payload: message 32 | }; 33 | }; 34 | 35 | export const hideNotification = () => { 36 | return { type: 'HIDE_NOTIFICATION' }; 37 | }; 38 | 39 | export default reducer; -------------------------------------------------------------------------------- /part1/unicafe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unicafe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.2", 12 | "web-vitals": "^1.1.0" 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/anecdotes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.2", 12 | "web-vitals": "^1.1.0" 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.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.2", 12 | "web-vitals": "^1.1.0" 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/courseinfo2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "courseinfo2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.2", 12 | "web-vitals": "^1.1.0" 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/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": ["@typescript-eslint"], 8 | "env": { 9 | "browser": true, 10 | "es6": true, 11 | "node": true 12 | }, 13 | "rules": { 14 | "@typescript-eslint/semi": ["error"], 15 | "@typescript-eslint/explicit-function-return-type": "off", 16 | "@typescript-eslint/explicit-module-boundary-types": "off", 17 | "@typescript-eslint/restrict-template-expressions": "off", 18 | "@typescript-eslint/restrict-plus-operands": "off", 19 | "@typescript-eslint/no-unsafe-member-access": "off", 20 | "@typescript-eslint/no-unused-vars": [ 21 | "error", 22 | { "argsIgnorePattern": "^_" } 23 | ], 24 | "no-case-declarations": "off" 25 | }, 26 | "parser": "@typescript-eslint/parser", 27 | "parserOptions": { 28 | "project": "./tsconfig.json" 29 | } 30 | } -------------------------------------------------------------------------------- /part2/countries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "countries", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.3", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-scripts": "4.0.3", 13 | "web-vitals": "^1.1.0" 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 | -------------------------------------------------------------------------------- /part4/blog-list-backend/controllers/users.js: -------------------------------------------------------------------------------- 1 | const usersRouter = require('express').Router(); 2 | const bcrypt = require('bcrypt'); 3 | const User = require('../models/user'); 4 | 5 | usersRouter.get('/', async (request, response) => { 6 | const users = await User 7 | .find({}) 8 | .populate('blogs', { title: 1, author: 1, url: 1}); 9 | 10 | response.json(users); 11 | }); 12 | 13 | usersRouter.post('/', async (request, response) => { 14 | const body = request.body; 15 | 16 | if (!body.password || body.password.length < 3) { 17 | return response 18 | .status(400) 19 | .json({ error: 'password must be at least 3 characters' }); 20 | } 21 | 22 | const saltRounds = 10; 23 | const passwordHash = await bcrypt.hash(body.password, saltRounds); 24 | 25 | const user = new User({ 26 | username: body.username, 27 | name: body.name, 28 | passwordHash, 29 | }); 30 | 31 | const savedUser = await user.save(); 32 | 33 | response.json(savedUser); 34 | }); 35 | 36 | module.exports = usersRouter; -------------------------------------------------------------------------------- /part2/countries/src/components/Country.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Weather from './Weather'; 3 | 4 | const Country = ({ country }) => { 5 | 6 | function numberWithCommas(number) { 7 | return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 8 | } 9 | 10 | return ( 11 |
12 |

{country.name}

13 |
Capital:{country.capital}
14 |
Population:{numberWithCommas(country.population)}
15 |
Languages:
16 |
    17 | {country.languages.map(language =>
  • {language.name}
  • )} 18 |
19 | {'Flag 20 | 24 |
25 | ); 26 | } 27 | 28 | 29 | 30 | export default Country; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/controllers/users.js: -------------------------------------------------------------------------------- 1 | const usersRouter = require('express').Router(); 2 | const bcrypt = require('bcrypt'); 3 | const User = require('../models/user'); 4 | 5 | usersRouter.get('/', async (request, response) => { 6 | const users = await User 7 | .find({}) 8 | .populate('blogs', { title: 1, author: 1, url: 1}); 9 | 10 | response.json(users); 11 | }); 12 | 13 | usersRouter.post('/', async (request, response) => { 14 | const body = request.body; 15 | 16 | if (!body.password || body.password.length < 3) { 17 | return response 18 | .status(400) 19 | .json({ error: 'password must be at least 3 characters' }); 20 | } 21 | 22 | const saltRounds = 10; 23 | const passwordHash = await bcrypt.hash(body.password, saltRounds); 24 | 25 | const user = new User({ 26 | username: body.username, 27 | name: body.name, 28 | passwordHash, 29 | }); 30 | 31 | const savedUser = await user.save(); 32 | 33 | response.json(savedUser); 34 | }); 35 | 36 | module.exports = usersRouter; -------------------------------------------------------------------------------- /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.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-scripts": "4.0.2", 13 | "web-vitals": "^1.1.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject", 20 | "lint": "eslint ." 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /part4/blog-list-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 body = request.body; 8 | 9 | const user = await User.findOne({ username: body.username }); 10 | 11 | const passwordCorrect = (user === null || !body?.password) 12 | ? false 13 | : await bcrypt.compare(body.password, user.passwordHash); 14 | 15 | if (!(user && passwordCorrect)) { 16 | return response.status(401).json({ 17 | error: 'invalid username or password' 18 | }); 19 | } 20 | 21 | const userForToken = { 22 | username: user.username, 23 | id: user._id, 24 | }; 25 | 26 | const token = jwt.sign( 27 | userForToken, 28 | process.env.SECRET, 29 | { expiresIn: '1h' } 30 | ); 31 | 32 | response 33 | .status(200) 34 | .send({ token, username: user.username, name: user.name }); 35 | 36 | }); 37 | 38 | module.exports = loginRouter; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/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 body = request.body; 8 | 9 | const user = await User.findOne({ username: body.username }); 10 | 11 | const passwordCorrect = (user === null || !body?.password) 12 | ? false 13 | : await bcrypt.compare(body.password, user.passwordHash); 14 | 15 | if (!(user && passwordCorrect)) { 16 | return response.status(401).json({ 17 | error: 'invalid username or password' 18 | }); 19 | } 20 | 21 | const userForToken = { 22 | username: user.username, 23 | id: user._id, 24 | }; 25 | 26 | const token = jwt.sign( 27 | userForToken, 28 | process.env.SECRET, 29 | { expiresIn: '1h' } 30 | ); 31 | 32 | response 33 | .status(200) 34 | .send({ token, username: user.username, name: user.name }); 35 | 36 | }); 37 | 38 | module.exports = loginRouter; -------------------------------------------------------------------------------- /part9/patientor-frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:react/recommended", 6 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 7 | ], 8 | "plugins": ["@typescript-eslint", "react"], 9 | "env": { 10 | "browser": true, 11 | "es6": true 12 | }, 13 | "rules": { 14 | "@typescript-eslint/semi": ["error"], 15 | "@typescript-eslint/explicit-function-return-type": 0, 16 | "@typescript-eslint/explicit-module-boundary-types": 0, 17 | "@typescript-eslint/no-unsafe-member-access": 0, 18 | "@typescript-eslint/no-unused-vars": [ 19 | "error", { "argsIgnorePattern": "^_" } 20 | ], 21 | "@typescript-eslint/no-explicit-any": 1, 22 | "no-case-declarations": 0, 23 | "react/prop-types": 0 24 | }, 25 | "settings": { 26 | "react": { 27 | "pragma": "React", 28 | "version": "detect" 29 | } 30 | }, 31 | "parser": "@typescript-eslint/parser", 32 | "parserOptions": { 33 | "project": "./tsconfig.json" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Recommendations.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useQuery } from '@apollo/client'; 3 | import { ALL_BOOKS, ME } from '../queries'; 4 | import BookTable from './BookTable'; 5 | 6 | const Recommendations = () => { 7 | const { data: meData, loading: meLoading, error: meError } = useQuery(ME, { 8 | fetchPolicy: 'cache-and-network', 9 | }); 10 | 11 | const genre = meData?.me?.favoriteGenre; 12 | 13 | const { data: booksData, loading: booksLoading, error: booksError } = useQuery(ALL_BOOKS, { 14 | variables: { genre }, 15 | skip: !genre, 16 | fetchPolicy: 'cache-and-network', 17 | }); 18 | 19 | if (booksLoading || meLoading) { 20 | return
loading...
; 21 | } 22 | 23 | if (booksError || meError) { 24 | return
Error: Could not load books
; 25 | } 26 | 27 | return ( 28 |
29 |

Recommendations

30 |
Your favorite genre: {genre}
31 | 32 |
33 | ); 34 | }; 35 | 36 | export default Recommendations; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/state/state.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer } from "react"; 2 | import { Patient, Diagnosis } from "../types"; 3 | 4 | import { Action } from "./reducer"; 5 | 6 | export type State = { 7 | patients: { [id: string]: Patient }; 8 | diagnoses: { [code: string]: Diagnosis }; 9 | }; 10 | 11 | const initialState: State = { 12 | patients: {}, 13 | diagnoses: {}, 14 | }; 15 | 16 | export const StateContext = createContext<[State, React.Dispatch]>([ 17 | initialState, 18 | () => initialState 19 | ]); 20 | 21 | type StateProviderProps = { 22 | reducer: React.Reducer; 23 | children: React.ReactElement; 24 | }; 25 | 26 | export const StateProvider: React.FC = ({ 27 | reducer, 28 | children 29 | }: StateProviderProps) => { 30 | const [state, dispatch] = useReducer(reducer, initialState); 31 | return ( 32 | 33 | {children} 34 | 35 | ); 36 | }; 37 | export const useStateValue = () => useContext(StateContext); 38 | -------------------------------------------------------------------------------- /part7/routed-anecdotes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routed-anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-router": "^5.2.0", 12 | "react-router-dom": "^5.2.0", 13 | "react-scripts": "4.0.2", 14 | "web-vitals": "^1.1.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "lint": "eslint ." 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 | -------------------------------------------------------------------------------- /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 getAll = async () => { 11 | const response = await axios.get(baseUrl); 12 | return response.data; 13 | }; 14 | 15 | const create = async (newObject) => { 16 | const config = { 17 | headers: { Authorization: token }, 18 | }; 19 | 20 | const response = await axios.post(baseUrl, newObject, config); 21 | return response.data; 22 | }; 23 | 24 | const update = async (id, newObject) => { 25 | const config = { 26 | headers: { Authorization: token }, 27 | }; 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 = { 35 | headers: { Authorization: token }, 36 | }; 37 | 38 | const response = await axios.delete(`${baseUrl}/${id}`, config); 39 | return response.data; 40 | }; 41 | 42 | export default { getAll, create, update, remove, setToken }; -------------------------------------------------------------------------------- /part4/blog-list-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-list-backend", 3 | "version": "1.0.0", 4 | "description": "Blog list backend for Full Stack Open course", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon index.js", 9 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand", 10 | "start:test": "cross-env NODE_ENV=test node index.js", 11 | "lint": "eslint . && echo 'Lint complete'" 12 | }, 13 | "author": "Ruel Neuman", 14 | "license": "MIT", 15 | "dependencies": { 16 | "bcrypt": "^5.0.1", 17 | "cors": "^2.8.5", 18 | "dotenv": "^8.2.0", 19 | "express": "^4.17.1", 20 | "express-async-errors": "^3.1.1", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^5.12.0", 23 | "mongoose-unique-validator": "^2.0.3" 24 | }, 25 | "devDependencies": { 26 | "cross-env": "^7.0.3", 27 | "eslint": "^7.22.0", 28 | "jest": "^26.6.3", 29 | "nodemon": "^2.0.7", 30 | "supertest": "^6.1.3" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part8/library-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.3.19", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.4.1", 9 | "@testing-library/user-event": "^7.2.1", 10 | "graphql": "^15.5.0", 11 | "react": "^16.12.0", 12 | "react-dom": "^16.12.0", 13 | "react-scripts": "^4.0.3", 14 | "subscriptions-transport-ws": "^0.9.18" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "lint": "eslint ." 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 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 | "devDependencies": { 39 | "eslint-plugin-jest": "^24.3.6" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/Togglable.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Togglable = ({ buttonLabel, children }) => { 5 | const [visible, setVisible] = useState(false); 6 | 7 | const hideWhenVisible = { display: visible ? 'none' : '' }; 8 | const showWhenVisible = { display: visible ? '' : 'none' }; 9 | 10 | const toggleVisibility = () => { 11 | setVisible(!visible); 12 | }; 13 | 14 | const childrenWithVisibilityToggle = React.Children.map(children, child => { 15 | return React.cloneElement(child, { toggleVisibility }); 16 | }); 17 | 18 | return ( 19 |
20 |
21 | 22 |
23 |
24 | {childrenWithVisibilityToggle} 25 | 26 |
27 |
28 | ); 29 | }; 30 | 31 | Togglable.propTypes = { 32 | buttonLabel: PropTypes.string.isRequired, 33 | children: PropTypes.element.isRequired, 34 | }; 35 | 36 | 37 | export default Togglable; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-list-backend-extended", 3 | "version": "1.0.0", 4 | "description": "Blog list backend for Full Stack Open course", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node index.js", 8 | "dev": "cross-env NODE_ENV=development nodemon index.js", 9 | "test": "cross-env NODE_ENV=test jest --verbose --runInBand", 10 | "start:test": "cross-env NODE_ENV=test node index.js", 11 | "lint": "eslint . && echo 'Lint complete'" 12 | }, 13 | "author": "Ruel Neuman", 14 | "license": "MIT", 15 | "dependencies": { 16 | "bcrypt": "^5.0.1", 17 | "cors": "^2.8.5", 18 | "dotenv": "^8.2.0", 19 | "express": "^4.17.1", 20 | "express-async-errors": "^3.1.1", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^5.12.0", 23 | "mongoose-unique-validator": "^2.0.3" 24 | }, 25 | "devDependencies": { 26 | "cross-env": "^7.0.3", 27 | "eslint": "^7.22.0", 28 | "jest": "^26.6.3", 29 | "nodemon": "^2.0.7", 30 | "supertest": "^6.1.3" 31 | }, 32 | "jest": { 33 | "testEnvironment": "node" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part3/phonebook-backend/models/person.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const uniqueValidator = require('mongoose-unique-validator'); 3 | 4 | const url = process.env.MONGODB_URI; 5 | 6 | mongoose 7 | .connect(url, { 8 | useNewUrlParser: true, 9 | useUnifiedTopology: true, 10 | useFindAndModify: false, 11 | useCreateIndex: true 12 | }) 13 | .then(() => { 14 | console.log('connected to MongoDB'); 15 | }) 16 | .catch((error) => { 17 | console.log('error connecting to MongoDB:', error.message); 18 | }); 19 | 20 | const personSchema = new mongoose.Schema({ 21 | name: { 22 | type: String, 23 | required: true, 24 | unique: true, 25 | minlength: 3, 26 | }, 27 | number: { 28 | type: String, 29 | required: true, 30 | minlength: 8, 31 | }, 32 | }); 33 | 34 | personSchema.plugin(uniqueValidator); 35 | 36 | personSchema.set('toJSON', { 37 | transform: (document, returnedObject) => { 38 | returnedObject.id = returnedObject._id.toString(); 39 | delete returnedObject._id; 40 | delete returnedObject.__v; 41 | } 42 | }); 43 | 44 | module.exports = mongoose.model('Person', personSchema); -------------------------------------------------------------------------------- /part6/unicafe-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unicafe-redux", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.6.3", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.2", 12 | "redux": "^4.0.5", 13 | "web-vitals": "^1.1.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject", 20 | "lint": "eslint ." 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 | "deep-freeze": "0.0.1", 42 | "eslint-plugin-jest": "^24.3.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/PatientPage/HealthCheckEntryDetails.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HealthCheckEntry } from "../types"; 3 | import { Card, Icon } from "semantic-ui-react"; 4 | import DiagnosisList from "./DiagnosisList"; 5 | import HealthRatingBar from "../components/HealthRatingBar"; 6 | 7 | interface HealthCheckEntryDetailsProps { 8 | entry: HealthCheckEntry; 9 | } 10 | 11 | const HealthCheckEntryDetails = ({ entry }: HealthCheckEntryDetailsProps) => { 12 | return ( 13 | 14 | 15 | 16 | {entry.date} 17 | 18 | 19 | {entry.specialist} 20 | {entry.description} 21 | 22 | 23 | 24 | {entry.diagnosisCodes 25 | ? 26 | :

No diagnosis codes

} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default HealthCheckEntryDetails; -------------------------------------------------------------------------------- /part6/redux-anecdotes/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "anecdotes": [ 3 | { 4 | "content": "If it hurts, do it more often", 5 | "id": 1, 6 | "votes": 0 7 | }, 8 | { 9 | "content": "Adding manpower to a late software project makes it later!", 10 | "id": 2, 11 | "votes": 1 12 | }, 13 | { 14 | "content": "The first 90 percent of the code accounts for the first 90 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.", 15 | "id": 3, 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": 4, 21 | "votes": 11 22 | }, 23 | { 24 | "content": "Premature optimization is the root of all evil.", 25 | "id": 5, 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": 6, 31 | "votes": 4 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /part2/phonebook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonebook", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-scripts": "4.0.2", 13 | "web-vitals": "^1.1.0" 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.16.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.7.1", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-scripts": "4.0.2", 13 | "web-vitals": "^1.1.0" 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 | "lint": "eslint ." 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.16.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part6/unicafe-redux/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es6': true, 6 | 'jest/globals': 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', 'jest' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 } 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'windows' 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always' 39 | ], 40 | 'eqeqeq': 'error', 41 | 'no-trailing-spaces': 'error', 42 | 'object-curly-spacing': [ 43 | 'error', 'always' 44 | ], 45 | 'arrow-spacing': [ 46 | 'error', { 'before': true, 'after': true } 47 | ], 48 | 'no-console': 0, 49 | 'react/prop-types': 0 50 | }, 51 | 'settings': { 52 | 'react': { 53 | 'version': 'detect' 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /part7/country-hook/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es6': true, 6 | 'jest/globals': 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', 'jest' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 } 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'windows' 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always' 39 | ], 40 | 'eqeqeq': 'error', 41 | 'no-trailing-spaces': 'error', 42 | 'object-curly-spacing': [ 43 | 'error', 'always' 44 | ], 45 | 'arrow-spacing': [ 46 | 'error', { 'before': true, 'after': true } 47 | ], 48 | 'no-console': 0, 49 | 'react/prop-types': 0 50 | }, 51 | 'settings': { 52 | 'react': { 53 | 'version': 'detect' 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /part7/ultimate-hooks/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es6': true, 6 | 'jest/globals': 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', 'jest' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 } 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'windows' 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always' 39 | ], 40 | 'eqeqeq': 'error', 41 | 'no-trailing-spaces': 'error', 42 | 'object-curly-spacing': [ 43 | 'error', 'always' 44 | ], 45 | 'arrow-spacing': [ 46 | 'error', { 'before': true, 'after': true } 47 | ], 48 | 'no-console': 0, 49 | 'react/prop-types': 0 50 | }, 51 | 'settings': { 52 | 'react': { 53 | 'version': 'detect' 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /part6/redux-anecdotes/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es6': true, 6 | 'jest/globals': 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', 'jest' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 } 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'windows' 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always' 39 | ], 40 | 'eqeqeq': 'error', 41 | 'no-trailing-spaces': 'error', 42 | 'object-curly-spacing': [ 43 | 'error', 'always' 44 | ], 45 | 'arrow-spacing': [ 46 | 'error', { 'before': true, 'after': true } 47 | ], 48 | 'no-console': 0, 49 | 'react/prop-types': 0 50 | }, 51 | 'settings': { 52 | 'react': { 53 | 'version': 'detect' 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /part7/routed-anecdotes/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | 'env': { 4 | 'browser': true, 5 | 'es6': true, 6 | 'jest/globals': 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', 'jest' 21 | ], 22 | 'rules': { 23 | 'indent': [ 24 | 'error', 25 | 2, 26 | { 'SwitchCase': 1 } 27 | ], 28 | 'linebreak-style': [ 29 | 'error', 30 | 'windows' 31 | ], 32 | 'quotes': [ 33 | 'error', 34 | 'single' 35 | ], 36 | 'semi': [ 37 | 'error', 38 | 'always' 39 | ], 40 | 'eqeqeq': 'error', 41 | 'no-trailing-spaces': 'error', 42 | 'object-curly-spacing': [ 43 | 'error', 'always' 44 | ], 45 | 'arrow-spacing': [ 46 | 'error', { 'before': true, 'after': true } 47 | ], 48 | 'no-console': 0, 49 | 'react/prop-types': 0 50 | }, 51 | 'settings': { 52 | 'react': { 53 | 'version': 'detect' 54 | } 55 | } 56 | }; -------------------------------------------------------------------------------- /part3/phonebook-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonebook-backend", 3 | "version": "1.0.0", 4 | "description": "Backend for the phonebook app", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "start": "node index.js", 9 | "dev": "nodemon index.js", 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build:ui": "rm -rf build && cd ../../part2/phonebook && npm run build --prod && cp -r build ../../part3/phonebook-backend/", 12 | "deploy": "cd ../.. && git subtree push --prefix part3/phonebook-backend heroku master && cd part3/phonebook-backend/", 13 | "deploy:full": "npm run build:ui && cd ../.. && git add . && git commit -m uibuild && cd part3/phonebook-backend/ && npm run deploy", 14 | "logs:prod": "heroku logs --tail", 15 | "lint": "eslint ." 16 | }, 17 | "author": "Ruel Neuman", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "eslint": "^7.22.0", 21 | "nodemon": "^2.0.7" 22 | }, 23 | "dependencies": { 24 | "cors": "^2.8.5", 25 | "dotenv": "^8.2.0", 26 | "express": "^4.17.1", 27 | "mongoose": "^5.11.19", 28 | "mongoose-unique-validator": "^2.0.3", 29 | "morgan": "^1.10.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part9/patientor-frontend/src/PatientPage/HospitalEntryDetails.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HospitalEntry } from "../types"; 3 | import { Card, Icon } from "semantic-ui-react"; 4 | import DiagnosisList from "./DiagnosisList"; 5 | 6 | interface HospitalEntryDetailsProps { 7 | entry: HospitalEntry; 8 | } 9 | 10 | const HospitalEntryDetails = ({ entry }: HospitalEntryDetailsProps) => { 11 | return ( 12 | 13 | 14 | 15 | {entry.date} 16 | 17 | 18 | {entry.specialist} 19 | 20 |

{entry.description}

21 |

Discharge Date: {entry.discharge.date}

22 |

Discharge Criteria: {entry.discharge.criteria}

23 |
24 |
25 | 26 | {entry.diagnosisCodes 27 | ? 28 | :

No diagnosis codes

} 29 |
30 |
31 | ); 32 | }; 33 | 34 | export default HospitalEntryDetails; -------------------------------------------------------------------------------- /part8/library-frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | 'env': { 5 | 'browser': true, 6 | 'es6': true, 7 | 'jest/globals': true 8 | }, 9 | 'extends': [ 10 | 'eslint:recommended', 11 | 'plugin:react/recommended', 12 | 'plugin:jest/recommended' 13 | ], 14 | 'parserOptions': { 15 | 'ecmaFeatures': { 16 | 'jsx': true 17 | }, 18 | 'ecmaVersion': 2020, 19 | 'sourceType': 'module' 20 | }, 21 | 'plugins': [ 22 | 'react', 'jest' 23 | ], 24 | 'rules': { 25 | 'indent': [ 26 | 'error', 27 | 2, 28 | { 'SwitchCase': 1 } 29 | ], 30 | 'linebreak-style': [ 31 | 'error', 32 | 'windows' 33 | ], 34 | 'quotes': [ 35 | 'error', 36 | 'single' 37 | ], 38 | 'semi': [ 39 | 'error', 40 | 'always' 41 | ], 42 | 'eqeqeq': 'error', 43 | 'no-trailing-spaces': 'error', 44 | 'object-curly-spacing': [ 45 | 'error', 'always' 46 | ], 47 | 'arrow-spacing': [ 48 | 'error', { 'before': true, 'after': true } 49 | ], 50 | 'no-console': 0, 51 | 'react/prop-types': 0 52 | }, 53 | 'settings': { 54 | 'react': { 55 | 'version': 'detect' 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "courseinfo-typescript", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.12.0", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "@types/jest": "^26.0.23", 10 | "@types/node": "^12.20.13", 11 | "@types/react": "^17.0.8", 12 | "@types/react-dom": "^17.0.5", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "typescript": "^4.3.2", 17 | "web-vitals": "^1.1.2" 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 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Authors.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useQuery } from '@apollo/client'; 3 | import { ALL_AUTHORS } from '../queries'; 4 | import BirthYearForm from './BirthYearForm'; 5 | 6 | const Authors = ({ token }) => { 7 | const { data, loading, error } = useQuery(ALL_AUTHORS); 8 | 9 | if (loading) { 10 | return
loading...
; 11 | } 12 | 13 | if (error) { 14 | return
Error: Could not load authors
; 15 | } 16 | 17 | const authors = data.allAuthors; 18 | 19 | return ( 20 |
21 |

Authors

22 | 23 | 24 | 25 | 26 | 29 | 32 | 33 | {authors.map((author) => 34 | 35 | 36 | 37 | 38 | 39 | )} 40 | 41 |
27 | Born 28 | 30 | Books 31 |
{author.name}{author.born}{author.bookCount}
42 | 43 | {token && } 44 |
45 | ); 46 | }; 47 | 48 | export default Authors; 49 | -------------------------------------------------------------------------------- /part5/bloglist-frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | 'env': { 5 | 'browser': true, 6 | 'es6': true, 7 | 'jest/globals': true, 8 | 'cypress/globals': true 9 | }, 10 | 'extends': [ 11 | 'eslint:recommended', 12 | 'plugin:react/recommended', 13 | 'plugin:cypress/recommended' 14 | ], 15 | 'parserOptions': { 16 | 'ecmaFeatures': { 17 | 'jsx': true 18 | }, 19 | 'ecmaVersion': 2018, 20 | 'sourceType': 'module' 21 | }, 22 | 'plugins': [ 23 | 'react', 'jest', 'cypress' 24 | ], 25 | 'rules': { 26 | 'indent': [ 27 | 'error', 28 | 2 29 | ], 30 | 'linebreak-style': [ 31 | 'error', 32 | 'windows' 33 | ], 34 | 'quotes': [ 35 | 'error', 36 | 'single' 37 | ], 38 | 'semi': [ 39 | 'error', 40 | 'always' 41 | ], 42 | 'eqeqeq': 'error', 43 | 'no-trailing-spaces': 'error', 44 | 'object-curly-spacing': [ 45 | 'error', 'always' 46 | ], 47 | 'arrow-spacing': [ 48 | 'error', { 'before': true, 'after': true } 49 | ], 50 | 'no-console': 0, 51 | 'react/prop-types': 0 52 | }, 53 | 'settings': { 54 | 'react': { 55 | 'version': 'detect' 56 | } 57 | } 58 | }; -------------------------------------------------------------------------------- /part3/phonebook-backend/build/static/js/2.732f3632.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.20.1 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v17.0.1 17 | * react-dom.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.1 26 | * react-jsx-runtime.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.1 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/reducers/userReducer.js: -------------------------------------------------------------------------------- 1 | import userService from '../services/users'; 2 | import handleError from '../utils/handleError'; 3 | 4 | const initialState = { 5 | users: [], 6 | status: 'idle' 7 | }; 8 | 9 | const reducer = (state = initialState, action) => { 10 | switch (action.type) { 11 | case 'INIT_USERS_PENDING': { 12 | return { 13 | ...state, 14 | status: 'loading', 15 | }; 16 | } 17 | case 'INIT_USERS_SUCCESS': { 18 | return { 19 | users: action.payload, 20 | status: 'succeeded', 21 | }; 22 | } 23 | case 'INIT_USERS_ERROR': { 24 | return { 25 | ...state, 26 | status: 'failed', 27 | }; 28 | } 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | export const initializeUsers = () => { 35 | return async (dispatch) => { 36 | dispatch({ type: 'INIT_USERS_PENDING' }); 37 | 38 | try { 39 | const users = await userService.getAll(); 40 | 41 | dispatch({ 42 | type: 'INIT_USERS_SUCCESS', 43 | payload: users, 44 | }); 45 | } catch (error) { 46 | handleError(error); 47 | 48 | dispatch({ type: 'INIT_USERS_ERROR' }); 49 | } 50 | }; 51 | }; 52 | 53 | export default reducer; -------------------------------------------------------------------------------- /part8/library-backend/typeDefs.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('apollo-server'); 2 | 3 | const typeDefs = gql` 4 | type Author { 5 | name: String! 6 | born: Int 7 | bookCount: Int! 8 | id: ID! 9 | } 10 | type Book { 11 | title: String! 12 | author: Author! 13 | published: Int! 14 | genres: [String!]! 15 | id: ID! 16 | } 17 | type User { 18 | username: String! 19 | favoriteGenre: String! 20 | id: ID! 21 | } 22 | type Token { 23 | value: String! 24 | } 25 | type Query { 26 | bookCount: Int! 27 | authorCount: Int! 28 | allBooks( 29 | author: String 30 | genre: String 31 | ): [Book!]! 32 | allAuthors: [Author!]! 33 | me: User 34 | } 35 | type Mutation { 36 | addBook( 37 | title: String! 38 | author: String! 39 | published: Int! 40 | genres: [String!]! 41 | ): Book! 42 | editAuthor( 43 | name: String! 44 | setBornTo: Int! 45 | ): Author 46 | createUser( 47 | username: String! 48 | favoriteGenre: String! 49 | ): User 50 | login( 51 | username: String! 52 | password: String! 53 | ): Token 54 | } 55 | type Subscription { 56 | bookAdded: Book! 57 | } 58 | `; 59 | 60 | module.exports = { typeDefs }; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/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 getAll = async () => { 11 | const response = await axios.get(baseUrl); 12 | return response.data; 13 | }; 14 | 15 | const create = async (newObject) => { 16 | const config = { 17 | headers: { Authorization: token }, 18 | }; 19 | 20 | const response = await axios.post(baseUrl, newObject, config); 21 | return response.data; 22 | }; 23 | 24 | const update = async (id, newObject) => { 25 | const config = { 26 | headers: { Authorization: token }, 27 | }; 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 = { 35 | headers: { Authorization: token }, 36 | }; 37 | 38 | const response = await axios.delete(`${baseUrl}/${id}`, config); 39 | return response.data; 40 | }; 41 | 42 | const comment = async (id, comment) => { 43 | const response = await axios.post(`${baseUrl}/${id}/comments`, { comment }); 44 | return response.data; 45 | }; 46 | 47 | export default { getAll, create, update, remove, comment, setToken }; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | 'env': { 5 | 'browser': true, 6 | 'es6': true, 7 | 'jest/globals': true, 8 | 'cypress/globals': true 9 | }, 10 | 'extends': [ 11 | 'eslint:recommended', 12 | 'plugin:react/recommended', 13 | 'plugin:cypress/recommended' 14 | ], 15 | 'parserOptions': { 16 | 'ecmaFeatures': { 17 | 'jsx': true 18 | }, 19 | 'ecmaVersion': 2018, 20 | 'sourceType': 'module' 21 | }, 22 | 'plugins': [ 23 | 'react', 'jest', 'cypress' 24 | ], 25 | 'rules': { 26 | 'indent': [ 27 | 'error', 28 | 2, 29 | { 'SwitchCase': 1 } 30 | ], 31 | 'linebreak-style': [ 32 | 'error', 33 | 'windows' 34 | ], 35 | 'quotes': [ 36 | 'error', 37 | 'single' 38 | ], 39 | 'semi': [ 40 | 'error', 41 | 'always' 42 | ], 43 | 'eqeqeq': 'error', 44 | 'no-trailing-spaces': 'error', 45 | 'object-curly-spacing': [ 46 | 'error', 'always' 47 | ], 48 | 'arrow-spacing': [ 49 | 'error', { 'before': true, 'after': true } 50 | ], 51 | 'no-console': 0, 52 | 'react/prop-types': 0 53 | }, 54 | 'settings': { 55 | 'react': { 56 | 'version': 'detect' 57 | } 58 | } 59 | }; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/reducers/notificationReducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | message: null, 3 | type: null 4 | }; 5 | 6 | const reducer = (state = initialState, action) => { 7 | switch (action.type) { 8 | case 'SHOW_NOTIFICATION': { 9 | const { message, type } = action.payload; 10 | return { 11 | message, 12 | type 13 | }; 14 | } 15 | case 'HIDE_NOTIFICATION': 16 | return { 17 | message: null, 18 | type: null 19 | }; 20 | default: 21 | return state; 22 | } 23 | }; 24 | 25 | let timeoutID = null; 26 | 27 | export const showNotificationWithTimeout = (message, type, seconds = 3) => { 28 | return async (dispatch) => { 29 | dispatch(showNotification(message, type)); 30 | 31 | if (timeoutID) { 32 | clearTimeout(timeoutID); 33 | } 34 | 35 | timeoutID = setTimeout(() => { 36 | dispatch(hideNotification()); 37 | }, seconds * 1000); 38 | }; 39 | }; 40 | 41 | export const showNotification = (message, type) => { 42 | return { 43 | type: 'SHOW_NOTIFICATION', 44 | payload: { 45 | message, 46 | type 47 | } 48 | }; 49 | }; 50 | 51 | export const hideNotification = () => { 52 | return { type: 'HIDE_NOTIFICATION' }; 53 | }; 54 | 55 | export default reducer; -------------------------------------------------------------------------------- /part9/patientor-backend/src/services/patientService.ts: -------------------------------------------------------------------------------- 1 | import patients from '../../data/patients'; 2 | import { 3 | PublicPatient, 4 | NewPatient, 5 | Patient, 6 | NewEntry, 7 | Entry, 8 | } from "../types"; 9 | import { v1 as uuid } from 'uuid'; 10 | 11 | const getPatients = (): Array => { 12 | return patients.map(({ id, name, dateOfBirth, gender, occupation }) => { 13 | return { 14 | id, 15 | name, 16 | dateOfBirth, 17 | gender, 18 | occupation, 19 | }; 20 | }); 21 | }; 22 | 23 | const addPatient = (newPatient: NewPatient): Patient => { 24 | const patient: Patient = { 25 | id: uuid(), 26 | ...newPatient 27 | }; 28 | 29 | patients.push(patient); 30 | 31 | return patient; 32 | }; 33 | 34 | const findPatientById = (id: string): Patient | undefined => { 35 | return patients.find((patient) => patient.id === id); 36 | }; 37 | 38 | const addEntry = (patientId: string, newEntry: NewEntry): Patient | undefined => { 39 | const entry: Entry = { 40 | id: uuid(), 41 | ...newEntry 42 | }; 43 | 44 | const patient = findPatientById(patientId); 45 | 46 | patient?.entries.push(entry); 47 | 48 | return patient; 49 | }; 50 | 51 | export default { 52 | getPatients, 53 | addPatient, 54 | findPatientById, 55 | addEntry, 56 | }; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/Togglable.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Togglable = ({ buttonLabel, children }) => { 6 | const [visible, setVisible] = useState(false); 7 | 8 | const hideWhenVisible = { display: visible ? 'none' : '' }; 9 | const showWhenVisible = { display: visible ? '' : 'none' }; 10 | 11 | const toggleVisibility = () => { 12 | setVisible(!visible); 13 | }; 14 | 15 | const childrenWithVisibilityToggle = React.Children.map(children, child => { 16 | return React.cloneElement(child, { toggleVisibility }); 17 | }); 18 | 19 | return ( 20 |
21 |
22 | 25 |
26 |
27 | {childrenWithVisibilityToggle} 28 | 31 |
32 |
33 | ); 34 | }; 35 | 36 | Togglable.propTypes = { 37 | buttonLabel: PropTypes.string.isRequired, 38 | children: PropTypes.element.isRequired, 39 | }; 40 | 41 | 42 | export default Togglable; -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/components/AnecdoteForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { addAnecdote } from '../reducers/anecdoteReducer'; 4 | import { showNotificationWithTimeout } from '../reducers/notificationReducer'; 5 | 6 | const AnecdoteForm = ({ addAnecdote, showNotificationWithTimeout }) => { 7 | const [formValue, setFormValue] = useState(''); 8 | 9 | const createAnecdote = async (event) => { 10 | event.preventDefault(); 11 | 12 | const anecdote = { content: formValue, votes: 0 }; 13 | 14 | addAnecdote(anecdote); 15 | 16 | const message = `Added: '${formValue}'`; 17 | showNotificationWithTimeout(message, 5); 18 | 19 | setFormValue(''); 20 | }; 21 | 22 | const handleChange = (event) => { 23 | setFormValue(event.target.value); 24 | }; 25 | 26 | return ( 27 |
28 |
29 | 35 |
36 | 37 |
38 | ); 39 | }; 40 | 41 | const mapDispatchToProps = { 42 | addAnecdote, 43 | showNotificationWithTimeout, 44 | }; 45 | 46 | export default connect(null, mapDispatchToProps)(AnecdoteForm); -------------------------------------------------------------------------------- /part1/courseinfo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | const App = () => { 5 | const course = { 6 | name: 'Half Stack application development', 7 | parts: [ 8 | { 9 | name: 'Fundamentals of React', 10 | exercises: 10 11 | }, 12 | { 13 | name: 'Using props to pass data', 14 | exercises: 7 15 | }, 16 | { 17 | name: 'State of a component', 18 | exercises: 14 19 | } 20 | ] 21 | } 22 | 23 | return ( 24 |
25 |
26 | 27 | 28 |
29 | ) 30 | } 31 | 32 | const Header = ({ course }) => { 33 | return (

{course}

); 34 | } 35 | 36 | const Content = ({ parts }) => { 37 | return ( 38 |
39 | 40 | 41 | 42 |
43 | ) 44 | } 45 | 46 | const Total = ({ parts }) => { 47 | return (

Number of exercises {parts[0].exercises + parts[1].exercises + parts[2].exercises}

); 48 | } 49 | 50 | const Part = ({ part }) => { 51 | return ( 52 |

53 | {part.name} {part.exercises} 54 |

55 | ) 56 | } 57 | 58 | ReactDOM.render(, document.getElementById('root')) -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/BlogList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { useSelector } from 'react-redux'; 4 | import AddBlogForm from './AddBlogForm'; 5 | import Togglable from './Togglable'; 6 | import List from '@material-ui/core/List'; 7 | import ListItem from '@material-ui/core/ListItem'; 8 | 9 | const BlogList = () => { 10 | const blogs = useSelector((state) => { 11 | return state.blogs.blogs.sort((a, b) => (b.likes || 0) - (a.likes || 0)); 12 | }); 13 | 14 | const status = useSelector((state) => state.blogs.status); 15 | 16 | const isLoggedIn = useSelector((state) => state.authentication.isLoggedIn); 17 | 18 | if (status === 'failed') return
Error: Could not load blogs
; 19 | 20 | if (status === 'loading') return
Loading...
; 21 | 22 | return ( 23 |
24 |

Blogs

25 | {isLoggedIn && 26 | 27 | } 28 | 29 | {blogs.map((blog) => { 30 | return ( 31 | 32 | {blog.title} 33 | 34 | ); 35 | })} 36 | 37 |
38 | ); 39 | }; 40 | 41 | export default BlogList; -------------------------------------------------------------------------------- /part9/patientor-backend/src/routes/patients.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import patientService from '../services/patientService'; 3 | import { toNewPatient, toNewEntry } from '../utils'; 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/', (_req, res) => { 8 | res.json(patientService.getPatients()); 9 | }); 10 | 11 | router.post('/', (req, res) => { 12 | try { 13 | const newPatient = toNewPatient(req.body); 14 | 15 | const addedPatient = patientService.addPatient(newPatient); 16 | res.json(addedPatient); 17 | } catch (error) { 18 | res.status(400).send({ error: error.message }); 19 | } 20 | }); 21 | 22 | router.get('/:id', (req, res) => { 23 | const patient = patientService.findPatientById(req.params.id); 24 | 25 | if (patient) { 26 | res.json(patient); 27 | } else { 28 | res.sendStatus(404); 29 | } 30 | }); 31 | 32 | router.post('/:id/entries', (req, res) => { 33 | try { 34 | const newEntry = toNewEntry(req.body); 35 | 36 | const updatedPatient = patientService.addEntry(req.params.id, newEntry); 37 | 38 | if (!updatedPatient) { 39 | res.status(400).send({ error: "invalid patient id" }); 40 | } else { 41 | res.json(updatedPatient); 42 | } 43 | } catch (error) { 44 | res.status(400).send({ error: error.message }); 45 | } 46 | }); 47 | 48 | export default router; -------------------------------------------------------------------------------- /part9/patientor-frontend/src/PatientPage/OccupationalHealthcareEntryDetails.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { OccupationalHealthcareEntry } from "../types"; 3 | import { Card, Icon } from "semantic-ui-react"; 4 | import DiagnosisList from "./DiagnosisList"; 5 | 6 | interface OccupationalHealthcareEntryDetailsProps { 7 | entry: OccupationalHealthcareEntry; 8 | } 9 | 10 | const OccupationalHealthcareEntryDetails = ({ entry }: OccupationalHealthcareEntryDetailsProps) => { 11 | return ( 12 | 13 | 14 | 15 | {entry.date} 16 | 17 | 18 | {entry.specialist} 19 | 20 |

{entry.description}

21 |

Employer: {entry.employerName}

22 | {entry.sickLeave && 23 |

Sick Leave: {entry.sickLeave.startDate} to {entry.sickLeave.endDate}

} 24 |
25 |
26 | 27 | {entry.diagnosisCodes 28 | ? 29 | :

No diagnosis codes

} 30 |
31 |
32 | 33 | ); 34 | }; 35 | 36 | export default OccupationalHealthcareEntryDetails; -------------------------------------------------------------------------------- /part5/bloglist-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloglist-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/user-event": "^12.6.3", 7 | "axios": "^0.21.1", 8 | "prop-types": "^15.7.2", 9 | "react": "^17.0.1", 10 | "react-dom": "^17.0.1", 11 | "react-scripts": "4.0.1", 12 | "web-vitals": "^0.2.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 | "cypress:open": "cypress open", 20 | "test:e2e": "cypress run", 21 | "lint": "eslint ." 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 | "@testing-library/jest-dom": "^5.11.10", 44 | "@testing-library/react": "^11.2.6", 45 | "cypress": "^7.0.1", 46 | "eslint-plugin-cypress": "^2.11.2", 47 | "eslint-plugin-jest": "^24.3.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /part2/courseinfo2/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Course from './components/Course'; 3 | 4 | const App = () => { 5 | const courses = [ 6 | { 7 | name: 'Half Stack application development', 8 | id: 1, 9 | parts: [ 10 | { 11 | name: 'Fundamentals of React', 12 | exercises: 10, 13 | id: 1 14 | }, 15 | { 16 | name: 'Using props to pass data', 17 | exercises: 7, 18 | id: 2 19 | }, 20 | { 21 | name: 'State of a component', 22 | exercises: 14, 23 | id: 3 24 | }, 25 | { 26 | name: 'Redux', 27 | exercises: 11, 28 | id: 4 29 | } 30 | ] 31 | }, 32 | { 33 | name: 'Node.js', 34 | id: 2, 35 | parts: [ 36 | { 37 | name: 'Routing', 38 | exercises: 3, 39 | id: 1 40 | }, 41 | { 42 | name: 'Middlewares', 43 | exercises: 7, 44 | id: 2 45 | } 46 | ] 47 | } 48 | ]; 49 | 50 | return ( 51 |
52 |

Web development curriculum

53 | {courses.map(course => 54 | 58 | )} 59 |
60 | ) 61 | } 62 | 63 | export default App; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/Navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import AppBar from '@material-ui/core/AppBar'; 4 | import ToolBar from '@material-ui/core/ToolBar'; 5 | import Button from '@material-ui/core/Button'; 6 | import { useDispatch, useSelector } from 'react-redux'; 7 | import { logOut } from '../reducers/authenticationReducer'; 8 | 9 | const Navigation = () => { 10 | 11 | const dispatch = useDispatch(); 12 | 13 | const { user, isLoggedIn } = useSelector((state) => state.authentication); 14 | 15 | const handleLogout = () => { 16 | dispatch(logOut()); 17 | }; 18 | 19 | return ( 20 | 21 | 22 | 25 | 28 | {isLoggedIn 29 | ? () 32 | : ()} 35 | {isLoggedIn ?
{user.name} is logged in
: null} 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default Navigation; -------------------------------------------------------------------------------- /part6/redux-anecdotes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-anecdotes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.6.3", 9 | "axios": "^0.21.1", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-redux": "^7.2.2", 13 | "react-scripts": "4.0.2", 14 | "redux": "^4.0.5", 15 | "redux-devtools-extension": "^2.13.9", 16 | "redux-thunk": "^2.3.0", 17 | "web-vitals": "^1.1.0" 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 .", 25 | "server": "json-server -p3001 --watch db.json" 26 | }, 27 | "proxy": "http://localhost:3001", 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "eslint-plugin-jest": "^24.3.5", 48 | "json-server": "^0.16.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /part7/routed-anecdotes/src/components/CreateNew.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | import { useField } from '../hooks'; 4 | 5 | const CreateNew = ({ addNew }) => { 6 | const [content, contentReset] = useField('content'); 7 | const [author, authorReset] = useField('author'); 8 | const [info, infoReset] = useField('info', 'url'); 9 | 10 | const history = useHistory(); 11 | 12 | const handleSubmit = (event) => { 13 | event.preventDefault(); 14 | 15 | addNew({ 16 | content: content.value, 17 | author: author.value, 18 | info: info.value, 19 | votes: 0 20 | }); 21 | 22 | history.push('/'); 23 | }; 24 | 25 | const resetFields = () => { 26 | contentReset(); 27 | authorReset(); 28 | infoReset(); 29 | }; 30 | 31 | return ( 32 |
33 |

create a new anecdote

34 |
35 |
36 | content 37 | 38 |
39 |
40 | author 41 | 42 |
43 |
44 | url for more info 45 | 46 |
47 | 48 | 49 |
50 |
51 | ); 52 | 53 | }; 54 | 55 | export default CreateNew; -------------------------------------------------------------------------------- /part4/blog-list-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 loginRouter = require('./controllers/login'); 7 | const usersRouter = require('./controllers/users'); 8 | const blogsRouter = require('./controllers/blogs'); 9 | const { requestLogger, tokenExtractor, unknownEndpoint, errorHandler } = require('./utils/middleware'); 10 | const logger = require('./utils/logger'); 11 | const mongoose = require('mongoose'); 12 | 13 | mongoose.connect( 14 | config.MONGODB_URI, 15 | { 16 | useNewUrlParser: true, 17 | useUnifiedTopology: true, 18 | useFindAndModify: false, 19 | useCreateIndex: true 20 | }) 21 | .then(() => { 22 | logger.info('connected to MongoDb'); 23 | }) 24 | .catch((error) => { 25 | logger.error('error connecting to MongoDB:', error.message); 26 | }); 27 | 28 | app.use(cors()); 29 | app.use(express.json()); 30 | app.use(requestLogger); 31 | app.use(tokenExtractor); 32 | 33 | app.use('/api/login', loginRouter); 34 | app.use('/api/users', usersRouter); 35 | app.use('/api/blogs', blogsRouter); 36 | 37 | if (process.env.NODE_ENV === 'test') { 38 | const testingRouter = require('./controllers/testing'); 39 | app.use('/api/testing', testingRouter); 40 | } 41 | 42 | app.use(unknownEndpoint); 43 | app.use(errorHandler); 44 | 45 | module.exports = app; -------------------------------------------------------------------------------- /part7/blog-list-backend-extended/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 loginRouter = require('./controllers/login'); 7 | const usersRouter = require('./controllers/users'); 8 | const blogsRouter = require('./controllers/blogs'); 9 | const { requestLogger, tokenExtractor, unknownEndpoint, errorHandler } = require('./utils/middleware'); 10 | const logger = require('./utils/logger'); 11 | const mongoose = require('mongoose'); 12 | 13 | mongoose.connect( 14 | config.MONGODB_URI, 15 | { 16 | useNewUrlParser: true, 17 | useUnifiedTopology: true, 18 | useFindAndModify: false, 19 | useCreateIndex: true 20 | }) 21 | .then(() => { 22 | logger.info('connected to MongoDb'); 23 | }) 24 | .catch((error) => { 25 | logger.error('error connecting to MongoDB:', error.message); 26 | }); 27 | 28 | app.use(cors()); 29 | app.use(express.json()); 30 | app.use(requestLogger); 31 | app.use(tokenExtractor); 32 | 33 | app.use('/api/login', loginRouter); 34 | app.use('/api/users', usersRouter); 35 | app.use('/api/blogs', blogsRouter); 36 | 37 | if (process.env.NODE_ENV === 'test') { 38 | const testingRouter = require('./controllers/testing'); 39 | app.use('/api/testing', testingRouter); 40 | } 41 | 42 | app.use(unknownEndpoint); 43 | app.use(errorHandler); 44 | 45 | module.exports = app; -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/AddBlogForm.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, fireEvent } from '@testing-library/react'; 3 | import '@testing-library/jest-dom/extend-expect'; 4 | import AddBlogForm from './AddBlogForm'; 5 | 6 | describe('', () => { 7 | let component; 8 | 9 | const blogObject = { 10 | title: 'Example Blog', 11 | author: 'Jane Doe', 12 | url: 'http://www.example.com/blog', 13 | likes: 0, 14 | }; 15 | 16 | const mockAddBlog = jest.fn(); 17 | 18 | beforeEach(() => { 19 | component = render( 20 | 21 | ); 22 | }); 23 | 24 | test('calls the addBlog fn with the correct details during a submit event', () => { 25 | const form = component.container.querySelector('form'); 26 | const titleInput = component.getByLabelText(/title/i); 27 | const authorInput = component.getByLabelText(/author/i); 28 | const urlInput = component.getByLabelText(/url/i); 29 | 30 | fireEvent.change(titleInput, { 31 | target: { value: blogObject.title }, 32 | }); 33 | 34 | fireEvent.change(authorInput, { 35 | target: { value: blogObject.author }, 36 | }); 37 | 38 | fireEvent.change(urlInput, { 39 | target: { value: blogObject.url }, 40 | }); 41 | 42 | fireEvent.submit(form); 43 | 44 | expect(mockAddBlog).toHaveBeenCalled(); 45 | expect(mockAddBlog).toHaveBeenCalledWith(blogObject); 46 | }); 47 | }); -------------------------------------------------------------------------------- /part5/bloglist-frontend/src/components/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const LoginForm = ({ handleLogin }) => { 5 | const [username, setUsername] = useState(''); 6 | const [password, setPassword] = useState(''); 7 | 8 | const handleSubmit = (event) => { 9 | event.preventDefault(); 10 | 11 | const user = { username, password }; 12 | handleLogin(user); 13 | 14 | setUsername(''); 15 | setPassword(''); 16 | }; 17 | 18 | return ( 19 |
20 |

Log in to Application

21 |
22 |
23 | 24 | setUsername(target.value)} 29 | /> 30 |
31 |
32 | 33 | setPassword(target.value)} 38 | /> 39 |
40 | 41 |
42 |
43 | ); 44 | }; 45 | 46 | LoginForm.propTypes = { 47 | handleLogin: PropTypes.func.isRequired, 48 | }; 49 | 50 | export default LoginForm; -------------------------------------------------------------------------------- /part7/blog-list-frontend-extended/src/components/CommentForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { addComment } from '../reducers/blogReducer'; 4 | import TextField from '@material-ui/core/TextField'; 5 | import Button from '@material-ui/core/Button'; 6 | import { makeStyles } from '@material-ui/core/styles'; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | form: { 10 | '& > *': { 11 | marginTop: theme.spacing(1), 12 | marginBottom: theme.spacing(1), 13 | }, 14 | }, 15 | })); 16 | 17 | const CommentForm = ({ blog }) => { 18 | const classes = useStyles(); 19 | 20 | const dispatch = useDispatch(); 21 | 22 | const [comment, setComment] = useState(''); 23 | 24 | const { isLoggedIn } = useSelector((state) => state.authentication); 25 | 26 | const handleSubmit = async (event) => { 27 | event.preventDefault(); 28 | 29 | dispatch(addComment(blog.id, comment)); 30 | 31 | setComment(''); 32 | }; 33 | 34 | if (!isLoggedIn) return null; 35 | 36 | return ( 37 |
38 |
39 | setComment(target.value)} 43 | /> 44 |
45 | 46 |
47 | ); 48 | }; 49 | 50 | export default CommentForm; -------------------------------------------------------------------------------- /part8/library-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { setContext } from '@apollo/client/link/context'; 5 | import { getMainDefinition } from '@apollo/client/utilities'; 6 | import { WebSocketLink } from '@apollo/client/link/ws'; 7 | 8 | import { 9 | ApolloClient, 10 | ApolloProvider, 11 | HttpLink, 12 | InMemoryCache, 13 | split, 14 | } from '@apollo/client'; 15 | 16 | const authLink = setContext((_, { headers }) => { 17 | const token = localStorage.getItem('library-user-token'); 18 | return { 19 | headers: { 20 | ...headers, 21 | authorization: token ? `Bearer ${token}` : null, 22 | } 23 | }; 24 | }); 25 | 26 | const httpLink = new HttpLink({ uri: 'http://localhost:4000' }); 27 | 28 | const wsLink = new WebSocketLink({ 29 | uri: 'ws://localhost:4000/graphql', 30 | options: { 31 | reconnect: true 32 | } 33 | }); 34 | 35 | const splitLink = split( 36 | ({ query }) => { 37 | const definition = getMainDefinition(query); 38 | return ( 39 | definition.kind === 'OperationDefinition' && 40 | definition.operation === 'subscription' 41 | ); 42 | }, 43 | wsLink, 44 | authLink.concat(httpLink), 45 | ); 46 | 47 | const client = new ApolloClient({ 48 | cache: new InMemoryCache(), 49 | link: splitLink, 50 | }); 51 | 52 | ReactDOM.render( 53 | 54 | 55 | , 56 | document.getElementById('root')); -------------------------------------------------------------------------------- /part9/patientor-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patientor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.21.1", 7 | "formik": "^2.2.6", 8 | "react": "^17.0.1", 9 | "react-dom": "^17.0.1", 10 | "react-router-dom": "^5.2.0", 11 | "semantic-ui-css": "^2.4.1", 12 | "semantic-ui-react": "^2.0.3", 13 | "uuid": "^8.3.2", 14 | "yup": "^0.32.9" 15 | }, 16 | "devDependencies": { 17 | "@types/axios": "^0.14.0", 18 | "@types/jest": "26.0.20", 19 | "@types/node": "12.11.7", 20 | "@types/react": "^17.0.2", 21 | "@types/react-dom": "^17.0.1", 22 | "@types/react-router-dom": "^5.1.7", 23 | "@types/uuid": "^8.3.0", 24 | "@typescript-eslint/eslint-plugin": "^4.16.1", 25 | "@typescript-eslint/parser": "^4.16.1", 26 | "eslint-config-react": "^1.1.7", 27 | "react-scripts": "^4.0.3", 28 | "typescript": "^4.2.2" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject", 35 | "lint": "eslint './src/**/*.{ts,tsx}'", 36 | "lint:fix": "eslint './src/**/*.{ts,tsx}' --fix" 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /part6/redux-anecdotes/src/reducers/anecdoteReducer.js: -------------------------------------------------------------------------------- 1 | import anecdoteService from '../services/anecdotes'; 2 | 3 | const reducer = (state = [], action) => { 4 | switch (action.type) { 5 | case 'VOTE': 6 | return state.map((anecdote) => { 7 | return anecdote.id !== action.payload.id 8 | ? anecdote 9 | : { ...anecdote, votes: anecdote.votes + 1 }; 10 | }); 11 | case 'NEW_ANECDOTE': 12 | return [...state, action.payload]; 13 | case 'INIT_ANECDOTES': 14 | return action.payload; 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | export const voteAnecdote = (id) => { 21 | return async (dispatch, getState) => { 22 | const anecdote = getState().anecdotes.find((anecdote) => anecdote.id === id); 23 | 24 | await anecdoteService.update(id, { ...anecdote, votes: anecdote.votes + 1 }); 25 | 26 | dispatch({ 27 | type: 'VOTE', 28 | payload: { id } 29 | }); 30 | }; 31 | }; 32 | 33 | export const addAnecdote = (content) => { 34 | return async (dispatch) => { 35 | const anecdote = await anecdoteService.createNew(content); 36 | dispatch({ 37 | type: 'NEW_ANECDOTE', 38 | payload: anecdote 39 | }); 40 | }; 41 | }; 42 | 43 | export const initializeAnecdotes = () => { 44 | return async (dispatch) => { 45 | const anecdotes = await anecdoteService.getAll(); 46 | dispatch({ 47 | type: 'INIT_ANECDOTES', 48 | payload: anecdotes 49 | }); 50 | }; 51 | }; 52 | 53 | export default reducer; -------------------------------------------------------------------------------- /part9/bmi/bmiCalculator.ts: -------------------------------------------------------------------------------- 1 | export const calculateBmi = (height: number, mass: number): string => { 2 | const bmi = mass / (height / 100) ** 2; 3 | 4 | if (bmi < 15) return 'Very severly underweight'; 5 | 6 | if (bmi < 16) return 'Severely underweight'; 7 | 8 | if (bmi < 18.5) return 'Underweight'; 9 | 10 | if (bmi < 25) return 'Normal (healthy weight)'; 11 | 12 | if (bmi < 30) return 'Overweight'; 13 | 14 | if (bmi < 35) return 'Obese Class I (Moderately obese)'; 15 | 16 | if (bmi < 40) return 'Obese Class II (Severely obese)'; 17 | 18 | if (bmi >= 40) return 'Obese Class III (Very severely obese)'; 19 | 20 | return 'Error: Unable to calculate BMI'; 21 | }; 22 | 23 | interface bmiArgs { 24 | height: number; 25 | weight: number; 26 | } 27 | 28 | const parseBmiArguments = (args: Array): bmiArgs => { 29 | if (args.length < 4) throw new Error('Not enough arguments'); 30 | if (args.length > 4) throw new Error('Too many arguments'); 31 | 32 | if (isNaN(Number(args[2])) && !isNaN(Number(args[3]))) { 33 | throw new Error('Provided values were not numbers!'); 34 | } 35 | 36 | return { 37 | height: Number(args[2]), 38 | weight: Number(args[3]) 39 | }; 40 | }; 41 | 42 | if (require.main === module) { 43 | try { 44 | const { height, weight } = parseBmiArguments(process.argv); 45 | console.log(calculateBmi(height, weight)); 46 | } catch (error) { 47 | if (error instanceof Error) { 48 | console.log("An error has occured: ", error.message); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /part8/library-frontend/src/components/Books.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useQuery } from '@apollo/client'; 3 | import { ALL_BOOKS } from '../queries'; 4 | import BookTable from './BookTable'; 5 | 6 | const Books = () => { 7 | const [filter, setFilter] = useState('all'); 8 | 9 | const { data: booksData, loading: booksLoading, error: booksError } = useQuery(ALL_BOOKS); 10 | 11 | const genres = [...(new Set(booksData?.allBooks.flatMap((book) => book.genres)))]; 12 | const options = ['all'].concat(genres); 13 | 14 | const { data: filteredBooksData, loading: filteredBooksLoading, error: filteredBooksError } = useQuery(ALL_BOOKS, { 15 | variables: { genre: filter !== 'all' ? filter : null }, 16 | fetchPolicy: 'cache-and-network', 17 | }); 18 | 19 | const books = filteredBooksData?.allBooks; 20 | 21 | if (booksLoading || filteredBooksLoading) { 22 | return
loading...
; 23 | } 24 | 25 | if (booksError || filteredBooksError) { 26 | return
Error: Could not load books
; 27 | } 28 | 29 | return ( 30 |
31 |

Books

32 |
show:
33 | 42 | 43 |
44 | ); 45 | }; 46 | 47 | export default Books; -------------------------------------------------------------------------------- /part9/courseinfo-typescript/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './components/Header'; 3 | import Content from './components/Content'; 4 | import Total from './components/Total'; 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 the leisured course part", 14 | type: "normal" 15 | }, 16 | { 17 | name: "Advanced", 18 | exerciseCount: 7, 19 | description: "This is the harded course part", 20 | type: "normal" 21 | }, 22 | { 23 | name: "Using props to pass data", 24 | exerciseCount: 7, 25 | groupProjectCount: 3, 26 | type: "groupProject" 27 | }, 28 | { 29 | name: "Deeper type usage", 30 | exerciseCount: 14, 31 | description: "Confusing description", 32 | exerciseSubmissionLink: "https://fake-exercise-submit.made-up-url.dev", 33 | type: "submission" 34 | }, 35 | { 36 | name: "Backend development", 37 | exerciseCount: 21, 38 | description: "Typing the backend", 39 | requirements: ["nodejs", "jest"], 40 | type: "special" 41 | }, 42 | ] 43 | 44 | return ( 45 |
46 |
47 | 48 | 49 |
50 | ); 51 | }; 52 | 53 | export default App; -------------------------------------------------------------------------------- /part3/phonebook-backend/build/static/js/runtime-main.a212f097.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,p=r[0],f=r[1],i=r[2],c=0,s=[];c { 11 | const vote = (id) => { 12 | voteAnecdote(id); 13 | 14 | const anecdote = anecdotes.find((anecdote) => { 15 | return anecdote.id === id; 16 | }); 17 | 18 | const message = `Voted for: '${anecdote.content}'`; 19 | showNotificationWithTimeout(message, 5); 20 | }; 21 | 22 | return ( 23 |
24 | {anecdotes.sort((a, b) => b.votes - a.votes).map(anecdote => 25 |
26 |
27 | {anecdote.content} 28 |
29 |
30 | has {anecdote.votes} 31 | 32 |
33 |
34 | )} 35 |
36 | ); 37 | }; 38 | 39 | const mapStateToProps = ({ anecdotes, filter }) => { 40 | const filteredAnecdotes = anecdotes.filter((anecdote) => { 41 | return anecdote.content.toLowerCase().includes(filter.toLowerCase()); 42 | }); 43 | 44 | return { 45 | anecdotes: filteredAnecdotes 46 | }; 47 | }; 48 | 49 | const mapDispatchToProps = { 50 | voteAnecdote, 51 | showNotificationWithTimeout, 52 | }; 53 | 54 | export default connect(mapStateToProps, mapDispatchToProps)(AnecdoteList); -------------------------------------------------------------------------------- /part8/library-frontend/src/components/BirthYearForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useMutation } from '@apollo/client'; 3 | import { EDIT_AUTHOR } from '../queries'; 4 | 5 | const BirthYearForm = ({ authors }) => { 6 | const [name, setName] = useState(authors[0].name); 7 | const [born, setBorn] = useState(''); 8 | 9 | const [editAuthor] = useMutation(EDIT_AUTHOR, { 10 | onError: (error) => { 11 | console.error(error); 12 | } 13 | }); 14 | 15 | const submit = async (event) => { 16 | event.preventDefault(); 17 | 18 | const bornInt = Number.parseInt(born, 10); 19 | 20 | editAuthor({ 21 | variables: { 22 | name, 23 | born: bornInt, 24 | } 25 | }); 26 | 27 | setBorn(''); 28 | }; 29 | 30 | return ( 31 |
32 |

Set birth year

33 |
34 | 43 |
44 | Born 45 | setBorn(target.value)} 49 | /> 50 |
51 | 52 |
53 |
54 | ); 55 | }; 56 | 57 | export default BirthYearForm; -------------------------------------------------------------------------------- /part9/patientor-frontend/README.md: -------------------------------------------------------------------------------- 1 | # Patientor - frontend 2 | 3 | A medical record app that provides and updates patient details and medical records. The provided front end was expanded to add pages for displaying patient details and medical records. Formik and Yup were used to create a maintainable form for adding medical records. Semantic UI React was also used. 4 | 5 | ## Screenshots 6 |

7 | 8 | 9 | 10 | 11 |

12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm install` 18 | 19 | Install the project dependencies. 20 | 21 | ### `npm start` 22 | 23 | Runs the app in the development mode.
24 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 25 | 26 | The page will reload if you make edits.
27 | You will also see any lint errors in the console. 28 | 29 | ### `npm build` 30 | 31 | Builds the app for production to the `build` folder.
32 | It correctly bundles React in production mode and optimizes the build for the best performance. 33 | 34 | The build is minified and the filenames include the hashes.
35 | Your app is ready to be deployed! 36 | 37 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 38 | -------------------------------------------------------------------------------- /part3/phonebook-backend/mongo.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | if (process.argv.length <= 2 || process.argv.length === 4 || process.argv.length >= 6) { 4 | console.log('Please provide the password as an argument: node mongo.js \nOptionally provide a name and number to add a person to the database: node mongo.js '); 5 | process.exit(1); 6 | } 7 | 8 | const password = process.argv[2]; 9 | const name = process.argv[3]; 10 | const number = process.argv[4]; 11 | 12 | const url = `mongodb+srv://ruel:${password}@cluster0.e31yt.mongodb.net/phonebook-app?retryWrites=true&w=majority`; 13 | 14 | mongoose 15 | .connect(url, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true }) 16 | .catch((error) => { 17 | console.log('Error connecting to MongoDB', error.message); 18 | }); 19 | 20 | const personSchema = new mongoose.Schema({ 21 | name: String, 22 | number: String, 23 | }); 24 | 25 | const Person = mongoose.model('Person', personSchema); 26 | 27 | if (process.argv.length === 3) { 28 | console.log('Phonebook:'); 29 | Person.find({}).then(result => { 30 | result.forEach(person => { 31 | console.log(`${person.name} ${person.number}`); 32 | }); 33 | mongoose.connection.close(); 34 | }); 35 | } 36 | 37 | if (process.argv.length === 5) { 38 | const person = new Person({ 39 | name: name, 40 | number: number, 41 | }); 42 | 43 | person.save().then(result => { 44 | console.log(`Added ${result.name} with number ${result.number} to the phonebook`); 45 | mongoose.connection.close(); 46 | }); 47 | } 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /part2/countries/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Search from './components/Search'; 3 | import Results from './components/Results'; 4 | 5 | import axios from 'axios'; 6 | 7 | const App = () => { 8 | const [query, setQuery] = useState(''); 9 | const [countries, setCountries] = useState(); 10 | const [gettingCountries, setGettingCountries] = useState(true); 11 | const [isError, setIsError] = useState(false); 12 | 13 | const updateCountries = () => { 14 | axios 15 | .get('https://restcountries.eu/rest/v2/all') 16 | .then(response => { 17 | setCountries(response.data) 18 | setGettingCountries(false); 19 | }) 20 | .catch(function (error) { 21 | if (error.response) { 22 | console.error(error.response.data); 23 | } else if (error.request) { 24 | console.error(error.request); 25 | } else { 26 | console.error('Error', error.message); 27 | } 28 | setIsError(true); 29 | }); 30 | } 31 | 32 | useEffect(updateCountries, []); 33 | 34 | const handleQuery = (event) => { 35 | setQuery(event.target.value); 36 | } 37 | 38 | return ( 39 |
40 | 44 | {gettingCountries && !isError &&
Getting countries data...
} 45 | {isError &&
Error: Could not get countries data
} 46 | {!gettingCountries && } 51 |
52 | ); 53 | } 54 | 55 | export default App; 56 | --------------------------------------------------------------------------------