├── .env.development ├── .firebase └── hosting.YnVpbGQ.cache ├── .firebaserc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CONTRIBUTING.md ├── DEV_README.md ├── LICENSE ├── README.md ├── database.rules.json ├── firebase.json ├── firestore.indexes.json ├── firestore.rules ├── functions ├── .gitignore ├── index.js ├── package-lock.json └── package.json ├── package-lock.json ├── package.json ├── pr-guide.pdf ├── project.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── mappypals-logo.ico ├── src ├── 404.html ├── App.css ├── App.js ├── App.test.js ├── api │ ├── apiHelpers.js │ └── userApi.js ├── assets │ ├── ConnectIcon.png │ ├── InviteIcon.png │ ├── SignUpIcon.png │ ├── bgImage.png │ └── svg │ │ ├── ConnectBoxIcon.svg │ │ ├── InviteFriendsBoxIcon.svg │ │ ├── SignUpBoxIcon.svg │ │ └── bgEarth.svg ├── components │ ├── BaseScss │ │ └── variables.scss │ ├── ErrorMessages │ │ ├── ErrorMessages.js │ │ ├── ErrorMessages.module.css │ │ └── PasswordMessage │ │ │ ├── PasswordMessage.js │ │ │ └── PasswordMessage.module.css │ ├── Firebase │ │ └── firebase.js │ ├── FriendSearch.js │ ├── HomeCard │ │ ├── home-card.component.js │ │ └── home-card.styles.js │ ├── Layout.js │ ├── Main.js │ ├── Navbar │ │ ├── Navbar.css │ │ └── Navbar.js │ ├── SearchBar.js │ ├── UI │ │ ├── Button │ │ │ ├── Button.js │ │ │ └── Button.module.css │ │ ├── Chip │ │ │ ├── Chip.js │ │ │ └── Chip.module.scss │ │ ├── Container │ │ │ └── Container.js │ │ ├── DropdownSelect │ │ │ ├── DropdownSelect.js │ │ │ └── DropdownSelect.module.css │ │ ├── Grid │ │ │ └── Grid.js │ │ ├── LinkTag │ │ │ ├── LinkTag.js │ │ │ └── LinkTag.module.css │ │ ├── Modal │ │ │ ├── Modal.js │ │ │ └── Modal.module.scss │ │ ├── Text │ │ │ └── Text.js │ │ ├── XButton │ │ │ ├── XButton.js │ │ │ └── XButton.module.css │ │ └── effects │ │ │ └── useWindowSize.effect.js │ └── helper.js ├── index.js ├── pages │ ├── About │ │ ├── About.css │ │ ├── About.js │ │ ├── TeamCard.js │ │ └── team-data.js │ ├── Contact │ │ ├── Contact.css │ │ └── Contact.js │ ├── FriendsList │ │ ├── FriendCard.js │ │ ├── FriendsList.css │ │ ├── FriendsList.js │ │ └── SearchFriends.js │ ├── Home │ │ ├── AddFriendForm │ │ │ ├── AddFriendForm.css │ │ │ ├── AddFriendForm.js │ │ │ ├── InvitationSent.css │ │ │ └── InvitationSent.js │ │ ├── Home.css │ │ ├── Home.js │ │ ├── Home.style.js │ │ ├── Map.js │ │ ├── Popup │ │ │ ├── Popup.js │ │ │ └── Popup.module.css │ │ └── mapbox-gl-geocoder.css │ ├── InviteFriends │ │ ├── InviteFriends.js │ │ └── InviteFriends.module.scss │ ├── Login │ │ ├── ForgotPassword.css │ │ ├── ForgotPassword.js │ │ ├── ForgotPassword.styles.scss │ │ ├── Form.js │ │ ├── Login.css │ │ ├── Login.js │ │ ├── ResetPassword.js │ │ ├── ResetPassword.styles.scss │ │ ├── Signup.js │ │ └── tail-spin.svg │ ├── Page404 │ │ ├── Page404.css │ │ └── Page404.js │ ├── PersonalProfile │ │ ├── PersonalProfile.js │ │ ├── PersonalProfile.styles.css │ │ ├── ProfileCard.js │ │ ├── ProfileContent.css │ │ ├── ProfileContent.js │ │ └── USER_DATA.js │ └── Settings │ │ ├── ProfileSettings │ │ ├── ProfileSettings.js │ │ ├── ProfileSettings.module.css │ │ └── displayInterests │ │ │ ├── displayInterests.js │ │ │ └── displayInterests.module.css │ │ ├── SettingsEmailPassword │ │ ├── EmailForm.js │ │ ├── PasswordForm.js │ │ ├── SettingsEmailPassword.css │ │ └── SettingsEmailPassword.js │ │ └── SettingsNavbar │ │ ├── SettingsNavbar.js │ │ └── SettingsNavbar.module.css ├── pics │ ├── 404Icon.svg │ ├── AboutUs.svg │ ├── Add.svg │ ├── AddSmall.png │ ├── FriendsIcon.svg │ ├── InviteFriends.svg │ ├── Logo.svg │ ├── LogoInverted.svg │ ├── MySettingsIcon.svg │ ├── SearchIcon.svg │ ├── Team.svg │ ├── adult-backlit-beach.jpg │ ├── blank-profile-picture.png │ ├── blank-profile-picture.psd │ └── default-profile-pic.jpg ├── server │ ├── createMockDB.js │ └── server.js ├── serviceWorker.js ├── store │ ├── actions │ │ ├── apiStatus.js │ │ ├── index.js │ │ ├── modals.js │ │ ├── types.js │ │ └── user.js │ ├── index.js │ └── reducers │ │ ├── apiStatus.js │ │ ├── index.js │ │ ├── initialState.js │ │ ├── modals.js │ │ └── user.js └── utils │ ├── helpers.js │ └── localStorage.js └── storage.rules /.env.development: -------------------------------------------------------------------------------- 1 | API_PORT=3002 2 | DOMAIN=http://localhost 3 | REACT_APP_API_URL=$DOMAIN:$API_PORT 4 | REACT_APP_MAPBOX_API_URL=https://api.mapbox.com/geocoding/v5/mapbox.places/ 5 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1564408325020,ec3674e6d6e35d6933dcb88300cc94f756d006af7b5aa2fba8c7834d1bfe9cdb 2 | manifest.json,1560118927023,6b50377417dc0a8a86bd922f13eaf38a3670b6a23cc5ce3da3efd594cc3a2741 3 | index.html,1564408325019,cac06c667aa269ad3ed98ba60ea4407b6fb63ef53495aa096e48c9b8d5ba7c42 4 | precache-manifest.f585102667bea0e518886c71137f5481.js,1564408325019,5250aca340cb5554e6d5aa07104e6bb965349a0f69bf161eba17609c9ec24447 5 | service-worker.js,1564408325019,4cb1c0c951fd2dcb581937f3bd9c66371836682f4d4a25ea9b13c8bcc0b32af4 6 | favicon.ico,1564408253932,249da0c98c2fc3d44173501feb2f42ffd871dabffded65a2cb35ca622418505c 7 | static/css/main.94f683b9.chunk.css,1564408324936,df5ef778e08e0d6ce17259ec9e8ae4d88859d740cf1b784fac66eabe836660bc 8 | static/js/runtime~main.a8a9905a.js,1564408324938,27518aed75eb917ee7575e8c911b596e850b5265b9e5694a7681cba901419f4d 9 | static/js/runtime~main.a8a9905a.js.map,1564408325020,d13b46ae2acf0d2863e1c6449a51f64702fa5b464c475c0ec20d6e8de9970ef9 10 | static/media/404Icon.26a0c39b.svg,1564408324937,dbd1a59b6975085b3a607558f4e3a8ea4b51eaa5814f8c150bd9279103112be0 11 | static/media/AboutUs.57cdba97.svg,1564408324936,5230b35a31683f565d5359c121a73326c2c6d53447521fa8563e73b9661dfdbb 12 | static/media/bgEarth.23805b90.svg,1564408324937,8e0a5cc1e1c5454dd223a63a42a0fbc62b36f24706ca5a0092877894c462084c 13 | static/media/ConnectIcon.3c3754fc.png,1564408324936,f1203a8de4545478a505ec94864623208180dc8580a9ba6be5b80e5e7efaf739 14 | static/media/InviteFriends.3ef088eb.svg,1564408324937,c25eff6132ee9d3efeaaf6314a49303a33541cd5116af7d51994d6ebbf98bbf6 15 | static/media/FriendsIcon.bb12134f.svg,1564408324937,d5f73a9a913fbd8a6d7b5e09dee2f01eda0fa9f1ea11c2939b5363d411b773ec 16 | static/media/Logo.d338c427.svg,1564408324937,fe6cdf039187baa992e12c487270725c108cd80f84b06cdf514f2e5affd54939 17 | static/media/SearchIcon.5a4cf910.svg,1564408324936,700315112da3d61e765f7e443aa5444938771f0b8a23f6f73b79aff550917c56 18 | static/media/tail-spin.f5acdf2c.svg,1564408324936,b8238ab6b27ab747a6072df69f2a02bf8e311be9678214a19056f981158ace1b 19 | static/media/Team.308f6f7b.svg,1564408324937,6232819dc4c12bfaaa11e47209b41083ac79e706ed28ef9b4f8ee28ceec5dea7 20 | static/media/MySettingsIcon.649c4940.svg,1564408324937,2d537a40da731afadba50a17fd7f5d00da9adb2bb00893d0170e3f23c3d40c2f 21 | static/css/main.94f683b9.chunk.css.map,1564408325020,a2aec3593dc8a70c614add6d18737590ac67a2411d5fa793dcfc4a181db76309 22 | static/js/main.9a2ff0c3.chunk.js,1564408324936,094cde06d606b3a327e2c0ec37f203902e5c5c9aca6b72c497dce32f22988c43 23 | mappypals-logo.ico,1564406798718,4d3128e5b7d6c6e14ecc0be72ebe60982682b68e289fb812140e4847e79e2174 24 | static/js/main.9a2ff0c3.chunk.js.map,1564408325022,7e258bfda59a9e61c97f651e6fe812d7bd7c4fdc6bfa7b5cc0e442970f786f73 25 | static/media/adult-backlit-beach.d96e6a53.jpg,1564408324934,03508e408eabf98e40265d19892f56fa9e540fafb4fa341ccb0e5cb54f963982 26 | static/js/2.d8c35629.chunk.js,1564408325020,3c43f4b615b95eb694eb756bba2f3a888f7fce8f4f88db5cb85249a42feee8fd 27 | static/js/2.d8c35629.chunk.js.map,1564408325025,1b386529360b12da929402a8a73914f9c44652a069e63e173fb777ad9225d47d 28 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "mappypals" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /server/node_modules 6 | /.pnp 7 | .pnp.js 8 | .vscode/ 9 | 10 | # For front end 11 | /server 12 | 13 | # testing 14 | /coverage 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | .env 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | debug.log 31 | src/server/db.json 32 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | cache: 5 | directories: 6 | - node_modules 7 | script: 8 | - npm run build 9 | deploy: 10 | provider: pages 11 | skip_cleanup: true 12 | github_token: $github_token 13 | local_dir: build 14 | on: 15 | branch: master 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to MappyPals 2 | 3 | When contributing to this repository, check the Trello board (link on Discord) for available tasks. If the task is: 4 | 5 | - **Available**: Go ahead and claim the task, and proceed to work on a PR 6 | - **Claimed**: If someone else has claimed the task, speak with them or one of the project admin. PRs will only be accepted from the person that claimed that task. 7 | - **Non-Existant**: If the feature does not appear on trello, discuss it on Discord or speak with a project admin. There may be a good reason this feature is not listed. 8 | 9 | The above is to ensure, everyone has the chance to get involved without waiting their time or rushing to add the feature. 10 | 11 | Please also note we have a code of conduct, please follow it in all your interactions with the project. 12 | 13 | ## Trello Process 14 | 15 | 1. **Staging Area** 16 | 17 | - If you have an idea, suggestion or have found a bug in the project. Create a task with a detailed description, if it's a bug add photo, and steps on how to replicate it. We'll then discuss it in the task comment section or on discord. If its approved by the community it will be added to the `Tasks(current)` area. You can also assign yourself to the task, that way nobody could work on that task but you if its approved. 18 | 19 | 2. **Tasks (current)** 20 | 21 | - This is where you can find available tasks! If you are looking for a new challenge, select a task that excites you, assign your self to it and move it into the `In Progress` area. 22 | - **_NOTE_** - Don't take tasks that have already been assigned to someone, without their or a project admins approval. 23 | 24 | 3. **In Progress** 25 | 26 | - This is where tasks that are being worked on belong. Drag your task into this section, when you start working on it. 27 | - **_NOTE_** Check out the [PR Guide](https://github.com/zero-to-mastery/mappypals/blob/master/pr-guide.pdf) by @Thijs, for more information on our forking/branching and keeping forks synced with the upstream. 28 | 29 | 4. **Review** 30 | 31 | - This is where tasks are place whilst the PR has been submitted and is awaiting approval. Once the code has been reviewed it will be merged and the task can be moved into the `Done` section. 32 | 33 | 5. **Done** 34 | - Tasks that have been completed, reviewed and merged can be found in this section. 35 | 36 | ## Pull Request Process 37 | 38 | Please checkout the Github Guide created by @thijs, it can be found [here](https://github.com/zero-to-mastery/mappypals/blob/master/pr-guide.pdf) 39 | 40 | When creating a PR, please ensure the follow have been addressed: 41 | 42 | 1. Update the README.md with details of changes to the interface, this includes new environment 43 | variables, exposed ports, useful file locations etc. 44 | 2. Ensure the PR comment is documented with the changes you have made, for example `updated code` isn't really useful. 45 | 3. After you submit the PR, check that Travis successfully built the project and there are no code conflicts, your PR cannot be merged without correcting failures to build or code conflicts. 46 | 47 | ## Code of Conduct 48 | 49 | ### Our Pledge 50 | 51 | In the interest of fostering an open and welcoming environment, we as 52 | contributors and maintainers pledge to making participation in our project and 53 | our community a harassment-free experience for everyone, regardless of age, body 54 | size, disability, ethnicity, gender identity and expression, level of experience, 55 | nationality, personal appearance, race, religion, or sexual identity and 56 | orientation. 57 | 58 | ### Our Standards 59 | 60 | Examples of behaviour that contributes to creating a positive environment 61 | include: 62 | 63 | - Using welcoming and inclusive language 64 | - Being respectful of differing viewpoints and experiences 65 | - Gracefully accepting constructive criticism 66 | - Focusing on what is best for the community 67 | - Showing empathy towards other community members 68 | 69 | Examples of unacceptable behaviour by participants include: 70 | 71 | - The use of sexualized language or imagery and unwelcome sexual attention or 72 | advances 73 | - Trolling, insulting/derogatory comments, and personal or political attacks 74 | - Public or private harassment 75 | - Publishing others' private information, such as a physical or electronic 76 | address, without explicit permission 77 | - Other conduct which could reasonably be considered inappropriate in a 78 | professional setting 79 | 80 | ### Our Responsibilities 81 | 82 | Project maintainers are responsible for clarifying the standards of acceptable 83 | behaviour and are expected to take appropriate and fair corrective action in 84 | response to any instances of unacceptable behaviour. 85 | 86 | Project maintainers have the right and responsibility to remove, edit, or 87 | reject comments, commits, code, wiki edits, issues, and other contributions 88 | that are not aligned to this Code of Conduct, or to ban temporarily or 89 | permanently any contributor for other behaviours that they deem inappropriate, 90 | threatening, offensive, or harmful. 91 | 92 | ### Scope 93 | 94 | This Code of Conduct applies both within project spaces and in public spaces 95 | when an individual is representing the project or its community. Examples of 96 | representing a project or community include using an official project e-mail 97 | address, posting via an official social media account, or acting as an appointed 98 | representative at an online or offline event. Representation of a project may be 99 | further defined and clarified by project maintainers. 100 | 101 | ### Enforcement 102 | 103 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be 104 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 105 | complaints will be reviewed and investigated and will result in a response that 106 | is deemed necessary and appropriate to the circumstances. The project team is 107 | obligated to maintain confidentiality with regard to the reporter of an incident. 108 | Further details of specific enforcement policies may be posted separately. 109 | 110 | Project maintainers who do not follow or enforce the Code of Conduct in good 111 | faith may face temporary or permanent repercussions as determined by other 112 | members of the project's leadership. 113 | 114 | ### Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 117 | available at [http://contributor-covenant.org/version/1/4][version] 118 | 119 | [homepage]: http://contributor-covenant.org 120 | [version]: http://contributor-covenant.org/version/1/4/ 121 | -------------------------------------------------------------------------------- /DEV_README.md: -------------------------------------------------------------------------------- 1 | # Important! 2 | 3 | - **Think in Components** - exmple: a **Button** can be created as a component of its own which can then be reused across the application. Building this way helps with consistency and resuability. 4 | 5 | - **Organize and group things as much as possible**. The bigger our codebase grows, the more likely it is for bugs to occur, but keeping code and features orginzed helps in tracking down defects. 6 | 7 | - **Let others know what you're work on**. You can do that posting in the **#mappypals** discord channel. This will help ensure that different people are not seperately building the same feature/functionality. 8 | 9 | - Use the **#mappypals** Discord channel to discuss and ask questions about the app. 10 | 11 | ## How to Write CSS 12 | 13 | - **Reuse UI folder elements** - [UI folder](https://github.com/zero-to-mastery/mappypals/tree/dev/src/components/UI). Instead of creating new elements. Reuse elements from UI folder. 14 | 15 | - **Use classes instead of selectors** - Do not write .p{} instead use classes. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zero To Mastery 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 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ 3 | "rules": { 4 | ".read": true, 5 | ".write": true 6 | } 7 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "firestore": { 6 | "rules": "firestore.rules", 7 | "indexes": "firestore.indexes.json" 8 | }, 9 | "hosting": { 10 | "public": "build", 11 | "headers": [ 12 | {"source": "/service-worker.js", "headers": [{"key": "Cache-Control", "value": "no-cache"}]} 13 | ], 14 | "ignore": [ 15 | "firebase.json", 16 | "**/.*", 17 | "**/node_modules/**" 18 | ], 19 | "rewrites": [ 20 | { 21 | "source": "**", 22 | "destination": "/index.html" 23 | } 24 | ] 25 | }, 26 | "storage": { 27 | "rules": "storage.rules" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [], 3 | "fieldOverrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | 3 | // // Create and Deploy Your First Cloud Functions 4 | // // https://firebase.google.com/docs/functions/write-firebase-functions 5 | // 6 | // exports.helloWorld = functions.https.onRequest((request, response) => { 7 | // response.send("Hello from Firebase!"); 8 | // }); 9 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "serve": "firebase serve --only functions", 6 | "shell": "firebase functions:shell", 7 | "start": "npm run shell", 8 | "deploy": "firebase deploy --only functions", 9 | "logs": "firebase functions:log" 10 | }, 11 | "engines": { 12 | "node": "8" 13 | }, 14 | "dependencies": { 15 | "firebase-admin": "^8.0.0", 16 | "firebase-functions": "^3.1.0" 17 | }, 18 | "devDependencies": { 19 | "firebase-functions-test": "^0.1.6" 20 | }, 21 | "private": true 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mappypals", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devserver": "live-server --browser=Chrome", 6 | "dependencies": { 7 | "axios": "^0.18.0", 8 | "concurrently": "^4.1.0", 9 | "deck.gl": "^7.0.11", 10 | "dotenv": "^8.0.0", 11 | "firebase": "^6.3.3", 12 | "isemail": "^3.2.0", 13 | "ky": "^0.10.0", 14 | "mapbox-gl": "^0.53.0", 15 | "node-sass": "^4.12.0", 16 | "react": "^16.8.2", 17 | "react-dom": "^16.8.2", 18 | "react-map-gl": "^4.0.11", 19 | "react-map-gl-geocoder": "^2.0.5", 20 | "react-mapbox-gl": "^4.2.0", 21 | "react-redux": "^7.1.0", 22 | "react-router-dom": "^4.3.1", 23 | "react-scripts": "3.0.1", 24 | "react-select": "^3.0.4", 25 | "redux": "^4.0.1", 26 | "redux-thunk": "^2.3.0", 27 | "styled-components": "^4.3.2", 28 | "tachyons": "^4.11.1", 29 | "validator": "^11.1.0" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start && node --require dotenv/config index.js", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test", 35 | "eject": "react-scripts eject", 36 | "format": "prettier --write \"**/*.{js,css,md}\"", 37 | "prestart:api": "node src/server/createMockDB.js", 38 | "start:api": "node src/server/server.js" 39 | }, 40 | "eslintConfig": { 41 | "extends": "react-app" 42 | }, 43 | "browserslist": [ 44 | ">0.2%", 45 | "not dead", 46 | "not ie <= 11", 47 | "not op_mini all" 48 | ], 49 | "devDependencies": { 50 | "json-server": "^0.15.0", 51 | "faker": "^4.1.0", 52 | "prettier": "1.17.1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pr-guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/pr-guide.pdf -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "repoName": "mappypals", 3 | "tagline": "Keeping track of your travelling buddies!", 4 | "color": "#cf48ff", 5 | "logo": "https://cdn.discordapp.com/attachments/568453633436418049/575615439539929089/logo-mappypals.PNG", 6 | "channelName": "mappypals", 7 | "desc": [ 8 | "MappyPals is a collaboration project managed by a small group of dedicated fellow students. Building an application to provide users a platform to keep track of which countries their friends and family are in, to make meeting up an easier task.", 9 | "MappyPals is being built upon the MERN stack, with the intention of releasing and maintaining the project in the real world based upon user suggestions and feedback. Therefor providing an awesome opportunity not only the ability to expand on and learn new skills, but also a project you can include on your resume and show off to friends, family and potential employers.", 10 | "Whilst working on any number of features that you would typically find in a social platform such as this, you will gain valuable experience in collaborating in a fairly large group and working with version control and source code management (Github), which are extremely valuable skills." 11 | ], 12 | "team": [ 13 | ["mubarakshow", "Project Lead"], 14 | ["Arnas Dičkus", "Project Manager"], 15 | ["Thijs", "Design/Frontend Lead"], 16 | ["phpist", "Backend Lead"] 17 | ], 18 | "links": [ 19 | ["Frontend Repo", "https://github.com/zero-to-mastery/mappypals"], 20 | ["Backend Repo", "https://github.com/zero-to-mastery/mappypals_backend"], 21 | ["Contributing", "https://github.com/zero-to-mastery/mappypals/blob/master/CONTRIBUTING.md"], 22 | ["Mockups", "https://www.figma.com/file/MWIsRxJQP2pPMXINVX5UNqDr/MappyPals-Mockups?node-id=0%3A1&redirected=1"], 23 | ["Github", "https://github.com/zero-to-mastery/mappypals/blob/master/pr-guide.pdf"] 24 | ], 25 | "poi": [ 26 | ["Readme", "https://github.com/zero-to-mastery/mappypals/blob/master/README.md"], 27 | ["Frontend Repo", "https://github.com/zero-to-mastery/mappypals"], 28 | ["Backend Repo", "https://github.com/zero-to-mastery/mappypals_backend"], 29 | ["PR Guide", "https://github.com/zero-to-mastery/mappypals_backend/blob/master/pr-guide.pdf"] 30 | ], 31 | "tasks": { 32 | "title": "Trello Tasks", 33 | "desc": "We use Trello to manage the tasks within this project, [here](https://trello.com/invite/mappypals/570aff1966dc01d4b1e212ef8bbb9a08) is your invitation. Further information on how to use use Trello to sign up to a task can be found in the [CONTRIBUTING.MD](https://github.com/zero-to-mastery/mappypals/blob/master/CONTRIBUTING.md) file on Github" 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 22 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 44 | Mappy Pals 45 | 46 | 47 | 50 |
51 |
52 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/mappypals-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/public/mappypals-logo.ico -------------------------------------------------------------------------------- /src/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |

