├── .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 |
48 | Oops! Please, active Script to run Javascript on this page properly. Check your browser settings!
49 |
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 |
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 |
10 | {props.children}
11 |
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 |
closeHandler(text)}>
8 | X
9 |
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 | handleChange(obj)} //returns an array with updated values
16 | className={classes.displayInterests}
17 | theme={theme => ({
18 | ...theme,
19 | borderRadius: 0,
20 | colors: {
21 | ...theme.colors,
22 | primary: '#6831de'
23 | }
24 | })}
25 | />
26 | );
27 | };
28 |
29 | export default DropdownSelect;
--------------------------------------------------------------------------------
/src/components/UI/DropdownSelect/DropdownSelect.module.css:
--------------------------------------------------------------------------------
1 | .displayInterests {
2 | border: 2px solid grey;
3 | }
4 | .displayInterests div:first-of-type {
5 | border: transparent;
6 | }
7 |
8 | .inputTextarea:hover,
9 | .inputTextarea:focus,
10 | .displayInterests:hover,
11 | .displayInterests:focus {
12 | outline: 0;
13 | border: 2px solid #6831de;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/UI/Grid/Grid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | export const RowStyle = styled.div`
5 | display: flex;
6 | flex-direction: row;
7 | align-items: flex-start;
8 | flex-wrap: wrap;
9 | justify-content: flex-start;
10 |
11 | ${({ flexDirection }) =>
12 | flexDirection && `flex-direction: ${flexDirection};`}
13 | ${({ alignItems }) => alignItems && `align-items: ${alignItems};`}
14 | ${({ flexWrap }) => flexWrap && `flex-wrap: ${flexWrap};`}
15 | ${({ justifyContent }) =>
16 | justifyContent && `justify-content: ${justifyContent};`}
17 | ${({ height }) => height && `height: ${height}vh`}
18 |
19 | `;
20 |
21 | const getWidthString = span => {
22 | if (!span) return;
23 |
24 | let width = (span / 12) * 100;
25 | return `width: ${width}%`;
26 | };
27 |
28 | export const ColumnStyle = styled.div`
29 | display: flex;
30 | flex-direction: column;
31 | ${({ alignItems }) => alignItems && `align-items: ${alignItems};`}
32 | ${({ flexWrap }) => flexWrap && `flex-wrap: ${flexWrap};`}
33 | ${({ justifyContent }) =>
34 | justifyContent && `justify-content: ${justifyContent};`}
35 |
36 | ${({ xs }) => (xs ? getWidthString(xs) : 'width: 100%')};
37 |
38 | @media only screen and (min-width: 768px) {
39 | ${({ sm }) => sm && getWidthString(sm)};
40 | }
41 |
42 | @media only screen and (min-width: 992px) {
43 | ${({ md }) => md && getWidthString(md)};
44 | }
45 |
46 | @media only screen and (min-width: 1200px) {
47 | ${({ lg }) => lg && getWidthString(lg)};
48 | }
49 | `;
50 |
51 | export function Row({ children, ...otherProps }) {
52 | return {children} ;
53 | }
54 |
55 | export function Column({ children, ...otherProps }) {
56 | return {children} ;
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/UI/LinkTag/LinkTag.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './LinkTag.module.css';
3 | const LinkTag = props => {
4 | return (
5 |
11 | {props.children}
12 |
13 | );
14 | };
15 |
16 | export default LinkTag;
17 |
--------------------------------------------------------------------------------
/src/components/UI/LinkTag/LinkTag.module.css:
--------------------------------------------------------------------------------
1 | .ALink {
2 | font-weight: bold;
3 | text-decoration: none;
4 | color: #ffffff;
5 | }
6 |
7 | .ALink:hover {
8 | opacity: 0.8;
9 | }
10 |
11 | .About {
12 | font-weight: 100;
13 | margin: 0 5px;
14 | font-size: 10px;
15 | padding: 0.5rem;
16 | border-radius: 7.5px;
17 | background: linear-gradient(
18 | 90deg,
19 | rgba(224, 59, 251, 1) 0%,
20 | rgba(104, 49, 222, 1) 100%
21 | );
22 | box-shadow: 0 3px 4px -2px grey;
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import classes from './Modal.module.scss';
5 | import { hideInviteFriends } from '../../../store/actions/modals';
6 |
7 | export const Modal = ({ show, children }) => {
8 | const showHideClassName = show
9 | ? `${classes.modal} ${classes.displayBlock}`
10 | : `${classes.modal} ${classes.displayNone}`;
11 | // react-redux hook
12 | const dispatch = useDispatch();
13 | return (
14 |
15 |
16 | dispatch(hideInviteFriends())}
19 | >
20 | X
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | Modal.propTypes = {
29 | show: PropTypes.bool.isRequired
30 | };
31 | export default Modal;
32 |
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.module.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | position: fixed;
3 | top: 50%;
4 | left: 50%;
5 | transform: translate(-80%, -50%);
6 | max-width: 80%;
7 | background: lightgrey;
8 | box-shadow: 5px 10px 18px rgba(0, 0, 0, 0.05);
9 | border: 2px solid gray;
10 | border-radius: 30px;
11 | height: auto;
12 | z-index: 2;
13 | }
14 | @media all and (max-width: 900px) {
15 | .modal {
16 | transform: translate(-50%, -50%);
17 | min-width: 90%;
18 | }
19 | }
20 | .header {
21 | display: flex;
22 | align-items: flex-end;
23 | }
24 | .modalMain {
25 | display: flex;
26 | }
27 | .closeButton {
28 | border: none;
29 | background: none;
30 | width: auto;
31 | margin-top: 16px;
32 | margin-right: 16px;
33 | font-size: 18px;
34 | // text-align: end;
35 | &:hover {
36 | color: gray;
37 | font-weight: 600;
38 | }
39 | }
40 | .displayBlock {
41 | display: block;
42 | }
43 |
44 | .displayNone {
45 | display: none;
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/UI/Text/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { css } from 'styled-components';
3 |
4 | const textStyle = css`
5 | ${({ align }) => align && `text-align: ${align};`}
6 | ${({ size }) => size && `font-size: ${size}%;`}
7 | ${({ spacing }) => spacing && `letter-spacing: ${spacing}em;`}
8 | ${({ color }) => `color: ${color ? color : `#000`};`}
9 | ${({ padding }) => padding && `padding: ${padding}rem;`}
10 | ${({ weight }) => weight && `font-weight: ${weight};`}
11 | ${({ uppercase }) => uppercase && `text-transform: uppercase;`}
12 | ${({ family }) => family && `font-family: ${family}, sans-serif;`}
13 | `;
14 |
15 | export const Text = ({ children, variant, ...otherProps }) => {
16 | const Element = variant
17 | ? styled(variant)`
18 | ${textStyle}
19 | `
20 | : styled.p`
21 | ${textStyle}
22 | `;
23 |
24 | return {children} ;
25 | };
26 |
27 | export default Text;
28 |
--------------------------------------------------------------------------------
/src/components/UI/XButton/XButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './XButton.module.css';
3 |
4 | const Xbutton = props => {
5 | return (
6 |
10 | {props.children}
11 |
12 | );
13 | };
14 |
15 | export default Xbutton;
16 |
--------------------------------------------------------------------------------
/src/components/UI/XButton/XButton.module.css:
--------------------------------------------------------------------------------
1 | .XButton {
2 | color: #595959;
3 | border: none;
4 | background: none;
5 | cursor: pointer;
6 | padding: 0;
7 | }
8 |
9 | .AddFriendForm {
10 | position: absolute;
11 | right: 16px;
12 | font-size: 25px;
13 | }
14 |
15 | .InvitationSent {
16 | position: absolute;
17 | right: 16px;
18 | top: 10px;
19 | font-size: 25px;
20 | }
21 |
22 | .DisplayInterests {
23 | position: absolute;
24 | right: 2px;
25 | top: 2px;
26 | font-size: 20px;
27 | color: #6831de;
28 | }
29 |
30 | .InterestsTag {
31 | font-size: 16px;
32 | border-radius: 5px;
33 | box-shadow: 2px 3px 4px -2px grey;
34 | margin: 0.4em 0.6rem;
35 | padding: 0.4em 1.8em;
36 | color: black;
37 | border: 1px solid #6831de;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/UI/effects/useWindowSize.effect.js:
--------------------------------------------------------------------------------
1 | // This hook returns an object containing the window's width and height
2 | // If executed server-side (no window object)
3 | // the value of width and height will be undefined
4 |
5 | import { useState, useEffect } from 'react';
6 |
7 | const useWindowSize = () => {
8 | const [windowSize, setWindowSize] = useState({
9 | width: window.innerWidth,
10 | height: window.innerHeight
11 | });
12 |
13 | useEffect(() => {
14 | const isClient = typeof window === 'object';
15 |
16 | const getSize = () => {
17 | return {
18 | width: isClient ? window.innerWidth : undefined,
19 | height: isClient ? window.innerHeight : undefined
20 | };
21 | };
22 |
23 | if (!isClient) {
24 | return false;
25 | }
26 |
27 | const handleResize = () => {
28 | setWindowSize(getSize());
29 | };
30 |
31 | window.addEventListener('resize', handleResize);
32 |
33 | return () => window.removeEventListener('resize', handleResize);
34 | }, []); // Empty array ensures that effect is only run on mount and unmount
35 |
36 | return windowSize;
37 | };
38 |
39 | export default useWindowSize;
40 |
--------------------------------------------------------------------------------
/src/components/helper.js:
--------------------------------------------------------------------------------
1 | const IsPasswordValid = password => {
2 | // Validation: https://stackoverflow.com/questions/18367258/validation-for-password-is-at-least-6-characters
3 | if (!password || password.length < 6) {
4 | return false;
5 | }
6 | // check for a number
7 | if (/[0-9]/.test(password) === false) {
8 | return false;
9 | }
10 | // check for a capital letter
11 | if (/[A-Z]/.test(password) === false) {
12 | return false;
13 | }
14 | // check for a lowercase letter
15 | if (/[a-z]/.test(password) === false) {
16 | return false;
17 | }
18 | // all requirements have been satisfied
19 | return true;
20 | };
21 |
22 | const IsPasswordIdentical = (password, confirmPassword) => {
23 | if (password === confirmPassword) {
24 | return true;
25 | }
26 | return false;
27 | };
28 | export { IsPasswordValid, IsPasswordIdentical };
29 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import { Provider } from 'react-redux';
5 |
6 | import App from './App';
7 | import * as serviceWorker from './serviceWorker';
8 | import store from './store';
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | ,
16 | document.getElementById('root')
17 | );
18 |
19 | // If you want your app to work offline and load faster, you can change
20 | // unregister() to register() below. Note this comes with some pitfalls.
21 | // Learn more about service workers: http://bit.ly/CRA-PWA
22 | serviceWorker.unregister();
23 |
--------------------------------------------------------------------------------
/src/pages/About/About.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Noto+Sans:400,700&display=swap');
2 | body {
3 | font-family: 'Noto Sans', sans-serif;
4 | }
5 | .aboutpage {
6 | display: flex;
7 | justify-content: center;
8 | padding: 3em 0;
9 | }
10 | div.user-photo img {
11 | max-height: 100%;
12 | border-radius: 50%;
13 | background: lightgray;
14 | }
15 | .dev-name {
16 | color: #6831de;
17 | font-weight: bold;
18 | }
19 | .dev-role {
20 | line-height: 1.5%;
21 | font-weight: 400;
22 | font-style: italic;
23 | font-size: 0.8rem;
24 | }
25 | .dev-info {
26 | margin-bottom: 3rem;
27 | }
28 | .user-links {
29 | display: flex;
30 | flex-direction: row;
31 | justify-content: center;
32 | padding: 0 5px;
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/About/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import team from './team-data';
3 | import TeamCard from './TeamCard';
4 | import styled from 'styled-components';
5 |
6 | const TeamCardGrid = styled.div`
7 | display: grid;
8 | grid-template-columns: auto auto auto;
9 | grid-column-gap: 6em;
10 | grid-row-gap: 4em;
11 |
12 | @media screen and (max-width: 600px) {
13 | grid-template-columns: auto auto;
14 | grid-column-gap: 1em;
15 | grid-row-gap: 2em;
16 | }
17 | `;
18 |
19 | const About = () => {
20 | return (
21 |
22 |
23 | {team.map((data, id) => {
24 | return (
25 |
35 | );
36 | })}
37 |
38 |
39 | );
40 | };
41 |
42 | export default About;
43 |
--------------------------------------------------------------------------------
/src/pages/About/TeamCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import './About.css';
4 | import ALink from '../../components/UI/LinkTag/LinkTag';
5 | const Card = styled.div`
6 | background-color: white;
7 | text-align: center;
8 | width: 12rem;
9 | height: auto;
10 | padding: 1.4rem 0.8rem;
11 | border-style: solid;
12 | border-width: 0.5px;
13 | border-color: lightgrey;
14 | border-radius: 7.5px;
15 | box-shadow: 2px 3px 4px -2px grey;
16 |
17 | transition: all 0.2s ease-in-out;
18 | &:hover {
19 | transform: scale(1.1);
20 | }
21 |
22 | @media screen and (max-width: 600px) {
23 | width: 10em;
24 | }
25 | `;
26 |
27 | const TeamCard = ({ id, name, role, location, portfolio, linkedin }) => (
28 |
29 |
30 |
31 |
35 |
36 |
37 |
{name}
38 |
{role}
39 |
40 |
41 |
47 | Portfolio
48 |
49 |
50 |
56 | LinkedIn
57 |
58 |
59 |
60 |
61 | );
62 |
63 | export default TeamCard;
64 |
--------------------------------------------------------------------------------
/src/pages/About/team-data.js:
--------------------------------------------------------------------------------
1 | const role = [
2 | 'Frontend',
3 | 'Backend',
4 | 'Fullstack',
5 | 'DevOps',
6 | 'Team Lead',
7 | 'UI/UX',
8 | 'Project Mgr.',
9 | 'Marketing',
10 | 'Product'
11 | ];
12 |
13 | const location = [
14 | 'Nigeria',
15 | 'UK',
16 | 'US',
17 | 'Australia',
18 | 'Russia',
19 | 'Lithuania',
20 | 'India',
21 | 'Moroco'
22 | ];
23 |
24 | const team = [
25 | {
26 | avatar: null,
27 | name: 'Mubarak Show',
28 | role: `${role[4] + ', ' + role[8]}`,
29 | location: location[0],
30 | isEmployed: false,
31 | portfolio: 'https://www.google.com/',
32 | linkedin: 'https://www.linkedin.com/'
33 | },
34 | {
35 | avatar: null,
36 | name: 'Marco Sciortino',
37 | role: role[2],
38 | location: location[1],
39 | isEmployed: false,
40 | portfolio: 'https://www.google.com//',
41 | linkedin: 'https://www.linkedin.com/'
42 | },
43 | {
44 | avatar: null,
45 | name: 'Arnas Dičkus',
46 | role: role[6],
47 | location: location[5],
48 | isEmployed: false,
49 | portfolio: 'https://www.arnasdickus.lt/',
50 | linkedin: 'https://www.linkedin.com/in/arnas-di%C4%8Dkus-a77b15162/'
51 | },
52 | {
53 | avatar: null,
54 | name: 'Yash Puthran',
55 | role: role[1],
56 | location: location[6],
57 | isEmployed: true,
58 | portfolio: 'https://www.google.com//',
59 | linkedin: 'https://www.linkedin.com/'
60 | },
61 | {
62 | avatar: null,
63 | name: 'Thijs Nijhof',
64 | role: `${role[5]}, ${role[0]}`,
65 | location: location[1],
66 | isEmployed: false,
67 | portfolio: 'https://www.google.com//',
68 | linkedin: 'https://www.linkedin.com/'
69 | },
70 | {
71 | avatar: null,
72 | name: 'Kristina',
73 | role: role[2],
74 | location: location[1],
75 | isEmployed: false,
76 | portfolio: 'https://www.google.com//',
77 | linkedin: 'https://www.linkedin.com/'
78 | },
79 | {
80 | avatar: null,
81 | name: 'Melissendra',
82 | role: role[0],
83 | location: location[1],
84 | isEmployed: false,
85 | portfolio: 'https://www.google.com//',
86 | linkedin: 'https://www.linkedin.com/'
87 | },
88 | {
89 | avatar: null,
90 | name: 'Matt Smith',
91 | role: role[3],
92 | location: location[1],
93 | isEmployed: false,
94 | portfolio: 'https://www.google.com//',
95 | linkedin: 'https://www.linkedin.com/'
96 | },
97 | {
98 | avatar: null,
99 | name: 'Yassine Belkaid',
100 | role: role[1],
101 | location: location[7],
102 | isEmployed: false,
103 | portfolio: 'https://www.google.com//',
104 | linkedin: 'https://www.linkedin.com//'
105 | }
106 | ];
107 |
108 | export default team;
109 |
--------------------------------------------------------------------------------
/src/pages/Contact/Contact.css:
--------------------------------------------------------------------------------
1 | .contactForm {
2 | display: flex;
3 | justify-content: center;
4 | padding-top: 15px;
5 | }
6 |
7 | .header {
8 | text-align: center;
9 | padding-bottom: 1em;
10 | }
11 |
12 | .header h2 {
13 | color: #6831de;
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/FriendsList/FriendCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import './FriendsList.css';
4 | import Button from '../../components/UI/Button/Button';
5 |
6 | const Card = styled.div`
7 | background-color: white;
8 | text-align: center;
9 | width: 150px;
10 | height: 215px;
11 | margin: 1em;
12 | padding: 15px;
13 | border: 0.5px solid #cccccc;
14 | border-radius: 10px;
15 | box-shadow: 1px 1px 1px 1px #989898;
16 | color: #6831de;
17 | transition: all 0.4s ease-in-out;
18 | &:hover {
19 | transform: scale(1.03);
20 | }
21 | @media (max-width: 493px) {
22 | width: 110px;
23 | margin: 1em auto;
24 | }
25 | `;
26 | const FriendCard = ({ name }) => (
27 |
28 |
29 |
30 |
34 |
35 | {name}
36 |
37 |
38 |
39 | Profile
40 |
41 |
42 |
43 | Message
44 |
45 |
46 |
47 | );
48 |
49 | export default FriendCard;
50 |
--------------------------------------------------------------------------------
/src/pages/FriendsList/FriendsList.css:
--------------------------------------------------------------------------------
1 | div.friend-photo {
2 | width: 80px;
3 | height: 80px;
4 | margin: 1em auto;
5 | }
6 | div.friend-photo img {
7 | max-height: 100%;
8 | border-radius: 50%;
9 | background: #cccccc;
10 | }
11 |
12 | .friendsText {
13 | font-family: 'Noto Sans', sans-serif;
14 | font-weight: lighter;
15 | }
16 |
17 | h1.friendsText {
18 | color: #6831de;
19 | }
20 |
21 | h3.friendsText {
22 | font-size: 1em;
23 | height: 15%;
24 | }
25 |
26 | p.friendsText {
27 | color: #fbfbfbd9;
28 | text-align: center;
29 | font-size: 0.9em;
30 | margin: 0 auto;
31 | }
32 |
33 | div.friends-search {
34 | width: 350px;
35 | height: 80px;
36 | margin: auto;
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | }
41 |
42 | .friends-search img {
43 | width: 60px;
44 | height: auto;
45 | z-index: 10;
46 | }
47 |
--------------------------------------------------------------------------------
/src/pages/FriendsList/FriendsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FriendCard from './FriendCard';
3 | import SearchFriends from './SearchFriends';
4 | import styled from 'styled-components';
5 | import { connect } from 'react-redux';
6 | import { getUserFriends } from '../../store/actions/user';
7 | const FriendsLayout = styled.div`
8 | width: calc(100vw - 300px);
9 | background: #fff;
10 | @media (max-width: 774px) {
11 | width: 100%;
12 | }
13 | `;
14 |
15 | const FriendsPageList = styled.div`
16 | margin-top: 3em;
17 | display: grid;
18 | grid-template-rows: auto auto;
19 | `;
20 | const FriendsTopBar = styled.div`
21 | width: 95%;
22 | margin: auto;
23 | height: 10vh;
24 | display: flex;
25 | justify-content: space-around;
26 | @media (max-width: 774px) {
27 | height: 20vh;
28 | flex-direction: column;
29 | justify-content: center;
30 | align-items: center;
31 | }
32 | `;
33 |
34 | const FriendCardGrid = styled.div`
35 | width: 90%;
36 | display: flex;
37 | flex-wrap: wrap
38 | justify-content: flex-start;
39 | align-items: center;
40 | margin: auto;
41 | @media (max-width: 493px) {
42 | width: 100%;
43 | }
44 | `;
45 |
46 | class FriendsList extends React.Component {
47 | constructor(props) {
48 | super(props);
49 | this.state = {
50 | friends: props.friends
51 | };
52 | }
53 |
54 | handleChange = e => {
55 | let currentList = this.props.friends;
56 | let newList = [];
57 |
58 | if (e.target.value !== '') {
59 | newList = currentList.filter(item => {
60 | const lc = item.name.toLowerCase();
61 |
62 | const filter = e.target.value.toLowerCase();
63 |
64 | return lc.includes(filter);
65 | });
66 | } else {
67 | newList = this.props.friends;
68 | }
69 |
70 | this.setState({
71 | friends: newList
72 | });
73 | };
74 | // Load user's friends once the component is mounted
75 | async componentDidMount() {
76 | const { friends, getUserFriends } = this.props;
77 | if (friends.length === 0) {
78 | try {
79 | await getUserFriends();
80 | this.setState({ friends: this.props.friends });
81 | } catch (error) {
82 | const errorMessage = `Loading friends list failed ${error}`;
83 | console.log(errorMessage);
84 | alert(errorMessage);
85 | }
86 | }
87 | }
88 | render() {
89 | return (
90 |
91 |
92 | Friends
93 |
94 |
95 |
96 |
97 | {this.state.friends.map((item, id) => {
98 | return ;
99 | })}
100 |
101 |
102 |
103 | );
104 | }
105 | }
106 | // connect component to the store
107 | const mapStateToProps = ({ user, apiCallInProgress }) => ({
108 | friends: user.friends,
109 | loading: apiCallInProgress
110 | });
111 | const mapDispatchToProps = {
112 | getUserFriends
113 | };
114 | export default connect(
115 | mapStateToProps,
116 | mapDispatchToProps
117 | )(FriendsList);
118 |
--------------------------------------------------------------------------------
/src/pages/FriendsList/SearchFriends.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import searchIcon from '../../pics/SearchIcon.svg';
3 | import styled from 'styled-components';
4 | import './FriendsList.css';
5 |
6 | const Input = styled.input`
7 | width: 220px;
8 | height: 55px;
9 | padding-left: 80px;
10 | color: #979797;
11 | background: #ffffff;
12 | border: 1px solid #6831de;
13 | border-radius: 30px;
14 | margin-left: -60px;
15 | box-shadow: 0 1px 1px 1px #bebebe;
16 | `;
17 |
18 | class SearchFriends extends React.Component {
19 | render() {
20 | return (
21 |
22 |
23 |
24 |
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 |
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 |
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 |
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 |
21 |
22 | Profile
23 |
24 |
25 |
30 | Message
31 |
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 |
59 | Email
60 |
68 |
69 |
70 | Add Email
71 |
72 |
73 |
74 | {emailList.map((email, index) => (
75 |
80 | ))}
81 |
82 |
83 |
88 | Send Invites
89 |
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 |
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 |
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 |
Go back
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 |
19 |
20 |
21 |
{`${firstName} ${lastName}`}
22 |
23 |
24 |
25 | Message
26 |
27 |
28 |
29 |
Join date:
30 |
{date}
31 |
32 |
33 |
Current location:
34 |
{location}
35 |
36 |
37 | Find me on map
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------