├── .env.temp ├── .gitignore ├── gulpfile.js ├── package.json ├── public ├── assets │ ├── addamigo.png │ ├── home.png │ └── noavatar.jpg ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt └── src ├── App.js ├── Components ├── AddAmigo.css ├── AddAmigo.js ├── EmptyChatRoom.css ├── EmptyChatRoom.js ├── Message.css ├── Message.js ├── ProfilePage.css ├── ProfilePage.js ├── SidebarChat.css └── SidebarChat.js ├── Context ├── AuthActions.js ├── AuthContext.js └── AuthReducer.js ├── Pages ├── Header.js ├── Home.js ├── HomeCSS │ ├── ChatRoom.css │ ├── Home.css │ └── Sidebar.css ├── Signin-up.css └── Signin.js ├── index.css ├── index.html ├── index.js ├── reportWebVitals.js └── setupTests.js /.env.temp: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL = "http://95.217.102.97:5000/" 2 | INLINE_RUNTIME_CHUNK=false 3 | GENERATE_SOURCEMAP=false 4 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const inlinesource = require('gulp-inline-source'); 3 | const replace = require('gulp-replace'); 4 | 5 | gulp.task('default', () => { 6 | return gulp 7 | .src('./build/*.html') 8 | .pipe(replace('.js">', '.js" inline>')) 9 | .pipe(replace('rel="stylesheet">', 'rel="stylesheet" inline>')) 10 | .pipe( 11 | inlinesource({ 12 | compress: false, 13 | ignore: ['png'], 14 | }) 15 | ) 16 | .pipe(gulp.dest('./build')); 17 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.4", 7 | "@material-ui/icons": "^4.11.2", 8 | "@memberstack/dom": "^1.9.9", 9 | "axios": "^0.21.1", 10 | "css-loader": "^6.7.3", 11 | "downloadjs": "^1.4.7", 12 | "emoji-mart": "3.0.1", 13 | "filesize": "^10.0.6", 14 | "moment": "^2.29.4", 15 | "react": "^17.0.2", 16 | "react-contenteditable": "^3.3.6", 17 | "react-dom": "^17.0.2", 18 | "react-icons": "^4.7.1", 19 | "react-loader-spinner": "^5.3.4", 20 | "react-modal": "^3.16.1", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "4.0.3", 23 | "react-scrollbars-custom": "^4.1.1", 24 | "react-transition-group": "^4.4.5", 25 | "socket.io-client": "^4.1.2", 26 | "style-loader": "^3.3.1", 27 | "timeago.js": "^4.0.2", 28 | "web-vitals": "^1.0.1" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts --openssl-legacy-provider start", 32 | "build": "react-scripts --openssl-legacy-provider build", 33 | "build:gulp": "react-scripts --openssl-legacy-provider build && npx gulp", 34 | "build:webpack": "webpack --mode production --config webpack.config.js", 35 | "build:bundle": "webpack --mode production", 36 | "test": "react-scripts test", 37 | "eject": "react-scripts eject" 38 | }, 39 | "eslintConfig": { 40 | "extends": [ 41 | "react-app", 42 | "react-app/jest" 43 | ] 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.20.12", 59 | "@babel/preset-env": "^7.20.2", 60 | "@babel/preset-react": "^7.18.6", 61 | "babel-loader": "^9.1.2", 62 | "path": "^0.12.7" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/assets/addamigo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crypto27dev/WebflowChat_FrontEnd/e1a8cbd54780bf35a270174709a0faec61385149/public/assets/addamigo.png -------------------------------------------------------------------------------- /public/assets/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crypto27dev/WebflowChat_FrontEnd/e1a8cbd54780bf35a270174709a0faec61385149/public/assets/home.png -------------------------------------------------------------------------------- /public/assets/noavatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crypto27dev/WebflowChat_FrontEnd/e1a8cbd54780bf35a270174709a0faec61385149/public/assets/noavatar.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crypto27dev/WebflowChat_FrontEnd/e1a8cbd54780bf35a270174709a0faec61385149/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | Amigo Chat 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import Signin from "./Pages/Signin.js" 2 | import Home from "./Pages/Home.js" 3 | import { useContext } from "react" 4 | import { AuthContext } from "./Context/AuthContext.js" 5 | 6 | function App() { 7 | const { isLoading } = useContext(AuthContext); 8 | return ( 9 |
10 | {isLoading ? ( 11 | 12 | ) : ( 13 | 14 | )} 15 |
16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /src/Components/AddAmigo.css: -------------------------------------------------------------------------------- 1 | .add-amigo-open { 2 | width: 300px; 3 | height: 300px; 4 | z-index: 5000; 5 | border-radius: 15px; 6 | flex-direction: column; 7 | background-color: rgb(245, 241, 237); 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | margin: auto; 14 | -webkit-box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 15 | 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); 16 | box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 17 | 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); 18 | } 19 | 20 | .add-amigo-close { 21 | display: none; 22 | } 23 | 24 | .close-div { 25 | display: flex; 26 | justify-content: flex-end; 27 | margin: 5px; 28 | } 29 | 30 | .close-div span { 31 | height: 20px; 32 | width: 20px; 33 | border-radius: 50%; 34 | cursor: pointer; 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | 40 | .close-symbol { 41 | font-size: 15px; 42 | font-weight: 600; 43 | margin: auto; 44 | margin-bottom: 3px; 45 | } 46 | 47 | .add-amigo-img{ 48 | height: 100px; 49 | margin: 5px 0; 50 | } 51 | 52 | .add-amigo-open form { 53 | display: flex; 54 | flex-direction: column; 55 | align-items: center; 56 | justify-content: space-around; 57 | } 58 | 59 | .add-amigo-open input { 60 | padding: 10px; 61 | font-size: 18px; 62 | outline: none; 63 | border: none; 64 | border-radius: 15px; 65 | display: flex; 66 | align-items: center; 67 | justify-content: center; 68 | background-color: white; 69 | } 70 | 71 | .add-amigo-open button { 72 | padding: 10px; 73 | border-radius: 8px; 74 | outline: none; 75 | border: none; 76 | margin-top: 20px; 77 | color: white; 78 | font-size: 16px; 79 | font-weight: 900; 80 | background-color: #316af3; 81 | cursor: pointer; 82 | } 83 | 84 | .add-amigo-open button:hover { 85 | background-color: white; 86 | color: #316af3; 87 | border: 1px solid #316af3; 88 | transition: all ease 0.5s; 89 | } 90 | -------------------------------------------------------------------------------- /src/Components/AddAmigo.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { useContext, useState } from 'react' 3 | import { AuthContext } from '../Context/AuthContext'; 4 | import './AddAmigo.css' 5 | 6 | function AddAmigo({addchattoggler,addchattoggle}) { 7 | 8 | const [amigousername, setAmigoUsername] = useState() 9 | const { user } = useContext(AuthContext) 10 | 11 | const API_URL = process.env.REACT_APP_API_URL 12 | 13 | const handleSubmit = async (e) => { 14 | e.preventDefault() 15 | try { 16 | const response = await axios.get(`${API_URL}api/users/?username=${amigousername}`) 17 | setAmigoUsername("") 18 | const data = { 19 | senderId: user._id, 20 | receiverId: response.data._id 21 | } 22 | await axios.post(API_URL+'api/chatrooms', data) 23 | } 24 | catch (err) { 25 | } 26 | window.location.reload(); 27 | } 28 | 29 | return ( 30 |
31 |
32 |

x

33 |
34 | 35 | { setAmigoUsername(e.target.value) }} required /> 36 | 37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | export default AddAmigo 44 | -------------------------------------------------------------------------------- /src/Components/EmptyChatRoom.css: -------------------------------------------------------------------------------- 1 | .EmptyChatroom { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | .EmptyChatroom img { 10 | width: 250px; 11 | height: auto; 12 | max-width: 500px; 13 | } 14 | 15 | .empty-chatroom-mainhead { 16 | font-size: 20px; 17 | font-weight: 500; 18 | color: black; 19 | letter-spacing: 0; 20 | } 21 | 22 | .empty-chatroom-subhead { 23 | font-size: 18px; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | text-align: center; 28 | margin-top: 5px; 29 | padding: 25px; 30 | color: rgb(92, 91, 91); 31 | line-height: 35px; 32 | letter-spacing: 0.8px; 33 | } 34 | -------------------------------------------------------------------------------- /src/Components/EmptyChatRoom.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./EmptyChatRoom.css"; 3 | 4 | function EmptyChatRoom() { 5 | const API_URL = process.env.REACT_APP_API_URL 6 | 7 | return ( 8 |
9 | 13 |

Dine meldinger

14 |
15 | ); 16 | } 17 | 18 | export default EmptyChatRoom; 19 | -------------------------------------------------------------------------------- /src/Components/Message.css: -------------------------------------------------------------------------------- 1 | .message-received { 2 | display: flex; 3 | flex-direction: column; 4 | position: relative; 5 | background-color: #ffffff; 6 | font-weight: 500; 7 | width: fit-content; 8 | max-width: 260px; 9 | border-radius: 15px; 10 | padding: 5px 15px 5px 15px; 11 | border-radius: 10px 10px 10px 0px; 12 | margin-bottom: 25px; 13 | font-family: 'Inter', sans-serif; 14 | font-size: 18px; 15 | } 16 | 17 | .message-sent { 18 | display: flex; 19 | flex-direction: column; 20 | position: relative; 21 | margin-left: auto; 22 | width: fit-content; 23 | max-width: 260px; 24 | padding: 5px 15px 5px 15px; 25 | border-radius: 10px 10px 0px 10px; 26 | background-color: #445dfa; 27 | color: white; 28 | font-family: 'Inter', sans-serif; 29 | margin-bottom: 25px; 30 | font-size: 18px; 31 | } 32 | 33 | /* .message-time { 34 | display: flex; 35 | justify-content: flex-end; 36 | font-size: 12px; 37 | margin-top: 3px; 38 | font-weight: 300; 39 | } */ 40 | 41 | .message-box { 42 | display: flex; 43 | flex-direction: row; 44 | padding: 15px 20px; 45 | background-color: #fff; 46 | transition: background-color 200ms ease-in; 47 | } 48 | 49 | .message-box:hover { 50 | background-color: #F8F8F8; 51 | } 52 | 53 | .message-image { 54 | width: 50px; 55 | height: 50px; 56 | border-radius: 50%; 57 | margin-right: 10px; 58 | } 59 | 60 | .message-header { 61 | display: flex; 62 | flex-direction: column; 63 | margin-bottom: 5px; 64 | gap: 4px; 65 | } 66 | 67 | .message-user { 68 | font-size: 16px; 69 | font-weight: 500; 70 | color: #333; 71 | } 72 | 73 | .message-time { 74 | font-size: 14px; 75 | color: #74767E; 76 | } 77 | 78 | .message-text { 79 | white-space: pre-line; 80 | font-size: 16px; 81 | font-weight: 400; 82 | line-height: 1.5; 83 | color: #555; 84 | margin: 0; 85 | } -------------------------------------------------------------------------------- /src/Components/Message.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import moment from "moment"; 3 | import { IconButton } from "@material-ui/core"; 4 | import axios from 'axios'; 5 | import download from 'downloadjs'; 6 | import GetAppIcon from '@material-ui/icons/GetApp'; 7 | import './Message.css' 8 | import { AuthContext } from "../Context/AuthContext"; 9 | 10 | function Message({ message, amigo, own }) { 11 | const { user } = useContext(AuthContext); 12 | const API_URL = process.env.REACT_APP_API_URL 13 | 14 | const checkFile = (text) => { 15 | if (!text) return false; 16 | if (text.startsWith("") && text.endsWith("")) return true; 17 | else return false; 18 | } 19 | 20 | const getFileUrl = (text) => { 21 | if (!text) return ''; 22 | return text.replace("", "").replace("", ""); 23 | } 24 | 25 | const getFileName = (text) => { 26 | if (!text) return ''; 27 | let filename = text.replace("", "").replace("", ""); 28 | return filename.slice(Date.now().toString().length); 29 | } 30 | 31 | const handleDownload = async (filename) => { 32 | const response = await axios.get(`${API_URL}api/messages/download`, { 33 | responseType: 'blob', 34 | params: { 35 | filename 36 | } 37 | }) 38 | const content = response.headers['content-type']; 39 | download(response.data, getFileName(filename), content); 40 | } 41 | 42 | return ( 43 |
44 | 48 | 49 |
50 |
51 | {own ? "Meg" : amigo?.firstname + " " + amigo?.lastname} 52 | {moment(message.createdAt).format("MMM DD YYYY, HH:mm")} 53 |
54 | 55 | {checkFile(message.text) ? 56 |
57 |

{getFileName(message.text)}

58 | handleDownload(getFileUrl(message.text))}> 59 | 60 | 61 |
62 | : 63 |

{message.text}

64 | } 65 |
66 |
67 | ) 68 | } 69 | 70 | export default Message 71 | -------------------------------------------------------------------------------- /src/Components/ProfilePage.css: -------------------------------------------------------------------------------- 1 | .profile-card-close { 2 | display: none; 3 | } 4 | 5 | .profile-card-open { 6 | max-width: 400px; 7 | height: 500px; 8 | display: flex; 9 | flex-direction: column; 10 | padding: 10px 30px; 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | bottom: 0; 16 | margin: auto; 17 | z-index: 5000; 18 | border-radius: 15px; 19 | background-color: rgb(245, 241, 237); 20 | -webkit-box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 21 | 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); 22 | box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 23 | 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12); 24 | } 25 | 26 | .profile-card-open form { 27 | display: flex; 28 | flex-direction: column; 29 | } 30 | 31 | .profile-card-open form label { 32 | padding-top: 15px; 33 | padding-bottom: 5px; 34 | } 35 | 36 | .username-input{ 37 | padding: 10px 8px; 38 | margin: 10px 0; 39 | font-size: 18px; 40 | outline: none; 41 | border: none; 42 | border-radius: 15px; 43 | } 44 | .profile-card-open button { 45 | padding: 10px; 46 | border-radius: 8px; 47 | outline: none; 48 | border: 1px solid #ffffff; 49 | margin-top: 20px; 50 | color: white; 51 | font-size: 16px; 52 | font-weight: 900; 53 | background-color: #12a4ff; 54 | cursor: pointer; 55 | } 56 | 57 | .profile-card-open button:hover { 58 | background-color: #4cb5f7; 59 | transition: all ease 0.5s; 60 | } 61 | 62 | .profile-image { 63 | height: 200px; 64 | width: 200px; 65 | border-radius: 50%; 66 | padding: 2px; 67 | display: flex; 68 | object-fit: cover; 69 | justify-content: center; 70 | align-items: center; 71 | margin-left: auto; 72 | margin-right: auto; 73 | } 74 | 75 | .update-profilepic { 76 | color: transparent; 77 | } 78 | .update-profilepic::-webkit-file-upload-button { 79 | visibility: hidden; 80 | } 81 | 82 | .update-profilepic::before { 83 | content: "Update Profile Pic"; 84 | color: #fff; 85 | display: inline-block; 86 | background: #fa0261; 87 | padding: 10px 22px; 88 | outline: none; 89 | display: flex; 90 | width: fit-content; 91 | align-items: center; 92 | justify-content: center; 93 | margin: 0 auto; 94 | -webkit-user-select: none; 95 | cursor: pointer; 96 | font-weight: 600; 97 | border-radius: 2px; 98 | outline: none; 99 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 100 | 0 3px 1px -2px rgba(0, 0, 0, 0.2); 101 | } 102 | .update-profilepic:focus { 103 | outline: none !important; 104 | } 105 | .update-profilepic:active::before { 106 | transform: scale(0.9) translate(0px, 2px); 107 | box-shadow: inset 4px 4px 5px 0px rgba(0, 0, 0, 0.2); 108 | } 109 | 110 | @media screen and (max-width:768px) { 111 | .profile-card-open{ 112 | max-width: 300px; 113 | } 114 | } 115 | 116 | .user-input { 117 | font-size: 20px; 118 | padding: 10px; 119 | background: white; 120 | border-radius: 10px; 121 | line-height: 1; 122 | } -------------------------------------------------------------------------------- /src/Components/ProfilePage.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import CloseIcon from "@material-ui/icons/Close"; 3 | import "./ProfilePage.css"; 4 | import { AuthContext } from "../Context/AuthContext"; 5 | import axios from "axios"; 6 | 7 | function ProfilePage({ toggler, togglestate }) { 8 | 9 | const { user } = useContext(AuthContext); 10 | const [username, setUsername] = useState(user?.firstname + " " + user?.lastname) 11 | const [photo, setPhoto] = useState("") 12 | const API_URL = process.env.REACT_APP_API_URL 13 | 14 | const handleSubmit = async (e) => { 15 | e.preventDefault() 16 | const config = { 17 | headers: { 18 | "Content-Type": "multipart/form-data", 19 | }, 20 | }; 21 | 22 | const updated_data = new FormData(); 23 | updated_data.append("username", username); 24 | if (photo !== "") { 25 | updated_data.append("photo", photo); 26 | } 27 | 28 | try { 29 | await axios.put(API_URL + 'api/users/', { 30 | params: { 31 | id: user?._id 32 | } 33 | }, updated_data, config) 34 | if (user?._id) { 35 | const result = await axios.get(API_URL + "api/users/", 36 | { 37 | params: { 38 | id: user?._id 39 | } 40 | } 41 | ) 42 | const data = JSON.stringify(result.data) 43 | localStorage.setItem("user", data) 44 | } 45 | } 46 | catch (err) { 47 | console.log(err) 48 | } 49 | window.location.reload() 50 | } 51 | 52 | return ( 53 |
54 |
55 |
56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 |
64 | {user?.firstname} {user?.lastname} 65 |
66 | 67 |
68 | {user?.email} 69 |
70 | 71 | {/* { setUsername(e.target.value) }} required> 72 | { 78 | setPhoto(e.target.files[0]); 79 | }} 80 | /> 81 | 82 | */} 83 |
84 |
85 |
86 | ); 87 | } 88 | 89 | export default ProfilePage; 90 | -------------------------------------------------------------------------------- /src/Components/SidebarChat.css: -------------------------------------------------------------------------------- 1 | .sidebarchat{ 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | justify-content: space-between; 6 | height: 90px; 7 | padding: 10px 15px; 8 | padding-right: 20px; 9 | background-color: none; 10 | border-bottom: 1px solid #EAEAEA; 11 | } 12 | 13 | .sidebarchat-select{ 14 | background: #EAF6FE; 15 | } 16 | 17 | .sidebarchat:hover{ 18 | background-color: #eaf6fe57; 19 | cursor: pointer; 20 | } 21 | 22 | .sidebar-chatoption { 23 | margin-top: 10px; 24 | } 25 | 26 | .online{ 27 | height: 14px; 28 | width: 14px; 29 | border-radius: 50%; 30 | position: absolute; 31 | bottom: 14px; 32 | left: 55px; 33 | background-color: #1DBF73; 34 | border: 3px solid white; 35 | } 36 | 37 | .profile-online { 38 | display: flex; 39 | align-items: center; 40 | gap: 5px; 41 | color: #1DBF73; 42 | } 43 | 44 | .profile-online span { 45 | height: 14px; 46 | width: 14px; 47 | border-radius: 50%; 48 | background-color: #1DBF73; 49 | } 50 | 51 | .offline{ 52 | height: 14px; 53 | width: 14px; 54 | border-radius: 50%; 55 | position: absolute; 56 | bottom: 14px; 57 | left: 55px; 58 | background-color: #B5B6BA; 59 | border: 3px solid white; 60 | } 61 | 62 | .profile-offline { 63 | display: flex; 64 | align-items: center; 65 | gap: 5px; 66 | color: #B5B6BA; 67 | } 68 | 69 | .profile-offline span { 70 | height: 14px; 71 | width: 14px; 72 | border-radius: 50%; 73 | background-color: #B5B6BA; 74 | } 75 | 76 | .sidebarchat-info-name{ 77 | text-overflow: ellipsis; 78 | white-space: nowrap; 79 | overflow: hidden; 80 | font-size: 16px; 81 | font-weight: 600; 82 | margin: 0; 83 | width: 140px; 84 | color: #333; 85 | } 86 | 87 | .sidebarchat-plan { 88 | color: #74767E; 89 | font-size: 14px; 90 | } 91 | 92 | .amigo-profilepic{ 93 | height: 55px; 94 | width: 55px; 95 | border-radius: 50%; 96 | object-fit: cover; 97 | object-position: center; 98 | margin-right: 15px; 99 | } 100 | 101 | .chatroom-profilepic { 102 | height: 60px; 103 | width: 60px; 104 | border-radius: 50%; 105 | object-fit: cover; 106 | object-position: center; 107 | margin-right: 15px; 108 | } 109 | 110 | .badge { 111 | display: flex; 112 | justify-content: center; 113 | align-items: center; 114 | width: 20px; 115 | height: 20px; 116 | background: #1DBF73; 117 | color: white; 118 | border-radius: 50%; 119 | font-size: 15px; 120 | line-height: 1; 121 | } 122 | 123 | .sidebarchat-plans { 124 | text-overflow: ellipsis; 125 | white-space: nowrap; 126 | overflow: hidden; 127 | width: 140px; 128 | display: flex; 129 | flex-direction: column; 130 | } 131 | 132 | .btn-favorite { 133 | background: transparent; 134 | border: none; 135 | padding: 0; 136 | } 137 | 138 | .latest_time { 139 | color: #74767E; 140 | font-size: 13px; 141 | text-overflow: ellipsis; 142 | white-space: nowrap; 143 | overflow: hidden; 144 | line-height: 1.5; 145 | } -------------------------------------------------------------------------------- /src/Components/SidebarChat.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import './SidebarChat.css' 3 | import axios from 'axios' 4 | import { io } from "socket.io-client" 5 | import { FaStar } from "react-icons/fa" 6 | import { FiStar } from "react-icons/fi" 7 | import { format } from "timeago.js" 8 | import { localeFunc } from '../Pages/utils' 9 | 10 | function SidebarChat({ chatroomtile, currentChat, currentUser, arrivalChat }) { 11 | const [user, setUser] = useState(null) 12 | const [isSelected, SetIsSelected] = useState(false); 13 | const [online, setOnline] = useState(false); 14 | const [favorite, setFavorite] = useState(false); 15 | const [latestAt, setLatestAt] = useState(''); 16 | const [unread, setUnread] = useState(0); 17 | const socket = useRef() 18 | const chatRef = useRef() 19 | 20 | const API_URL = process.env.REACT_APP_API_URL 21 | 22 | const getAmigodetails = async (amigoId) => { 23 | try { 24 | if (amigoId) { 25 | const response = await axios.get(API_URL + 'api/users/user/', 26 | { 27 | params: { 28 | id: amigoId 29 | } 30 | } 31 | ) 32 | setUser(response.data) 33 | } 34 | } 35 | catch (err) { 36 | console.log(err) 37 | } 38 | } 39 | const getLatestMessage = async () => { 40 | try { 41 | const response = await axios.get(API_URL + 'api/messages/latest/' + chatroomtile?._id) 42 | const data = response.data; 43 | if (data.length > 0) { 44 | setLatestAt(localeFunc(format(data[0].createdAt))); 45 | } 46 | } catch (err) { 47 | console.log(err); 48 | } 49 | } 50 | 51 | const getUnreadCount = async () => { 52 | try { 53 | const resp = await axios.get(API_URL + 'api/messages/count/', { 54 | params: { 55 | chatroomId: chatroomtile?._id, 56 | userId: currentUser?._id 57 | } 58 | }) 59 | setUnread(resp.data); 60 | if (resp.data > 0) { 61 | document.title = "Upit Chat" + ` (${resp.data})`; 62 | } else { 63 | document.title = "Upit Chat"; 64 | } 65 | } catch (err) { 66 | console.log(err) 67 | } 68 | } 69 | 70 | const handleFavorite = () => { 71 | const storage = localStorage.getItem("favorites"); 72 | let favorites = {} 73 | if (storage) { 74 | favorites = JSON.parse(storage); 75 | } 76 | if (favorites && chatroomtile?._id) { 77 | favorites[chatroomtile?._id] = !favorite; 78 | } 79 | setFavorite(!favorite); 80 | localStorage.setItem("favorites", JSON.stringify(favorites)); 81 | } 82 | 83 | useEffect(() => { 84 | const storage = localStorage.getItem("favorites"); 85 | let favorites = {}; 86 | if (storage) { 87 | favorites = JSON.parse(storage); 88 | } 89 | if (favorites && chatroomtile?._id) { 90 | setFavorite(favorites[chatroomtile?._id]); 91 | } 92 | }, [chatroomtile]) 93 | 94 | useEffect(() => { 95 | const amigoId = chatroomtile.members.find((m) => m !== currentUser._id); 96 | const currentId = currentChat?.members.find((m) => m !== currentUser._id); 97 | if (amigoId === currentId) { 98 | SetIsSelected(true); 99 | } else { 100 | SetIsSelected(false); 101 | } 102 | 103 | chatRef.current = currentChat; 104 | 105 | getAmigodetails(amigoId) 106 | getUnreadCount() 107 | getLatestMessage() 108 | }, [currentUser, currentChat, chatroomtile, online, API_URL]) 109 | 110 | useEffect(() => { 111 | socket.current = io(API_URL); 112 | const amigoId = chatroomtile.members.find((m) => m !== currentUser._id); 113 | socket.current.on("getUsers", (users) => { 114 | setOnline(users.find((user) => user.userId === amigoId)); 115 | }) 116 | }, [API_URL, chatroomtile]) 117 | 118 | useEffect(() => { 119 | if (document.hidden || chatroomtile?._id !== currentChat?._id) { 120 | getUnreadCount(); 121 | } 122 | }, [arrivalChat, currentChat]) 123 | 124 | return ( 125 |
126 |
127 | 128 |
129 |
130 |

{user ? user?.firstname + " " + user?.lastname : ""}

131 | 132 | {user?.plans.length === 1 && ( 133 | 134 | {user.plans[0].planName} 135 | 136 | )} 137 | {user?.plans.length > 1 && ( 138 | <> 139 | 140 | {user?.plans[0].planName} 141 | 142 | {/* 143 | +{user?.plans.length - 1} more 144 | */} 145 | 146 | )} 147 | 148 |
149 |
150 |
151 |
152 | {latestAt ? latestAt.replace(' ago', '') : 'Akkurat nå'} 153 |
154 |
155 | {unread > 0 && {unread}} 156 | 159 |
160 |
161 |
162 | ) 163 | } 164 | 165 | export default SidebarChat -------------------------------------------------------------------------------- /src/Context/AuthActions.js: -------------------------------------------------------------------------------- 1 | export const LoginStart = (userCredentials) =>({ 2 | type:"LOGIN_START" 3 | }); 4 | 5 | export const LoginSuccess = (user) =>({ 6 | type:"LOGIN_SUCCESS", 7 | payload: user 8 | }) 9 | 10 | export const LoginFailure = (error) =>({ 11 | type:"LOGIN_FAILURE", 12 | payload: error 13 | }) -------------------------------------------------------------------------------- /src/Context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useEffect, useReducer } from "react" 2 | import AuthReducer from "./AuthReducer" 3 | 4 | const INITIAL_STATE = { 5 | user: null, 6 | isFetching:false, 7 | error:false 8 | } 9 | 10 | /* Reads the data from the Provider and changes INITIAL_STATE */ 11 | export const AuthContext = createContext(INITIAL_STATE) 12 | 13 | /* Children here are the Components that need to get the data.[In this Application we specified App COmponent as Child in index.js so that we can server every every component exist in the app */ 14 | /* This will provide data to all the children that we are giving here */ 15 | export const AuthContextProvider = ({children}) =>{ 16 | const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE); 17 | const [isLoading, setLoading] = useState(true); 18 | const [currentMem, setCurrentMem] = useState(''); 19 | 20 | useEffect(()=>{ 21 | if (typeof state.user !== "string") { 22 | localStorage.setItem("user", JSON.stringify(state.user)) 23 | } 24 | },[state.user]) 25 | 26 | return ( 27 | 39 | {children} 40 | 41 | ) 42 | } -------------------------------------------------------------------------------- /src/Context/AuthReducer.js: -------------------------------------------------------------------------------- 1 | const AuthReducer = (state, action) => { 2 | switch (action.type) { 3 | case "LOGIN_START": 4 | return { 5 | user: action.payload, 6 | isLoading: true, 7 | error: false 8 | }; 9 | case "LOGIN_SUCCESS": 10 | return { 11 | user: action.payload, 12 | isLoading: false, 13 | error: false 14 | }; 15 | case "LOGIN_FAILURE": 16 | return { 17 | user: null, 18 | isLoading: false, 19 | error: action.payload 20 | }; 21 | 22 | default: 23 | return state 24 | } 25 | } 26 | 27 | export default AuthReducer; -------------------------------------------------------------------------------- /src/Pages/Header.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Crypto27dev/WebflowChat_FrontEnd/e1a8cbd54780bf35a270174709a0faec61385149/src/Pages/Header.js -------------------------------------------------------------------------------- /src/Pages/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef, useState } from "react"; 2 | import { Scrollbar } from "react-scrollbars-custom"; 3 | import "./HomeCSS/Home.css"; 4 | import "./HomeCSS/Sidebar.css"; 5 | import "./HomeCSS/ChatRoom.css"; 6 | import Message from "../Components/Message.js"; 7 | import AddAmigo from "../Components/AddAmigo.js"; 8 | import ProfilePage from "../Components/ProfilePage"; 9 | import SidebarChat from "../Components/SidebarChat.js"; 10 | import EmptyChatRoom from "../Components/EmptyChatRoom"; 11 | import { AuthContext } from "../Context/AuthContext"; 12 | 13 | import axios from "axios"; 14 | import { io } from "socket.io-client"; 15 | import "emoji-mart/css/emoji-mart.css"; 16 | import { Picker } from "emoji-mart"; 17 | import TextareaAutosize from "@material-ui/core/TextareaAutosize"; 18 | import moment from "moment"; 19 | import Modal from 'react-modal'; 20 | import { filesize } from 'filesize'; 21 | 22 | import { IoSend } from "react-icons/io5"; 23 | import { AiFillSafetyCertificate } from "react-icons/ai"; 24 | import MenuIcon from "@material-ui/icons/Menu"; 25 | import CloseIcon from "@material-ui/icons/Close"; 26 | import SearchIcon from "@material-ui/icons/Search"; 27 | import { Button, IconButton } from "@material-ui/core"; 28 | import ExitToAppIcon from "@material-ui/icons/ExitToApp"; 29 | import PersonAddIcon from "@material-ui/icons/PersonAdd"; 30 | import AttachFileIcon from "@material-ui/icons/AttachFile"; 31 | import InsertEmoticonIcon from "@material-ui/icons/InsertEmoticon"; 32 | import { format } from "timeago.js" 33 | import { localeFunc } from '../Pages/utils' 34 | 35 | const MAX_FILE_SIZE = 10_485_760; 36 | 37 | function Home() { 38 | const [chatroomtiles, setChatroomtiles] = useState([]); 39 | const [currentchat, setCurrentchat] = useState(null); 40 | const [arrivalChat, setArrivalChat] = useState(null); 41 | const [messages, setMessages] = useState([]); 42 | const [newMessage, setNewMessage] = useState(""); 43 | const [search, setSearch] = useState(""); 44 | const [online, setOnline] = useState(false); 45 | const [arrivalMessage, setArrivalMessage] = useState(null); 46 | const [amigo, setAmigo] = useState(); 47 | const [amigoDetail, setAmigoDetail] = useState(); 48 | const [open, setOpen] = useState(false); 49 | const [file, setFile] = useState(null); 50 | const [fileModal, setFileModal] = useState(false); 51 | const { user, currentMem } = useContext(AuthContext); 52 | const API_KEY = process.env.REACT_APP_MEMBERSTACK_KEY; 53 | const BASE_URL = 'https://admin.memberstack.com/members'; 54 | const headers = { "X-API-KEY": API_KEY }; 55 | const roomRef = useRef(); 56 | const socket = useRef(); 57 | const chatRef = useRef(); 58 | 59 | const API_URL = process.env.REACT_APP_API_URL 60 | 61 | function sendNotification(message, avatar, firstname) { 62 | if (document.hidden) { 63 | const notification = new Notification("New message from Upit Chat", { 64 | icon: avatar, 65 | body: `@${firstname}: ${message}` 66 | }) 67 | } 68 | } 69 | 70 | function checkPageStatus(message, avatar, firstname) { 71 | if (!("Notification" in window)) { 72 | alert("This browser does not support system notifications!") 73 | } else if (Notification.permission === "granted") { 74 | sendNotification(message, avatar, firstname) 75 | } else if (Notification.permission !== "denied") { 76 | Notification.requestPermission((permission) => { 77 | if (permission === "granted") { 78 | sendNotification(message, avatar, firstname) 79 | } 80 | }) 81 | } 82 | } 83 | 84 | const setRead = async (roomId, userId) => { 85 | if (roomId) { 86 | await axios.put(API_URL + 'api/messages/', { 87 | roomId, 88 | userId 89 | }) 90 | } 91 | } 92 | 93 | const getUnreadTotalCount = async () => { 94 | try { 95 | const resp = await axios.get(API_URL + 'api/messages/total/', { 96 | params: { 97 | userId: user?._id 98 | } 99 | }) 100 | if (resp?.data.length > 0) { 101 | document.title = "Upit Chat" + ` (${resp.data[0].unread})`; 102 | } else { 103 | document.title = "Upit Chat"; 104 | } 105 | } catch (err) { 106 | console.log(err) 107 | } 108 | } 109 | 110 | useEffect(() => { 111 | socket.current = io(API_URL); 112 | socket.current.on("getMessage", (data) => { 113 | setArrivalMessage({ 114 | sender: data.senderId, 115 | text: data.text, 116 | createdAt: Date.now(), 117 | }); 118 | setArrivalChat(!arrivalChat); 119 | checkPageStatus(data.text, data.avatar, data.firstname); 120 | if (!document.hidden && data.chatroomId === chatRef.current?._id) { 121 | setRead(data.chatroomId, user?._id); 122 | } 123 | if (document.hidden) { 124 | getUnreadTotalCount(); 125 | } 126 | }); 127 | }, [API_URL]); 128 | 129 | useEffect(() => { 130 | arrivalMessage && 131 | currentchat?.members.includes(arrivalMessage.sender) && 132 | setMessages((prev) => [...prev, arrivalMessage]); 133 | }, [arrivalMessage, currentchat]); 134 | 135 | useEffect(() => { 136 | socket.current.emit("addUser", user?._id); 137 | }, [user, chatroomtiles, currentchat, socket]); 138 | 139 | /* Fetching the Chat Tiles */ 140 | useEffect(() => { 141 | const getChatroomtiles = async (searchText) => { 142 | try { 143 | let data = null; 144 | if (currentMem) { 145 | const response = await axios.get(`${API_URL}api/users/?member=${currentMem}`) 146 | data = { 147 | senderId: user._id, 148 | receiverId: response.data._id 149 | } 150 | await axios.post(API_URL + 'api/chatrooms', data) 151 | } 152 | 153 | const res = await axios.get(API_URL + "api/chatrooms", { 154 | params: { 155 | user_id: user._id 156 | } 157 | }); 158 | setChatroomtiles(res.data); 159 | 160 | if (data) { 161 | const resp = await axios.post(API_URL + 'api/chatrooms/get', data); 162 | chatRef.current = resp.data[0]; 163 | setCurrentchat(resp.data[0]); 164 | } 165 | } catch (err) { 166 | console.log(err); 167 | } 168 | }; 169 | (async () => { 170 | await getChatroomtiles(); 171 | })(); 172 | }, [user?._id, currentMem, API_URL]); 173 | 174 | /* Fetching the Chat Tile user details */ 175 | useEffect(() => { 176 | const amigoId = currentchat?.members.find((m) => m !== user._id); 177 | socket.current.on("getUsers", (users) => { 178 | setOnline(users.find((user) => user.userId === amigoId)); 179 | }) 180 | const getUserInfo = async (mem_id) => { 181 | try { 182 | const resp = await axios.get(`${BASE_URL}/${mem_id}`, { headers }); 183 | return resp.data.data; 184 | } catch (err) { 185 | console.log(err); 186 | } 187 | return null; 188 | } 189 | const getAmigodetails = async () => { 190 | try { 191 | if (amigoId) { 192 | const response = await axios.get(API_URL + "api/users/user", 193 | { 194 | params: { 195 | id: amigoId 196 | } 197 | } 198 | ); 199 | const amigoData = response.data; 200 | setAmigo(amigoData); 201 | const response2 = await getUserInfo(amigoData.mem_id); 202 | setAmigoDetail(response2); 203 | } 204 | } catch (err) { } 205 | }; 206 | if (currentchat) { 207 | getAmigodetails(); 208 | } 209 | }, [user, currentchat, API_URL]); 210 | 211 | /* Fetching ChatRoom Messages */ 212 | useEffect(() => { 213 | const getMessages = async () => { 214 | try { 215 | if (user && currentchat) { 216 | setRead(currentchat?._id, user?._id); 217 | } 218 | const response = await axios.get(API_URL + "api/messages/" + currentchat?._id); 219 | setMessages(response.data); 220 | } catch (err) { 221 | console.log(err); 222 | } 223 | }; 224 | if (currentchat) { 225 | getMessages(); 226 | } 227 | }, [currentchat, API_URL]); 228 | 229 | /* Scroll to the recent message */ 230 | useEffect(() => { 231 | scrollItThere(); 232 | }, [messages]); 233 | 234 | const scrollItThere = () => { 235 | roomRef.current?.scroll({ 236 | top: roomRef.current?.scrollHeight, 237 | behavior: 'smooth' 238 | }); 239 | } 240 | 241 | /* Emoji Picker */ 242 | const addEmoji = (e) => { 243 | let emoji = e.native; 244 | setNewMessage(newMessage + emoji); 245 | }; 246 | const [pick, setPick] = useState(false); 247 | const openPicker = () => { 248 | setPick(!pick); 249 | }; 250 | 251 | /* Posting a Message */ 252 | 253 | const handleMessageKey = (e) => { 254 | if (e.keyCode == 13 && !e.shiftKey) { 255 | e.preventDefault(); 256 | handleSubmit(e); 257 | return false; 258 | } 259 | } 260 | 261 | const handleMessage = (e) => { 262 | setNewMessage(e.target.value); 263 | } 264 | 265 | const handleSubmit = async (e, fileName = "") => { 266 | e?.preventDefault(); 267 | let textMessage = newMessage || fileName; 268 | if (!textMessage) return; 269 | const sendingMessage = { 270 | chatroomId: currentchat._id, 271 | senderId: user._id, 272 | text: textMessage, 273 | }; 274 | 275 | const receiverId = currentchat.members.find( 276 | (member) => member !== user._id 277 | ); 278 | socket.current.emit("sendMessage", { 279 | senderId: user._id, 280 | receiverId, 281 | chatroomId: currentchat?._id, 282 | firstname: user?.firstname, 283 | avatar: user?.avatar ? user.avatar : API_URL + "api/images/noavatar.png", 284 | text: textMessage, 285 | }); 286 | try { 287 | if (user && currentchat) { 288 | setRead(currentchat?._id, user?._id); 289 | setArrivalChat(!arrivalChat); 290 | } 291 | const response = await axios.post(API_URL + "api/messages/", sendingMessage); 292 | setMessages([...messages, response.data]); 293 | setNewMessage(""); 294 | } catch (err) { 295 | console.log(err); 296 | } 297 | setPick(false) 298 | }; 299 | 300 | const handleFileUpload = async (e) => { 301 | const onefile = e.target.files[0]; 302 | if (onefile?.size < MAX_FILE_SIZE) { 303 | setFile(onefile); 304 | setFileModal(true); 305 | } else { 306 | window.alert(`Max file size: 10 MB\nCurrent file size: ${filesize(onefile?.size, { base: 2, standard: "jedec" })}`) 307 | } 308 | } 309 | 310 | const handleFileSend = async (e) => { 311 | const config = { 312 | headers: { 313 | "Content-Type": "multipart/form-data", 314 | }, 315 | }; 316 | const sendData = new FormData(); 317 | sendData.append("chatroomId", currentchat._id); 318 | sendData.append("senderId", user._id); 319 | sendData.append("fileData", file); 320 | sendData.append("fileName", file.name); 321 | 322 | try { 323 | axios.post(API_URL + 'api/messages/upload', sendData, config, { 324 | onUploadProgress: (event) => { 325 | const progress = Math.round((event.loaded / event.total) * 100); 326 | console.log(`Progress: ${progress}%`); 327 | } 328 | }).then(resp => { 329 | setNewMessage("") 330 | handleSubmit(e, resp.data.text); 331 | setFileModal(false); 332 | }) 333 | } 334 | catch (err) { 335 | console.log(err) 336 | } 337 | } 338 | 339 | /* Logout */ 340 | const logout = () => { 341 | localStorage.removeItem("user"); 342 | window.location.href = "/"; 343 | }; 344 | 345 | /* AddChat Toggle Setup */ 346 | 347 | const [addtoggle, setAddtoggle] = useState(false); 348 | const addchatToggler = () => { 349 | addtoggle === false ? setAddtoggle(true) : setAddtoggle(false); 350 | }; 351 | 352 | /* Profile Page Toggle Setup */ 353 | const [profiletoggle, setProfiletoggle] = useState(false); 354 | const profiletoggler = () => { 355 | profiletoggle === false ? setProfiletoggle(true) : setProfiletoggle(false); 356 | }; 357 | 358 | const handleCloseModal = () => { 359 | setFileModal(false); 360 | setFile(""); 361 | } 362 | 363 | return ( 364 |
365 | {/* Chat Adding Card */} 366 | { addchatToggler(); }} addchattoggle={addtoggle} /> 367 | 368 | {/* Profile Page Card - Update */} 369 | { profiletoggler(); }} togglestate={profiletoggle} /> 370 | 371 | {/* Sidebar Open Menu */} 372 | {open 373 | ? "" 374 | :
{ setOpen(true); }} > 375 | 376 | 377 | 378 |
379 | } 380 | 381 | {/* Add Chat Icon */} 382 | {/*
383 | 384 | 385 | 386 |
*/} 387 | 388 | {/* Sidebar, ChatRoom */} 389 |
390 | 391 | {/* Sidebar */} 392 |
393 |
394 |
395 |
396 |
{ setOpen(false); }} > 397 | 398 | 399 | 400 |
401 |
402 |
403 | { profiletoggler(); }} > 404 | 405 | 406 | {user?.firstname + " " + user?.lastname} 407 |
408 | 409 | 410 | 411 |
412 |
413 |
414 |
415 | 416 | setSearch(e.target.value)} /> 417 |
418 |
419 | 420 | 421 | 422 |
423 |
424 |
425 | 426 | {/* Chatroom tiles */} 427 | 428 | 429 |
430 | {chatroomtiles.filter(opt => { 431 | if (search) { 432 | for (let i = 0; i < opt.usernames.length; i++) { 433 | const item = opt.usernames[i]; 434 | const fullname = user.firstname + " " + user.lastname; 435 | if (item !== fullname && item.toLowerCase().includes(search.toLowerCase())) { 436 | return true; 437 | } 438 | } 439 | return false; 440 | } else { 441 | return true; 442 | } 443 | }).map((chatroomtile, index) => ( 444 |
{ 447 | chatRef.current = chatroomtile; 448 | setCurrentchat(chatroomtile); 449 | setOpen(false); 450 | }} 451 | > 452 | 453 |
454 | ))} 455 |
456 |
457 |
458 | 459 | {/* Chatroom */} 460 |
461 | {currentchat ? ( 462 | <> 463 |
464 |
465 | 466 |
467 | 470 | 471 | {amigo?.plans.map((plan, index) => ( 472 | 473 | {plan.planName}{index < amigo?.plans.length - 1 ?  og  : ''} 474 | 475 | ))} 476 | 477 |
478 | Sist sett: {amigo && amigo.logAt ? localeFunc(format(amigo.logAt)) : ""} 479 | | 480 | Lokal tid: {amigo && amigo.logAt ? moment(amigo.logAt).format("MMM DD, YYYY, HH:mm") : ""} 481 |
482 |
483 |
484 |
485 | {/*
486 | 487 | 488 |
*/} 489 |
490 |
491 |
492 |
493 |
{ setPick(false) }}> 494 |
495 |
496 |
497 |
498 | 499 | 500 | Du er i trygge hender 501 | 502 |
503 |
504 |
505 | 506 | For økt sikkerhet, hold betalinger og kommunikasjon innenfor Upit. Lær mer 507 | 508 |
509 | {messages.map((message, index) => ( 510 |
511 | 512 |
513 | ))} 514 |
515 |
516 |
517 | 518 | 519 | 520 | 527 | 532 |
533 |
534 | 544 | 545 | 546 |
547 | 551 |
552 |
553 |
554 |
555 |
556 | 557 |

{online ? "Aktiv" : "Inaktiv"}

558 |
559 | Om   560 | {amigo ? amigo?.firstname + " " + amigo?.lastname : ""} 561 | 562 |
563 |
564 | Lokasjon 565 | {amigoDetail?.customFields?.lokasjon} 566 |
567 |
568 | Spesialist 569 | {amigoDetail?.customFields?.jobbkategori} 570 |
571 |
572 | Telefon 573 | {amigoDetail?.customFields?.telefonnummer} 574 |
575 |
576 | Medlem siden 577 | {moment(amigoDetail?.createdAt).format("MMM YYYY")} 578 |
579 |
580 |
581 |
582 |
583 | 584 |
585 | 586 | ) : ( 587 | 588 | )} 589 |
590 |
591 | {fileModal && ( 592 | 599 |

Send File

600 |
601 | file 602 |
603 | {file?.name} 604 | {filesize(file?.size, { base: 2, standard: "jedec" })} 605 |
606 |
607 | Max file size: 10 MB 608 | 611 |
612 | )} 613 |
614 | ); 615 | } 616 | 617 | export default Home; 618 | -------------------------------------------------------------------------------- /src/Pages/HomeCSS/ChatRoom.css: -------------------------------------------------------------------------------- 1 | .chatroom { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | position: relative; 6 | } 7 | 8 | .chatroom-header { 9 | display: flex; 10 | align-items: end; 11 | justify-content: space-between; 12 | padding: 20px; 13 | border-bottom: 1px solid #eaeaea; 14 | border-top-right-radius: 10px; 15 | z-index: 80; 16 | } 17 | 18 | .chatroom-chatinfo-right { 19 | display: flex; 20 | flex-direction: column; 21 | gap: 3px; 22 | } 23 | 24 | .chatroom-chatinfo { 25 | display: flex; 26 | align-items: center; 27 | } 28 | 29 | .chatroom-chatinfo-name { 30 | font-size: 18px; 31 | font-weight: 500; 32 | color: #333; 33 | } 34 | 35 | .chatroom-chatinfo-name p { 36 | margin: 0; 37 | } 38 | 39 | .chatroom-options { 40 | display: flex; 41 | align-items: center; 42 | } 43 | 44 | .chatroom-container { 45 | display: flex; 46 | flex-direction: row; 47 | height: calc(100% - 99px); 48 | } 49 | 50 | .chatroom-messages-container { 51 | flex: 1; 52 | padding: 20px 0; 53 | overflow-y: scroll; 54 | overflow-x: hidden; 55 | } 56 | 57 | /* Scrollbar */ 58 | 59 | .chatroom-messages-container::-webkit-scrollbar-track { 60 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 61 | border-radius: 10px; 62 | background-color: #f5f5f5; 63 | } 64 | 65 | .chatroom-messages-container::-webkit-scrollbar { 66 | width: 8px; 67 | background-color: #f5f5f5; 68 | } 69 | 70 | .chatroom-messages-container::-webkit-scrollbar-thumb { 71 | border-radius: 10px; 72 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 73 | background-color: rgb(0 0 0 / 20%); 74 | } 75 | 76 | .chatroom-profile { 77 | padding: 20px; 78 | width: 300px; 79 | min-width: 300px; 80 | display: flex; 81 | flex-direction: column; 82 | } 83 | 84 | .profile-photo { 85 | aspect-ratio: 1; 86 | border-radius: 15px; 87 | object-fit: cover; 88 | } 89 | 90 | .emoji-picker-open { 91 | position: absolute; 92 | bottom: 8.5%; 93 | z-index: 1000; 94 | } 95 | 96 | .emoji-picker-close { 97 | display: none; 98 | } 99 | 100 | .chatroom-footer { 101 | display: flex; 102 | align-items: center; 103 | gap: 10px; 104 | border-top: 0.1px solid rgb(212, 212, 212); 105 | padding: 10px 15px; 106 | height: fit-content; 107 | box-shadow: 0px -2px 15px rgba(0, 0, 0, 0.07); 108 | padding-top: 20px; 109 | padding-bottom: 20px; 110 | border-top: 0.1px solid #d4d4d478; 111 | } 112 | 113 | .chatroom-plans { 114 | display: flex; 115 | flex-direction: row; 116 | } 117 | 118 | .chatroom-plan { 119 | color: #333; 120 | font-size: 14px; 121 | } 122 | 123 | .chatroom-top-header { 124 | display: flex; 125 | flex-direction: row; 126 | gap: 5px; 127 | color: #74767e; 128 | font-size: 12px; 129 | } 130 | 131 | .chatroom-footer form { 132 | display: flex; 133 | flex-direction: row; 134 | align-items: center; 135 | justify-content: center; 136 | height: fit-content; 137 | width: 100%; 138 | } 139 | 140 | .chatroom-footer form button { 141 | display: none; 142 | } 143 | 144 | .chatroom-safety { 145 | display: flex; 146 | flex-direction: column; 147 | align-items: center; 148 | padding: 10px 20px; 149 | gap: 10px; 150 | } 151 | 152 | .chatroom-safety-header { 153 | display: flex; 154 | align-items: center; 155 | color: #74767e; 156 | font-weight: 500; 157 | font-size: 16px; 158 | gap: 5px; 159 | } 160 | 161 | .chatroom-safety-text { 162 | font-size: 12px; 163 | color: #74767e; 164 | text-align: center; 165 | } 166 | 167 | .message-input { 168 | font-family: "Inter", sans-serif; 169 | padding: 12px 20px; 170 | height: 15px; 171 | outline: none; 172 | border: none; 173 | border-radius: 25px; 174 | font-size: 17px; 175 | background-color: rgb(0 0 0 / 4%); 176 | resize: none; 177 | color: #333; 178 | word-wrap: break-word; 179 | word-break: break-all; 180 | width: 100%; 181 | } 182 | 183 | .message-input::-webkit-scrollbar-track { 184 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 185 | border-radius: 10px; 186 | background-color: #f5f5f5; 187 | } 188 | 189 | .message-input::-webkit-scrollbar { 190 | width: 8px; 191 | background-color: #f5f5f5; 192 | } 193 | 194 | .message-input::-webkit-scrollbar-thumb { 195 | border-radius: 10px; 196 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 197 | background-color: rgb(0 0 0 / 20%); 198 | } 199 | 200 | .input-button { 201 | display: none; 202 | } 203 | 204 | .profile-description { 205 | margin-top: 10px; 206 | display: flex; 207 | flex-direction: column; 208 | } 209 | 210 | .desc-title { 211 | margin-top: 20px; 212 | font-size: 16px; 213 | font-weight: 600; 214 | color: #333; 215 | } 216 | 217 | .desc-text { 218 | font-size: 16px; 219 | padding: 15px 0; 220 | border-bottom: 1px solid #EAEAEA; 221 | } 222 | 223 | .desc-text-value { 224 | font-weight: 600; 225 | color: #333; 226 | text-overflow: ellipsis; 227 | white-space: nowrap; 228 | overflow: hidden; 229 | } 230 | 231 | .desc-profile-name { 232 | color: #333; 233 | } 234 | 235 | .chatroom-search-container { 236 | display: flex; 237 | align-items: center; 238 | border-radius: 15px; 239 | border: 1px solid #EAEAEA; 240 | background: transparent; 241 | width: 100%; 242 | } 243 | 244 | .chatroom-searchicon { 245 | margin-left: 15px; 246 | color: #333; 247 | } 248 | 249 | .chatroom-search-container input { 250 | border: none; 251 | outline: none; 252 | height: 30px; 253 | border-radius: 15px; 254 | padding: 5px 15px; 255 | font-size: 16px; 256 | display: flex; 257 | width: 100%; 258 | margin-right: 25px; 259 | background-color: transparent; 260 | } 261 | 262 | .chatroom-footer-lefticons { 263 | display: flex; 264 | gap: 10px; 265 | } 266 | .icon-picker { 267 | display: block; 268 | } 269 | 270 | .send-icon { 271 | color: #316af3; 272 | } 273 | 274 | @media screen and (max-width:1200px) { 275 | .chatroom-profile { 276 | display: none; 277 | } 278 | .chatroom-search { 279 | display: none; 280 | } 281 | } 282 | 283 | @media screen and (max-width: 768px) { 284 | .chatroom-footer { 285 | padding: 10px 5px; 286 | } 287 | .chatroom-search { 288 | display: block; 289 | } 290 | .chatroom-footer-lefticons { 291 | display: flex; 292 | } 293 | .icon-picker { 294 | display: none !important; 295 | } 296 | .chatroom { 297 | flex: 1; 298 | } 299 | .chatroom-top-header span:nth-child(2), .chatroom-top-header span:nth-child(3) { 300 | display: none; 301 | } 302 | .chatroom-footer-righticon button{ 303 | width: 48px; 304 | height: 48px; 305 | min-width: 48px; 306 | padding: 0 !important; 307 | } 308 | .chatroom-footer-righticon .send-text { 309 | display: none; 310 | } 311 | .chatroom-chatinfo { 312 | margin-left: 30px; 313 | } 314 | } 315 | 316 | @media screen and (max-width: 576px) { 317 | .chatroom-search { 318 | display: none; 319 | } 320 | } -------------------------------------------------------------------------------- /src/Pages/HomeCSS/Home.css: -------------------------------------------------------------------------------- 1 | .home { 2 | display: grid; 3 | place-items: center; 4 | height: 100vh; 5 | background-color: #0e1012; 6 | position: relative; 7 | padding: 0 50px; 8 | } 9 | 10 | .home-components { 11 | display: flex; 12 | height: 95vh; 13 | width: 100%; 14 | margin: auto; 15 | background-color: #fff; 16 | border-radius: 10px; 17 | -webkit-box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 18 | 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); 19 | box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 20 | 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); 21 | } 22 | 23 | .menu-open{ 24 | position: absolute; 25 | z-index: 1000; 26 | top: 26px; 27 | left: -1px; 28 | border-radius: 50%; 29 | } 30 | 31 | .menu-close{ 32 | z-index: 1000; 33 | border-radius: 50%; 34 | } 35 | 36 | .add-chatroom-icon { 37 | position: absolute; 38 | top: 6%; 39 | left: 85%; 40 | z-index: 2000; 41 | } 42 | 43 | @media screen and (min-width: 768px) { 44 | .menu-close { 45 | display: none; 46 | } 47 | .menu-open { 48 | display: none; 49 | } 50 | } 51 | 52 | @media screen and (max-width: 768px) { 53 | .home { 54 | overflow: hidden; 55 | padding: 0; 56 | } 57 | .home-components { 58 | height: 100vh; 59 | width: 100vw; 60 | border-radius: 0; 61 | } 62 | .add-chatroom-icon { 63 | position: absolute; 64 | top: 1%; 65 | left: 85%; 66 | } 67 | } 68 | 69 | .btn-send { 70 | background: #12A4FF !important; 71 | border-radius: 30px !important; 72 | padding: 12px 20px !important; 73 | color: white !important; 74 | font-size: 16px; 75 | } 76 | 77 | .btn-send-file { 78 | background: #fff !important; 79 | border-radius: 30px !important; 80 | padding: 12px 20px !important; 81 | color: white !important; 82 | font-size: 16px; 83 | color: #000 !important; 84 | } 85 | 86 | .sidebar-members { 87 | width: 100%; 88 | height: 100%; 89 | } 90 | 91 | .ScrollbarsCustom-Wrapper { 92 | inset: 0 !important; 93 | } 94 | 95 | .ScrollbarsCustom-Track { 96 | width: 8px !important; 97 | } 98 | 99 | .ScrollbarsCustom-Thumb { 100 | background: rgb(0 0 0 / 20%) !important; 101 | } 102 | 103 | .file-upload { 104 | display: none; 105 | } 106 | 107 | .ReactModal__Overlay { 108 | opacity: 0; 109 | transform: translateY(-100px); 110 | transition: all 100ms ease-in-out; 111 | background-color: rgb(255 255 255 / 0%) !important; 112 | z-index: 99; 113 | } 114 | 115 | .ReactModal__Overlay--after-open { 116 | opacity: 1; 117 | transform: translateY(0px); 118 | } 119 | 120 | .ReactModal__Overlay--before-close { 121 | opacity: 0; 122 | transform: translateY(-100px); 123 | } 124 | 125 | .ReactModal__Content { 126 | background: #12a4ff !important; 127 | border: none !important; 128 | border-radius: 10px !important; 129 | max-width: 400px; 130 | width: 100%; 131 | height: fit-content; 132 | top: 0; 133 | bottom: 0; 134 | left: 0; 135 | right: 0; 136 | margin: auto; 137 | display: flex; 138 | flex-direction: column; 139 | gap: 10px; 140 | } 141 | 142 | .modal-title { 143 | color: white; 144 | } 145 | 146 | .modal-file-img { 147 | width: 40px; 148 | height: 40px; 149 | background: white; 150 | border-radius: 50%; 151 | padding: 10px; 152 | } 153 | 154 | .modal-file-name { 155 | font-size: 20px; 156 | color: white; 157 | width: 300px; 158 | overflow: hidden; 159 | text-overflow:ellipsis; 160 | white-space: nowrap; 161 | } 162 | 163 | .modal-file-size { 164 | font-size: 16px; 165 | color: white; 166 | } 167 | 168 | .modal-max-file { 169 | color: white; 170 | } -------------------------------------------------------------------------------- /src/Pages/HomeCSS/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | display: flex; 3 | flex-direction: column; 4 | border-right: 1px solid #EAEAEA; 5 | width: 350px; 6 | min-width: 350px; 7 | } 8 | 9 | .sidebar-top-header { 10 | border-bottom: 1px solid #EAEAEA; 11 | } 12 | 13 | .sidebar-mobile-header { 14 | display: none; 15 | } 16 | 17 | .sidebar-header { 18 | display: flex; 19 | justify-content: space-between; 20 | padding: 10px 15px; 21 | align-items: center; 22 | position: relative; 23 | } 24 | 25 | .sidebar-search { 26 | background-color: #fff; 27 | display: flex; 28 | align-items: center; 29 | justify-content: flex-start; 30 | height: 50px; 31 | padding: 10px; 32 | margin-bottom: 10px; 33 | } 34 | 35 | .sidebar-searchicon { 36 | margin-left: 15px; 37 | } 38 | 39 | .sidebar-search-container { 40 | display: flex; 41 | align-items: center; 42 | border-radius: 15px; 43 | background-color: #f5f5f5; 44 | width: 100%; 45 | } 46 | 47 | .sidebar-search-container input { 48 | border: none; 49 | outline: none; 50 | height: 40px; 51 | border-radius: 15px; 52 | padding: 5px 15px; 53 | font-size: 16px; 54 | display: flex; 55 | width: 100%; 56 | margin-right: 25px; 57 | background-color: #f5f5f5; 58 | } 59 | 60 | .user-profile { 61 | width: 50px !important; 62 | } 63 | 64 | .user-profile .MuiIconButton-label { 65 | width: 50px !important; 66 | } 67 | 68 | .user-profile-image { 69 | height: 50px; 70 | width: 50px; 71 | object-fit: cover; 72 | border-radius: 50%; 73 | position: absolute; 74 | } 75 | 76 | .logout-mobile-option { 77 | display: none; 78 | } 79 | 80 | .sidebar-mobile-profile-name { 81 | display: none; 82 | } 83 | 84 | @media screen and (max-width: 768px) { 85 | .sidebar { 86 | display: none; 87 | } 88 | .sidebar-header { 89 | justify-content: start; 90 | } 91 | .sidebar-mobile-profile-name { 92 | display: block; 93 | font-size: 18px; 94 | font-weight: 400; 95 | margin-left: 10px; 96 | } 97 | .sidebar-mobile-header { 98 | display: flex; 99 | justify-content: space-between; 100 | padding-left: 15px; 101 | padding-right: 6px; 102 | } 103 | .sidebar.active { 104 | position: relative; 105 | width: auto; 106 | height: 100vh; 107 | width: 100vw; 108 | display: flex; 109 | position: fixed; 110 | left: 0; 111 | opacity: 1; 112 | transition: all 0.5s ease; 113 | background-color: #fff; 114 | z-index: 100; 115 | } 116 | .user-profile-image { 117 | position: absolute; 118 | } 119 | .logout-option { 120 | display: none; 121 | } 122 | .logout-mobile-option { 123 | display: block; 124 | margin-left: 10px; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Pages/Signin-up.css: -------------------------------------------------------------------------------- 1 | .signin-container,.signup-container { 2 | height: 100vh; 3 | width: 100vw; 4 | background-color: #316af3; 5 | } 6 | 7 | a { 8 | text-decoration: none; 9 | } 10 | 11 | .signin-card, .signup-card{ 12 | position: absolute; 13 | left: 50%; 14 | top: 50%; 15 | transform: translate(-50%, -50%); 16 | font-family: sans-serif; 17 | width: 100%; 18 | max-width: 350px; 19 | margin-left: auto; 20 | margin-right: auto; 21 | margin-top: 38px; 22 | margin-bottom: 38px; 23 | border-radius: 10px !important; 24 | background-color: #ffff; 25 | box-shadow: 2px 5px 20px rgba(0, 0, 0, 0.1); 26 | } 27 | 28 | form { 29 | /* padding: 30px; */ 30 | } 31 | 32 | .signin-title, .signup-title{ 33 | text-align: center; 34 | font-weight: bold; 35 | margin: 0; 36 | } 37 | 38 | .signup-option, .signup-option { 39 | background-color: rgb(69, 69, 185, 0.2); 40 | border-bottom-left-radius: 10px; 41 | border-bottom-right-radius: 10px; 42 | } 43 | 44 | .line { 45 | text-align: center; 46 | font-weight: bold; 47 | border-bottom: 2px solid rgb(245 239 239); 48 | line-height: 2px; 49 | margin: 25px 0; 50 | } 51 | 52 | .error-message{ 53 | color: red; 54 | font-weight: 600; 55 | } 56 | 57 | .signin-fields, .signup-fields{ 58 | display: flex; 59 | flex-direction: column; 60 | } 61 | 62 | .signin-fields label, .signup-fields label { 63 | color: rgb(170 166 166); 64 | text-align: left; 65 | margin-top: 15px; 66 | } 67 | 68 | .signin-textbox, .signup-textbox{ 69 | padding: 15px 20px; 70 | margin-top: 5px; 71 | margin-bottom: 5px; 72 | border: 1px solid #ccc; 73 | border-radius: 8px; 74 | box-sizing: border-box; 75 | outline: none; 76 | font-size: 20px; 77 | } 78 | 79 | .signin-button,.signup-button { 80 | background-color: rgb(69, 69, 185); 81 | color: white; 82 | padding: 18px 20px; 83 | margin-top: 25px; 84 | margin-left: auto !important; 85 | margin-left: auto !important; 86 | width: 100%; 87 | border-radius: 10px; 88 | border: none; 89 | display: flex; 90 | align-items: center; 91 | justify-content: center; 92 | cursor: pointer; 93 | font-size: 18px; 94 | font-weight: 600; 95 | } 96 | 97 | .forget-pass { 98 | text-align: center; 99 | display: block; 100 | } 101 | 102 | .signup-question { 103 | text-align: center; 104 | font-weight: bold; 105 | margin-top: 15px !important; 106 | padding: 15px; 107 | } 108 | 109 | 110 | /*input file*/ 111 | .file-input { 112 | color: transparent; 113 | margin-bottom:0!important; 114 | } 115 | .file-input::-webkit-file-upload-button { 116 | visibility: hidden; 117 | } 118 | 119 | .file-input::before { 120 | content: "Upload Profile Pic"; 121 | color: #fff; 122 | display: inline-block; 123 | background: #fa0261; 124 | padding: 10px 22px; 125 | outline: none; 126 | width: fit-content; 127 | margin: 10px auto 10px auto; 128 | -webkit-user-select: none; 129 | cursor: pointer; 130 | font-weight: 600; 131 | border-radius: 2px; 132 | outline: none; 133 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 134 | 0 3px 1px -2px rgba(0, 0, 0, 0.2); 135 | } 136 | .file-input:focus { 137 | outline: none !important; 138 | } 139 | 140 | .file-input:active::before { 141 | transform: scale(0.9) translate(0px, 2px); 142 | box-shadow: inset 4px 4px 5px 0px rgba(0, 0, 0, 0.2); 143 | } 144 | 145 | .btn { 146 | width: 120px; 147 | font-size: 16px; 148 | font-weight: 600; 149 | padding: 10px 20px; 150 | border-radius: 5px; 151 | border: none; 152 | } 153 | 154 | .signin-container { 155 | display: flex; 156 | justify-content: center; 157 | align-items: center; 158 | overflow: hidden; 159 | } 160 | 161 | .signin-content { 162 | 163 | } 164 | 165 | .btn-group { 166 | display: flex; 167 | gap: 20px; 168 | } 169 | 170 | .signin-content h1 { 171 | color: white; 172 | font-size: 40px; 173 | margin-bottom: 20px; 174 | text-align: center; 175 | } 176 | 177 | .signin-content h3 { 178 | color: white; 179 | margin-top: 20px; 180 | text-align: center; 181 | } 182 | 183 | footer { 184 | position:absolute; 185 | bottom: 0; 186 | margin-bottom: 10px; 187 | display: flex; 188 | gap: 10px; 189 | } 190 | 191 | footer span { 192 | color: white; 193 | } -------------------------------------------------------------------------------- /src/Pages/Signin.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from 'react' 2 | import './Signin-up.css' 3 | import axios from 'axios' 4 | import { Comment } from 'react-loader-spinner'; 5 | import { AuthContext } from '../Context/AuthContext' 6 | 7 | function Signin() { 8 | const { dispatch, setLoading, setCurrentMem } = useContext(AuthContext); 9 | const API_URL = process.env.REACT_APP_API_URL; 10 | const API_KEY = process.env.REACT_APP_MEMBERSTACK_KEY; 11 | const BASE_URL = 'https://admin.memberstack.com/members'; 12 | const headers = { "X-API-KEY": API_KEY }; 13 | 14 | useEffect(() => { 15 | (() => { 16 | try { 17 | // const token = window.location.pathname.replace('/', ''); 18 | const token = window.location.search.replace('?', ''); 19 | const param = JSON.parse(atob(token)); 20 | loginCall(param, dispatch); 21 | } catch (err) { 22 | console.log(err); 23 | } 24 | })() 25 | document.title = "Upit Chat"; 26 | }, []); 27 | 28 | const getUserInfo = async (mem_id) => { 29 | try { 30 | const resp = await axios.get(`${BASE_URL}/${mem_id}`, { headers }); 31 | return resp.data.data; 32 | } catch (err) { 33 | console.log(err); 34 | } 35 | return null; 36 | } 37 | 38 | const registerCall = async (member_id) => { 39 | try { 40 | const profile = await getUserInfo(member_id); 41 | const res = await axios.post(API_URL + "api/auth/signin", { 42 | firstname: profile.customFields.fornavn, 43 | lastname: profile.customFields.etternavn, 44 | email: profile.auth.email, 45 | mem_id: profile.id, 46 | avatar: profile.customFields.profilbilde, 47 | plans: profile.planConnections 48 | }); 49 | } 50 | catch (err) { 51 | console.log("Register Err:", err) 52 | } 53 | } 54 | 55 | const loginCall = async (param, dispatch) => { 56 | dispatch({ type: "LOGIN_START" }); 57 | try { 58 | const userInfo = { 59 | email: param.email, 60 | mem_id: param.mem_id 61 | } 62 | 63 | const profile = await getUserInfo(userInfo.mem_id); 64 | const res = await axios.post(API_URL + "api/auth/signin", { 65 | firstname: profile.customFields.fornavn, 66 | lastname: profile.customFields.etternavn, 67 | email: profile.auth.email, 68 | mem_id: profile.id, 69 | avatar: profile.customFields.profilbilde, 70 | plans: profile.planConnections 71 | }); 72 | 73 | if (param?.to) { 74 | await registerCall(param.to); 75 | } 76 | await login(res.data._id); 77 | 78 | dispatch({ type: "LOGIN_SUCCESS", payload: res.data }); 79 | setLoading(false); 80 | setCurrentMem(param.to); 81 | } 82 | catch (err) { 83 | console.log("Login Err:", err) 84 | dispatch({ type: "LOGIN_FAILURE", payload: err }) 85 | } 86 | } 87 | 88 | const login = async (id) => { 89 | try { 90 | await axios.put(API_URL + "api/auth/" + id); 91 | } 92 | catch (err) { 93 | console.log(err) 94 | } 95 | } 96 | 97 | return ( 98 |
99 |
100 | 110 |
111 |
112 | ) 113 | } 114 | 115 | export default Signin -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800&display=swap"); 2 | 3 | * { 4 | margin: 0; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | font-family: "Inter", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", 10 | "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | } 14 | 15 | code { 16 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 17 | monospace; 18 | } 19 | 20 | .flex { 21 | display: flex; 22 | } 23 | 24 | .flex-row { 25 | flex-direction: row; 26 | } 27 | 28 | .flex-col { 29 | flex-direction: column; 30 | } 31 | 32 | .justify-center { 33 | justify-content: center; 34 | } 35 | 36 | .justify-between { 37 | justify-content: space-between; 38 | } 39 | 40 | .justify-end { 41 | justify-content: flex-end; 42 | } 43 | 44 | .justify-evenly { 45 | justify-content: space-evenly; 46 | } 47 | 48 | .align-items-center { 49 | align-items: center; 50 | } 51 | 52 | .flex-row-between { 53 | display: flex; 54 | justify-content: space-between; 55 | } 56 | 57 | .gap-5 { 58 | gap: 5px; 59 | } 60 | 61 | .gap-10 { 62 | gap: 10px; 63 | } 64 | 65 | .gap-15 { 66 | gap: 15px; 67 | } 68 | 69 | .gap-20 { 70 | gap: 20px; 71 | } 72 | 73 | .fw-400 { 74 | font-weight: 400; 75 | } 76 | 77 | .fw-500 { 78 | font-weight: 500; 79 | } 80 | 81 | .fw-600 { 82 | font-weight: 600; 83 | } 84 | 85 | .gray-primary { 86 | color: #74767e; 87 | } 88 | 89 | .black-primary { 90 | color: #333; 91 | } 92 | 93 | .fs-14 { 94 | font-size: 14px; 95 | } 96 | 97 | .fs-15 { 98 | font-size: 15px; 99 | } 100 | 101 | .fs-16 { 102 | font-size: 16px; 103 | } 104 | 105 | .pl-5 { 106 | padding-left: 5px; 107 | } 108 | 109 | .ml-5 { 110 | margin-left: 5px; 111 | } 112 | 113 | .MuiIconButton-root { 114 | background: rgb(0 0 0 / 4%); 115 | } 116 | 117 | .w-100 { 118 | width: 100%; 119 | } 120 | 121 | .h-100 { 122 | height: 100%; 123 | } 124 | 125 | .safety-line { 126 | flex: 1; 127 | height: 1px; 128 | background: #eaeaea; 129 | } 130 | 131 | .MuiButton-label { 132 | font-size: 15px; 133 | font-family: "Inter"; 134 | font-weight: 700; 135 | line-height: 1; 136 | } 137 | 138 | .text-underline { 139 | text-decoration-line: underline; 140 | } 141 | 142 | .m-0 { 143 | margin: 0 !important; 144 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import { AuthContextProvider } from "./Context/AuthContext" 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | --------------------------------------------------------------------------------