The specified file was not found on this website. Please check the URL for mistakes and try again.

29 |

Why am I seeing this?

30 |

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | *,*::before,*::after{ 2 | box-sizing: border-box; 3 | } 4 | body, 5 | html { 6 | padding: 0px; 7 | margin: 0px; 8 | } 9 | #root { 10 | display: grid; 11 | grid-template-columns: auto 300px; 12 | max-width: 100vw; 13 | max-height: 100vh; 14 | width: 100%; 15 | height: 90vh; 16 | } 17 | 18 | @media screen and (max-width: 900px) { 19 | #root { 20 | display: inline-flex; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import Navbar from './components/Navbar/Navbar'; 4 | import Main from './components/Main'; 5 | import Layout from './components/Layout'; 6 | 7 | class App extends Component { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 |
14 | 15 | 16 | ); 17 | } 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/api/apiHelpers.js: -------------------------------------------------------------------------------- 1 | const handleResponse = async response => { 2 | if (response.ok) return response.json(); 3 | // server side validation error 4 | if (response.status === 400) { 5 | const error = await response.text(); 6 | console.log(error); 7 | throw new Error(error); 8 | } 9 | // network error 10 | throw new Error('Network response error'); 11 | }; 12 | // Log errors 13 | const handleError = error => { 14 | console.error(`API call error ${error}`); 15 | throw error; 16 | }; 17 | export { handleResponse, handleError }; 18 | -------------------------------------------------------------------------------- /src/api/userApi.js: -------------------------------------------------------------------------------- 1 | import { handleError, handleResponse } from './apiHelpers'; 2 | 3 | // we should use .env variables for constants and keys 4 | const url = process.env.REACT_APP_API_URL + '/friends/'; 5 | 6 | export const getFriends = async () => { 7 | try { 8 | const response = await fetch(url); 9 | return handleResponse(response); 10 | } catch (error) { 11 | return handleError(error); 12 | } 13 | }; 14 | 15 | export default getFriends; 16 | -------------------------------------------------------------------------------- /src/assets/ConnectIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/assets/ConnectIcon.png -------------------------------------------------------------------------------- /src/assets/InviteIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/assets/InviteIcon.png -------------------------------------------------------------------------------- /src/assets/SignUpIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/assets/SignUpIcon.png -------------------------------------------------------------------------------- /src/assets/bgImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/assets/bgImage.png -------------------------------------------------------------------------------- /src/assets/svg/ConnectBoxIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/assets/svg/InviteFriendsBoxIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/assets/svg/SignUpBoxIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/BaseScss/variables.scss: -------------------------------------------------------------------------------- 1 | $dark: #6831DE; 2 | $light: #E03BFB; -------------------------------------------------------------------------------- /src/components/ErrorMessages/ErrorMessages.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './ErrorMessages.module.css'; 3 | 4 | const ErrorMessage = ({ content }) => { 5 | return ( 6 | 7 | {content} 8 | 9 | ); 10 | }; 11 | 12 | export default ErrorMessage; 13 | -------------------------------------------------------------------------------- /src/components/ErrorMessages/ErrorMessages.module.css: -------------------------------------------------------------------------------- 1 | .warning { 2 | color: red; 3 | font-size: 12px; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/ErrorMessages/PasswordMessage/PasswordMessage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import classes from './PasswordMessage.module.css'; 3 | class PasswordMessage extends Component { 4 | constructor(props) { 5 | super(props); 6 | 7 | this.state = { 8 | passwordLength: false, 9 | numberLength: false, 10 | capitalLetter: false, 11 | passwordIdentical: false 12 | }; 13 | } 14 | componentDidUpdate(oldProps) { 15 | // Update every time new values are added. 16 | const newProps = this.props; 17 | if (oldProps.password !== newProps.password) { 18 | this.resetCheck(); 19 | this.passwordValidation( 20 | this.props.password, 21 | this.props.confirmPassword 22 | ); 23 | } 24 | if (oldProps.confirmPassword !== newProps.confirmPassword) { 25 | this.resetCheck(); 26 | this.passwordValidation( 27 | this.props.password, 28 | this.props.confirmPassword 29 | ); 30 | } 31 | } 32 | // Can't reuse helper.js validation here. So it become double logic. 33 | passwordValidation = (password, confirmPassword) => { 34 | // Validation: https://stackoverflow.com/questions/18367258/validation-for-password-is-at-least-6-characters 35 | if (password.length >= 6) { 36 | this.setState({ passwordLength: true }); 37 | } 38 | // check for a number 39 | if (!/[0-9]/.test(password) === false) { 40 | this.setState({ numberLength: true }); 41 | } 42 | // check for a capital letter and lowercase letter 43 | if ( 44 | /[A-Z]/.test(password) === true && 45 | /[a-z]/.test(password) === true 46 | ) { 47 | this.setState({ capitalLetter: true }); 48 | } 49 | // Check appears only if 50 | if (confirmPassword.length > 0) { 51 | this.setState({ passwordIdentical: true }); 52 | } 53 | }; 54 | resetCheck = () => { 55 | this.setState({ 56 | passwordLength: false, 57 | numberLength: false, 58 | capitalLetter: false, 59 | passwordIdentical: false 60 | }); 61 | }; 62 | render() { 63 | const { 64 | passwordLength, 65 | numberLength, 66 | capitalLetter, 67 | passwordIdentical 68 | } = this.state; 69 | let passwordLengthVar = ( 70 |
    71 |
  • 72 | Be at least 6 character long 73 |
  • 74 |
75 | ); 76 | let numberLengthVar = ( 77 |
    78 |
  • 79 | Include at least one number 80 |
  • 81 |
82 | ); 83 | let capitalLetterVar = ( 84 |
    85 |
  • 86 | Include both lower and upper case character 87 |
  • 88 |
89 | ); 90 | // By default be hidden. 91 | let passwordIdenticalVar = ''; 92 | 93 | if (passwordLength) { 94 | passwordLengthVar = ( 95 |
    96 |
  • 97 | Be at least 6 character long 98 |
  • 99 |
