├── .editorconfig ├── src ├── App.css ├── components │ ├── SideNav │ │ └── SideNav.css │ ├── BookDetails │ │ ├── BookDetails.css │ │ └── BookDetails.jsx │ ├── Library │ │ ├── Library.module.css │ │ ├── Library.css │ │ └── data.json │ ├── Register │ │ └── Register.css │ ├── SettingIcon │ │ ├── Setting.css │ │ └── Setting.jsx │ ├── Hooks │ │ ├── useProfileData.js │ │ └── useCheckBoxToggle.js │ ├── BookSearch │ │ ├── BookSearch.jsx │ │ └── BookSearch.css │ ├── FormikContainer │ │ └── CreateProfileForm │ │ │ ├── SelectInputField.jsx │ │ │ ├── IDE_Options.js │ │ │ ├── operatingSystemOptions.js │ │ │ ├── programingSkillOptions.js │ │ │ └── CreateProfileForm.css │ ├── Feedback │ │ ├── Feedback.css │ │ ├── CaptureFeedback.jsx │ │ ├── DirectFeedback.jsx │ │ └── EmailFeedback.jsx │ └── SearchPopup │ │ └── PopupState.jsx ├── pages │ ├── ResetPassword │ │ └── ResetPassword.css │ ├── Profile │ │ ├── CreateProfileAlert.css │ │ ├── CreateProfileAlert.jsx │ │ └── Profile.css │ ├── userDetails │ │ └── UserDetails.jsx │ ├── Settings │ │ ├── utils │ │ │ ├── alert.js │ │ │ ├── i18n.js │ │ │ └── language.js │ │ ├── languageOptions.js │ │ ├── Privacy.css │ │ ├── Notifications.css │ │ ├── Settings.css │ │ ├── SideSettingsDrawer.jsx │ │ └── LanguageSetting.jsx │ ├── Login │ │ ├── Login.css │ │ └── Login.jsx │ ├── Support │ │ └── Support.css │ ├── ForgotPassword │ │ ├── ForgotPassword.css │ │ └── ForgotPassword.jsx │ ├── PrivacyPolicy │ │ └── PrivacyPolicy.css │ └── Home │ │ ├── Home.css │ │ └── Home.jsx ├── server │ ├── .gitignore │ ├── setup │ │ ├── cleanup.sh │ │ ├── setup.sh │ │ ├── docker-compose.yml │ │ └── setup.js │ ├── .variable.env │ ├── routes │ │ ├── supportRoutes.js │ │ ├── reviewRoutes.js │ │ ├── settingsRoutes.js │ │ ├── profileRoutes.js │ │ ├── booksRoutes.js │ │ └── userRoutes.js │ ├── model │ │ ├── feedBackModel.js │ │ ├── tokenModel.js │ │ ├── supportModel.js │ │ ├── userModel.js │ │ ├── reviewModel.js │ │ ├── booksModel.js │ │ ├── userBooksLibraryModel.js │ │ ├── settingsModel.js │ │ └── profileModel.js │ ├── README.md │ ├── controller │ │ ├── reviewController.js │ │ ├── supportController.js │ │ ├── feedBackController.js │ │ ├── profileController.js │ │ ├── settingsController.js │ │ └── booksController.js │ ├── package.json │ └── index.js ├── assets │ ├── codebooker.png │ └── languages.js ├── tests │ ├── App.test.js │ ├── mocks │ │ ├── server.js │ │ └── handlers.js │ ├── home.test.js │ ├── login.test.js │ └── signup.test.js ├── index.css ├── reportWebVitals.js ├── ProtectedRoute.jsx ├── setupTests.js ├── index.js ├── App.js ├── logo.svg └── hooks │ └── useAxios.js ├── .env ├── CODEOWNERS ├── docs ├── Clone.png ├── fork.png ├── CreateFork.png ├── CreateForkBtn.png ├── ForkedBranch.png ├── ForkClone.md └── DEVSETUP.md ├── .vscode ├── launch.json ├── tasks.json ├── codebooker.code-workspace ├── extensions.json └── settings.json ├── public ├── favicon.ico ├── robots.txt ├── codebooker192.png ├── codebooker512.png ├── favicon-16x16.png ├── favicon-32x32.png ├── Assets │ ├── Avatar.webp │ ├── book-img.png │ ├── avatar-img.png │ ├── codebooker-logo.png │ └── profile-banner-img.png ├── apple-touch-icon.png ├── manifest.json └── index.html ├── .eslintignore ├── svgr.config.js ├── .prettierignore ├── postcss.config.js ├── tailwind.config.js ├── .prettierrc ├── CONTRIBUTORS.md ├── .github ├── config.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── SPONSORS.md ├── SECURITY.md ├── CHANGELOG.md ├── .eslintrc.js ├── LICENSE ├── TODO.md ├── README.md ├── package.json ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md /.editorconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | GENERATE_SOURCEMAP=false -------------------------------------------------------------------------------- /src/components/SideNav/SideNav.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # defaults 2 | 3 | * @gbowne1 -------------------------------------------------------------------------------- /src/components/BookDetails/BookDetails.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/ResetPassword/ResetPassword.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/server/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /src/server/setup/cleanup.sh: -------------------------------------------------------------------------------- 1 | docker-compose down -------------------------------------------------------------------------------- /src/server/setup/setup.sh: -------------------------------------------------------------------------------- 1 | docker-compose up -d 2 | node setup.js -------------------------------------------------------------------------------- /src/pages/Profile/CreateProfileAlert.css: -------------------------------------------------------------------------------- 1 | .img{ 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /docs/Clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/docs/Clone.png -------------------------------------------------------------------------------- /docs/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/docs/fork.png -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [] 4 | } 5 | -------------------------------------------------------------------------------- /docs/CreateFork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/docs/CreateFork.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Third party 2 | **/node_modules 3 | 4 | # Build products 5 | build/ 6 | coverage/ -------------------------------------------------------------------------------- /docs/CreateForkBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/docs/CreateForkBtn.png -------------------------------------------------------------------------------- /docs/ForkedBranch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/docs/ForkedBranch.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/codebooker192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/codebooker192.png -------------------------------------------------------------------------------- /public/codebooker512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/codebooker512.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/Assets/Avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/Assets/Avatar.webp -------------------------------------------------------------------------------- /public/Assets/book-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/Assets/book-img.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/codebooker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/src/assets/codebooker.png -------------------------------------------------------------------------------- /public/Assets/avatar-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/Assets/avatar-img.png -------------------------------------------------------------------------------- /svgr.config.js: -------------------------------------------------------------------------------- 1 | // .svgrrc.js 2 | module.exports = { 3 | icon: true, 4 | expandProps: false, 5 | }; 6 | -------------------------------------------------------------------------------- /public/Assets/codebooker-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/Assets/codebooker-logo.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | internals/generators/ 4 | internals/scripts/ 5 | package-lock.json 6 | package.json -------------------------------------------------------------------------------- /public/Assets/profile-banner-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbowne1/codebooker/HEAD/public/Assets/profile-banner-img.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/userDetails/UserDetails.jsx: -------------------------------------------------------------------------------- 1 | const UserDetails = () => { 2 | return
UserDetails
; 3 | }; 4 | 5 | export default UserDetails; 6 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [{}] 5 | } 6 | -------------------------------------------------------------------------------- /src/server/.variable.env: -------------------------------------------------------------------------------- 1 | MONGO_DB= 2 | PORT= 3 | 4 | //Credentials for sending/receiving feedback from server 5 | MAIL_USERNAME="senderAddressEmail" 6 | MAIL_PASSWORD="senderAddressPassword" -------------------------------------------------------------------------------- /.vscode/codebooker.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {}, 8 | "extensions": { 9 | "recommendations": [] 10 | } 11 | } -------------------------------------------------------------------------------- /src/tests/App.test.js: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { render, screen, fireEvent } from '@testing-library/react'; 3 | 4 | describe('App Test', () => { 5 | test('App', () => {}); 6 | }); 7 | -------------------------------------------------------------------------------- /src/assets/languages.js: -------------------------------------------------------------------------------- 1 | import en from './locales/en.json'; 2 | import fr from './locales/fr.json'; 3 | import es from './locales/es.json'; 4 | export const locale = { 5 | en, 6 | fr, 7 | es, 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/Library/Library.module.css: -------------------------------------------------------------------------------- 1 | .tableactionicon:hover { 2 | cursor: pointer; 3 | } 4 | 5 | .addToGlobalLibrarySuccess { 6 | color: green; 7 | } 8 | 9 | .addToGlobalLibraryError { 10 | color: red 11 | } -------------------------------------------------------------------------------- /src/components/Register/Register.css: -------------------------------------------------------------------------------- 1 | .register-container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | min-height: 100vh; 7 | padding: 16px; 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/Settings/utils/alert.js: -------------------------------------------------------------------------------- 1 | import { atom } from 'recoil'; 2 | const settings = JSON.parse(localStorage.getItem('settings')); 3 | export const userSettingsAtom = atom({ 4 | key: 'userSettings', 5 | default: settings ?? [], 6 | }); 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{js,jsx,ts,tsx}'], 4 | important: '#root', 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | }; 10 | -------------------------------------------------------------------------------- /src/server/routes/supportRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { newSupport } = require('../controller/supportController'); 5 | router.post('/newsupport', newSupport); 6 | module.exports = router; 7 | -------------------------------------------------------------------------------- /src/server/routes/reviewRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { newReview } = require('../controller/reviewController'); 5 | 6 | router.post('/newreview', newReview); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /src/server/routes/settingsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { userSettings } = require('../controller/settingsController'); 4 | router.post('/user-settings', userSettings); 5 | 6 | module.exports = router; -------------------------------------------------------------------------------- /src/tests/mocks/server.js: -------------------------------------------------------------------------------- 1 | // src/mocks/server.js 2 | import { setupServer } from 'msw/node'; 3 | import { handlers } from './handlers'; 4 | 5 | // This configures a request mocking server with the given request handlers. 6 | export const server = setupServer(...handlers); 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsxSingleQuote": true, 3 | "singleQuote": true, 4 | "semi": true, 5 | "printWidth": 80, 6 | "trailingComma": "es5", 7 | "bracketSpacing": true, 8 | "tabWidth": 4, 9 | "useTabs": false, 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | We acknowledge the following people for their dedications to make this project a success. 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/server/routes/profileRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { newProfile, getProfile } = require('../controller/profileController'); 5 | router.post('/new-profile', newProfile); 6 | router.get('/get-profile', getProfile); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord Chat 4 | url: 5 | about: Ask questions and discuss with other Vben users in real time. 6 | - name: Questions & Discussions 7 | url: https://github.com/gbowne1/codebooker/discussions 8 | about: Use GitHub discussions for message-board style questions and discussions. 9 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind components; 2 | @tailwind utilities; 3 | 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 7 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 8 | 'Helvetica Neue', sans-serif; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/pages/Settings/languageOptions.js: -------------------------------------------------------------------------------- 1 | import { appLanguageOptions } from './utils/i18n'; 2 | import { sortLangCodes } from './utils/language'; 3 | const sorted = sortLangCodes(appLanguageOptions.map((item) => item.code)); 4 | 5 | export const languageOptions = appLanguageOptions 6 | .sort((a, b) => sorted.indexOf(a.code) - sorted.indexOf(b.code)) 7 | .map((opt) => ({ 8 | id: opt.code, 9 | name: `${opt.name}${opt.nativeName ? ` — ${opt.nativeName}` : ''}`, 10 | })); 11 | -------------------------------------------------------------------------------- /SPONSORS.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: sponsors 3 | title: Sponsoring 4 | permalink: /sponsors/ 5 | --- 6 | 7 | ## Sponsors 8 | 9 | | Name | GitHub | Social | 10 | | ------------- | -------------------------------------- | ------------------------------ | 11 | | Gregory Bowne | [@gbowne1](https://github.com/gbowne1) | | 12 | 13 | ## Sponsoring this project 14 | 15 | If you would like to sponsor the development 16 | -------------------------------------------------------------------------------- /src/server/setup/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | mongo: 5 | image: mongo 6 | container_name: db-mongo-server 7 | restart: always 8 | ports: 9 | # : 10 | - 27123:27017 11 | environment: 12 | MONGO_INITDB_DATABASE: test 13 | MONGO_INITDB_ROOT_USERNAME: test # MongoDB username 14 | MONGO_INITDB_ROOT_PASSWORD: test # MongoDB password 15 | -------------------------------------------------------------------------------- /src/server/model/feedBackModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const feedbackSchema = new mongoose.Schema({ 4 | rating: { type: Number, required: true }, 5 | author: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: 'User', 9 | }, 10 | feedback: { type: String, required: true }, 11 | createdAt: { type: Date, default: Date.now }, 12 | }); 13 | 14 | const FeedBack = mongoose.model('FeedBack', feedbackSchema); 15 | 16 | module.exports = FeedBack; 17 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then( 4 | ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 5 | getCLS(onPerfEntry); 6 | getFID(onPerfEntry); 7 | getFCP(onPerfEntry); 8 | getLCP(onPerfEntry); 9 | getTTFB(onPerfEntry); 10 | } 11 | ); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/server/routes/booksRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { 5 | getAllBooks, 6 | newBook, 7 | deleteBook, 8 | getAllFromFile, 9 | addBookPersonalLibrary, 10 | } = require('../controller/booksController'); 11 | const { get } = require('lodash'); 12 | 13 | router.get('/getall', getAllBooks); 14 | router.post('/newbook', newBook); 15 | router.post('/add-book-to-personal-library', addBookPersonalLibrary); 16 | router.delete('/:id', deleteBook); 17 | router.get('/filedata', getAllFromFile); 18 | 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /src/ProtectedRoute.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet, Navigate } from 'react-router-dom'; 3 | import { useJwt } from 'react-jwt'; 4 | const UserAuthenticated = () => { 5 | const token = JSON.parse(localStorage.getItem('token')); 6 | const { decodedToken, isExpired } = useJwt(token); 7 | console.log(decodedToken); 8 | // const user = JSON.parse(localStorage.getItem('user')); 9 | let isloggedin = false; 10 | if (token && token.length !== 0) { 11 | isloggedin = !isExpired; 12 | } 13 | return
{isloggedin ? : }
; 14 | }; 15 | 16 | export default UserAuthenticated; 17 | -------------------------------------------------------------------------------- /src/server/model/tokenModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const tokenSchema = new mongoose.Schema({ 4 | userId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | ref: 'User', 8 | }, 9 | token: { 10 | type: String, 11 | required: true, 12 | }, 13 | 14 | created_on: { 15 | type: Date, 16 | required: true, 17 | }, 18 | expires_in: { 19 | type: Date, 20 | required: true, 21 | index: { expires: '1s' }, 22 | }, 23 | }); 24 | 25 | const Token = mongoose.model('Token', tokenSchema); 26 | 27 | module.exports = Token; 28 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | // src/setupTests.js 7 | import { server } from './tests/mocks/server.js'; 8 | // Establish API mocking before all tests. 9 | beforeAll(() => server.listen()); 10 | 11 | // Reset any request handlers that we may add during the tests, 12 | // so they don't affect other tests. 13 | afterEach(() => server.resetHandlers()); 14 | 15 | // Clean up after the tests are finished. 16 | afterAll(() => server.close()); 17 | -------------------------------------------------------------------------------- /src/components/SettingIcon/Setting.css: -------------------------------------------------------------------------------- 1 | .app-container { 2 | display: flex; 3 | justify-content: flex-end; 4 | align-items: center; 5 | /* height: 60px; */ 6 | /* padding-right: 20px; */ 7 | margin-left: 15px; 8 | /* background-color: #f0f0f0; */ 9 | } 10 | 11 | .settings-sidebar { 12 | position: fixed; 13 | top: 0; 14 | right: -200px; 15 | width: 200px; 16 | height: 40vh; 17 | padding: 20px; 18 | margin-top: 80px; 19 | background-color: #1976d2; 20 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 21 | transition: transform 0.3s ease-out; 22 | } 23 | 24 | .settings-sidebar.open { 25 | transform: translateX(-200px); 26 | } 27 | -------------------------------------------------------------------------------- /src/server/model/supportModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const supportSchema = new mongoose.Schema({ 4 | userId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | ref: 'User', 8 | }, 9 | description: { 10 | type: String, 11 | unique: true, 12 | required: true, 13 | }, 14 | status: { 15 | type: String, 16 | enum: ['Open', 'In Progress', 'Resolved'], 17 | default: 'Open', 18 | }, 19 | createdAt: { 20 | type: Date, 21 | default: Date.now, 22 | }, 23 | }); 24 | const Support = mongoose.model('Support', supportSchema); 25 | module.exports = Support; 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Code Booker", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "codebooker192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "codebooker512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#ffffff", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/Settings/Privacy.css: -------------------------------------------------------------------------------- 1 | .button{ 2 | position: relative; 3 | background-color: #d2d2d2; 4 | width:50px; 5 | height: 21px; 6 | border-radius: 100px; 7 | cursor: pointer; 8 | transition: 0.2s; 9 | } 10 | 11 | .button::before{ 12 | position: absolute; 13 | content: ''; 14 | background-color: #fff; 15 | width: 18px; 16 | height: 18px; 17 | border-radius: 100px; 18 | margin: .1em; 19 | transition: 0.2s; 20 | } 21 | 22 | #profile-visibility, 23 | #friends, #public{ 24 | display: none; 25 | } 26 | 27 | input:checked + .button{ 28 | background-color: #1976d2; 29 | } 30 | input:checked + .button::before{ 31 | transform: translateX(28px); 32 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/server/README.md: -------------------------------------------------------------------------------- 1 | 1. First, you'll need to have a running MongoDB server on your localhost. You can do this by navigating into `/src/server/setup/`, then 2 | 1. run `docker-compose up -d`, then `node setup.js` 3 | 2. Or, using bash, execute `setup.sh`. 4 | 5 | > **_NOTE:_** To remove the MongoDB container run `docker-compose down` or, using bash, execute `cleanup.sh` 6 | 7 | 2. Create .env and copy the data in .variable.env and paste it in .env file 8 | 9 | 3. Put the Port number and mongo url 10 | 11 | 4. Run setup.js file inside setup folder to create a dummy user 12 | 13 | ``` 14 | email: "test@gmail.com" or username: "test" 15 | password: "test" 16 | ``` 17 | 18 | 5. Run the index.js to run the server 19 | -------------------------------------------------------------------------------- /src/server/model/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const passportLocalMongoose = require('passport-local-mongoose'); 3 | const { Schema } = mongoose; 4 | 5 | const userSchema = new Schema({ 6 | username: { 7 | type: String, 8 | unique: true, 9 | required: true, 10 | }, 11 | email: { 12 | type: String, 13 | unique: true, 14 | required: true, 15 | }, 16 | password: { 17 | type: String, 18 | required: true, 19 | }, 20 | created_on: { 21 | type: Date, 22 | default: Date.now, 23 | }, 24 | }); 25 | 26 | userSchema.plugin(passportLocalMongoose); 27 | module.exports = mongoose.model('User', userSchema); 28 | -------------------------------------------------------------------------------- /src/server/controller/reviewController.js: -------------------------------------------------------------------------------- 1 | const Book = require('../model/booksModel'); 2 | const Review = require('../model/reviewModel'); 3 | 4 | module.exports.newReview = async (req, res) => { 5 | const { description, rating, id } = req.body; 6 | 7 | try { 8 | const book = await Book.findById(id); 9 | if (book) { 10 | const newreview = new Review({ description, rating }); 11 | book.reviews.push(newreview); 12 | await newreview.save(); 13 | await book.save(); 14 | res.status(200).json(book); 15 | } 16 | } catch (err) { 17 | console.error(err); 18 | res.status(500).json({ message: 'Server Error' }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/server/model/reviewModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const reviewSchema = new mongoose.Schema({ 4 | // For now author is commented because there is no still login in this project so it is difficult to manipulate the data 5 | // author: { 6 | // type: mongoose.Schema.Types.ObjectId, 7 | // ref: 'User' 8 | // }, 9 | description: { 10 | type: String, 11 | required: true, 12 | }, 13 | rating: { 14 | type: Number, 15 | default: 0, 16 | }, 17 | createdAt: { 18 | type: Date, 19 | default: Date.now, 20 | }, 21 | }); 22 | 23 | const Review = mongoose.model('Review', reviewSchema); 24 | 25 | module.exports = Review; 26 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import './pages/Settings/utils/i18n'; 6 | import reportWebVitals from './reportWebVitals'; 7 | import { StyledEngineProvider } from '@mui/styled-engine'; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | // If you want to start measuring performance in your app, pass a function 19 | // to log results (for example: reportWebVitals(console.log)) 20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 21 | reportWebVitals(); 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | # [Unreleased] - 2023-04-09 9 | 10 | ## Added 11 | 12 | - Initial Commit 13 | 14 | ### Fixed 15 | 16 | ### Changed 17 | 18 | ### Removed 19 | 20 | # [0.0.1] - 2023-04-09 21 | 22 | - Initial Commit 23 | 24 | ## Added 25 | 26 | - Initial Commit 27 | - Adding initial files and components. 28 | 29 | ## Fixed 30 | 31 | ## Changed 32 | 33 | ## Removed 34 | 35 | [unreleased]: https://github.com/gbowne1/codebooker/compare/v1.1.1...HEAD 36 | [0.0.1]: https://github.com/gbowne1/codebooker/releases/tag/v0.0.1 37 | -------------------------------------------------------------------------------- /src/server/model/booksModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const bookSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: { type: String, required: true }, 6 | category: { type: String, required: true }, 7 | description: { type: String }, 8 | publisher: { type: String }, 9 | rating: { type: Number }, 10 | ISBN: { type: String, unique: true }, 11 | year: { type: Number }, 12 | edition: { type: Number }, 13 | reviews: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Review' }], 14 | userId: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | required: true, 17 | ref: 'User', 18 | }, 19 | createdAt: { type: Date, default: Date.now }, 20 | }); 21 | 22 | const Book = mongoose.model('Book', bookSchema); 23 | 24 | module.exports = Book; 25 | -------------------------------------------------------------------------------- /src/server/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { 4 | login, 5 | signup, 6 | logout, 7 | forgotPassword, 8 | resetPassword, 9 | } = require('../controller/userController'); 10 | const { 11 | allfeedback, 12 | addfeedback, 13 | emailFeedback, 14 | } = require('../controller/feedBackController'); 15 | 16 | //auth 17 | router.post('/login', login); 18 | router.post('/logout', logout); 19 | router.post('/register', signup); 20 | 21 | //forgot password 22 | router.post('/forgot-password', forgotPassword); 23 | router.put('/reset-password/:token', resetPassword); 24 | 25 | //feedback routes 26 | router.get('/feedback/all', allfeedback); 27 | router.post('/feedback/new', addfeedback); 28 | router.post('/feedback/mail', emailFeedback); 29 | 30 | module.exports = router; 31 | -------------------------------------------------------------------------------- /src/server/setup/setup.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ path: '../.env' }); 2 | const bcrypt = require('bcryptjs'); 3 | const mongoose = require('mongoose'); 4 | mongoose.connect(process.env.MONGO_DB); 5 | console.log(__dirname + ''); 6 | mongoose.Promise = global.Promise; // Tell Mongoose to use ES6 promises 7 | 8 | async function createUser() { 9 | try { 10 | const User = require('../model/userModel'); 11 | const passwordHash = await bcrypt.hash('test', 12); 12 | await new User({ 13 | username: 'test', 14 | email: 'test@gmail.com', 15 | password: passwordHash, 16 | }).save(); 17 | console.log('👍👍👍👍👍👍👍👍 User created : Done!'); 18 | process.exit(); 19 | } catch (e) { 20 | console.log('\n👎👎👎👎👎👎👎👎 Error! The Error info is below'); 21 | console.log(e); 22 | process.exit(); 23 | } 24 | } 25 | createUser(); 26 | -------------------------------------------------------------------------------- /src/server/model/userBooksLibraryModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userLibraryBookSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | author: { type: String, required: true }, 6 | category: { type: String, required: true }, 7 | description: { type: String }, 8 | publisher: { type: String }, 9 | rating: { type: Number }, 10 | ISBN: { type: String, unique: true }, 11 | year: { type: Number }, 12 | edition: { type: Number }, 13 | reviews: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Review' }], 14 | userId: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | required: true, 17 | ref: 'User', 18 | }, 19 | createdAt: { type: Date, default: Date.now }, 20 | }); 21 | 22 | const UserBooksLibraryModel = mongoose.model( 23 | 'UserBooksLibraryModel', 24 | userLibraryBookSchema 25 | ); 26 | 27 | module.exports = UserBooksLibraryModel; 28 | -------------------------------------------------------------------------------- /src/components/Hooks/useProfileData.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { useMemo, useState } from 'react'; 3 | 4 | const useProfileData = () => { 5 | const [loading, setLoading] = useState(false); 6 | const [profileData, setProfileData] = useState(null); 7 | 8 | const getProfileDataFromDB = async () => { 9 | try { 10 | setLoading(true); 11 | const response = await axios.get( 12 | 'http://localhost:3001/api/profile/get-profile' 13 | ); 14 | if (response.status === 200) { 15 | setProfileData(response.data); 16 | } 17 | } catch (error) { 18 | console.error('Error fetching user profile:', error); 19 | } finally { 20 | setLoading(false); 21 | } 22 | }; 23 | useMemo(() => { 24 | getProfileDataFromDB(); 25 | }, []); 26 | return { loading, profileData }; 27 | }; 28 | 29 | export default useProfileData; 30 | -------------------------------------------------------------------------------- /src/pages/Settings/utils/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { locale } from '../../../assets/languages'; 3 | import { initReactI18next } from 'react-i18next'; 4 | import { getLocaleInfo } from './language'; 5 | 6 | // Languages 7 | export const langCodes = Object.keys(locale); 8 | const savedLanguage = JSON.parse(localStorage.getItem('langCode')); 9 | const resources = Object.fromEntries( 10 | Object.entries(locale).map((entry) => [entry[0], { translation: entry[1] }]) 11 | ); 12 | i18n.use(initReactI18next).init({ 13 | fallbackLng: savedLanguage || 'en', 14 | resources, 15 | interpolation: { 16 | escapeValue: false, 17 | }, 18 | }); 19 | 20 | export const appLanguageOptions = langCodes.map((lang) => { 21 | const langObj = getLocaleInfo(lang); 22 | if (!langObj) 23 | throw new Error( 24 | `Language with code ${lang} cannot be found in database` 25 | ); 26 | return langObj; 27 | }); 28 | export default i18n; 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | settings: { 7 | react: { 8 | version: 'detect', 9 | }, 10 | }, 11 | extends: [ 12 | 'plugin:react/recommended', 13 | 'plugin:prettier/recommended', 14 | 'react-app', 15 | 'react-app/jest', 16 | 'eslint:recommended', 17 | ], 18 | overrides: [ 19 | { 20 | env: { 21 | node: true, 22 | }, 23 | files: ['.eslintrc.{js,cjs}'], 24 | parserOptions: { 25 | sourceType: 'script', 26 | }, 27 | }, 28 | ], 29 | parserOptions: { 30 | ecmaVersion: 'latest', 31 | sourceType: 'module', 32 | }, 33 | plugins: ['react', 'prettier'], 34 | rules: { 35 | 'jsx-quotes': ['error', 'prefer-single'], 36 | quotes: ['error', 'single'], 37 | 'prettier/prettier': ['error', { singleQuote: true }], 38 | 'react/react-in-jsx-scope': 'off', 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gregory Bowne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/ForkClone.md: -------------------------------------------------------------------------------- 1 | # Fork and Clone 2 | 3 | ## Steps to fork the repo: 4 | 5 | ### Step 1 6 | 7 | Go to the main GitHub page of the repo: 8 | 9 | (https://github.com/gbowne1/codebooker/) 10 | 11 | ### Step 2 12 | 13 | Once you are on the main GitHub page of the repo, click the Fork button at the top right of the page. 14 | 15 | ![](fork.png) 16 | 17 | ### Step 3 18 | 19 | You will now be on a page with the heading "Create a new fork". 20 | There are two fields which are required, Owner and Repository name. Make sure the owner is you and the repository name is correct. These fields should already be populated when you cliked to this page in Step 2. 21 | 22 | ![](CreateFork.png) 23 | 24 | ### Step 3 25 | 26 | Click on the green "Create fork" button on the lower right hand corner of the page 27 | 28 | ![](CreateForkBtn.png) 29 | 30 | ### Step 4 31 | 32 | You will now have a forked version of the original repo that is up to date with the master branch of the original repo. 33 | 34 | ![](ForkedBranch.png) 35 | 36 | ## Steps to clone the repo: 37 | 38 | ### Step 1 39 | 40 | Clone this repo to your local machine using (https://github.com/gbowne1/codebooker.git) 41 | -------------------------------------------------------------------------------- /src/components/BookSearch/BookSearch.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './BookSearch.css'; 3 | import PropTypes from 'prop-types'; 4 | import PopperPopupState from '../SearchPopup/PopupState'; 5 | 6 | function SearchWindow({ matches, filter, setFilter, isDarkMode }) { 7 | return ( 8 |
9 | {matches ? ( 10 | 16 | ) : ( 17 | setFilter(e.target.value)} 23 | /> 24 | )} 25 |
26 | ); 27 | } 28 | 29 | SearchWindow.propTypes = { 30 | matches: PropTypes.bool, 31 | filter: PropTypes.string, 32 | setFilter: PropTypes.func, 33 | isDarkMode: PropTypes.bool, 34 | }; 35 | 36 | export default SearchWindow; 37 | -------------------------------------------------------------------------------- /src/pages/Login/Login.css: -------------------------------------------------------------------------------- 1 | .login-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | gap: 1.5rem; 7 | height: 100vh; 8 | font-family: 'Roboto', 'Open Sans', sans-serif; 9 | } 10 | 11 | .login-wrapper-icon { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | .login-wrapper-input { 19 | display: flex; 20 | flex-direction: column; 21 | gap: 1rem; 22 | width: 35%; 23 | } 24 | 25 | .MuiInputBase-input { 26 | font-family: 'Roboto', 'Open Sans', sans-serif; 27 | } 28 | 29 | .checkbox-wrapper input { 30 | margin-right: 0.5rem; 31 | } 32 | 33 | .login-button { 34 | background-color: #1976d2; 35 | color: #fff; 36 | padding: 0.5rem 1.25rem; 37 | border: none; 38 | font-weight: 700; 39 | font-size: 1.25rem; 40 | border-radius: 0.25rem; 41 | cursor: pointer; 42 | width: 35%; 43 | } 44 | 45 | .login-wrapper-forgot-register { 46 | display: flex; 47 | width: 35%; 48 | justify-content: space-between; 49 | align-items: center; 50 | flex-wrap: wrap; 51 | } 52 | -------------------------------------------------------------------------------- /src/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "Backend Server for the CodeBooker", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "server": "nodemon src/server/index.js" 9 | }, 10 | "proxy": "http://localhost:3001", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/gbowne1/codebooker.git" 14 | }, 15 | "keywords": [ 16 | "CodeBooker" 17 | ], 18 | "author": "gbowne1,Logesh", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/gbowne1/codebooker/issues" 22 | }, 23 | "homepage": "https://github.com/gbowne1/codebooker#readme", 24 | "dependencies": { 25 | "bcryptjs": "^2.4.3", 26 | "body-parser": "^1.20.2", 27 | "cors": "^2.8.5", 28 | "dotenv": "^16.3.1", 29 | "express": "^4.18.2", 30 | "express-session": "^1.17.3", 31 | "jsonwebtoken": "^9.0.1", 32 | "mongodb": "^5.7.0", 33 | "mongoose": "^7.3.4", 34 | "nodemailer": "^6.9.4", 35 | "passport": "^0.6.0", 36 | "passport-local": "^1.0.0", 37 | "passport-local-mongoose": "^8.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/pages/Support/Support.css: -------------------------------------------------------------------------------- 1 | .support-wrapper { 2 | position: relative; 3 | max-width: 100vw; 4 | width: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | 9 | .global-header { 10 | width: 100%; 11 | background-color: #1976d2; 12 | z-index: 1; 13 | position: fixed; 14 | } 15 | 16 | .help-navigation { 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | padding: 0 10px 0 10px; 21 | height: 60px; 22 | } 23 | 24 | .support-search-form { 25 | display: flex; 26 | height: 40px; 27 | width: 100%; 28 | position: relative; 29 | } 30 | 31 | .search { 32 | height: 100%; 33 | flex: 1; 34 | border: none; 35 | outline: none; 36 | padding: 10px; 37 | } 38 | 39 | .support-form { 40 | width: 100%; 41 | display: grid; 42 | place-items: center; 43 | } 44 | 45 | .support-input-wrapper { 46 | display: flex; 47 | flex-direction: column; 48 | gap: 1rem; 49 | width: 35%; 50 | } 51 | 52 | .form-group { 53 | display: flex; 54 | flex-direction: column; 55 | } 56 | 57 | @media screen and (max-width: 991px) { 58 | .help-navigation { 59 | gap: 5px; 60 | } 61 | 62 | .login-wrapper-input { 63 | width: 80%; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/pages/ForgotPassword/ForgotPassword.css: -------------------------------------------------------------------------------- 1 | .forgotpassword-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | gap: 1.5rem; 7 | height: 100vh; 8 | font-family: 'Roboto', 'Open Sans', sans-serif; 9 | } 10 | 11 | .forgotpassword-wrapper-icon { 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | .forgotpassword-wrapper-input { 19 | display: flex; 20 | flex-direction: column; 21 | gap: 1rem; 22 | width: 35%; 23 | } 24 | 25 | .MuiInputBase-input { 26 | font-family: 'Roboto', 'Open Sans', sans-serif; 27 | } 28 | 29 | .checkbox-wrapper input { 30 | margin-right: 0.5rem; 31 | } 32 | 33 | .forgotpassword-button { 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | background-color: #1976d2; 38 | color: #fff; 39 | padding: 0.5rem 1.25rem; 40 | border: none; 41 | font-weight: 500; 42 | font-size: 0.9rem; 43 | border-radius: 0.25rem; 44 | cursor: pointer; 45 | width: 35%; 46 | } 47 | 48 | .forgotpassword-wrapper-cancel { 49 | display: flex; 50 | width: 35%; 51 | justify-content: space-between; 52 | align-items: center; 53 | flex-wrap: wrap; 54 | } 55 | -------------------------------------------------------------------------------- /src/pages/PrivacyPolicy/PrivacyPolicy.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@700&family=Roboto&display=swap'); 2 | 3 | body { 4 | background-color: #f5f5f5; 5 | color: #333; 6 | font-family: 'Josefin Sans', sans-serif; 7 | } 8 | 9 | h1 { 10 | font-weight: bold; 11 | } 12 | 13 | /* @media screen and (max-width: 1530px) { 14 | h1 { 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | } 19 | } */ 20 | 21 | .privacy-wrapper { 22 | margin-left: 50px; 23 | margin-right: 50px; 24 | padding-bottom: 5rem; 25 | } 26 | 27 | .privacy-title { 28 | padding-top: 9rem; 29 | } 30 | 31 | .mt-spacing { 32 | margin-top: 4.5rem; 33 | display: flex; 34 | gap: 3rem; 35 | } 36 | 37 | .update-info { 38 | padding-top: .3rem; 39 | border-top: .5rem solid #5c5c5c; 40 | width: 360px; 41 | } 42 | 43 | .main-text { 44 | flex-wrap: wrap; 45 | max-width: 760px; 46 | } 47 | 48 | .list{ 49 | font-size: 16px; 50 | } 51 | 52 | @media screen and (max-width: 991px){ 53 | .mt-spacing{ 54 | flex-direction: column; 55 | } 56 | 57 | .main-text{ 58 | max-width: unset; 59 | } 60 | .update-info { 61 | width: auto; 62 | } 63 | 64 | .privacy-wrapper { 65 | margin-left: 25px; 66 | margin-right: 25px; 67 | } 68 | } -------------------------------------------------------------------------------- /src/tests/mocks/handlers.js: -------------------------------------------------------------------------------- 1 | // src/mocks/handlers.js 2 | import { rest } from 'msw'; 3 | const user = { 4 | _id: '64c891e5ecd534e790f4f518', 5 | username: 'test', 6 | email: 'test@gmail.com', 7 | password: '$2a$12$DboEjKLb6kVTbO92x6vgiOeOKoO2eGUZ1qgzIs8wQ9kXTppKb/7qu', 8 | created_on: '2023-08-01T05:02:29.461Z', 9 | __v: 0, 10 | }; 11 | 12 | export const handlers = [ 13 | // Handles a POST /login request 14 | // Mock the login API endpoint 15 | rest.post('http://localhost:3001/api/user/login', (req, res, ctx) => { 16 | return res( 17 | ctx.status(200), 18 | ctx.json({ 19 | token: 'mockToken', 20 | user: user, 21 | }) 22 | ); 23 | }), 24 | 25 | // Mock the signup API endpoint 26 | rest.post('http://localhost:3001/api/user/register', (req, res, ctx) => { 27 | const { username, email, password } = req.body; 28 | if (!username || !email || !password) { 29 | return res( 30 | ctx.status(400), 31 | ctx.json({ message: 'Invalid input data' }) 32 | ); 33 | } 34 | 35 | // Simulate a successful registration 36 | return res( 37 | ctx.status(200), 38 | ctx.json({ 39 | token: 'mockToken', 40 | user: user, 41 | }) 42 | ); 43 | }), 44 | ]; 45 | -------------------------------------------------------------------------------- /src/components/BookSearch/BookSearch.css: -------------------------------------------------------------------------------- 1 | /* .BookSearch-header {} */ 2 | .search-window { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | box-sizing: border-box; 7 | } 8 | 9 | .search-input { 10 | width: 300px; 11 | padding: 0.563rem; 12 | font-size: 0.875rem; 13 | border: none; 14 | border-radius: 0.5rem; 15 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 16 | margin-right: 10px; 17 | letter-spacing: 0.5px; 18 | } 19 | .search-input:focus, 20 | .search-input:active, 21 | .search-input:hover { 22 | box-shadow: 2px 4px 16px rgba(28, 32, 36, 0.6); 23 | } 24 | 25 | .search-input:focus { 26 | outline: none; 27 | } 28 | 29 | .search-button { 30 | background-color: rgb(0, 98, 204); 31 | color: #fff; 32 | border: none; 33 | padding: 0.563rem; 34 | font-size: 0.875rem; 35 | cursor: pointer; 36 | letter-spacing: 0.5px; 37 | } 38 | 39 | .search-button:hover { 40 | background-color: #0062cc; 41 | } 42 | 43 | .search-input.small { 44 | width: 100%; 45 | box-sizing: border-box; 46 | } 47 | 48 | .input_container { 49 | display: flex; 50 | box-sizing: border-box; 51 | align-items: center; 52 | color: grey; 53 | padding-left: 10px; 54 | height: 50px; 55 | width: 100vw; 56 | margin-bottom: 100px; 57 | } 58 | 59 | .input_container input { 60 | color: black; 61 | box-sizing: border-box; 62 | background-color: transparent; 63 | } 64 | -------------------------------------------------------------------------------- /src/components/BookDetails/BookDetails.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState, useEffect } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | function BookDetails({ bookId }) { 5 | const [bookDetails, setBookDetails] = useState(null); 6 | 7 | const getBookDetails = useCallback(async () => { 8 | try { 9 | // make API call to fetch book details 10 | const response = await fetch(`/api/books/${bookId}`); 11 | const data = await response.json(); 12 | // set book details in state 13 | setBookDetails(data); 14 | } catch (error) { 15 | // handle error 16 | console.error(error); 17 | } 18 | }, [bookId, setBookDetails]); 19 | 20 | // call getBookDetails when component mounts 21 | useEffect(() => { 22 | getBookDetails(); 23 | }, [getBookDetails]); 24 | 25 | return ( 26 |
27 | {bookDetails ? ( 28 |
29 |

{bookDetails.title}

30 |

Author: {bookDetails.author}

31 |

Genre: {bookDetails.genre}

32 |

Description: {bookDetails.description}

33 |
34 | ) : ( 35 |

Loading book details...

36 | )} 37 |
38 | ); 39 | } 40 | 41 | BookDetails.propTypes = { 42 | bookId: PropTypes.number, 43 | }; 44 | export default BookDetails; 45 | -------------------------------------------------------------------------------- /src/pages/Home/Home.css: -------------------------------------------------------------------------------- 1 | .search-window { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | transition: 6 | background-color 0.3s, 7 | color 0.3s; 8 | padding: 0px 10px; 9 | } 10 | 11 | .light { 12 | background-color: #f5f5f5; 13 | color: #333; 14 | } 15 | 16 | .dark { 17 | background-color: #333; 18 | color: #f5f5f5; 19 | } 20 | 21 | .AppBar { 22 | min-height: 70px; 23 | } 24 | 25 | .codebooker-logo{ 26 | position: relative; 27 | height: 192px; 28 | width: 192px; 29 | } 30 | 31 | .codebooker-logo img { 32 | width: 100%; 33 | height: 100%; 34 | object-fit: contain; 35 | } 36 | 37 | .animate{ 38 | top: 53%; 39 | position: absolute; 40 | height: 17px; 41 | width: 190px; 42 | background: #333; 43 | overflow: hidden; 44 | border-radius: 5px; 45 | z-index: -1; 46 | } 47 | 48 | .animate::before{ 49 | content: ''; 50 | position: absolute; 51 | inset: -5px; 52 | background: linear-gradient(315deg, #00ccff, #d400d4); 53 | transition: .5s; 54 | animation: circle-text-animation 4s linear infinite; 55 | } 56 | 57 | .animate::after{ 58 | content: ''; 59 | position: absolute; 60 | inset: 2px; 61 | background: linear-gradient(315deg, #00ccff, #d400d4); 62 | border-radius: 5px; 63 | } 64 | 65 | @keyframes circle-text-animation { 66 | 0%{ 67 | transform: rotate(0deg); 68 | } 69 | 100%{ 70 | transform: rotate(360deg); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/FormikContainer/CreateProfileForm/SelectInputField.jsx: -------------------------------------------------------------------------------- 1 | import { FormControl, FormHelperText, MenuItem, Select } from '@mui/material'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | const SelectInputField = ({ name, value, options }) => { 5 | return ( 6 | 7 | 20 | {options.error || ' '} 21 | 22 | ); 23 | }; 24 | SelectInputField.propTypes = { 25 | name: PropTypes.string.isRequired, 26 | value: PropTypes.string.isRequired, 27 | options: PropTypes.shape({ 28 | value: PropTypes.string.isRequired, 29 | onChange: PropTypes.func.isRequired, 30 | onBlur: PropTypes.func.isRequired, 31 | items: PropTypes.arrayOf( 32 | PropTypes.shape({ 33 | value: PropTypes.string.isRequired, 34 | label: PropTypes.string.isRequired, 35 | }) 36 | ).isRequired, 37 | error: PropTypes.string, // Optional error message 38 | }).isRequired, 39 | }; 40 | export default SelectInputField; 41 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # CodeBooker 2 | 3 | This is a book recommendation app created with React 18.2 and MUI. 4 | 5 | It is targeted specifically for coders/programmers that are looking for book recommendations on programming/coding. 6 | 7 | It allows users to organize, recommend and review any book on coding or programming. 8 | 9 | This project is built using React 18.2 and MUI v5.11.6 with core components built with JSX. Funtionality with JS. 10 | 11 | ## Frontend 12 | 13 | - [ ] Create a User Profile context menu below the UserIcon 14 | 15 | - Menu items include Login/Logout toggle with Logi & LogoutIcon '; 16 | 17 | - [ ] Create a 200-250px Drawer that opens from the Hamburger Menu. 18 | 19 | - [ ] Create a functional dark mode / light mode theme controller using the toggle switch just to the left of the UserIcon. 20 | 21 | - Darkmode color themes be black, gray, white 22 | - Lightmode will be White and some good colorization. Color suggestions will be Red, Orange, Yellow, Green, Blue, Indigo, Violet 23 | 24 | - [ ] Create a component for the view that renders the users library of books they have added. 25 | 26 | ## Backend 27 | 28 | - [ ] Create initial backend with Prisma ORM or MongoDB. 29 | - [ ] Create an external API users can consume in their application. 30 | 31 | ## In Progress 32 | 33 | - [ ] Creating Library component 34 | - [ ] create a table of books 35 | 36 | ## Completed Column ✓ 37 | 38 | - [x] Initial Work 39 | - Create TopNav 40 | - [x] Initial Commit to GitHub 41 | - [ ] Created .gitub special files 42 | - [ ] Created community documentation 43 | - [ ] Created developer documentation 44 | -------------------------------------------------------------------------------- /src/components/Library/Library.css: -------------------------------------------------------------------------------- 1 | .Library-header { 2 | background-color: #90caf9; 3 | margin-top: 66px; 4 | padding-top: 66px; 5 | margin-left: 255px; 6 | padding-left: 255px; 7 | } 8 | 9 | .Add-book-button-container { 10 | position: fixed; 11 | bottom: 35px; 12 | z-index: 5; 13 | } 14 | 15 | .more-container { 16 | position: relative; 17 | } 18 | 19 | .more-horiz-icon { 20 | position: relative; 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | border-radius: 50%; 25 | height: 30px; 26 | width: 30px; 27 | outline: none; 28 | border: none; 29 | background-color: inherit; 30 | } 31 | 32 | .more-horiz-icon:hover { 33 | background-color: #f0f0f0; 34 | color: #aec6cf; 35 | transition: 300ms ease-in-out; 36 | cursor: pointer; 37 | } 38 | 39 | .more-span { 40 | background-color: #333333; 41 | padding: 3px; 42 | font-size: 12px; 43 | position: absolute; 44 | top: 32px; 45 | z-index: 1; 46 | border-radius: 2px; 47 | line-height: 15px; 48 | display: none; 49 | } 50 | 51 | .more-horiz-icon:hover ~ .more-span { 52 | color: #aec6cf; 53 | display: block; 54 | } 55 | .s{ 56 | position: absolute; 57 | top: 50%; 58 | left: 50%; 59 | transform: translate(-50%, -50%); 60 | } 61 | .skeleton{ 62 | width: 100%; 63 | height: 100vh; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | } 68 | 69 | /* .skeleton { 70 | width: 100%; 71 | height: 100vh; 72 | display: flex; 73 | justify-content: center; 74 | align-items: center; 75 | } */ 76 | -------------------------------------------------------------------------------- /src/server/model/settingsModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const settingSchema = new mongoose.Schema({ 4 | userId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | ref: 'User', 8 | }, 9 | push_notification: { 10 | new_book_release: { 11 | type: Boolean, 12 | default: false, 13 | }, 14 | book_recommendation: { 15 | type: Boolean, 16 | default: false, 17 | }, 18 | book_review_update: { 19 | type: Boolean, 20 | default: false, 21 | } 22 | }, 23 | email_notification: { 24 | codebooker_updates: { 25 | type: Boolean, 26 | default: false, 27 | }, 28 | tips: { 29 | type: Boolean, 30 | default: false, 31 | }, 32 | suggestions: { 33 | type: Boolean, 34 | default: false, 35 | } 36 | }, 37 | privacy: { 38 | keep_profile_private: { 39 | type: Boolean, 40 | default: false, 41 | }, 42 | friends_only: { 43 | type: Boolean, 44 | default: false, 45 | }, 46 | public: { 47 | type: Boolean, 48 | default: false, 49 | } 50 | }, 51 | reading_preferences: { 52 | favorite_genre: { 53 | type:[String], 54 | default: undefined 55 | }, 56 | favorite_author: { 57 | type:[String] 58 | }, 59 | }, 60 | created_at: { 61 | type: Date, 62 | default: Date.now 63 | } 64 | }); 65 | 66 | const Settings = mongoose.model('Settings', settingSchema); 67 | module.exports = Settings; -------------------------------------------------------------------------------- /src/server/model/profileModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const profileSchema = new mongoose.Schema({ 4 | userId: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | required: true, 7 | ref: 'User', 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | ref: 'User', 14 | }, 15 | profile_picture: { 16 | type: String, 17 | }, 18 | bio:{ 19 | type:String, 20 | required: true, 21 | }, 22 | name:{ 23 | type:String, 24 | required: true, 25 | }, 26 | alias:{ 27 | type: String, 28 | required: true, 29 | unique:true 30 | }, 31 | location:{ 32 | type: String, 33 | }, 34 | city:{ 35 | type: String, 36 | }, 37 | education:{ 38 | type: String, 39 | }, 40 | age: { 41 | type: Number, 42 | default: null, 43 | required: true, 44 | }, 45 | display_age: { 46 | type: Boolean, 47 | }, 48 | occupation:{ 49 | type: String, 50 | }, 51 | skills:{ 52 | type: String, 53 | }, 54 | text_editor: { 55 | type: String, 56 | }, 57 | operating_system: { 58 | type: String, 59 | }, 60 | portfolio_link: { 61 | type: String, 62 | }, 63 | youtube:{ 64 | type: String, 65 | }, 66 | twitter:{ 67 | type: String, 68 | }, 69 | linkedIn:{ 70 | type: String, 71 | }, 72 | github: { 73 | type: String, 74 | }, 75 | gitlab: { 76 | type: String, 77 | }, 78 | created_at: { 79 | type: Date, 80 | default: Date.now 81 | } 82 | }); 83 | 84 | const Profile = mongoose.model('Profile', profileSchema); 85 | module.exports = Profile; -------------------------------------------------------------------------------- /src/components/Feedback/Feedback.css: -------------------------------------------------------------------------------- 1 | /* MODAL Styling */ 2 | .overlay { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | background-color: rgba(0, 0, 0, 0.7); 9 | } 10 | 11 | .feedback-modal { 12 | position: absolute; 13 | top: 50%; 14 | left: 50%; 15 | width: 55%; 16 | height: 60%; 17 | transform: translate(-50%, -50%); 18 | background: white; 19 | padding: 20px; 20 | border-radius: 10px; 21 | } 22 | 23 | .close-button { 24 | position: relative; 25 | z-index: 9999; 26 | border: none; 27 | font-weight: bold; 28 | font-size: 1.6vw; 29 | cursor: pointer; 30 | background: none; 31 | } 32 | 33 | /* FORM Styling */ 34 | .modal-form { 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: space-around; 38 | height: 80%; 39 | } 40 | 41 | .feedback-header { 42 | text-align: center; 43 | font-weight: bold; 44 | font-size: 2vw; 45 | } 46 | 47 | .feedback-textarea { 48 | width: 100%; 49 | height: 120px; 50 | margin-bottom: 10px; 51 | padding: 10px; 52 | border-radius: 5px; 53 | border: 1px solid #ccc; 54 | resize: none; 55 | } 56 | 57 | .button-group { 58 | display: flex; 59 | justify-content: space-around; 60 | margin-top: auto; 61 | } 62 | 63 | .button-group button { 64 | border-radius: 3px; 65 | color: white; 66 | background-color: rgb(46, 125, 50); 67 | font-weight: bold; 68 | font-size: 1em; 69 | padding: 0.5em; 70 | cursor: pointer; 71 | transition: box-shadow 0.3s ease; 72 | } 73 | 74 | .button-group button:hover { 75 | background-color: rgb(36, 110, 40); 76 | } 77 | 78 | .feedback-textarea { 79 | resize: none; 80 | } 81 | 82 | .rating-container { 83 | display: flex; 84 | justify-content: center; 85 | margin-bottom: 20px; 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/Settings/Notifications.css: -------------------------------------------------------------------------------- 1 | .button{ 2 | position: relative; 3 | background-color: #d2d2d2; 4 | width:50px; 5 | height: 21px; 6 | border-radius: 100px; 7 | cursor: pointer; 8 | transition: 0.2s; 9 | } 10 | 11 | .button::before{ 12 | position: absolute; 13 | content: ''; 14 | background-color: #fff; 15 | width: 18px; 16 | height: 18px; 17 | border-radius: 100px; 18 | margin: .1em; 19 | transition: 0.2s; 20 | } 21 | #check, #reviews, 22 | #recommendation, 23 | #updates, #tips, 24 | #suggestions{ 25 | display: none; 26 | } 27 | input:checked + .button{ 28 | background-color: #1976d2; 29 | } 30 | input:checked + .button::before{ 31 | transform: translateX(28px); 32 | } 33 | 34 | .select-btn{ 35 | cursor: pointer; 36 | display: flex; 37 | height: 55px; 38 | font-size: 22px; 39 | padding: 0 20px; 40 | justify-content: space-between; 41 | background-color: #d2d2d2; 42 | align-items: center; 43 | } 44 | 45 | .select-btn .inner-text p{ 46 | line-height: 5px; 47 | } 48 | 49 | .select-btn .inner-text p:first-child{ 50 | font-size: 14px; 51 | } 52 | .inner-text .default-lang{ 53 | font-size: 12px; 54 | font-weight: 600; 55 | } 56 | 57 | .content{ 58 | top: 80%; 59 | position: absolute; 60 | width: 100%; 61 | background-color: #f5f3f3; 62 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 63 | 0 8px 16px rgba(0, 0, 0, 0.05); 64 | z-index: 1; 65 | } 66 | 67 | .content-list{ 68 | max-height: 250px; 69 | overflow-y: auto; 70 | } 71 | 72 | .content-list ul { 73 | padding: 0px; 74 | list-style: none; 75 | } 76 | 77 | .content-list ul li { 78 | height: 50px; 79 | padding: 15px 0px 0px 20px; 80 | } 81 | .content-list ul li:hover { 82 | cursor: pointer; 83 | background: #e9e3e3; 84 | color: #1976d2; 85 | } 86 | -------------------------------------------------------------------------------- /src/components/FormikContainer/CreateProfileForm/IDE_Options.js: -------------------------------------------------------------------------------- 1 | const IDE_Options = [ 2 | { 3 | value: 'Android Studio', 4 | label: 'Android Studio', 5 | }, 6 | { 7 | value: 'Arduino IDE', 8 | label: 'Arduino IDE', 9 | }, 10 | { 11 | value: 'Atom', 12 | label: 'Atom', 13 | }, 14 | { 15 | value: 'CLion', 16 | label: 'CLion', 17 | }, 18 | { 19 | value: 'Code::Blocks', 20 | label: 'Code::Blocks', 21 | }, 22 | { 23 | value: 'Eclipse', 24 | label: 'Eclipse', 25 | }, 26 | { 27 | value: 'Emacs', 28 | label: 'Emacs', 29 | }, 30 | { 31 | value: 'IntelliJ IDEA', 32 | label: 'IntelliJ IDEA', 33 | }, 34 | { 35 | value: 'Jupyter Notebook', 36 | label: 'Jupyter Notebook', 37 | }, 38 | { 39 | value: 'NetBeans', 40 | label: 'NetBeans', 41 | }, 42 | { 43 | value: 'PyCharm', 44 | label: 'PyCharm', 45 | }, 46 | { 47 | value: 'PHPStorm', 48 | label: 'PHPStorm', 49 | }, 50 | { 51 | value: 'Rider', 52 | label: 'Rider', 53 | }, 54 | { 55 | value: 'RStudio', 56 | label: 'RStudio', 57 | }, 58 | { 59 | value: 'Sublime Text', 60 | label: 'Sublime Text', 61 | }, 62 | { 63 | value: 'Visual Studio Code (VS Code)', 64 | label: 'Visual Studio Code (VS Code)', 65 | }, 66 | { 67 | value: 'Vim', 68 | label: 'Vim', 69 | }, 70 | { 71 | value: 'Visual Studio', 72 | label: 'Visual Studio', 73 | }, 74 | { 75 | value: 'WebStorm', 76 | label: 'WebStorm', 77 | }, 78 | { 79 | value: 'Xcode', 80 | label: 'Xcode', 81 | }, 82 | ]; 83 | 84 | export default IDE_Options; 85 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Home from './pages/Home/Home'; 3 | import { Route, Routes } from 'react-router-dom'; 4 | import { BrowserRouter as Router } from 'react-router-dom'; 5 | import { RecoilRoot } from 'recoil'; 6 | import Login from './pages/Login/Login'; 7 | import Profile from './pages/Profile/Profile'; 8 | import Register from './components/Register/Register'; 9 | import UserAuthenticated from './ProtectedRoute'; 10 | import ForgotPassword from './pages/ForgotPassword/ForgotPassword'; 11 | import ResetPassword from './pages/ResetPassword/ResetPassword'; 12 | import Support from './pages/Support/Support'; 13 | import PrivacyPolicy from './pages/PrivacyPolicy/PrivacyPolicy'; 14 | import Settings from './pages/Settings/Settings'; 15 | export default function App() { 16 | return ( 17 | 18 | 19 | 20 | }> 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | 26 | } /> 27 | } /> 28 | } /> 29 | } 32 | /> 33 | } 36 | /> 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/FormikContainer/CreateProfileForm/operatingSystemOptions.js: -------------------------------------------------------------------------------- 1 | const operatingSystemOptions = [ 2 | { 3 | value: 'Microsoft Windows', 4 | label: 'Microsoft Windows', 5 | }, 6 | { 7 | value: 'macOS', 8 | label: 'macOS', 9 | }, 10 | { 11 | value: 'Linux', 12 | label: 'Linux', 13 | }, 14 | { 15 | value: 'Android', 16 | label: 'Android', 17 | }, 18 | { 19 | value: 'iOS', 20 | label: 'iOS', 21 | }, 22 | { 23 | value: 'Unix', 24 | label: 'Unix', 25 | }, 26 | { 27 | value: 'Chrome OS', 28 | label: 'Chrome OS', 29 | }, 30 | { 31 | value: 'BSD', 32 | label: 'BSD', 33 | }, 34 | { 35 | value: 'Solaris', 36 | label: 'Solaris', 37 | }, 38 | { 39 | value: 'IBM z/OS', 40 | label: 'IBM z/OS', 41 | }, 42 | { 43 | value: 'tvOS', 44 | label: 'tvOS', 45 | }, 46 | { 47 | value: 'watchOS', 48 | label: 'watchOS', 49 | }, 50 | { 51 | value: 'Windows Server', 52 | label: 'Windows Server', 53 | }, 54 | { 55 | value: 'AIX', 56 | label: 'AIX', 57 | }, 58 | { 59 | value: 'Mobile App Development', 60 | label: 'Mobile App Development', 61 | }, 62 | { 63 | value: 'HP-UX', 64 | label: 'HP-UX', 65 | }, 66 | { 67 | value: 'UI/UX Design', 68 | label: 'UI/UX Design', 69 | }, 70 | { 71 | value: 'Tizen', 72 | label: 'Tizen', 73 | }, 74 | { 75 | value: 'Kali Linux', 76 | label: 'Kali Linux', 77 | }, 78 | { 79 | value: 'Raspberry Pi OS', 80 | label: 'Raspberry Pi OS', 81 | }, 82 | { 83 | value: 'FreeRTOS', 84 | label: 'FreeRTOS', 85 | }, 86 | ]; 87 | export default operatingSystemOptions; 88 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request Template 2 | 3 | ## Description 4 | 5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 6 | 7 | Please delete options that are not relevant and please make sure that you X the appropriate boxes. 8 | 9 | Fixes # (issue) 10 | 11 | ## Type of change 12 | 13 | - [ ] Bug fix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] This change requires a documentation update 17 | 18 | ## How Has This Been Tested? 19 | 20 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. If you can, also describe your test and any test libraries for instance, Jest or Mocha for Javascript. 21 | 22 | - [ ] Test A 23 | - [ ] Test B 24 | 25 | **Test Configuration**: 26 | 27 | - Hardware: 28 | 29 | - Operating System: 30 | 31 | - Toolchain: 32 | 33 | - Browser: 34 | 35 | - SDK: 36 | 37 | ## Checklist 38 | 39 | - [ ] My code follows the style guidelines of this project 40 | - [ ] I have performed a self-review of my own code 41 | - [ ] I have commented my code, particularly in hard-to-understand areas 42 | - [ ] I have made corresponding changes to the documentation 43 | - [ ] My changes generate no new warnings 44 | - [ ] I have added tests that prove my fix is effective or that my feature works 45 | - [ ] New and existing unit tests pass locally with my changes 46 | - [ ] Any dependent changes have been merged and published in downstream modules 47 | - [ ] I have checked my code and corrected any misspellings 48 | -------------------------------------------------------------------------------- /src/tests/home.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import Home from '../pages/Home/Home'; 4 | import { MemoryRouter } from 'react-router'; 5 | 6 | // Mock localStorage 7 | const localStorageMock = (() => { 8 | let store = {}; 9 | 10 | return { 11 | getItem: (key) => store[key] || null, 12 | setItem: (key, value) => { 13 | store[key] = value.toString(); 14 | }, 15 | removeItem: (key) => { 16 | delete store[key]; 17 | }, 18 | clear: () => { 19 | store = {}; 20 | }, 21 | }; 22 | })(); 23 | 24 | const user = { 25 | _id: '64c891e5ecd534e790f4f518', 26 | username: 'test', 27 | email: 'test@gmail.com', 28 | password: '$2a$12$DboEjKLb6kVTbO92x6vgiOeOKoO2eGUZ1qgzIs8wQ9kXTppKb/7qu', 29 | created_on: '2023-08-01T05:02:29.461Z', 30 | __v: 0, 31 | }; 32 | beforeEach(() => { 33 | // Set up a mock localStorage before each test 34 | Object.defineProperty(window, 'localStorage', { 35 | value: localStorageMock, 36 | }); 37 | localStorage.setItem('user', JSON.stringify(user)); 38 | localStorage.setItem( 39 | 'token', 40 | JSON.stringify( 41 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3RAZ21haWwuY29tIiwiaWQiOiI2NGM4OTFlNWVjZDUzNGU3OTBmNGY1MTgiLCJpYXQiOjE2OTQ5NTQ1NTgsImV4cCI6MTY5NTEyNzM1OH0.w6APSk-KNPXdZB7K9PnLdcUEv6nbZ3ER4AcWkvMltTM' 42 | ) 43 | ); 44 | }); 45 | 46 | afterEach(() => { 47 | // Clean up the mock localStorage after each test 48 | window.localStorage.clear(); 49 | }); 50 | 51 | describe('Home Test', () => { 52 | test('In presence of localStorage', () => { 53 | render( 54 | 55 | 56 | 57 | ); 58 | const nav = screen.getByText(/codebooker/i); 59 | expect(nav).toBeInTheDocument(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "editorconfig.editorconfig", 8 | "ritwickdey.liveserver", 9 | "ecmel.vscode-html-css", 10 | "mohsen1.prettify-json", 11 | "ms-vscode.js-debug-nightly", 12 | "mgmcdermott.vscode-language-babel", 13 | "xabikos.javascriptsnippets", 14 | "pranaygp.vscode-css-peek", 15 | "zignd.html-css-class-completion", 16 | "visualstudioexptteam.vscodeintellicode", 17 | "zainchen.json", 18 | "firefox-devtools.vscode-firefox-debug", 19 | "sidthesloth.html5-boilerplate", 20 | "aeschli.vscode-css-formatter", 21 | "rvest.vs-code-prettier-eslint", 22 | "anseki.vscode-color", 23 | "stylelint.vscode-stylelint", 24 | "quick-lint.quick-lint-js", 25 | "ms-vsliveshare.vsliveshare", 26 | "ms-vscode.live-server", 27 | "EditorConfig.EditorConfig", 28 | "WallabyJs.console-ninja", 29 | "VisualStudioExptTeam.vscodeintellicode", 30 | "christian-kohler.path-intellisense", 31 | "HookyQR.beautify", 32 | "abusaidm.html-snippets", 33 | "shd101wyy.markdown-preview-enhanced", 34 | "mikestead.dotenv", 35 | "aaron-bond.better-comments", 36 | "mhutchie.git-graph", 37 | "GitHub.github-vscode-theme", 38 | "dsznajder.es7-react-js-snippets", 39 | "streetsidesoftware.code-spell-checker", 40 | "yzhang.markdown-all-in-one", 41 | "Zignd.html-css-class-completion", 42 | "naumovs.color-highlight", 43 | "DavidAnson.vscode-markdownlint", 44 | "GitHub.vscode-pull-request-github", 45 | "xabikos.JavaScriptSnippets", 46 | "redhat.vscode-yaml", 47 | "formulahendry.auto-close-tag" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 18 | 19 | 28 | CodeBooker 29 | 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/server/controller/supportController.js: -------------------------------------------------------------------------------- 1 | const User = require('../model/userModel'); 2 | const Support = require('../model/supportModel'); 3 | const nodemailer = require('nodemailer'); 4 | 5 | module.exports.newSupport = async (req, res) => { 6 | const { name, email, description } = req.body; 7 | try { 8 | // find user 9 | const submittingUser = await User.findOne({ email }); 10 | if (!submittingUser) { 11 | return res.status(404).json({ 12 | message: 13 | 'Please use your Codebooker email address or verify your email address (NOT FOUND)', 14 | }); 15 | } 16 | 17 | // save to db 18 | const newSupport = new Support({ 19 | userId: submittingUser._id, 20 | description, 21 | status: 'In Progress', 22 | createdAt: Date.now(), 23 | }); 24 | 25 | await newSupport.save(); 26 | // send message to support mail address 27 | const message = ` 28 |

New Support Request

29 |
  • 30 |

    User name:${name}

    31 |

    User email:${email}

    32 |
  • 33 |

    ${description}

    34 | `; 35 | const transporter = nodemailer.createTransport({ 36 | service: 'gmail', 37 | auth: { 38 | user: process.env.MAIL_USERNAME, 39 | pass: process.env.MAIL_PASSWORD, 40 | }, 41 | }); 42 | 43 | const mailOptions = { 44 | from: email, 45 | to: process.env.MAIL_USERNAME, 46 | subject: 'Assistance Needed', 47 | html: message, 48 | }; 49 | 50 | transporter.sendMail(mailOptions, (error, info) => { 51 | if (error) { 52 | console.log(error); 53 | } else { 54 | res.status(200).json('Email sent!'); 55 | console.log('Email sent: ' + info.response); 56 | } 57 | }); 58 | res.status(200); 59 | } catch (err) { 60 | console.error(err); 61 | res.status(500).json({ message: 'Internal server error' }); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/components/FormikContainer/CreateProfileForm/programingSkillOptions.js: -------------------------------------------------------------------------------- 1 | const programingSkillOptions = [ 2 | { 3 | value: 'Back-end Development', 4 | label: 'Back-end Development', 5 | }, 6 | { 7 | value: 'Blockchain Development', 8 | label: 'Blockchain Development', 9 | }, 10 | { 11 | value: 'Cloud Computing', 12 | label: 'Cloud Computing', 13 | }, 14 | { 15 | value: 'Cybersecurity', 16 | label: 'Cybersecurity', 17 | }, 18 | { 19 | value: 'Data Science', 20 | label: 'Data Science', 21 | }, 22 | { 23 | value: 'Database Management', 24 | label: 'Database Management', 25 | }, 26 | { 27 | value: 'DevOps', 28 | label: 'DevOps', 29 | }, 30 | { 31 | value: 'Embedded Systems', 32 | label: 'Embedded Systems', 33 | }, 34 | { 35 | value: 'Front-end Development', 36 | label: 'Front-end Development', 37 | }, 38 | { 39 | value: 'Full-stack Development', 40 | label: 'Full-stack Development', 41 | }, 42 | { 43 | value: 'Functional Programming', 44 | label: 'Functional Programming', 45 | }, 46 | { 47 | value: 'Game Development', 48 | label: 'Game Development', 49 | }, 50 | { 51 | value: 'IoT (Internet of Things)', 52 | label: 'IoT (Internet of Things)', 53 | }, 54 | { 55 | value: 'Machine Learning and AI', 56 | label: 'Machine Learning and AI', 57 | }, 58 | { 59 | value: 'Mobile App Development', 60 | label: 'Mobile App Development', 61 | }, 62 | { 63 | value: 'Scripting Languages', 64 | label: 'Scripting Languages', 65 | }, 66 | { 67 | value: 'UI/UX Design', 68 | label: 'UI/UX Design', 69 | }, 70 | { 71 | value: 'Version Control', 72 | label: 'Version Control', 73 | }, 74 | { 75 | value: 'Web Development Frameworks', 76 | label: 'Web Development Frameworks', 77 | }, 78 | { 79 | value: 'Web Security', 80 | label: 'Web Security', 81 | }, 82 | ]; 83 | 84 | export default programingSkillOptions; 85 | -------------------------------------------------------------------------------- /src/server/controller/feedBackController.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | const FeedBack = require('../model/feedBackModel'); 3 | 4 | module.exports.addfeedback = async (req, res) => { 5 | const { feedback, rating, author } = req.body; 6 | try { 7 | const newfeedback = new FeedBack({ feedback, rating, author }); 8 | await newfeedback.save(); 9 | res.status(200).json(newfeedback); 10 | } catch (err) { 11 | console.error(err); 12 | res.status(500).json({ message: 'Server Error' }); 13 | } 14 | }; 15 | 16 | module.exports.emailFeedback = async (req, res) => { 17 | try { 18 | const { username, userId, userEmail, feedback, rating } = req.body; 19 | 20 | // message to send 21 | const message = ` 22 |

    ${username} has submitted some feedback.

    23 |

    userId: ${userId}

    24 |

    User email: ${userEmail}

    25 |

    Feedback: ${feedback}

    26 |

    Rating: ${rating}

    27 | `; 28 | 29 | const transporter = nodemailer.createTransport({ 30 | service: 'gmail', 31 | auth: { 32 | user: process.env.MAIL_USERNAME, 33 | pass: process.env.MAIL_PASSWORD, 34 | }, 35 | }); 36 | 37 | const mailOptions = { 38 | from: process.env.MAIL_USERNAME, 39 | to: process.env.MAIL_USERNAME, 40 | subject: 'CodeBooker: New Feedback Received', 41 | html: message, 42 | }; 43 | 44 | transporter.sendMail(mailOptions, (error, info) => { 45 | if (error) { 46 | console.log(error); 47 | } else { 48 | res.status(200).json('Email sent!'); 49 | console.log('Email sent: ' + info.response); 50 | } 51 | }); 52 | } catch (err) { 53 | // Add catch block to handle exceptions 54 | console.log(err.message); 55 | res.status(500).json('Something went wrong...'); 56 | } 57 | }; 58 | 59 | module.exports.allfeedback = async (req, res) => { 60 | try { 61 | const newfeedback = await FeedBack.find(); 62 | res.status(200).json(newfeedback); 63 | } catch (err) { 64 | console.error(err); 65 | res.status(500).json({ message: 'Server Error' }); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | // Common Importing Statements 2 | const express = require('express'); 3 | const bodyParser = require('body-parser'); 4 | const mongoose = require('mongoose'); 5 | const dotenv = require('dotenv'); 6 | const cors = require('cors'); 7 | const passport = require('passport'); 8 | const session = require('express-session'); 9 | 10 | // routes 11 | const userRoutes = require('./routes/userRoutes'); 12 | const booksRoutes = require('./routes/booksRoutes'); 13 | const reviewRoutes = require('./routes/reviewRoutes'); 14 | const supportRoutes = require('./routes/supportRoutes'); 15 | const profileRoutes = require('./routes/profileRoutes'); 16 | const settingsRoutes = require('./routes/settingsRoutes'); 17 | const sessionConfig = { 18 | secret: 'keyboard cat', 19 | resave: false, // don't save session if unmodified 20 | saveUninitialized: false, // don't create session until something stored 21 | }; 22 | 23 | // initialization 24 | dotenv.config(); 25 | const app = express(); 26 | app.use(express.json({ extended: true })) 27 | .use(express.urlencoded({ extended: true })) 28 | .use(cors()) 29 | .use(bodyParser.json()) 30 | .use(bodyParser.urlencoded({ extended: true })) 31 | .use(session(sessionConfig)) 32 | .use(passport.session()); 33 | 34 | // db connection 35 | mongoose 36 | .connect(process.env.MONGO_DB) 37 | .then(() => { 38 | console.log('Db connection open'); 39 | }) 40 | .catch((err) => { 41 | console.log(err.message, 'oops err'); 42 | }); 43 | 44 | // default route 45 | app.get('/api/', (req, res) => { 46 | res.send('Backend Server Up'); 47 | }); 48 | 49 | // routes for user 50 | app.use('/api/user', userRoutes); 51 | 52 | // routes for books 53 | app.use('/api/books', booksRoutes); 54 | 55 | // routes for review 56 | app.use('/api/review', reviewRoutes); 57 | 58 | // routes for support/help 59 | app.use('/api/support', supportRoutes); 60 | 61 | // routes for profile 62 | app.use('/api/profile', profileRoutes); 63 | 64 | // routes for user settings 65 | app.use('/api/settings', settingsRoutes); 66 | 67 | app.use('/api/*', (req, res) => { 68 | res.send('404 No routes found'); 69 | }); 70 | 71 | // listening port 72 | // port 73 | const PORT = process.env.PORT || 3001; 74 | app.listen(PORT, () => { 75 | console.log(`Server is running at Port ${PORT}`); 76 | }); 77 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[json]": { 3 | "editor.quickSuggestions": { 4 | "strings": true, 5 | "comments": "on" 6 | }, 7 | "editor.defaultFormatter": "ZainChen.json", 8 | "editor.suggest.insertMode": "replace" 9 | }, 10 | "[html]": { 11 | "editor.suggest.insertMode": "replace", 12 | "editor.defaultFormatter": "vscode.html-language-features" 13 | }, 14 | "[css]": { 15 | "editor.suggest.insertMode": "replace", 16 | "editor.defaultFormatter": "vscode.css-language-features" 17 | }, 18 | "[javascript]": { 19 | "editor.maxTokenizationLineLength": 2500, 20 | "editor.defaultFormatter": "esbenp.prettier-vscode" 21 | }, 22 | "[markdown]": { 23 | "editor.unicodeHighlight.ambiguousCharacters": false, 24 | "editor.unicodeHighlight.invisibleCharacters": false, 25 | "editor.wordWrap": "on", 26 | "editor.quickSuggestions": { 27 | "comments": "off", 28 | "strings": "off", 29 | "other": "off" 30 | }, 31 | "cSpell.fixSpellingWithRenameProvider": true, 32 | "cSpell.advanced.feature.useReferenceProviderWithRename": true, 33 | "cSpell.advanced.feature.useReferenceProviderRemove": "/^#+\\s/", 34 | "editor.defaultFormatter": "vscode.markdown-language-features" 35 | }, 36 | "[jsonc]": { 37 | "editor.quickSuggestions": { 38 | "strings": true 39 | }, 40 | "editor.suggest.insertMode": "replace", 41 | "editor.defaultFormatter": "vscode.json-language-features" 42 | }, 43 | "[yaml]": { 44 | "editor.insertSpaces": true, 45 | "editor.tabSize": 2, 46 | "editor.autoIndent": "keep", 47 | "editor.quickSuggestions": { 48 | "other": true, 49 | "comments": false, 50 | "strings": true 51 | }, 52 | "editor.defaultFormatter": "redhat.vscode-yaml" 53 | }, 54 | "[javascriptreact]": { 55 | "editor.defaultFormatter": "esbenp.prettier-vscode", 56 | "editor.formatOnSave": false 57 | }, 58 | "liveServer.settings.port": 5500, 59 | "files.associations": { 60 | "*.jsx": "javascriptreact", 61 | "*.js": "javascript" 62 | }, 63 | "emmet.includeLanguages": { 64 | "javascriptreact": "javascriptreact" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/SettingIcon/Setting.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import SettingsIcon from '@mui/icons-material/Settings'; 3 | import './Setting.css'; 4 | 5 | function Setting() { 6 | const [showSettings, setShowSettings] = useState(false); 7 | const [randomSetting1, setRandomSetting1] = useState(false); 8 | const [randomSetting2, setRandomSetting2] = useState('Option 1'); 9 | const [randomSetting3, setRandomSetting3] = useState(10); 10 | 11 | const toggleSettings = () => { 12 | setShowSettings((prevShowSettings) => !prevShowSettings); 13 | }; 14 | 15 | const toggleRandomSetting1 = () => { 16 | setRandomSetting1(!randomSetting1); 17 | }; 18 | 19 | const handleRandomSetting2Change = (event) => { 20 | setRandomSetting2(event.target.value); 21 | }; 22 | 23 | const handleRandomSetting3Change = (event) => { 24 | setRandomSetting3(parseInt(event.target.value)); 25 | }; 26 | 27 | return ( 28 |
    29 | 30 |
    31 |

    Settings Menu

    32 |
    33 | 38 | 39 |
    40 |
    41 | 42 | 50 |
    51 |
    52 | 53 | 58 |
    59 |
    60 |
    61 | ); 62 | } 63 | export default Setting; 64 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/Settings/utils/language.js: -------------------------------------------------------------------------------- 1 | import countryLanguages from '@ladjs/country-language'; 2 | import { getTag } from '@sozialhelden/ietf-language-tags'; 3 | const languageOrder = ['en', 'fr', 'es']; 4 | 5 | const defaultLanguageCodes = [ 6 | 'ar-SA', 7 | 'bg-BG', 8 | 'bn-BD', 9 | 'cs-CZ', 10 | 'ca-AD', 11 | 'de-DE', 12 | 'el-GR', 13 | 'en-US', 14 | 'es-ES', 15 | 'et-EE', 16 | 'fa-IR', 17 | 'fr-FR', 18 | 'gl-ES', 19 | 'gu-IN', 20 | 'he-IL', 21 | 'id-ID', 22 | 'it-IT', 23 | 'ja-JP', 24 | 'ko-KR', 25 | 'lv-LV', 26 | 'ne-NP', 27 | 'nl-NL', 28 | 'pl-PL', 29 | 'pt-BR', 30 | 'ru-RU', 31 | 'sl-SI', 32 | 'sv-SE', 33 | 'ta-LK', 34 | 'th-TH', 35 | 'tr-TR', 36 | 'vi-VN', 37 | 'zh-CN', 38 | ]; 39 | 40 | function populateLanguageCode(language) { 41 | if (language.includes('-')) return language; 42 | if (language.length !== 2) return language; 43 | return ( 44 | defaultLanguageCodes.find((v) => v.startsWith(`${language}-`)) ?? 45 | language 46 | ); 47 | } 48 | 49 | export function sortLangCodes(langCodes) { 50 | const languagesOrder = [...languageOrder].reverse(); 51 | const results = langCodes.sort((a, b) => { 52 | const langOrderA = languagesOrder.findIndex( 53 | (v) => a.startsWith(`${v}-`) || a === v 54 | ); 55 | const langOrderB = languagesOrder.findIndex( 56 | (v) => b.startsWith(`${v}-`) || b === v 57 | ); 58 | if (langOrderA !== -1 || langOrderB !== -1) 59 | return langOrderB - langOrderA; 60 | 61 | return a.localeCompare(b); 62 | }); 63 | 64 | return results; 65 | } 66 | 67 | export function getLocaleInfo(locale) { 68 | const realLocale = populateLanguageCode(locale); 69 | const tag = getTag(realLocale, true); 70 | if (!tag?.language?.Subtag) return null; 71 | let output = null; 72 | countryLanguages.getLanguage(tag.language.Subtag, (_err, lang) => { 73 | if (lang) output = lang; 74 | }); 75 | if (!output) return null; 76 | 77 | const extras = []; 78 | if (tag.region?.Description) extras.push(tag.region.Description[0]); 79 | if (tag.script?.Description) extras.push(tag.script.Description[0]); 80 | const extraStringified = extras.map((v) => `(${v})`).join(' '); 81 | return { 82 | code: tag.parts.langtag ?? realLocale, 83 | isRtl: output.direction === 'RTL', 84 | name: output.name[0] + (extraStringified ? ` ${extraStringified}` : ''), 85 | nativeName: output.nativeName[0] || undefined, 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/pages/ForgotPassword/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import CircularProgress from '@mui/material/CircularProgress'; 2 | import EmailIcon from '@mui/icons-material/Email'; 3 | import { Avatar, TextField, Typography } from '@mui/material'; 4 | import axios from 'axios'; 5 | import React, { useState } from 'react'; 6 | import './ForgotPassword.css'; 7 | import { Link } from 'react-router-dom'; 8 | 9 | const ForgotPassword = () => { 10 | const [loading, setLoading] = useState(false); 11 | const [userEmail, setUserEmail] = useState(''); 12 | const handleForgotPassword = async () => { 13 | try { 14 | setLoading(true); 15 | const response = await axios.post( 16 | 'http://localhost:3001/api/user/forgot-password', 17 | { email: userEmail } 18 | ); 19 | setUserEmail(''); 20 | console.log(response); 21 | } catch (error) { 22 | console.error(error); 23 | } 24 | 25 | setLoading(false); 26 | }; 27 | 28 | return ( 29 |
    30 |
    31 | 32 | 33 | 34 | 35 | Update your password 36 | 37 |
    38 | 39 | Enter your email address and click SEND EMAIL. 40 | 41 |
    42 |
    43 |
    44 | setUserEmail(e.target.value)} 48 | /> 49 |
    50 | 51 | 64 |
    65 | 66 | Cancel 67 | 68 |
    69 |
    70 | ); 71 | }; 72 | 73 | export default ForgotPassword; 74 | -------------------------------------------------------------------------------- /src/tests/login.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, fireEvent, waitFor, screen } from '@testing-library/react'; 3 | 4 | import { MemoryRouter } from 'react-router-dom'; 5 | import Login from '../pages/Login/Login'; 6 | 7 | describe('Login Component', () => { 8 | it('should render the login form', () => { 9 | render( 10 | 11 | 12 | 13 | ); 14 | 15 | const usernameInput = screen.getByLabelText('Username or Email'); 16 | const passwordInput = screen.getByLabelText('Password'); 17 | const rememberMeCheckbox = screen.getByRole('checkbox', { 18 | name: 'Remember Me', 19 | }); 20 | const signInButton = screen.getByRole('button', { name: 'Sign In' }); 21 | const forgotPasswordLink = screen.getByText('Forgot Password'); 22 | const registerLink = screen.getByText('register here'); 23 | 24 | expect(usernameInput).toBeInTheDocument(); 25 | expect(passwordInput).toBeInTheDocument(); 26 | expect(rememberMeCheckbox).toBeInTheDocument(); 27 | expect(signInButton).toBeInTheDocument(); 28 | expect(forgotPasswordLink).toBeInTheDocument(); 29 | expect(registerLink).toBeInTheDocument(); 30 | }); 31 | 32 | it('should handle form submission', async () => { 33 | render( 34 | 35 | 36 | 37 | ); 38 | 39 | const usernameInput = screen.getByLabelText('Username or Email'); 40 | const passwordInput = screen.getByLabelText('Password'); 41 | const rememberMeCheckbox = screen.getByRole('checkbox', { 42 | name: 'Remember Me', 43 | }); 44 | const signInButton = screen.getByRole('button', { name: 'Sign In' }); 45 | 46 | fireEvent.change(usernameInput, { 47 | target: { value: 'test@gmail.com' }, 48 | }); 49 | fireEvent.change(passwordInput, { target: { value: '123456' } }); 50 | fireEvent.click(rememberMeCheckbox); 51 | 52 | const user = { 53 | _id: '64c891e5ecd534e790f4f518', 54 | username: 'test', 55 | email: 'test@gmail.com', 56 | password: 57 | '$2a$12$DboEjKLb6kVTbO92x6vgiOeOKoO2eGUZ1qgzIs8wQ9kXTppKb/7qu', 58 | created_on: '2023-08-01T05:02:29.461Z', 59 | __v: 0, 60 | }; 61 | 62 | fireEvent.click(signInButton); 63 | 64 | await waitFor(() => 65 | expect(localStorage.getItem('token')).toEqual( 66 | JSON.stringify('mockToken') 67 | ) 68 | ); 69 | expect(localStorage.getItem('user')).toEqual(JSON.stringify(user)); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/server/controller/profileController.js: -------------------------------------------------------------------------------- 1 | const Profile = require('../model/profileModel'); 2 | const User = require('../model/userModel'); 3 | 4 | module.exports.newProfile = async (req, res) => { 5 | const { 6 | name, 7 | alias, 8 | bio, 9 | location, 10 | age, 11 | education, 12 | occupation, 13 | operatingSystem, 14 | programingSkills, 15 | favoriteEditor, 16 | githubLink, 17 | portfolioURL, 18 | youtubeLink, 19 | linkedInLink, 20 | twitterLink, 21 | gitLab, 22 | showAge, 23 | selectedFile, 24 | city, 25 | userEmail, 26 | } = req.body; 27 | try { 28 | // find user 29 | const user = await User.findOne({ email: userEmail }); 30 | if (!user) { 31 | return res.status(404).json({ 32 | message: 'Sorry no user with that email exist(USER NOT FOUND)', 33 | }); 34 | } 35 | // Save profile to db 36 | const newProfile = new Profile({ 37 | userId: user._id, 38 | email: userEmail, 39 | profile_picture: selectedFile, 40 | name, 41 | bio, 42 | alias, 43 | location, 44 | city, 45 | education, 46 | age, 47 | display_age: showAge, 48 | occupation, 49 | skills: programingSkills, 50 | text_editor: favoriteEditor, 51 | operating_system: operatingSystem, 52 | portfolio_link: portfolioURL, 53 | youtube: youtubeLink, 54 | linkedIn: linkedInLink, 55 | twitter: twitterLink, 56 | gitlab: gitLab, 57 | github: githubLink, 58 | created_at: Date.now(), 59 | }); 60 | await newProfile.save(); 61 | res.status(200).json({ 62 | message: 'Profile Created Successful', 63 | }); 64 | } catch (err) { 65 | console.error(err); 66 | res.status(500).json({ message: 'Internal server error' }); 67 | } 68 | }; 69 | 70 | module.exports.getProfile = async (req, res) => { 71 | const { userEmail } = req.body; 72 | try { 73 | // Find the user profile by email 74 | const userProfile = await Profile.findOne({ email: userEmail }); 75 | if (!userProfile) { 76 | return res.status(404).json({ 77 | message: 78 | 'Sorry no user with that profile exist(USER NOT FOUND)', 79 | }); 80 | } 81 | // Send response with user profile data to frontend 82 | res.status(200).json(userProfile); 83 | } catch (err) { 84 | console.error(err); 85 | res.status(500).json({ message: 'Internal server error' }); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /src/pages/Settings/Settings.css: -------------------------------------------------------------------------------- 1 | .AppBar { 2 | height: 60px; 3 | } 4 | 5 | .settings-container{ 6 | position: relative; 7 | margin-top: 90px; 8 | padding: 0 50px; 9 | min-height: 100vh; 10 | padding-bottom: 90px; 11 | } 12 | 13 | nav{ 14 | display: flex; 15 | flex-direction: column; 16 | height: calc(100vh - 56px); 17 | top: 0; 18 | left: 0; 19 | position: fixed; 20 | width: 240px; 21 | padding: 90px 10px; 22 | transition: left 300ms ease; 23 | background-color: #1b1a2f; 24 | } 25 | 26 | header{ 27 | margin-bottom: 20px; 28 | } 29 | 30 | .top{ 31 | display: flex; 32 | align-items: center; 33 | gap: 11px; 34 | margin: 20px 0px 20px 0px; 35 | } 36 | 37 | .user-avatar { 38 | border-radius: 50%; 39 | overflow: hidden; 40 | width: 60px; 41 | height: 60px; 42 | } 43 | 44 | .user-avatar img { 45 | width: 100%; 46 | height: 100%; 47 | object-fit: cover; 48 | } 49 | .search-box{ 50 | display: flex; 51 | margin-top: 20px; 52 | position: relative; 53 | } 54 | 55 | 56 | .search-box input { 57 | width: 100%; 58 | height: 40px; 59 | padding: 0 5px; 60 | } 61 | .clear-icon, .search-icon { 62 | font-size: 14px; 63 | position: absolute; 64 | top: 35%; 65 | right: 2%; 66 | } 67 | .clear-icon{ 68 | margin-right: 20px; 69 | } 70 | 71 | .nav-options{ 72 | margin-top: 20px; 73 | } 74 | 75 | .default-element { 76 | padding-left: 5px; 77 | } 78 | 79 | .nav-options button { 80 | width: 100%; 81 | height: 40px; 82 | border: none; 83 | outline: none; 84 | background-color: inherit; 85 | font-size: 16px; 86 | text-align: left; 87 | font-weight: 500; 88 | cursor: pointer; 89 | } 90 | 91 | .nav-options button:hover { 92 | background-color: #e9e3e3 !important; 93 | color: #fff; 94 | } 95 | 96 | 97 | .codebooker-logo{ 98 | cursor: pointer; 99 | height: inherit; 100 | margin-top: .5em; 101 | } 102 | 103 | 104 | main{ 105 | width: 100%; 106 | max-width: 970px; 107 | margin-left: auto; 108 | padding-bottom: 200px; 109 | padding-top: 20px; 110 | } 111 | 112 | .alert{ 113 | position: fixed; 114 | display: flex; 115 | justify-content: space-between; 116 | align-items: center; 117 | bottom: 0; 118 | left: 0; 119 | right: 0; 120 | width: 100%; 121 | padding: 15px; 122 | background-color:#1b1a2f; 123 | 124 | } 125 | .hide { 126 | display: none; 127 | } 128 | @media screen and (max-width: 1024px) { 129 | main{ 130 | max-width: 640px; 131 | } 132 | } 133 | @media screen and (max-width: 912px) { 134 | main{ 135 | max-width: 540px; 136 | } 137 | } 138 | 139 | 140 | @media screen and (max-width: 820px){ 141 | main{ 142 | padding-top: unset; 143 | max-width: unset; 144 | } 145 | .settings-container{ 146 | padding: 0 15px; 147 | } 148 | .alert{ 149 | display: grid; 150 | place-items: center; 151 | padding: 7px; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeBooker 2 | 3 | This is a book recommendation app created with React 18.2 and MUI. 4 | 5 | It is not a bookstore eCommerce application. 6 | 7 | It is targeted specifically for coders/programmers who are looking for recommendations for programming/coding books to read. 8 | 9 | It allows users to organize, recommend and review any book on coding or programming. 10 | 11 | This app is not targeting any other book genre. 12 | 13 | This project is built using: 14 | 15 | [![MongoDB](https://img.shields.io/badge/MongoDB-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white)](https://www.nodejs.com) 16 | [![Express](https://img.shields.io/badge/Express.js-404D59?style=for-the-badge)](https://expressjs.com) 17 | [![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)](https://react.dev/) 18 | [![NodeJS](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white)](https://www.nodejs.com) 19 | [![MaterialUI](https://img.shields.io/badge/Material--UI-0081CB?style=for-the-badge&logo=material-ui&logoColor=white)](https://www.mui.com) 20 | [![Prisma](https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white)](https://www.prisma.io/) 21 | [![Jest](https://img.shields.io/badge/Jest-323330?style=for-the-badge&logo=Jest&logoColor=white)](https://jestjs.io) 22 | 23 | Bootstrapped with Create-React-App v5 aka react-scripts using `npx create-react-app codebooker` 24 | 25 | If you would like to contribute to this repository, please read our [Contributing](https://github.com/gbowne1/codebooker/blob/master/CONTRIBUTING.md) document. We welcome your pull requests here. 26 | 27 | This project is built from community contributions and community driven design. Submit a PR with your design for review along with a screenshot and we will look at it for possible merge. We have found that this process works the best. Community members can also help imporove the design through issues and Pull Requests, see the contributing document. 28 | 29 | We recommend using this with Node 18.15.0, be sure and let us know. We do not recommend using any other versions that are not currently on LTS. 30 | 31 | You should already be familiar with React 17 and or 18, JavaScript ES5/ES6/ES7 and JSX. If you are not there are several tutorials we recommend using before you get started with. To learn React, go here: 32 | 33 | The backend of this project is currently under construction so please use the Create React App development server, 34 | inside the root of the project folder: 35 | 36 | # After installing the project dependencies 37 | npm install 38 | 39 | # To run the backend server separately 40 | npm run server 41 | 42 | #### [CodeBooker API documentation](https://bmacarini.github.io/codebooker-documentation/) 43 | 44 | # To run the frontend separately 45 | npm start 46 | 47 | # To run both the frontend and the backend server concurrently in development mode 48 | npm run dev 49 | 50 | 51 | This is not hosted anywhere yet and is not yet production-ready. 52 | 53 | If you would like something to work on, there are plenty of TODO's here 54 | -------------------------------------------------------------------------------- /src/pages/Profile/CreateProfileAlert.jsx: -------------------------------------------------------------------------------- 1 | import CreateIcon from '@mui/icons-material/Create'; 2 | import { Box, Button, Modal, Typography } from '@mui/material'; 3 | import React, { useState } from 'react'; 4 | import { useNavigate } from 'react-router'; 5 | import './CreateProfileAlert.css'; 6 | const CreateProfileAlert = () => { 7 | const [openModal, setOpenModal] = useState(true); 8 | const navigate = useNavigate(); 9 | const style = { 10 | position: 'absolute', 11 | top: '50%', 12 | left: '50%', 13 | transform: 'translate(-50%, -50%)', 14 | width: 540, 15 | bgcolor: 'background.paper', 16 | border: '1px solid #3394d9', 17 | boxShadow: 24, 18 | p: 2, 19 | gap: 2, 20 | display: 'flex', 21 | justifyContent: 'space-between', 22 | alignItems: 'center', 23 | }; 24 | return ( 25 | 26 | 27 | 32 | popup-img 37 | 38 | 43 | Hey! 44 | 50 | Complete your profile to get the best out of CodeBooker. 51 | 52 | 67 | 68 | setOpenModal(false)} 70 | sx={{ 71 | position: 'absolute', 72 | width: '30px', 73 | height: '30px', 74 | background: '#fff', 75 | color: '#1976d2', 76 | top: '-30px', 77 | right: '-25px', 78 | display: 'grid', 79 | placeItems: 'center', 80 | borderRadius: '50%', 81 | cursor: 'pointer', 82 | }} 83 | > 84 | X 85 | 86 | 87 | 88 | ); 89 | }; 90 | export default CreateProfileAlert; 91 | -------------------------------------------------------------------------------- /src/hooks/useAxios.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback, useMemo } from 'react'; 2 | import axios from 'axios'; 3 | 4 | const useAxios = ( 5 | config, 6 | initialState = { response: null, error: null, loading: true } 7 | ) => { 8 | const [response, setResponse] = useState(initialState.response); 9 | const [error, setError] = useState(initialState.error); 10 | const [loading, setLoading] = useState(initialState.loading); 11 | 12 | // Use useMemo to avoid unnecessary reruns 13 | const axiosConfig = useMemo( 14 | () => ({ 15 | ...config, 16 | }), 17 | [config] 18 | ); 19 | 20 | const fetchData = useCallback(async () => { 21 | setLoading(true); 22 | try { 23 | const result = await axios(axiosConfig); 24 | setResponse(result.data); 25 | setError(null); 26 | } catch (err) { 27 | setError(err.message); 28 | setResponse(null); 29 | } finally { 30 | setLoading(false); 31 | } 32 | }, [axiosConfig]); 33 | 34 | useEffect(() => { 35 | const source = axios.CancelToken.source(); 36 | axiosConfig.cancelToken = source.token; 37 | 38 | fetchData(); 39 | 40 | // Cancel the request if component unmounts 41 | return () => source.cancel('Axios request canceled.'); 42 | }, [fetchData, axiosConfig]); 43 | 44 | return { response, error, loading, refetch: fetchData }; 45 | }; 46 | 47 | export default useAxios; 48 | 49 | /* Examples of usage */ 50 | 51 | // Example Usage for a POST Request 52 | // function PostCommentForm() { 53 | // const [comment, setComment] = useState(''); 54 | // const { response, error, loading, refetch } = useAxios({ 55 | // url: '/api/comments', 56 | // method: 'post', 57 | // data: { comment }, 58 | // }, { loading: false }); // Prevents automatic fetching 59 | 60 | // const handleSubmit = (e) => { 61 | // e.preventDefault(); 62 | // refetch(); // Trigger the POST request manually 63 | // }; 64 | 65 | // return ( 66 | //
    67 | //