100 | ); 101 | } 102 | if (numberLength) { 103 | numberLengthVar = ( 104 |
    105 |
  • 106 | Include at least one number 107 |
  • 108 |
109 | ); 110 | } 111 | if (capitalLetter) { 112 | capitalLetterVar = ( 113 |
    114 |
  • 115 | Include both lower and upper case character 116 |
  • 117 |
118 | ); 119 | } 120 | if ( 121 | passwordIdentical && 122 | this.props.password === this.props.confirmPassword 123 | ) { 124 | passwordIdenticalVar = ( 125 |
    126 |
  • Password match
  • 127 |
128 | ); 129 | } 130 | if ( 131 | passwordIdentical && 132 | this.props.password !== this.props.confirmPassword 133 | ) { 134 | passwordIdenticalVar = ( 135 |
    136 |
  • Password match
  • 137 |
138 | ); 139 | } 140 | return ( 141 |
142 |

143 | 144 | Your Password need to: 145 |

146 | {capitalLetterVar} 147 | {numberLengthVar} 148 | {passwordLengthVar} 149 | {passwordIdenticalVar} 150 |
151 | ); 152 | } 153 | } 154 | 155 | export default PasswordMessage; 156 | -------------------------------------------------------------------------------- /src/components/ErrorMessages/PasswordMessage/PasswordMessage.module.css: -------------------------------------------------------------------------------- 1 | ul { 2 | margin: 0; 3 | list-style-type: none; 4 | } 5 | li { 6 | margin-bottom: 10px; 7 | } 8 | .incorrect { 9 | text-indent: -1.4em; 10 | color: black; 11 | font-weight: 700; 12 | font-size: 12px; 13 | } 14 | 15 | .incorrect:before { 16 | color: red; 17 | font-size: 20px; 18 | font-family: 'Font Awesome 5 Free'; 19 | font-weight: 900; 20 | content: '\f00d'; 21 | margin: 0 1rem 0 -15px; 22 | } 23 | .correct { 24 | text-indent: -1.4em; 25 | color: green; 26 | font-weight: 700; 27 | font-size: 12px; 28 | } 29 | 30 | .correct:before { 31 | color: green; 32 | font-size: 20px; 33 | font-family: 'Font Awesome 5 Free'; 34 | font-weight: 900; 35 | content: '\f00c'; 36 | margin: 0 10px 0 -15px; 37 | color: green; 38 | } 39 | 40 | .container { 41 | margin-bottom: 20px; 42 | margin-top: 20px; 43 | padding: 10px; 44 | border: 2px solid red; 45 | border-radius: 10px; 46 | } 47 | 48 | .LockIcon { 49 | font-size: 20px; 50 | color: red; 51 | padding-right: 10px; 52 | } 53 | .title { 54 | color: red; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Firebase/firebase.js: -------------------------------------------------------------------------------- 1 | import app from 'firebase/app'; 2 | import 'firebase/auth'; 3 | import 'firebase/firestore'; 4 | const config = { 5 | apiKey: process.env.REACT_APP_API_KEY, 6 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 7 | databaseURL: process.env.REACT_APP_DATABASE_URL, 8 | projectId: process.env.REACT_APP_PROJECT_ID, 9 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 10 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, 11 | appId: process.env.REACT_APP_APP_ID 12 | }; 13 | 14 | class Firebase { 15 | constructor() { 16 | app.initializeApp(config); 17 | this.auth = app.auth(); 18 | this.db = app.firestore(); 19 | this.googleProvider = new app.auth.GoogleAuthProvider(); 20 | this.facebookProvider = new app.auth.FacebookAuthProvider(); 21 | this.twitterProvider = new app.auth.TwitterAuthProvider(); 22 | } 23 | // user api 24 | user = uid => this.db.collection('users').doc(uid); 25 | users = () => this.db.collection('users'); 26 | 27 | // Auth api. End points called asynchronously, they need to be 28 | // resolved later 29 | createUserEmailAndPassword = (email, password) => 30 | this.auth.createUserWithEmailAndPassword(email, password); 31 | 32 | updateProfile = updates => 33 | this.auth.currentUser.updateProfile({ ...updates }); 34 | 35 | signInEmailPassword = (email, password) => 36 | this.auth.signInWithEmailAndPassword(email, password); 37 | signOut = () => this.auth.signOut(); 38 | passwordReset = email => this.auth.sendPasswordResetEmail(email); 39 | passwordUpdate = password => this.auth.currentUser.updatePassword(password); 40 | // Social logins 41 | googleSignIn = () => this.auth.signInWithPopup(this.googleProvider); 42 | facebookSignIn = () => this.auth.signInWithPopup(this.facebookProvider); 43 | twitterSignIn = () => this.auth.signInWithPopup(this.twitterProvider); 44 | } 45 | export default new Firebase(); 46 | -------------------------------------------------------------------------------- /src/components/HomeCard/home-card.component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Text from '../UI/Text/Text'; 4 | 5 | import { Row, Column } from '../UI/Grid/Grid'; 6 | 7 | import { Circle, Card } from './home-card.styles'; 8 | 9 | export const HomeCard = ({ number, title, icon, text, ...otherProps }) => { 10 | const fontSize = otherProps.textSize ? otherProps.textSize : 1; 11 | 12 | return ( 13 | 14 | 15 | 16 | {number} 17 | 18 | 19 | 24 | {title} 25 | 26 | 27 | 28 | 29 | 30 | 31 | card-logo 32 | 33 | 34 | 35 | 36 | 37 | 38 | {text} 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default HomeCard; 47 | -------------------------------------------------------------------------------- /src/components/HomeCard/home-card.styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import Container from '../UI/Container/Container'; 3 | 4 | export const Circle = styled.div` 5 | height: 30px; 6 | width: 30px; 7 | color: #fff; 8 | background-color: #6831de; 9 | border-radius: 100%; 10 | font-weight: bold; 11 | font-family: 'Poppins', sans-serif; 12 | text-align: center; 13 | font-size: 120%; 14 | `; 15 | 16 | export const Card = styled(Container)` 17 | display: flex; 18 | justify-content: space-between; 19 | height: 405px; 20 | width: 285px; 21 | background-color: #fff; 22 | border: 1px solid #6831de; 23 | border-radius: 15px; 24 | box-shadow: 0 8px 6px -6px black; 25 | `; 26 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // The Layout componet is where we define how the pages ({children}) are laid out. 4 | // You may style this componet for general page layout 5 | // To create a specific layout for a specific page, create its own css file. 6 | 7 | export default ({ children }) => {children}; 8 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import Login from '../pages/Login/Login'; 4 | import Home from '../pages/Home/Home'; 5 | import Contact from '../pages/Contact/Contact'; 6 | import About from '../pages/About/About'; 7 | import Signup from '../pages/Login/Signup'; 8 | import ForgotPassword from '../pages/Login/ForgotPassword'; 9 | import ResetPassword from '../pages/Login/ResetPassword'; 10 | import ProfileSettings from '../pages/Settings/ProfileSettings/ProfileSettings'; 11 | import SettingsEmailPassword from '../pages/Settings/SettingsEmailPassword/SettingsEmailPassword'; 12 | import FriendsList from '../pages/FriendsList/FriendsList'; 13 | import Page404 from '../pages/Page404/Page404'; 14 | import PersonalProfile from '../pages/PersonalProfile/PersonalProfile'; 15 | import InviteFriends from '../pages/InviteFriends/InviteFriends'; 16 | // The Main component renders one of the three provided 17 | // Routes (provided that one matches). Both the /roster 18 | // and /schedule routes will match any pathname that starts 19 | // with /roster or /schedule. The / route will only match 20 | // when the pathname is exactly the string "/" 21 | const Main = () => ( 22 |
23 | 24 | 25 | {/* https://stackoverflow.com/questions/53007905/react-router-with-react-16-6-suspense-invalid-prop-component-of-type-object/53019873 26 | Used Render instead of component 27 | to counter warning if redux connect used on file. */} 28 | 29 | } /> 30 | 31 | 32 | 33 | 34 | } 37 | /> 38 | 39 | 43 | 47 | 48 | 49 | 50 |
51 | ); 52 | 53 | export default Main; 54 | -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.css: -------------------------------------------------------------------------------- 1 | /*b3442e 636863 b49410 d1d1d1 568b98*/ 2 | 3 | hr { 4 | border: none; 5 | background-color: lightgrey; 6 | height: 5px; 7 | width: 80%; 8 | margin: 0px auto; 9 | } 10 | #nav-bar { 11 | justify-content: center; 12 | position: fixed; 13 | order: 2; 14 | right: 0px; 15 | z-index: 99; 16 | display: grid; 17 | grid-template-rows: repeat(5, 20%); 18 | grid-template-columns: 1fr; 19 | width: 300px; 20 | background-color: white; 21 | height: 100vh; 22 | font-family: 'Poppins', sans-serif; 23 | } 24 | .item-wrapper { 25 | display: flex; 26 | width: 100%; 27 | height: 100%; 28 | justify-content: center; 29 | align-items: center; 30 | border-top: 5px solid lightgrey; 31 | } 32 | .item-wrapper:nth-child(1) { 33 | border-top: 0px; 34 | } 35 | .item-wrapper:nth-child(1) a { 36 | color: #e03bfb; 37 | } 38 | .nav-item { 39 | margin: 0px; 40 | text-align: center; 41 | height: auto; 42 | } 43 | #nav-bar svg { 44 | height: 4rem; 45 | } 46 | .nav-item p { 47 | margin: 0; 48 | } 49 | 50 | .item-wrapper:hover, 51 | .item-wrapper:hover a { 52 | background-color: lightgrey; 53 | color: #e03bfb; 54 | } 55 | .no-hover:hover, 56 | .no-hover:hover a { 57 | background-color: white; 58 | color: black; 59 | } 60 | .scale { 61 | display: flex; 62 | flex-direction: column; 63 | } 64 | .scale a { 65 | text-align: center; 66 | width: 100%; 67 | } 68 | 69 | .hamburger { 70 | position: absolute; 71 | z-index: 99; 72 | display: flex; 73 | right: 10px; 74 | top: 10px; 75 | background-color: #fbfbfb; 76 | width: 50px; 77 | height: 30px; 78 | cursor: pointer; 79 | font-size: 30px; 80 | color: #6831de; 81 | text-align: center; 82 | justify-content: center; 83 | border-radius: 5px; 84 | } 85 | .hamburger-icon { 86 | display: block; 87 | position: relative; 88 | transform: scale(1.5, 0.8); 89 | line-height: 30px; 90 | } 91 | .setting-item { 92 | font-weight: bold; 93 | cursor: pointer; 94 | } 95 | .settings-navbar { 96 | position: absolute; 97 | right: 300px; 98 | background-color: #ffffff; 99 | } 100 | .setting-navbar-item { 101 | width: 300px; 102 | padding: 33px 0; 103 | font-weight: 400; 104 | margin: 0; 105 | cursor: pointer; 106 | color: #000; 107 | border: 4px solid lightgray; 108 | } 109 | .setting-navbar-item:hover { 110 | background-color: #d3d3d3; 111 | color: #e03bfb; 112 | } 113 | .u-border-bottom { 114 | border-bottom: 2px solid #c4c4c4; 115 | } 116 | @media (max-width: 900px) { 117 | #nav-bar svg { 118 | height: 7rem; 119 | } 120 | } 121 | @media (max-width: 750px) { 122 | #nav-bar svg { 123 | height: 4rem; 124 | } 125 | } 126 | @media screen and (min-width: 375px) and (orientation: portrait) { 127 | .nav-item * { 128 | width: 100%; 129 | } 130 | .nav-item p { 131 | margin: 5px auto; 132 | } 133 | } 134 | @media screen and (max-width: 320px) { 135 | #nav-bar { 136 | width: 80%; 137 | } 138 | .nav-item { 139 | height: 100%; 140 | display: inline-flex; 141 | align-items: center; 142 | justify-content: center; 143 | } 144 | .nav-item * { 145 | width: 35%; 146 | } 147 | .nav-item p { 148 | margin: 0px; 149 | width: auto; 150 | right: 0px; 151 | } 152 | } 153 | @media screen and (orientation: landscape) and (max-width: 900px) { 154 | .item-wrapper { 155 | height: auto; 156 | } 157 | .nav-item { 158 | display: inline-flex; 159 | align-items: center; 160 | justify-content: center; 161 | height: 50%; 162 | } 163 | .no-hover { 164 | display: flex; 165 | flex-direction: row; 166 | justify-content: center; 167 | width: 100%; 168 | height: 100%; 169 | } 170 | .no-hover a { 171 | width: 40%; 172 | margin: 10px; 173 | } 174 | .no-hover p { 175 | margin: 0px; 176 | height: 35px; 177 | width: 100%; 178 | line-height: 35px; 179 | } 180 | .nav-item svg { 181 | height: 50px; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import SearchIcon from '../pics/SearchIcon.svg'; 3 | 4 | const LineBlock = styled.div` 5 | z-index: 999; 6 | display: flex; 7 | flex-direction: column; 8 | background-color: transparent; 9 | margin-left: 100px; 10 | `; 11 | 12 | const DropDown = styled.form` 13 | z-index: 101; 14 | position: relative; 15 | top: 100px; 16 | background-color: transparent; 17 | font-family: 'Poppins', sans-serif; 18 | line-height: 1.75; 19 | } 20 | fieldset { 21 | border: #ffffff; 22 | } 23 | .no-friends { 24 | color: #6831DE; 25 | padding: 0.5rem; 26 | } 27 | 28 | .friends { 29 | z-index: 100; 30 | display: flex; 31 | flex-direction: column; 32 | align-items: flex-start; 33 | margin-left: 10px; 34 | background-color: white; 35 | border-bottom: 1px solid #6831DE; 36 | list-style: none; 37 | margin-top: 0; 38 | max-height: vh - 150; 39 | color: grey; 40 | font-size: 1.2rem; 41 | overflow-y: no-scroll; 42 | width: 230px; 43 | } 44 | .friend li { 45 | display: flex; 46 | flex-direction: row; 47 | align-items: flex-start; 48 | color: grey; 49 | } 50 | img { 51 | margin-top: 5px; 52 | margin-right: 10px; 53 | border-radius: 25px; 54 | border: 1px solid lightgrey; 55 | } 56 | .friend-active, 57 | .friend li:hover { 58 | background-color: seashell; 59 | color: black; 60 | font-size: 1.3rem; 61 | font-weight: 400; 62 | } 63 | 64 | .friend li:not(:last-of-type) { 65 | border-bottom: 1px solid #6831DE; 66 | } 67 | `; 68 | 69 | const SearchStyles = styled.div` 70 | z-index: 99; 71 | display: inline-flex; 72 | flex-direction: row; 73 | justify-content: center; 74 | height: 50px; 75 | select { 76 | width: 160px; 77 | height: 50px; 78 | font-size: 1.2rem; 79 | text-indent: 55px; 80 | background-image: url(${SearchIcon}); 81 | background-size: 50px 50px; 82 | background-repeat: no-repeat; 83 | border: none; 84 | border-radius: 25px 0px 0px 25px; 85 | } 86 | input { 87 | width: 270px; 88 | height: 30px; 89 | padding: 10px; 90 | font-size: 1.2rem; 91 | text-indent: 17px; 92 | border: none; 93 | border-radius: 0px 25px 25px 0px; 94 | } 95 | input:focus { 96 | border-style: none; 97 | outline: none; 98 | } 99 | `; 100 | 101 | export { DropDown, SearchStyles, LineBlock }; 102 | -------------------------------------------------------------------------------- /src/components/UI/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './Button.module.css'; 3 | 4 | const button = props => { 5 | return ( 6 | 12 | ); 13 | }; 14 | 15 | export default button; 16 | -------------------------------------------------------------------------------- /src/components/UI/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | .Button { 2 | width: auto; 3 | background: linear-gradient(to bottom right, #e03bfb, #6831de); 4 | color: #ffffff; 5 | border: none; 6 | border-radius: 10px; 7 | font-size: 1.2rem; 8 | font-weight: 600; 9 | white-space: nowrap; 10 | cursor: pointer; 11 | } 12 | 13 | .Button:hover, 14 | .Button:focus { 15 | opacity: 0.8; 16 | } 17 | .Submit { 18 | padding: 0.4rem 2.4rem; 19 | text-transform: uppercase; 20 | } 21 | 22 | .FriendList { 23 | font-size: 12px; 24 | display: flex; 25 | align-items: center; 26 | width: 80%; 27 | height: 25px; 28 | margin: 0.4em auto; 29 | padding: 0.25em 1em; 30 | } 31 | 32 | .YouSendAnInvite { 33 | padding: 0.4rem 2.4rem; 34 | margin-bottom: 10px; 35 | margin-right: 10px; 36 | } 37 | .GetStarted { 38 | padding: 10px 30px; 39 | background-image: linear-gradient(to right, #a731a5, #550554); 40 | text-transform: uppercase; 41 | } 42 | .ForgotPassword { 43 | padding: 20px; 44 | width: 100%; 45 | font-size: 1rem; 46 | } 47 | 48 | .ProfileSettingsInterests { 49 | padding: 10px 10px; 50 | margin-top: 15px; 51 | } 52 | 53 | .ProfileCard { 54 | padding: 10px 10px; 55 | width: 100%; 56 | } 57 | 58 | @media all and (min-width: 630px) { 59 | .ForgotPassword { 60 | font-size: 25px; 61 | } 62 | } 63 | 64 | .SettingsEmailPassword { 65 | padding: 10px 15px; 66 | height: 30px; 67 | line-height: 12px; 68 | font-size: 1rem; 69 | } 70 | 71 | .Page404 { 72 | padding: 0.5rem 3rem; 73 | font-size: 1rem; 74 | } 75 | @media (max-width: 700px) { 76 | .ProfileSettingsInterests{ 77 | width: 100%; 78 | } 79 | } 80 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 81 | .SettingsEmailPassword { 82 | font-size: 1.35rem; 83 | height: 45px; 84 | } 85 | } 86 | 87 | .Navbar { 88 | width: 80%; 89 | max-width: 15rem; 90 | margin: 10px 1.875rem; 91 | background-image: linear-gradient(to right, #cf48ff, #6831de); 92 | font-size: 1.5em; 93 | line-height: 3.125rem; 94 | transition: transform 0.3s ease-in; 95 | } 96 | 97 | .Navbar:hover { 98 | /* Opacity 1 to overide default button opacity */ 99 | opacity: 1; 100 | transition: transform 0.3s ease-in; 101 | transform: scale(1.05); 102 | } 103 | .InviteFriends { 104 | padding: 8px 18px; 105 | height: 30px; 106 | font-size: 0.7rem; 107 | } 108 | @media (max-width: 900px) { 109 | .Navbar { 110 | margin: 2px; 111 | } 112 | } 113 | 114 | @media screen and (max-width: 320px) { 115 | .Navbar { 116 | margin: 2px; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/components/UI/Chip/Chip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './Chip.module.scss'; 3 | 4 | export const Chip = ({ text, closeHandler }) => ( 5 |
6 |

{text}

7 | 10 |
11 | ); 12 | 13 | export default Chip; 14 | -------------------------------------------------------------------------------- /src/components/UI/Chip/Chip.module.scss: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | margin: 4px; 6 | background-color: rgb(195, 192, 192); 7 | border-radius: 5px; 8 | font-size: 0.7rem; 9 | height: 24px; 10 | } 11 | .button { 12 | background-color: Transparent; 13 | background-repeat: no-repeat; 14 | border: none; 15 | cursor: pointer; 16 | overflow: hidden; 17 | outline: none; 18 | } 19 | .text { 20 | padding-left: 4px; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/UI/Container/Container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const ContainerStyle = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | ${({ row }) => row && `flex-direction: row;`} 8 | ${({ justifyContent }) => 9 | `justify-content: ${justifyContent ? justifyContent : `flex-start`};`} 10 | ${({ alignItems }) => alignItems && `align-items: ${alignItems};`} 11 | ${({ wrap }) => `flex-wrap: ${wrap ? wrap : `nowrap`};`} 12 | ${({ center }) => center && `justify-content: center; align-items: center;`} 13 | ${({ height }) => height && `height: ${height}vh`} 14 | ${({ width }) => width && `width: ${width}vw`} 15 | ${({ bgColor }) => `background: ${bgColor ? bgColor : `#000`};`} 16 | ${({ color }) => `color: ${color ? color : `#fff`};`} 17 | `; 18 | 19 | export const Container = ({ ...otherProps }) => ( 20 | 21 | ); 22 | 23 | export default Container; 24 | -------------------------------------------------------------------------------- /src/components/UI/DropdownSelect/DropdownSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Select from 'react-select'; 3 | import makeAnimated from 'react-select/animated'; 4 | import classes from './DropdownSelect.module.css' 5 | 6 | const DropdownSelect = (props) => { 7 | const animatedComponents = makeAnimated(); 8 | const {options,handleChange} = props 9 | return ( 10 | 29 | 30 | 31 | ); 32 | } 33 | } 34 | export default SearchFriends; 35 | -------------------------------------------------------------------------------- /src/pages/Home/AddFriendForm/AddFriendForm.css: -------------------------------------------------------------------------------- 1 | #add-new { 2 | display: none; 3 | position: absolute; 4 | bottom: 8px; 5 | right: 16px; 6 | color: black; 7 | min-height: 0vh; 8 | } 9 | .AddFriendsTitle { 10 | color: #6831de; 11 | text-align: center; 12 | font-size: 20px; 13 | font-weight: 400; 14 | } 15 | .u-pb { 16 | padding-bottom: 10px; 17 | } 18 | .no-bg { 19 | background-image: none !important; 20 | height: auto !important; 21 | } 22 | 23 | @media screen and (max-width: 700px) { 24 | .u-pb { 25 | padding-bottom: 0; 26 | } 27 | #add-new { 28 | bottom: 0; 29 | right: 0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/Home/AddFriendForm/AddFriendForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../Home.css'; 3 | import '../../Login/Login.css'; 4 | import Form from '../../Login/Form'; 5 | import './AddFriendForm.css'; 6 | import Button from '../../../components/UI/Button/Button'; 7 | import XButton from '../../../components/UI/XButton/XButton'; 8 | 9 | class AddFriendForm extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | firstname: '', 14 | lastname: '', 15 | email: '' 16 | }; 17 | } 18 | onChange = event => { 19 | this.setState({ 20 | [event.target.name]: event.target.value 21 | }); 22 | }; 23 | handleSubmit = event => { 24 | event.preventDefault(); 25 | 26 | this.props.onFriendLoaded(this.state); 27 | 28 | this.setState({ 29 | firstName: '', 30 | lastname: '', 31 | email: '' 32 | }); 33 | }; 34 | 35 | render() { 36 | return ( 37 |
42 |
43 | 47 | X 48 | 49 |
50 | Add a friend by sending an invite 51 |
52 |
53 | 64 | 75 |
76 | 86 |
87 | 94 |
95 |
96 |
97 | ); 98 | } 99 | } 100 | export default AddFriendForm; 101 | -------------------------------------------------------------------------------- /src/pages/Home/AddFriendForm/InvitationSent.css: -------------------------------------------------------------------------------- 1 | .main { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | background-color: white; 7 | } 8 | .titleInvite { 9 | font-weight: 300; 10 | font-size: 20px; 11 | color: #6831de; 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/Home/AddFriendForm/InvitationSent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './InvitationSent.css'; 3 | import Form from '../../Login/Form'; 4 | import XButton from '../../../components/UI/XButton/XButton'; 5 | import Button from '../../../components/UI/Button/Button'; 6 | 7 | const InvitationSent = ({ InvitationForm, hideInvitationForm }) => { 8 | const hideForm = event => { 9 | event.preventDefault(); 10 | hideInvitationForm(); 11 | }; 12 | 13 | return ( 14 |
15 |
16 | 17 | X 18 | 19 |
Great! You've send an invite
20 |
21 | 24 | 27 |
28 |
29 |
30 | ); 31 | }; 32 | 33 | export default InvitationSent; 34 | -------------------------------------------------------------------------------- /src/pages/Home/Home.css: -------------------------------------------------------------------------------- 1 | section { 2 | display: flex; 3 | flex-direction: column; 4 | height: 70vh; 5 | margin: auto; 6 | text-align: center; 7 | font-family: 'Play', sans-serif; 8 | width: 100%; 9 | color: white; 10 | font-weight: lighter; 11 | /* Allows to position AddFriendForm.js, in center relative to map */ 12 | position: relative; 13 | } 14 | 15 | #not-logged-in { 16 | background-image: url('../../pics/adult-backlit-beach.jpg'); 17 | background-position: center; 18 | background-repeat: no-repeat; 19 | background-size: cover; 20 | height: 100vh; 21 | } 22 | 23 | .title { 24 | display: flex; 25 | height: auto; 26 | width: 100%; 27 | font-size: 3.5em; 28 | } 29 | 30 | .title p { 31 | margin: auto; 32 | } 33 | 34 | .intro { 35 | font-size: 1.3em; 36 | width: 100%; 37 | } 38 | 39 | .signup-btn { 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | width: 100%; 44 | height: 60%; 45 | margin-top: auto; 46 | } 47 | 48 | .local-icon { 49 | display: inline-flex; 50 | justify-content: flex-start; 51 | align-items: center; 52 | width: 100%; 53 | height: 100px; 54 | margin-top: 20px; 55 | margin-bottom: 10vw; 56 | } 57 | 58 | .icon { 59 | margin: 0 30px; 60 | height: 6em; 61 | } 62 | 63 | section h1 { 64 | width: 100%; 65 | font-size: 4em; 66 | margin: 0px; 67 | } 68 | 69 | section h3 { 70 | font-size: 3em; 71 | width: 100%; 72 | } 73 | 74 | /*#not-logged-in div div {*/ 75 | /* border-radius: 100%;*/ 76 | /* width: 150px;*/ 77 | /* height: 150px;*/ 78 | /* margin: 80px;*/ 79 | /* background-color: #f1f1ff;*/ 80 | /* transition: transform 0.2s ease-out, background-color 0.2s linear;*/ 81 | /*}*/ 82 | 83 | /*#not-logged-in div div:hover {*/ 84 | /* background-color: #b3442edd;*/ 85 | /*}*/ 86 | 87 | /*div[aria]:hover:after {*/ 88 | /* content: attr(aria);*/ 89 | /* display: block;*/ 90 | /* font-family: "Play", sans-serif;*/ 91 | /* font-size: 20px;*/ 92 | /*}*/ 93 | 94 | /*#not-logged-in div div:hover {*/ 95 | /* transform: scale(1.3);*/ 96 | /*}*/ 97 | 98 | /*section i {*/ 99 | /* font-size: 6em;*/ 100 | /* padding: 25px 15px;*/ 101 | /*}*/ 102 | 103 | .user-pointer { 104 | position: absolute; 105 | font-size: 3em; 106 | margin-top: 50px; 107 | margin-left: 70px; 108 | color: white; 109 | } 110 | 111 | a { 112 | font-weight: bold; 113 | text-decoration: none; 114 | color: #000; 115 | } 116 | 117 | .signup:hover { 118 | transform: scale(1.1); 119 | } 120 | 121 | main section { 122 | height: 100%; 123 | } 124 | section canvas { 125 | position: relative !important; 126 | } 127 | 128 | .mapboxgl-marker i { 129 | color: transparent; 130 | background-image: url('../../pics/AddSmall.png'); 131 | background-size: center; 132 | width: 36px; 133 | height: 60px; 134 | padding: 0px; 135 | position: relative; 136 | top: -20px; 137 | } 138 | 139 | .mapboxgl-marker #me { 140 | color: blue; 141 | background-image: none; 142 | font-size: 45px; 143 | top: -40px; 144 | left: -18px; 145 | } 146 | 147 | .mapboxgl-marker i:hover { 148 | transform: scale(1.3); 149 | } 150 | 151 | #popup { 152 | position: absolute; 153 | width: 50vw; 154 | height: 0px; 155 | bottom: 0px; 156 | border-radius: 20px; 157 | border-bottom-left-radius: 0px; 158 | border-bottom-right-radius: 0px; 159 | left: 25%; 160 | background-color: #1f3a9388; 161 | color: black; 162 | display: flex; 163 | flex-wrap: wrap; 164 | text-align: center; 165 | justify-content: center; 166 | overflow: hidden; 167 | transition: height 0.6s ease-in-out; 168 | } 169 | 170 | #popup div { 171 | text-align: center; 172 | width: 40%; 173 | height: 50px; 174 | margin: auto; 175 | display: flex; 176 | flex-direction: column; 177 | justify-content: center; 178 | align-items: center; 179 | color: white; 180 | border-radius: 18px; 181 | border: none; 182 | font-weight: bold; 183 | background-color: #1f3a93aa; 184 | } 185 | #popup div * { 186 | width: 90%; 187 | background-color: transparent; 188 | border: none; 189 | color: white; 190 | } 191 | #popup div label { 192 | font-size: 10px; 193 | text-align: left; 194 | } 195 | #popup div input { 196 | height: 20px; 197 | font-size: 15px; 198 | text-align: center; 199 | outline: none; 200 | } 201 | 202 | @media (max-width: 900px) { 203 | main { 204 | width: 100%; 205 | } 206 | } 207 | 208 | @media (max-width: 400px) { 209 | .local-icon { 210 | width: 100%; 211 | height: 60px; 212 | margin-top: 10px; 213 | margin-bottom: 6em; 214 | } 215 | .icon { 216 | height: 50px; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/pages/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | import { Link } from 'react-router-dom'; 4 | 5 | import Map from './Map'; 6 | import { useSelector } from 'react-redux'; 7 | import { 8 | MainContainer, 9 | Wrapper, 10 | Title, 11 | ImgWrapper, 12 | Paragraph, 13 | StyledContainer, 14 | StyledColumn, 15 | SignUpButton 16 | } from './Home.style.js'; 17 | 18 | import './Home.css'; 19 | 20 | import SignUpIcon from '../../assets/SignUpIcon.png'; 21 | import InviteIcon from '../../assets/InviteIcon.png'; 22 | import ConnectIcon from '../../assets/ConnectIcon.png'; 23 | import { ReactComponent as EarthIcon } from '../../assets/svg/bgEarth.svg'; 24 | 25 | import { Row, Column } from '../../components/UI/Grid/Grid'; 26 | import HomeCard from '../../components/HomeCard/home-card.component.js'; 27 | 28 | import useWindowSize from '../../components/UI/effects/useWindowSize.effect'; 29 | 30 | const Home = () => { 31 | const size = useWindowSize(); 32 | // react-redux hooks 33 | const loggedIn = useSelector(state => state.user.uid !== undefined); 34 | // react hooks 35 | const [fontSize, setFontSize] = useState(1); 36 | 37 | useEffect(() => { 38 | const width = size.width; 39 | 40 | if (width >= 1900) { 41 | setFontSize(2); 42 | } else if (width >= 1200) { 43 | setFontSize(1.3); 44 | } else if (width >= 768) { 45 | setFontSize(1.2); 46 | } else if (width >= 500) { 47 | setFontSize(1.1); 48 | } else { 49 | setFontSize(1); 50 | } 51 | }, [size]); 52 | 53 | return loggedIn ? ( 54 |
55 | 56 |
57 | ) : ( 58 | 59 | 60 | 61 | How MappyPals works 62 | 63 | 64 | MappyPals is an exciting new app that can keep you up to 65 | date with your international friends. You can follow them, 66 | message them, make new friends and see what they are up to! 67 | All in one easy-to-use app! 68 | 69 | 70 | {size.width >= 1200 ? ( 71 | 72 | 73 | 74 | 75 | ) : ( 76 | 77 | )} 78 | 79 | ); 80 | }; 81 | 82 | function TheThreeSteps() { 83 | return ( 84 | 90 | 95 | 96 | 102 | 103 | 104 | 110 | 111 | 112 | 118 | 119 | 120 | 121 | 126 | 127 | 128 | Sign Up! 129 | 130 | 131 | 132 | 133 | ); 134 | } 135 | 136 | export default Home; 137 | -------------------------------------------------------------------------------- /src/pages/Home/Home.style.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import Container from '../../components/UI/Container/Container'; 4 | import Text from '../../components/UI/Text/Text'; 5 | 6 | import { Column } from '../../components/UI/Grid/Grid'; 7 | 8 | export const MainContainer = styled(Container)` 9 | height: 100vh; 10 | align-items: center; 11 | background: #fff; 12 | `; 13 | 14 | export const Wrapper = styled(Container)` 15 | background-color: rgba(255, 255, 255, 0); 16 | width: 100%; 17 | justifycontent: center; 18 | text-align: center; 19 | 20 | @media only screen and (min-width: 500px) { 21 | width: 90%; 22 | } 23 | @media only screen and (min-width: 768px) { 24 | width: 80%; 25 | } 26 | @media only screen and (min-width: 1200px) { 27 | width: 800px; 28 | } 29 | `; 30 | 31 | export const ImgWrapper = styled(Container)` 32 | background: #fff; 33 | position: relative; 34 | width: 100%; 35 | `; 36 | 37 | export const SignUpButton = styled.div` 38 | font-family: 'Poppins', sans-serif; 39 | font-weight: bolder; 40 | font-size: 2em; 41 | text-align: center; 42 | text-transform: uppercase; 43 | background-image: linear-gradient(to bottom right, #e03bfb, #6831de); 44 | color: #fff; 45 | margin-bottom: 1rem; 46 | padding: 1rem 4rem; 47 | border: 1px solid #000; 48 | border-radius: 10px; 49 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 50 | cursor: pointer; 51 | transition: transform 0.5s ease-in-out; 52 | 53 | &:hover { 54 | transform: scale(1.05); 55 | box-shadow: 0px 6px 4px rgba(0, 0, 0, 0.25); 56 | } 57 | 58 | @media only screen and (min-width: 1200px) { 59 | margin-top: 25%; 60 | padding: 0.8rem 5rem; 61 | } 62 | 63 | @media only screen and (max-width: 370px) { 64 | height: 2rem; 65 | font-size: 1.2rem; 66 | margin-top: 20%; 67 | 68 | 69 | } 70 | 71 | @media only screen and (min-width: 370px) { 72 | height: 1.7rem; 73 | font-size: 1.2rem; 74 | margin-top: 18%; 75 | } 76 | `; 77 | 78 | export const StyledColumn = styled(Column)` 79 | margin: 1rem; 80 | 81 | @media only screen and (min-width: 1200px) { 82 | margin: 0; 83 | } 84 | `; 85 | 86 | export const StyledText = styled(Text)` 87 | font-family: 'Poppins', sans-serif; 88 | `; 89 | 90 | export const Title = styled(StyledText)` 91 | padding: 1rem; 92 | margin-bottom: 0; 93 | 94 | @media only screen and (min-width: 1200px) { 95 | margin-top: 0; 96 | } 97 | `; 98 | 99 | export const Paragraph = styled(StyledText)` 100 | padding: 0 1rem; 101 | 102 | @media only screen and (min-width: 1200px) { 103 | margin-top: 0; 104 | } 105 | `; 106 | 107 | export const StyledContainer = styled(Container)` 108 | background: rgba(255, 255, 255, 0); 109 | @media only screen and (min-width: 1200px) { 110 | position: absolute; 111 | top: 20%; 112 | left: 0; 113 | right: 0; 114 | bottom: 0; 115 | } 116 | `; 117 | -------------------------------------------------------------------------------- /src/pages/Home/Popup/Popup.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import classes from './Popup.module.css'; 3 | import Image from '../../../pics/default-profile-pic.jpg'; 4 | class Popup extends PureComponent { 5 | render() { 6 | const { info } = this.props; 7 | console.log(this.props.info.firstName); 8 | // const displayName = `${info.city}, ${info.state}`; 9 | 10 | return ( 11 | 12 |
13 |
14 | 15 |

16 | {info.firstName} {info.lastName} 17 |

18 |
19 |
20 | 24 | 32 |
33 |
34 |

This is description

35 |
36 | ); 37 | } 38 | } 39 | 40 | export default Popup; 41 | -------------------------------------------------------------------------------- /src/pages/Home/Popup/Popup.module.css: -------------------------------------------------------------------------------- 1 | .img { 2 | background-size: cover; 3 | border-radius: 50% 50% 50% 50%; 4 | width: 75px; 5 | } 6 | .fullName { 7 | margin-top: 0; 8 | margin-bottom: 0; 9 | color: #6831de; 10 | } 11 | .container { 12 | display: flex; 13 | justify-content: space-between; 14 | padding: 20px; 15 | } 16 | .buttonContainer { 17 | display: flex; 18 | flex-direction: column; 19 | flex-wrap: nowrap; 20 | } 21 | .button { 22 | display: flex; 23 | flex-wrap: nowrap; 24 | text-align: left; 25 | background-color: #ffffff; 26 | border: 2px solid #6831de; 27 | color: #6831de; 28 | border-radius: 10px; 29 | padding: 5px; 30 | padding-left: 15px; 31 | margin-bottom: 10px; 32 | cursor: pointer; 33 | } 34 | .button:hover { 35 | opacity: 0.8; 36 | } 37 | 38 | .description { 39 | margin-top: 0; 40 | margin-bottom: 0; 41 | padding-bottom: 1.875rem; 42 | color: #000000; 43 | } 44 | .icon { 45 | margin-right: 5px; 46 | font-size: 15px; 47 | } 48 | 49 | @media (max-width: 740px) { 50 | .container { 51 | display: block; 52 | } 53 | .buttonContainer { 54 | padding-top: 10px; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/pages/Home/mapbox-gl-geocoder.css: -------------------------------------------------------------------------------- 1 | /* Basics */ 2 | .mapboxgl-ctrl-geocoder, 3 | .mapboxgl-ctrl-geocoder *, 4 | .mapboxgl-ctrl-geocoder *:after, 5 | .mapboxgl-ctrl-geocoder *:before { 6 | box-sizing: border-box; 7 | } 8 | 9 | .mapboxgl-ctrl-geocoder { 10 | font-size: 18px; 11 | line-height: 24px; 12 | font-family: 'Open Sans', 'Helvetica Neue', Arial, Helvetica, sans-serif; 13 | position: relative; 14 | background-color: #fff; 15 | width: 100%; 16 | min-width: 240px; 17 | z-index: 1; 18 | border-radius: 4px; 19 | transition: width 0.25s, min-width 0.25s; 20 | } 21 | 22 | .mapboxgl-ctrl-geocoder--input { 23 | font: inherit; 24 | width: 100%; 25 | border: 0; 26 | background-color: transparent; 27 | margin: 0; 28 | height: 50px; 29 | color: #404040; /* fallback */ 30 | color: rgba(0, 0, 0, 0.75); 31 | padding: 6px 45px; 32 | text-overflow: ellipsis; 33 | white-space: nowrap; 34 | overflow: hidden; 35 | } 36 | 37 | .mapboxgl-ctrl-geocoder--input::-ms-clear { 38 | display: none; /* hide input clear button in IE */ 39 | } 40 | 41 | .mapboxgl-ctrl-geocoder--input:focus { 42 | color: #404040; /* fallback */ 43 | color: rgba(0, 0, 0, 0.75); 44 | outline: 0; 45 | box-shadow: none; 46 | outline: thin dotted\8; 47 | } 48 | 49 | .mapboxgl-ctrl-geocoder .mapboxgl-ctrl-geocoder--pin-right > * { 50 | z-index: 2; 51 | position: absolute; 52 | right: 8px; 53 | top: 7px; 54 | display: none; 55 | } 56 | 57 | .mapboxgl-ctrl-geocoder, 58 | .mapboxgl-ctrl-geocoder .suggestions { 59 | box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1); 60 | } 61 | 62 | /* Collapsed */ 63 | .mapboxgl-ctrl-geocoder.mapboxgl-ctrl-geocoder--collapsed { 64 | width: 50px; 65 | min-width: 50px; 66 | transition: width 0.25s, min-width 0.25s; 67 | } 68 | 69 | /* Suggestions */ 70 | .mapboxgl-ctrl-geocoder .suggestions { 71 | background-color: #fff; 72 | border-radius: 4px; 73 | left: 0; 74 | list-style: none; 75 | margin: 0; 76 | padding: 0; 77 | position: absolute; 78 | width: 100%; 79 | top: 110%; /* fallback */ 80 | top: calc(100% + 6px); 81 | z-index: 1000; 82 | overflow: hidden; 83 | font-size: 15px; 84 | } 85 | 86 | .mapboxgl-ctrl-bottom-left .suggestions, 87 | .mapboxgl-ctrl-bottom-right .suggestions { 88 | top: auto; 89 | bottom: 100%; 90 | } 91 | 92 | .mapboxgl-ctrl-geocoder .suggestions > li > a { 93 | cursor: default; 94 | display: block; 95 | padding: 6px 12px; 96 | color: #404040; 97 | } 98 | 99 | .mapboxgl-ctrl-geocoder .suggestions > .active > a, 100 | .mapboxgl-ctrl-geocoder .suggestions > li > a:hover { 101 | color: #404040; 102 | background-color: #f3f3f3; 103 | text-decoration: none; 104 | cursor: pointer; 105 | } 106 | 107 | .mapboxgl-ctrl-geocoder--suggestion-title { 108 | font-weight: bold; 109 | } 110 | 111 | .mapboxgl-ctrl-geocoder--suggestion-title, 112 | .mapboxgl-ctrl-geocoder--suggestion-address { 113 | text-overflow: ellipsis; 114 | overflow: hidden; 115 | white-space: nowrap; 116 | } 117 | 118 | /* Icons */ 119 | .mapboxgl-ctrl-geocoder--icon { 120 | display: inline-block; 121 | vertical-align: middle; 122 | speak: none; 123 | fill: #757575; 124 | top: 15px; 125 | } 126 | 127 | .mapboxgl-ctrl-geocoder--icon-search { 128 | position: absolute; 129 | top: 13px; 130 | left: 12px; 131 | width: 23px; 132 | height: 23px; 133 | } 134 | 135 | .mapboxgl-ctrl-geocoder--button { 136 | padding: 0; 137 | margin: 0; 138 | border: none; 139 | cursor: pointer; 140 | background: #fff; 141 | line-height: 1; 142 | } 143 | 144 | .mapboxgl-ctrl-geocoder--icon-close { 145 | width: 20px; 146 | height: 20px; 147 | margin-top: 8px; 148 | margin-right: 3px; 149 | } 150 | 151 | .mapboxgl-ctrl-geocoder--button:hover .mapboxgl-ctrl-geocoder--icon-close { 152 | fill: #909090; 153 | } 154 | 155 | .mapboxgl-ctrl-geocoder--icon-loading { 156 | width: 26px; 157 | height: 26px; 158 | margin-top: 5px; 159 | margin-right: 0px; 160 | -moz-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); 161 | -webkit-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); 162 | animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); 163 | } 164 | 165 | /* Animation */ 166 | @-webkit-keyframes rotate { 167 | from { 168 | -webkit-transform: rotate(0); 169 | transform: rotate(0); 170 | } 171 | to { 172 | -webkit-transform: rotate(360deg); 173 | transform: rotate(360deg); 174 | } 175 | } 176 | 177 | @keyframes rotate { 178 | from { 179 | -webkit-transform: rotate(0); 180 | transform: rotate(0); 181 | } 182 | to { 183 | -webkit-transform: rotate(360deg); 184 | transform: rotate(360deg); 185 | } 186 | } 187 | 188 | /* Media queries*/ 189 | @media screen and (min-width: 640px) { 190 | .mapboxgl-ctrl-geocoder.mapboxgl-ctrl-geocoder--collapsed { 191 | width: 36px; 192 | min-width: 36px; 193 | } 194 | 195 | .mapboxgl-ctrl-geocoder { 196 | width: 33.3333%; 197 | font-size: 15px; 198 | line-height: 20px; 199 | max-width: 360px; 200 | } 201 | .mapboxgl-ctrl-geocoder .suggestions { 202 | font-size: 13px; 203 | } 204 | 205 | .mapboxgl-ctrl-geocoder--icon { 206 | top: 8px; 207 | } 208 | 209 | .mapboxgl-ctrl-geocoder--icon-close { 210 | width: 16px; 211 | height: 16px; 212 | margin-top: 3px; 213 | margin-right: 0; 214 | } 215 | 216 | .mapboxgl-ctrl-geocoder--icon-search { 217 | left: 7px; 218 | width: 20px; 219 | height: 20px; 220 | } 221 | 222 | .mapboxgl-ctrl-geocoder--input { 223 | height: 36px; 224 | padding: 6px 35px; 225 | } 226 | 227 | .mapboxgl-ctrl-geocoder--icon-loading { 228 | width: 26px; 229 | height: 26px; 230 | margin-top: -2px; 231 | margin-right: -5px; 232 | } 233 | 234 | .mapbox-gl-geocoder--error { 235 | color: #909090; 236 | padding: 6px 12px; 237 | font-size: 16px; 238 | text-align: center; 239 | } 240 | } 241 | 242 | /* Popup adjustments */ 243 | .mapboxgl-popup-close-button { 244 | font-size: 1.875rem; 245 | color: #6831de; 246 | } 247 | -------------------------------------------------------------------------------- /src/pages/InviteFriends/InviteFriends.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { hideInviteFriends } from '../../store/actions/modals'; 4 | import Modal from '../../components/UI/Modal/Modal'; 5 | import Button from '../../components/UI/Button/Button'; 6 | import Chip from '../../components/UI/Chip/Chip'; 7 | import classes from './InviteFriends.module.scss'; 8 | import isEmail from 'validator/lib/isEmail'; 9 | export const InviteFriends = () => { 10 | // react-redux hooks 11 | const dispatch = useDispatch(); 12 | const { show } = useSelector(state => ({ 13 | show: state.modals.inviteFriends 14 | })); 15 | // React hooks 16 | const [emailList, setEmailList] = useState([]); 17 | 18 | const [text, setText] = useState(''); 19 | 20 | const handleInputChange = event => { 21 | const newText = event.target.value; 22 | setText(newText); 23 | }; 24 | const handleAddEmail = event => { 25 | // check if the email already exists 26 | if (emailList.includes(text)) { 27 | alert('Email already added'); 28 | setText(''); 29 | } else if (isEmail(text)) { 30 | setEmailList([...emailList, text]); 31 | setText(''); 32 | } 33 | }; 34 | const handleSendInvites = event => { 35 | if (emailList.length > 0) { 36 | setEmailList([]); 37 | dispatch(hideInviteFriends()); 38 | } else { 39 | alert('No emails to send'); 40 | } 41 | }; 42 | const removeEmail = emailToRemove => { 43 | const index = emailList.indexOf(emailToRemove); 44 | if (index !== -1) { 45 | const newState = [...emailList]; 46 | newState.splice(index, 1); 47 | setEmailList(newState); 48 | } 49 | }; 50 | 51 | return ( 52 | 53 |
54 |

55 | Send and invite to multiple friends 56 |

57 |
58 | 69 | 72 |
73 |
74 | {emailList.map((email, index) => ( 75 | 80 | ))} 81 |
82 |
83 | 90 |
91 |
92 |
93 | ); 94 | }; 95 | export default InviteFriends; 96 | -------------------------------------------------------------------------------- /src/pages/InviteFriends/InviteFriends.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../components/BaseScss/variables.scss'; 2 | .root { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | justify-content: center; 7 | font-family: 'Poppins', sans-serif; 8 | font-weight: 400; 9 | } 10 | .title { 11 | color: $dark; 12 | font-weight: 400; 13 | font-size: 1.4rem; 14 | text-align: center; 15 | } 16 | .form { 17 | display: flex; 18 | text-align: left; 19 | align-items: center; 20 | justify-content: center; 21 | flex-grow: 2; 22 | padding: 16px; 23 | margin: 8px 16px; 24 | } 25 | .input { 26 | width: 100%; 27 | box-sizing: border-box; 28 | font-size: 1.2rem; 29 | border: none; 30 | border-bottom: 2px solid grey; 31 | } 32 | .input:hover { 33 | outline: 0; 34 | border-bottom: 3px solid #6831de; 35 | } 36 | .input:hover:focus { 37 | outline: 0; 38 | border-bottom: 3px solid #6831de; 39 | } 40 | .input[type='text'] { 41 | width: 90%; 42 | } 43 | .label { 44 | display: block; 45 | margin-right: 12px; 46 | padding-bottom: 30px; 47 | color: #6831de; 48 | } 49 | .emailList { 50 | display: flex; 51 | flex-wrap: wrap; 52 | align-items: center; 53 | max-lines: 3; 54 | justify-content: center; 55 | max-height:100px; 56 | overflow-y:auto; 57 | } 58 | .sendInvite{ 59 | margin:16px auto; 60 | } 61 | -------------------------------------------------------------------------------- /src/pages/Login/ForgotPassword.css: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | 5 | .text-medium { 6 | font-size: 16px; 7 | } 8 | 9 | .text-large { 10 | font-size: 20px; 11 | } 12 | 13 | .message { 14 | color: green; 15 | text-decoration: none; 16 | } 17 | 18 | @media all and (min-width: 630px) { 19 | .text-medium { 20 | font-size: 22px; 21 | } 22 | .text-large { 23 | font-size: 25px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/Login/ForgotPassword.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | 4 | import Form from './Form'; 5 | import Button from '../../components/UI/Button/Button'; 6 | 7 | import './Login.css'; 8 | import './ForgotPassword.styles.scss'; 9 | 10 | class ForgotPassword extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | email: '', 15 | message: '', 16 | hasEmailSent: false 17 | }; 18 | } 19 | 20 | handleChange = event => { 21 | this.setState({ 22 | [event.target.name]: event.target.value 23 | }); 24 | }; 25 | 26 | handleSubmit = event => { 27 | event.preventDefault(); 28 | const url = process.env.URL || 'http://localhost:3001/'; 29 | axios({ 30 | url: `${url}users/reset`, 31 | method: 'POST', 32 | data: JSON.stringify(this.state), 33 | headers: { 34 | 'Content-Type': 'application/json' 35 | } 36 | }) 37 | .then(res => { 38 | if (res.status === 200) { 39 | this.setState({ 40 | message: 41 | 'We have emailed your password reset link. Check your inbox', 42 | hasEmailSent: true 43 | }); 44 | } 45 | }) 46 | .catch(err => { 47 | alert( 48 | 'Ooops. Something went wrong. Please check if your email is correct' 49 | ); 50 | }); 51 | 52 | console.log(JSON.stringify(this.state)); 53 | 54 | // Clear inputs. 55 | this.setState({ email: '' }); 56 | }; 57 | 58 | render() { 59 | const { email, message, hasEmailSent } = this.state; 60 | 61 | return ( 62 |
63 |
64 |

Did you forget your password?

65 |

66 | Enter the email address from your account below and we 67 | will send your password reset link. 68 |

69 |
70 | 73 | 82 |
83 | 84 | 87 | {hasEmailSent && ( 88 |
{message}
89 | )} 90 |
91 |
92 | ); 93 | } 94 | } 95 | 96 | export default ForgotPassword; 97 | -------------------------------------------------------------------------------- /src/pages/Login/ForgotPassword.styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../components/BaseScss/variables.scss'; 2 | 3 | .Login { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | padding: 0; 9 | min-height: 100vh; 10 | 11 | .container { 12 | margin-top: 10%; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: space-between; 16 | align-items: center; 17 | box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 2px 1px -1px rgba(0,0,0,0.12); 18 | border-radius: 30px; 19 | 20 | .headline { 21 | font-size: 1.3rem; 22 | text-align: center; 23 | } 24 | 25 | .sub-headline { 26 | font-size: 1rem; 27 | text-align: center; 28 | } 29 | 30 | .group { 31 | margin-top: 10%; 32 | } 33 | 34 | .form-input-label { 35 | margin: 0; 36 | margin-left: 10px; 37 | } 38 | 39 | .form-input { 40 | margin-top: 10px; 41 | margin-bottom: 10%; 42 | } 43 | 44 | .form-input::placeholder { 45 | font-size: 1rem; 46 | } 47 | } 48 | 49 | /* Success alert when user submits a valid email address */ 50 | .success-alert { 51 | display: flex; 52 | justify-content: center; 53 | width: 100%; 54 | font-size: 1rem; 55 | background-color: #d4edda; 56 | border: 1px solid #c3e6cb; 57 | border-radius: .25rem; 58 | color: #155724; 59 | text-align: center; 60 | margin-top: 5%; 61 | padding: .5rem; 62 | } 63 | } 64 | 65 | @media (min-width: 360px) and (max-width: 410px) { 66 | .Login { 67 | height: 100%; 68 | 69 | .container { 70 | margin: 0%; 71 | } 72 | } 73 | } 74 | 75 | @media (min-width: 411px) and (max-width: 767px) { 76 | .Login .container { 77 | padding: 5% 6%; 78 | } 79 | } 80 | 81 | @media (min-width: 768px) and (max-width: 1439px) { 82 | .Login .container { 83 | .headline { 84 | font-size: 1.7em; 85 | } 86 | 87 | .sub-headline { 88 | font-size: 1.2em; 89 | } 90 | 91 | .group { 92 | margin-top: 5%; 93 | width: 95%; 94 | font-size: 1.2em; 95 | 96 | .form-input-label { 97 | margin: 0; 98 | font-size: 1em; 99 | } 100 | 101 | .form-input::placeholder { 102 | font-size: 1.2rem; 103 | } 104 | } 105 | } 106 | } 107 | 108 | @media (min-width: 1440px) { 109 | .Login { 110 | 111 | .container { 112 | margin: 0%; 113 | .headline { 114 | font-size: 1.6em; 115 | margin-bottom: 10%; 116 | } 117 | 118 | .sub-headline { 119 | font-size: 1.1em; 120 | } 121 | 122 | .group { 123 | margin-top: 10%; 124 | width: 95%; 125 | font-size: 1.3em; 126 | 127 | .form-input-label { 128 | margin: 0; 129 | font-size: 1em; 130 | } 131 | 132 | .form-input::placeholder { 133 | font-size: 1.4rem; 134 | } 135 | } 136 | } 137 | .success-alert { 138 | font-size: 1.3rem; 139 | } 140 | } 141 | } 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/pages/Login/Form.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Form = styled.form` 4 | box-shadow: 5px 10px 18px rgba(0, 0, 0, 0.05); 5 | border-radius: 30px; 6 | font-family: 'Poppins', sans-serif; 7 | font-weight: 400; 8 | @media (max-width: 410px) { 9 | padding: 10px 25px; 10 | font-size: 0.5rem; 11 | margin-top: 50px; 12 | margin-left: 10px; 13 | line-height: 1.25; 14 | } 15 | @media (min-width: 411px) { 16 | padding: 30px 70px; 17 | font-size: 0.8rem; 18 | line-height: 1.75; 19 | } 20 | fieldset { 21 | border: #ffffff; 22 | } 23 | label { 24 | display: block; 25 | margin-bottom: 1rem; 26 | } 27 | input, 28 | select { 29 | width: 100%; 30 | box-sizing: border-box; 31 | padding: 0.5rem; 32 | font-size: 1.2rem; 33 | border: none; 34 | border-bottom: 2px solid grey; 35 | &:hover { 36 | outline: 0; 37 | border-bottom: 3px solid #6831DE; 38 | } 39 | &:focus { 40 | outline: 0; 41 | border-bottom: 3px solid #6831DE; 42 | } 43 | } 44 | 45 | input[type="text"] { 46 | width: 90%; 47 | } 48 | 49 | .nameContainer { 50 | width: 100%; 51 | display: grid; 52 | grid-gap: 15px; 53 | grid-template-columns: 50% 50%; 54 | grid-template-rows: 20px 20px; 55 | grid-template-areas: 56 | "a b" 57 | "c d"; 58 | margin-bottom: 2.3rem; 59 | } 60 | .item1 { 61 | grid-area: a; 62 | align-self: start; 63 | } 64 | .item2 { 65 | grid-area: c; 66 | align-self: end; 67 | } 68 | .item3 { 69 | grid-area: b; 70 | align-self: start; 71 | } 72 | .item4 { 73 | grid-area: d; 74 | align-self: end; 75 | } 76 | 77 | 78 | a { 79 | color: #6831DE; 80 | font-weight: normal; 81 | line-height: 1.5; 82 | } 83 | 84 | a:link { 85 | text-decoration: none; 86 | } 87 | 88 | a:visited { 89 | text-decoration: none; 90 | font-color: #6831DE; 91 | font-weight: normal; 92 | } 93 | 94 | a:hover { 95 | text-decoration: underline; 96 | } 97 | 98 | a:active { 99 | text-decoration: underline; 100 | } 101 | } 102 | 103 | .forgot-password { 104 | display: inline; 105 | justify-content: flex-end; 106 | transform: translateY(-2rem); 107 | } 108 | 109 | .btnContainer { 110 | padding-top: 15px; 111 | padding-bottom: 7px; 112 | margin: 0 auto; 113 | text-align: center; 114 | } 115 | 116 | button:hover { 117 | opacity: 0.8; 118 | } 119 | 120 | 121 | `; 122 | 123 | export default Form; 124 | -------------------------------------------------------------------------------- /src/pages/Login/Login.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 360px) { 2 | .Login { 3 | padding: 60px 0; 4 | background-image: url('../../pics/adult-backlit-beach.jpg'); 5 | background-position: center; 6 | background-repeat: no-repeat; 7 | background-size: cover; 8 | height: 100%; 9 | } 10 | 11 | .Login form { 12 | margin: 10px; 13 | max-width: 345px; 14 | overflow-x: hidden; 15 | background-color: white; 16 | font-family: 'Poppins', sans-serif; 17 | } 18 | 19 | #newPassword { 20 | margin-bottom: -1.5rem; 21 | color: red; 22 | font-size: 1rem; 23 | line-height: 1; 24 | transform: translateY(-1.5rem); 25 | } 26 | 27 | .text-center-wrap { 28 | text-align: center; 29 | white-space: wrap; 30 | } 31 | 32 | .heavy { 33 | font-weight: 600; 34 | font-size: 1.2rem; 35 | } 36 | .light { 37 | font-weight: 400; 38 | font-size: 1.2rem; 39 | margin-bottom: 2.3rem; 40 | } 41 | 42 | .u-text-center { 43 | text-align: center; 44 | white-space: nowrap; 45 | margin: 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pages/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { USER_SIGN_IN_API_CALL } from '../../store/actions/types'; 5 | import './Login.css'; 6 | import Form from './Form.js'; 7 | import { 8 | userSignIn, 9 | userSignInGoogle, 10 | userSignInFacebook 11 | } from '../../store/actions/user'; 12 | import ErrorMessage from '../../components/ErrorMessages/ErrorMessages'; 13 | import Button from '../../components/UI/Button/Button'; 14 | export const Login = () => { 15 | // react-redux hooks 16 | const dispatch = useDispatch(); 17 | const loading = useSelector(state => 18 | state.apiCallsInProgress.includes(USER_SIGN_IN_API_CALL) 19 | ); 20 | const emailVerified = useSelector(state => state.user.emailVerified); 21 | // react hooks 22 | const [email, setEmail] = useState(''); 23 | const [password, setPassword] = useState(''); 24 | const [error, setError] = useState(''); 25 | const [redirect, setRedirect] = useState(false); 26 | 27 | const handleSubmit = async event => { 28 | event.preventDefault(); 29 | try { 30 | await dispatch(userSignIn(email, password)); 31 | setEmail(''); 32 | setPassword(''); 33 | // redirect 34 | setRedirect(true); 35 | } catch (error) { 36 | setError(error.message); 37 | console.error(error); 38 | } 39 | }; 40 | 41 | const handleGoogleLogin = async event => { 42 | event.preventDefault(); 43 | try { 44 | await dispatch(userSignInGoogle()); 45 | setRedirect(true); 46 | } catch (error) { 47 | setError(error.message); 48 | console.error(error); 49 | } 50 | }; 51 | const handleFacebookLogin = async event => { 52 | event.preventDefault(); 53 | try { 54 | await dispatch(userSignInFacebook()); 55 | setRedirect(true); 56 | } catch (error) { 57 | setError(error.message); 58 | console.error(error); 59 | } 60 | }; 61 | const handleTwitterLogin = async event => { 62 | event.preventDefault(); 63 | alert('Coming soon'); 64 | }; 65 | 66 | return ( 67 | <> 68 | {redirect && } 69 |
70 |
71 | 82 | 93 |
94 | I forgot my password 95 |
96 |
97 | 100 |
101 | {loading &&
Loading...
} 102 | {error && ( 103 |
104 | 105 |
106 | )} 107 |

108 | No account? 109 | 110 | {' '} 111 | SignUp 112 | 113 |  Or connect via: 114 |

115 |
116 | 117 | 118 | 119 |
120 |
121 |
122 | 123 | ); 124 | }; 125 | export default Login; 126 | -------------------------------------------------------------------------------- /src/pages/Login/ResetPassword.styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../components/BaseScss/variables.scss'; 2 | 3 | .error { 4 | margin: 0; 5 | padding: 0; 6 | .container { 7 | min-height: 40vh; 8 | padding: 10%; 9 | background: #fff; 10 | .headline { 11 | font-size: 1.6em; 12 | } 13 | 14 | .group { 15 | width: 80%; 16 | display: flex; 17 | flex-direction: row; 18 | justify-content: space-between; 19 | 20 | .link { 21 | color: $dark; 22 | padding: 10px; 23 | border: 1px solid $light; 24 | border-radius: .25rem; 25 | 26 | &:hover { 27 | color: $light; 28 | border-color: $dark; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | .loading { 36 | margin: 0; 37 | padding: 0; 38 | background: linear-gradient(to right, $light, $dark); 39 | .container { 40 | box-shadow: none; 41 | color: #fff; 42 | margin: 0; 43 | } 44 | img { 45 | height: 15vh; 46 | } 47 | } 48 | 49 | @media (min-width: 360px) and (max-width: 424px) { 50 | .error { 51 | padding: 5%; 52 | } 53 | } 54 | 55 | @media (min-width: 425px) and (max-width: 767px) { 56 | .error { 57 | padding: 10%; 58 | .container { 59 | font-size: 1.3em; 60 | .sub-headline { 61 | font-size: 0.9em; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/pages/Login/tail-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/pages/Page404/Page404.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 320px) { 2 | .t-c { 3 | text-align: center; 4 | } 5 | 6 | .p404-container { 7 | margin: 7rem auto; 8 | max-width: 400px; 9 | color: #6831de; 10 | } 11 | 12 | .p404-header { 13 | font-size: 2rem; 14 | font-weight: 400; 15 | } 16 | 17 | .p404-emoji { 18 | width: 10rem; 19 | height: 10rem; 20 | } 21 | 22 | .p404-text { 23 | margin: 2rem 0 2rem; 24 | font-weight: 400; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/Page404/Page404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Button from '../../components/UI/Button/Button'; 4 | import './Page404.css'; 5 | import { ReactComponent as Emoji } from '../../pics/404Icon.svg'; 6 | 7 | const Page404 = () => { 8 | return ( 9 |
10 |

404 Error - Page not found

11 | 12 |

13 | Sorry, we couldn't find what you are looking for 14 |

15 | 16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default Page404; 23 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/PersonalProfile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import './PersonalProfile.styles.css'; 4 | import ProfileCard from './ProfileCard'; 5 | import ProfileContent from './ProfileContent'; 6 | import USER_DATA from './USER_DATA'; 7 | 8 | const Profile = styled.div` 9 | display: flex; 10 | @media (max-width: 720px) { 11 | display: grid; 12 | } 13 | `; 14 | 15 | class PersonalProfile extends React.Component { 16 | render() { 17 | const { 18 | id, 19 | firstName, 20 | lastName, 21 | date, 22 | location, 23 | interests 24 | } = USER_DATA[0]; 25 | return ( 26 |
27 |

Friends > Profile

28 | 29 | 36 | 37 | 38 |
39 | ); 40 | } 41 | } 42 | 43 | export default PersonalProfile; 44 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/PersonalProfile.styles.css: -------------------------------------------------------------------------------- 1 | .top-title { 2 | padding-left: 2rem; 3 | } 4 | 5 | .profile-photo img { 6 | border-radius: 50%; 7 | width: 10rem; 8 | height: 10rem; 9 | background-color: blueviolet; 10 | } 11 | 12 | .text-medium { 13 | font-size: 18px; 14 | } 15 | 16 | .text-large { 17 | font-size: 22px; 18 | font-weight: normal; 19 | } 20 | 21 | .color-purple { 22 | color: #6831de; 23 | } 24 | 25 | @media (max-width: 720px) { 26 | .profile-content-title { 27 | text-align: center; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/ProfileCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '../../components/UI/Button/Button'; 3 | import styled from 'styled-components'; 4 | import './PersonalProfile.styles.css'; 5 | 6 | const Card = styled.div` 7 | text-align: center; 8 | padding: 1rem 2rem; 9 | `; 10 | 11 | const ProfileCard = ({ id, firstName, lastName, date, location }) => ( 12 | 13 | 14 |
15 | Profile Avatars 19 |
20 |
21 |

{`${firstName} ${lastName}`}

22 |
23 |
24 | 27 |
28 |
29 |

Join date:

30 |

{date}

31 |
32 |
33 |

Current location:

34 |

{location}

35 |
36 |
37 | 38 |
39 |
40 |
41 | ); 42 | 43 | export default ProfileCard; 44 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/ProfileContent.css: -------------------------------------------------------------------------------- 1 | .text-large-and-purple { 2 | font-size: 22px; 3 | font-weight: normal; 4 | color: #6831de; 5 | } 6 | 7 | @media (max-width: 720px) { 8 | .text-large-and-purple { 9 | text-align: center; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/ProfileContent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import XButton from '../../components/UI/XButton/XButton'; 3 | import styled from 'styled-components'; 4 | import './PersonalProfile.styles.css'; 5 | 6 | const ProfileContentWrapper = styled.div` 7 | flex-direction: column; 8 | padding: 0 5rem 0 1rem; 9 | @media (max-width: 720px) { 10 | padding-left: 5rem; 11 | } 12 | `; 13 | 14 | class ProfileContent extends React.Component { 15 | render() { 16 | return ( 17 | 18 |
19 |

20 | About me 21 |

22 |

23 | Lorem ipsum dolor sit amet consectetur adipisicing elit. 24 | Atque voluptatibus ex corporis deserunt. Consequuntur 25 | rem consectetur non iusto, atque quos nesciunt eaque 26 | quisquam error, quibusdam impedit, at ratione maiores 27 | eum!Lorem ipsum dolor sit amet consectetur adipisicing 28 | elit. Atque voluptatibus ex corporis deserunt. 29 | Consequuntur rem consectetur non iusto, atque quos 30 | nesciunt eaque quisquam error, quibusdam impedit, at 31 | ratione maiores eum!Lorem ipsum dolor sit amet 32 | consectetur adipisicing elit. Atque voluptatibus ex 33 | corporis deserunt. Consequuntur rem consectetur non 34 | iusto, atque quos nesciunt eaque quisquam error, 35 | quibusdam impedit, at ratione maiores eum! 36 |

37 |
38 |
39 |

40 | Interests 41 |

42 | {this.props.interests.map((interest, id) => ( 43 | 44 | {interest} 45 | 46 | ))} 47 |
48 |
49 | ); 50 | } 51 | } 52 | 53 | export default ProfileContent; 54 | -------------------------------------------------------------------------------- /src/pages/PersonalProfile/USER_DATA.js: -------------------------------------------------------------------------------- 1 | const USER_DATA = [ 2 | { 3 | id: 0, 4 | firstName: `Cong`, 5 | lastName: `Liu`, 6 | date: `2019-07-21`, 7 | location: `China`, 8 | interests: [`basketball`, `swimming`, `public speaking`] 9 | } 10 | ]; 11 | 12 | export default USER_DATA; 13 | -------------------------------------------------------------------------------- /src/pages/Settings/ProfileSettings/ProfileSettings.module.css: -------------------------------------------------------------------------------- 1 | .img { 2 | object-fit: cover; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | .container { 7 | width: 80%; 8 | max-width: 1250px; 9 | margin: 40px auto 20px; 10 | padding: 15px; 11 | padding-bottom: 50px; 12 | display: grid; 13 | grid-template-columns: 200px 1fr; 14 | } 15 | .changePicture { 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | width: 100%; 20 | height: 100%; 21 | display: flex; 22 | justify-items: center; 23 | opacity: 0; 24 | transition: opacity 0.3s, background-color 0.3s; 25 | } 26 | .changePicture p { 27 | margin: auto; 28 | color: #fff; 29 | } 30 | .changePicture:hover { 31 | cursor: pointer; 32 | opacity: 1; 33 | background-color: rgba(0, 0, 0, 0.6); 34 | } 35 | .pictureItem { 36 | width: 200px; 37 | height: 200px; 38 | border: 2px solid grey; 39 | border-radius: 50%; 40 | overflow: hidden; 41 | text-align: center; 42 | margin: 0 auto 40px; 43 | position: relative; 44 | } 45 | .nameContainer { 46 | width: 100%; 47 | display: flex; 48 | justify-items: center; 49 | } 50 | .nameContainer div:last-child { 51 | text-align: right; 52 | } 53 | 54 | /* Inputs copied from form.js */ 55 | .input, 56 | .inputTextarea { 57 | box-sizing: border-box; 58 | padding: 0.5rem; 59 | font-size: 1.2rem; 60 | border: none; 61 | border-bottom: 2px solid grey; 62 | width: 50%; 63 | margin: 15px 0; 64 | } 65 | .inputTextarea { 66 | min-width: 100%; 67 | max-width: 100%; 68 | height: 150px; 69 | border: 2px solid grey; 70 | resize: vertical; 71 | } 72 | .input:hover, 73 | .input:focus { 74 | outline: 0; 75 | border-bottom: 2px solid #6831de; 76 | } 77 | 78 | .inputTextarea:hover, 79 | .inputTextarea:focus,.pictureItem:hover,.pictureItem:focus { 80 | outline: 0; 81 | border: 2px solid #6831de; 82 | } 83 | /* Input ending */ 84 | 85 | .submitContainer { 86 | margin-top: 20px; 87 | } 88 | .form { 89 | width: 90%; 90 | text-align: left; 91 | display: flex; 92 | flex-direction: column; 93 | align-items: center; 94 | margin: 0 auto; 95 | } 96 | 97 | @media (max-width: 700px) { 98 | .container { 99 | display: flex; 100 | flex-direction: column; 101 | } 102 | .displayInterests { 103 | justify-content: center; 104 | } 105 | .u_width_80 { 106 | width: 80%; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/pages/Settings/ProfileSettings/displayInterests/displayInterests.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './displayInterests.module.css'; 3 | import XButton from '../../../../components/UI/XButton/XButton'; 4 | 5 | const displayInterests = props => { 6 | return ( 7 | 8 |
9 | {props.info} 10 | 15 | X 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default displayInterests; 23 | -------------------------------------------------------------------------------- /src/pages/Settings/ProfileSettings/displayInterests/displayInterests.module.css: -------------------------------------------------------------------------------- 1 | .element { 2 | margin-right: 10px; 3 | border: 1px solid #6831de; 4 | padding: 10px 30px; 5 | padding-top: 15px; 6 | margin-bottom: 15px; 7 | border-radius: 10px; 8 | position: relative; 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsEmailPassword/EmailForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './SettingsEmailPassword.css'; 3 | import Button from '../../../components/UI/Button/Button'; 4 | class EmailForm extends Component { 5 | state = { 6 | // if user sees current email in input box then 7 | // needs to be derived from web token or props 8 | email: 'testemail@gmail.com', 9 | success: null, 10 | error: null 11 | }; 12 | 13 | onSubmit = event => { 14 | event.preventDefault(); 15 | // fetch made here 16 | }; 17 | 18 | onChange = event => { 19 | this.setState({ email: event.target.value }); 20 | }; 21 | 22 | render() { 23 | const { email, success, error } = this.state; 24 | let errorMessage; 25 | let successMessage; 26 | if (error) { 27 | errorMessage = ( 28 | 29 | {error} 30 | 31 | ); 32 | } 33 | if (success) { 34 | successMessage = ( 35 | 39 | {success} 40 | 41 | ); 42 | } 43 | return ( 44 |
45 |
46 | {errorMessage} 47 | {successMessage} 48 | 51 | 59 |
60 |
61 | 64 |
65 |
66 | ); 67 | } 68 | } 69 | 70 | export default EmailForm; 71 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsEmailPassword/PasswordForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Button from '../../../components/UI/Button/Button'; 3 | 4 | class PasswordForm extends Component { 5 | state = { 6 | password: '', 7 | newPassword: '', 8 | confirmPassword: '', 9 | success: null, 10 | error: null 11 | }; 12 | 13 | onSubmit = event => { 14 | event.preventDefault(); 15 | const { newPassword, confirmPassword } = this.state; 16 | if (newPassword !== confirmPassword) { 17 | this.setState({ 18 | error: 'Passwords must match' 19 | }); 20 | } 21 | }; 22 | 23 | onChange = event => { 24 | this.setState({ [event.target.name]: event.target.value }); 25 | }; 26 | 27 | render() { 28 | const { 29 | password, 30 | newPassword, 31 | confirmPassword, 32 | error, 33 | success 34 | } = this.state; 35 | let errorMessage; 36 | let successMessage; 37 | if (error) { 38 | errorMessage = ( 39 | 40 | {error} 41 | 42 | ); 43 | } 44 | if (success) { 45 | successMessage = ( 46 | 50 | {success} 51 | 52 | ); 53 | } 54 | return ( 55 |
56 |
57 | 62 | 70 |
71 |
72 | 75 | 83 |
84 |
85 | 88 | 96 |
97 |
98 | 107 |
108 |
109 | ); 110 | } 111 | } 112 | 113 | export default PasswordForm; 114 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsEmailPassword/SettingsEmailPassword.css: -------------------------------------------------------------------------------- 1 | .container--settings { 2 | background: #fff; 3 | } 4 | 5 | .settings-wrapper { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | } 10 | 11 | .settings-wrapper--top-pad { 12 | padding-top: 15px; 13 | } 14 | 15 | .form { 16 | width: 30vw; 17 | } 18 | 19 | .form--settings { 20 | font-family: 'Poppins', sans-serif; 21 | font-weight: 400; 22 | } 23 | 24 | .form__section { 25 | margin: 12px 0; 26 | padding-bottom: 5px; 27 | } 28 | 29 | .form__label--settings { 30 | display: block; 31 | margin: 15px 0 5px 0; 32 | padding-top: 20px; 33 | } 34 | 35 | .form__input { 36 | box-sizing: border-box; 37 | border: none; 38 | } 39 | 40 | .form__input--settings { 41 | display: block; 42 | width: 100%; 43 | margin: 10px 0; 44 | padding: 0.5rem; 45 | font-size: 1rem; 46 | border-bottom: 2px solid grey; 47 | } 48 | 49 | .form__input--settings:hover, 50 | .form__input--settings:focus, 51 | .form__input--settings:active { 52 | outline: 0; 53 | border-bottom: 2px solid #6831de; 54 | } 55 | 56 | .form__msg { 57 | font-style: italic; 58 | padding-left: 3vw; 59 | } 60 | 61 | .form__msg--err { 62 | color: #ff0000; 63 | } 64 | 65 | .form__msg--success { 66 | color: #6831de; 67 | } 68 | 69 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 70 | .settings-wrapper--top-pad { 71 | padding-top: 10vh; 72 | } 73 | 74 | .form__label--settings, 75 | .form__input--settings { 76 | font-size: 1.35rem; 77 | } 78 | } 79 | 80 | @media only screen and (min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) { 81 | .container--settings { 82 | padding-bottom: 15px; 83 | } 84 | 85 | .settings-wrapper--top-pad { 86 | padding-top: 10vh; 87 | } 88 | } 89 | 90 | @media only screen and (min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) { 91 | .form { 92 | width: 45vw; 93 | } 94 | } 95 | 96 | @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) { 97 | .form { 98 | width: 50vw; 99 | } 100 | } 101 | 102 | @media only screen and (min-device-width: 320px) and (max-device-width: 823px) { 103 | .form { 104 | width: 70vw; 105 | } 106 | 107 | .settings-wrapper--bottom-pad { 108 | padding-bottom: 15px; 109 | } 110 | 111 | .form__msg { 112 | display: block; 113 | padding-left: 0; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsEmailPassword/SettingsEmailPassword.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import EmailForm from './EmailForm'; 3 | import PasswordForm from './PasswordForm'; 4 | import SettingsNavbar from '../SettingsNavbar/SettingsNavbar'; 5 | 6 | class SettingsEmailPassword extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | EmailPasswordSelected: true 12 | }; 13 | } 14 | render() { 15 | return ( 16 |
17 | 20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 | ); 28 | } 29 | } 30 | 31 | export default SettingsEmailPassword; 32 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsNavbar/SettingsNavbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './SettingsNavbar.module.css'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const SettingsNavbar = props => { 6 | return ( 7 |
8 | 14 | Profile Settings 15 | 16 | 22 | Email and Password 23 | 24 |
25 | ); 26 | }; 27 | export default SettingsNavbar; 28 | -------------------------------------------------------------------------------- /src/pages/Settings/SettingsNavbar/SettingsNavbar.module.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | max-width: 1250px; 3 | margin: 20px auto; 4 | padding-left: 10px; 5 | display: flex; 6 | border-bottom: 2px solid #6831de; 7 | flex-wrap: nowrap; 8 | justify-content: flex-start; 9 | align-items: flex-start; 10 | } 11 | 12 | .item { 13 | border-top-left-radius: 10px; 14 | border-top-right-radius: 10px; 15 | padding: 10px 5px; 16 | margin-right: 30px; 17 | cursor: pointer; 18 | font-weight: 400; 19 | } 20 | 21 | .active { 22 | border-top-left-radius: 10px; 23 | border-top-right-radius: 10px; 24 | padding: 10px 5px; 25 | margin-right: 30px; 26 | cursor: pointer; 27 | color: white; 28 | background-color: #6831de; 29 | font-weight: 400; 30 | } 31 | -------------------------------------------------------------------------------- /src/pics/404Icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/pics/AboutUs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/pics/Add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/pics/AddSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/pics/AddSmall.png -------------------------------------------------------------------------------- /src/pics/FriendsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/pics/InviteFriends.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/pics/Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/pics/LogoInverted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pics/SearchIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/pics/Team.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/pics/adult-backlit-beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/pics/adult-backlit-beach.jpg -------------------------------------------------------------------------------- /src/pics/blank-profile-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/pics/blank-profile-picture.png -------------------------------------------------------------------------------- /src/pics/blank-profile-picture.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/pics/blank-profile-picture.psd -------------------------------------------------------------------------------- /src/pics/default-profile-pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/mappypals/47c275042b524664c257db1c50891f2b00ec921f/src/pics/default-profile-pic.jpg -------------------------------------------------------------------------------- /src/server/createMockDB.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const faker = require('faker'); 4 | const times = 50; 5 | 6 | // declare collections here 7 | const friends = []; 8 | 9 | // generate fake data inside the loop 10 | for (let i = 0; i < times; i++) { 11 | // generate fake friends 12 | friends.push({ 13 | id: i, 14 | avatar: faker.internet.avatar(), 15 | name: faker.name.findName() 16 | }); 17 | } 18 | // Add collectiont to fake db 19 | const data = JSON.stringify({ friends }); 20 | const filepath = path.join(__dirname, 'db.json'); 21 | 22 | fs.writeFile(filepath, data, function(err) { 23 | err ? console.log(err) : console.log('Mock DB created.'); 24 | }); 25 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | // Setup server and router 2 | const jsonServer = require('json-server'); 3 | const server = jsonServer.create(); 4 | const path = require('path'); 5 | const router = jsonServer.router(path.join(__dirname, 'db.json')); 6 | 7 | // use default middle-wares 8 | const middlewares = jsonServer.defaults(); 9 | server.use(middlewares); 10 | 11 | // Use body-parser to handle POST, PUT and PATCH (json-server's body-parser) 12 | server.use(jsonServer.bodyParser); 13 | 14 | // Simulate one second delay in all requests 15 | server.use((req, res, next) => { 16 | setTimeout(next, 1000); 17 | }); 18 | 19 | // Add customs routes here. Always before json-server router!!! 20 | // Example 21 | // server.post('/signup/', (req, res, next) => { 22 | // // custom logic 23 | // }); 24 | 25 | // json-server router (default) 26 | server.use(router); 27 | 28 | // Server start 29 | const port = 3002; 30 | server.listen(port, () => 31 | console.log(`json-server is running on port ${port}`) 32 | ); 33 | 34 | // Create a friendly URL slug. This could be moved to another file 35 | // also we can create some validation functions for post requests 36 | // We can omit this for now, but it could come handy later 37 | const friendlySlug = value => 38 | value 39 | .replace(/[^a-z0-9_]+/gi, '-') 40 | .replace(/^-|-$/g, '') 41 | .toLowerCase(); 42 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && 110 | contentType.indexOf('javascript') === -1) 111 | ) { 112 | // No service worker found. Probably a different app. Reload the page. 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister().then(() => { 115 | window.location.reload(); 116 | }); 117 | }); 118 | } else { 119 | // Service worker found. Proceed as normal. 120 | registerValidSW(swUrl, config); 121 | } 122 | }) 123 | .catch(() => { 124 | console.log( 125 | 'No internet connection found. App is running in offline mode.' 126 | ); 127 | }); 128 | } 129 | 130 | export function unregister() { 131 | if ('serviceWorker' in navigator) { 132 | navigator.serviceWorker.ready.then(registration => { 133 | registration.unregister(); 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/store/actions/apiStatus.js: -------------------------------------------------------------------------------- 1 | import { BEGIN_API_CALL, API_CALL_ERROR } from './types'; 2 | export const beginApiCall = caller => ({ type: BEGIN_API_CALL, caller }); 3 | export const apiCallError = caller => ({ type: API_CALL_ERROR, caller }); 4 | -------------------------------------------------------------------------------- /src/store/actions/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | authLogin, 3 | authLoginFailed, 4 | authLoginStart, 5 | authLoginSucceeded 6 | } from './auth'; 7 | -------------------------------------------------------------------------------- /src/store/actions/modals.js: -------------------------------------------------------------------------------- 1 | import { 2 | SHOW_INVITE_FRIENDS_MODAL, 3 | HIDE_INVITE_FRIENDS_MODAL 4 | } from '../actions/types'; 5 | 6 | const showInviteFriends = () => ({ type: SHOW_INVITE_FRIENDS_MODAL }); 7 | const hideInviteFriends = () => ({ type: HIDE_INVITE_FRIENDS_MODAL }); 8 | 9 | export { showInviteFriends, hideInviteFriends }; 10 | -------------------------------------------------------------------------------- /src/store/actions/types.js: -------------------------------------------------------------------------------- 1 | // user related actions 2 | export const USER_GET_FRIENDS_SUCCESS = 'USER_GET_FRIENDS_SUCCESS'; 3 | export const USER_SIGN_IN_SUCCESS = 'USER_SIGN_IN_SUCCESS'; 4 | export const USER_SIGN_OUT_SUCCESS = 'USER_SIGN_OUT_SUCCESS'; 5 | export const USER_SIGN_UP_SUCCESS = 'USER_SIGN_UP_SUCCESS'; 6 | export const USER_CHANGE_PASSWORD_SUCCESS = 'USER_CHANGE_PASSWORD_SUCCESS'; 7 | export const USER_RESET_PASSWORD_SUCCESS = 'USER_RESET_PASSWORD_SUCCESS'; 8 | export const USER_UPDATE_PROFILE_SUCCESS = 'USER_UPDATE_PROFILE_SUCCESS'; 9 | 10 | // api status 11 | export const BEGIN_API_CALL = 'BEGIN_API_CALL'; 12 | export const API_CALL_ERROR = 'API_CALL_ERROR'; 13 | 14 | // api calls 15 | export const LOAD_USER_FRIENDS_API_CALL = 'LOAD_USER_FRIENDS_API_CALL'; 16 | export const USER_SIGN_IN_API_CALL = 'USER_SIGN_IN_API_CALL'; 17 | export const USER_SIGN_OUT_API_CALL = 'USER_SIGN_OUT_API_CALL'; 18 | export const USER_SIGN_UP_API_CALL = 'USER_SIGN_UP_API_CALL'; 19 | export const USER_UPDATE_PROFILE_API_CALL = 'USER_UPDATE_PROFILE_API_CALL'; 20 | 21 | // modals 22 | export const SHOW_INVITE_FRIENDS_MODAL = 'SHOW_INVITE_FRIENDS_MODAL'; 23 | export const HIDE_INVITE_FRIENDS_MODAL = 'HIDE_INVITE_FRIENDS_MODAL'; 24 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | import rootReducer from './reducers'; 5 | 6 | const composeEnhancers = 7 | process.env.NODE_ENV === 'development' 8 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 9 | : null || compose; 10 | const store = createStore( 11 | rootReducer, 12 | composeEnhancers(applyMiddleware(thunk)) 13 | ); 14 | 15 | export default store; 16 | -------------------------------------------------------------------------------- /src/store/reducers/apiStatus.js: -------------------------------------------------------------------------------- 1 | import { BEGIN_API_CALL, API_CALL_ERROR } from '../actions/types'; 2 | import initialState from './initialState'; 3 | 4 | const actionTypeEndsInSuccess = type => { 5 | return type.substring(type.length - 8) === '_SUCCESS'; 6 | }; 7 | const compareTypes = (apiCall, caller) => { 8 | return ( 9 | apiCall === caller || 10 | apiCall.substring(0, apiCall.length - 9) === 11 | caller.substring(0, caller.length - 8) 12 | ); 13 | }; 14 | // Every time an api call is made, add it to the global state 15 | // and when it fail or succeed remove it from the global state 16 | // Use the apiCallsInProgress to render spinners while the data is being fetched 17 | // Also this approach simplifies redux actions 18 | // (no need to create XXX_START, XXX_FAIL, XXX_SUCCESS for each action) 19 | const apiStatus = (state = initialState.apiCallsInProgress, action) => { 20 | if (action.type === BEGIN_API_CALL) { 21 | return [...state, action.caller]; 22 | } else if ( 23 | // if api call failed or succeeded, remove it from the 24 | // global state holding api calls in progress 25 | action.type === API_CALL_ERROR || 26 | actionTypeEndsInSuccess(action.type) 27 | ) { 28 | return state.filter(apiCall => !compareTypes(apiCall, action.caller)); 29 | } 30 | 31 | return state; 32 | }; 33 | 34 | export default apiStatus; 35 | -------------------------------------------------------------------------------- /src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import userReducer from './user'; 4 | import apiStatusReducer from './apiStatus'; 5 | import modalsReducer from './modals'; 6 | const rootReducer = combineReducers({ 7 | user: userReducer, 8 | apiCallsInProgress: apiStatusReducer, 9 | modals: modalsReducer 10 | }); 11 | 12 | export default rootReducer; 13 | -------------------------------------------------------------------------------- /src/store/reducers/initialState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | user: { 3 | uid: undefined, 4 | photoULR: undefined, 5 | displayName: '', 6 | email: '', 7 | emailVerified: false, 8 | settings: {}, 9 | friends: [] // other users 10 | }, 11 | apiCallsInProgress: [], // ['LOAD_USER_FRIENDS_API_CALL'] 12 | modals: { inviteFriends: false, contactUs: false } 13 | }; 14 | -------------------------------------------------------------------------------- /src/store/reducers/modals.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | import { 3 | SHOW_INVITE_FRIENDS_MODAL, 4 | HIDE_INVITE_FRIENDS_MODAL 5 | } from '../actions/types'; 6 | export const modalsReducer = (state = initialState.modals, action) => { 7 | switch (action.type) { 8 | case SHOW_INVITE_FRIENDS_MODAL: 9 | return { ...state, inviteFriends: true }; 10 | case HIDE_INVITE_FRIENDS_MODAL: 11 | return { ...state, inviteFriends: false }; 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default modalsReducer; 18 | -------------------------------------------------------------------------------- /src/store/reducers/user.js: -------------------------------------------------------------------------------- 1 | import * as ACTION from '../actions/types'; 2 | import initialState from './initialState'; 3 | 4 | export const userReducer = (state = initialState.user, action) => { 5 | switch (action.type) { 6 | case ACTION.USER_GET_FRIENDS_SUCCESS: 7 | return { ...state, friends: action.friends }; 8 | case ACTION.USER_SIGN_IN_SUCCESS: 9 | case ACTION.USER_SIGN_UP_SUCCESS: 10 | return { ...state, ...action.user }; 11 | case ACTION.USER_SIGN_OUT_SUCCESS: 12 | return initialState.user; 13 | case ACTION.USER_UPDATE_PROFILE_SUCCESS: 14 | return { ...state, ...action.updates }; 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | export default userReducer; 21 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | export const updateObject = (oldObject, newObject) => { 2 | return { 3 | ...oldObject, 4 | ...newObject 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/localStorage.js: -------------------------------------------------------------------------------- 1 | const setLocalData = (token, userId) => { 2 | localStorage.setItem('token', token); 3 | localStorage.setItem('userId', userId); 4 | }; 5 | 6 | const getLocalData = () => { 7 | return { 8 | token: localStorage.getItem('token'), 9 | userId: localStorage.getItem('userId') 10 | }; 11 | }; 12 | 13 | export { setLocalData, getLocalData }; 14 | -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | match /{allPaths=**} { 4 | allow read, write: if request.auth!=null; 5 | } 6 | } 7 | } 8 | --------------------------------------------------------------------------------