├── .DS_Store
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── client
├── .gitignore
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.js
│ ├── AuthRoute.js
│ ├── ProtectedRoute.js
│ ├── api
│ └── api.js
│ ├── assets
│ ├── RTMP.png
│ └── ohmystream.png
│ ├── components
│ ├── Authentication
│ │ ├── FbAuth.css
│ │ ├── FbAuth.js
│ │ ├── TwitchAuth.css
│ │ └── TwitchAuth.js
│ ├── Avatars
│ │ ├── BroadcastAvatar.css
│ │ ├── BroadcastAvatar.js
│ │ ├── DisabledBroadcastAvatar.css
│ │ └── DisabledBroadcastAvatar.js
│ ├── Buttons
│ │ ├── BroadcastButton.css
│ │ ├── BroadcastButton.js
│ │ ├── Button.css
│ │ ├── Button.js
│ │ ├── DestinationButton.css
│ │ ├── DestinationButton.js
│ │ ├── ReferralButton.css
│ │ ├── ReferralButton.js
│ │ ├── StudioButton.css
│ │ └── StudioButton.js
│ ├── Canvas
│ │ └── Canvas.js
│ ├── Card
│ │ ├── Card.css
│ │ └── Card.js
│ ├── ChatMessage
│ │ ├── ChatMessage.css
│ │ └── ChatMessage.js
│ ├── Messages
│ │ ├── NoDestinationsMessage.css
│ │ └── NoDestinationsMessage.js
│ ├── Navbar
│ │ ├── Navbar.css
│ │ ├── Navbar.js
│ │ ├── SideNavbar.css
│ │ ├── SideNavbar.js
│ │ ├── SidebarData.js
│ │ ├── TopNavbar.js
│ │ └── TopNavbar.module.css
│ ├── Selected
│ │ ├── Selected.css
│ │ └── Selected.js
│ ├── TextArea
│ │ ├── TextArea.css
│ │ └── TextArea.js
│ ├── TextInput
│ │ ├── TextInput.css
│ │ └── TextInput.js
│ ├── Timer
│ │ ├── Timer.css
│ │ └── Timer.js
│ ├── TrialExpired
│ │ ├── TrialExpired.css
│ │ └── TrialExpired.js
│ └── ViewCounter
│ │ ├── ViewCounter.css
│ │ └── ViewCounter.js
│ ├── constants
│ └── constants.js
│ ├── containers
│ ├── Billing
│ │ ├── Billing.css
│ │ ├── Billing.js
│ │ ├── PricingPlan.css
│ │ └── PricingPlan.js
│ ├── Broadcast
│ │ ├── Broadcast.css
│ │ └── Broadcast.js
│ ├── Code
│ │ ├── Code.css
│ │ └── Code.js
│ ├── Destinations
│ │ ├── Destinations.css
│ │ └── Destinations.js
│ ├── Discount
│ │ ├── Discount.css
│ │ └── Discount.js
│ ├── Login
│ │ ├── Login.css
│ │ └── Login.js
│ ├── PageNotFound
│ │ ├── PageNotFound.css
│ │ └── PageNotFound.js
│ ├── Referral
│ │ ├── Referral.css
│ │ └── Referral.js
│ ├── Register
│ │ ├── Register.css
│ │ └── Register.js
│ ├── Settings
│ │ ├── Settings.css
│ │ └── Settings.js
│ └── Studio
│ │ ├── Studio.css
│ │ └── Studio.js
│ ├── index.js
│ ├── styles
│ └── styles.js
│ ├── utils
│ ├── accurateTimer.js
│ ├── deleteAllCookies.js
│ ├── deleteCookie.js
│ ├── eventTrack.js
│ ├── formatTime.js
│ ├── getCookie.js
│ ├── getUrlParams.js
│ ├── hubspotEmail.js
│ ├── setCookie.js
│ ├── shareOnFacebook.js
│ ├── shareOnTwitter.js
│ ├── timeFromUserRegistration.js
│ ├── toastSuccessMessage.js
│ ├── twitchDestinationUtils.js
│ └── useInterval.js
│ ├── variables.css
│ └── website
│ ├── Buttons
│ ├── PriceButton.css
│ └── PriceButton.js
│ ├── Collapsible
│ ├── Accordion.css
│ ├── Accordion.js
│ └── Panel.js
│ ├── Footer
│ ├── Footer.css
│ └── Footer.js
│ ├── Input
│ ├── Input.css
│ └── Input.js
│ ├── PricingPlan
│ ├── PricingPlan.css
│ └── PricingPlan.js
│ ├── Privacy
│ ├── Privacy.css
│ └── Privacy.js
│ ├── Spinner
│ ├── Spinner.js
│ └── Spinner.module.css
│ ├── Terms
│ ├── Terms.css
│ └── Terms.js
│ └── Website
│ ├── Website.css
│ └── Website.js
├── env.example
├── env.sh
├── server
├── db.js
├── ffmpeg.js
├── ffmpeg2.js
├── ffmpeg_h.txt
├── package.json
├── routes
│ ├── authentication.js
│ ├── broadcasts.js
│ ├── compareCode.js
│ ├── destinations.js
│ ├── emailEventNotifications.js
│ ├── facebookAuthorization.js
│ ├── facebookBroadcast.js
│ ├── facebookViewCount.js
│ ├── hubspot.js
│ ├── referral.js
│ ├── stripe.js
│ ├── twitchAuthorization.js
│ ├── twitchBroadcast.js
│ ├── twitchViewCount.js
│ ├── youtubeAuthorization.js
│ ├── youtubeBroadcast.js
│ └── youtubeViewCount.js
├── schema.sql
├── server.js
└── utils
│ ├── bindYoutubeBroadcastToStream.js
│ ├── createYoutubeBroadcast.js
│ ├── createYoutubeStream.js
│ ├── emailCodeTemplate.js
│ ├── refreshTwitchToken.js
│ ├── removeDbFacebookValues.js
│ ├── sendAuthCode.js
│ ├── updateDbFacebookValues.js
│ ├── updateDbTwitchValues.js
│ ├── updateDbYoutubeValues.js
│ ├── updateYoutubeAccessToken.js
│ └── validateEmail.js
├── startup.sh
└── update.sh
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/.DS_Store
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "terminal.foreground": "#E9E9F4",
4 | "terminalCursor.background": "#E9E9F4",
5 | "terminalCursor.foreground": "#E9E9F4",
6 | "terminal.ansiBlack": "#282936",
7 | "terminal.ansiBlue": "#62D6E8",
8 | "terminal.ansiBrightBlack": "#626483",
9 | "terminal.ansiBrightBlue": "#62D6E8",
10 | "terminal.ansiBrightCyan": "#A1EFE4",
11 | "terminal.ansiBrightGreen": "#EBFF87",
12 | "terminal.ansiBrightMagenta": "#B45BCF",
13 | "terminal.ansiBrightRed": "#EA51B2",
14 | "terminal.ansiBrightWhite": "#F7F7FB",
15 | "terminal.ansiBrightYellow": "#00F769",
16 | "terminal.ansiCyan": "#A1EFE4",
17 | "terminal.ansiGreen": "#EBFF87",
18 | "terminal.ansiMagenta": "#B45BCF",
19 | "terminal.ansiRed": "#EA51B2",
20 | "terminal.ansiWhite": "#E9E9F4",
21 | "terminal.ansiYellow": "#00F769"
22 | },
23 | "[javascript]": {
24 | "editor.defaultFormatter": "esbenp.prettier-vscode",
25 | "editor.formatOnSave": true,
26 | "editor.wordWrap": "on"
27 | },
28 | "prettier.semi": false,
29 | "prettier.singleQuote": true
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 ohmystream
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Livestreaming (ohmystream.co)
2 |
3 | NOTE: I am no longer actively maintaining this project. The production postgres DB is no longer running. If you want a simple example of how to use FFmpeg to restream check out [this repo](https://github.com/toshvelaga/twitch-streamer).
4 |
5 |
6 |
7 |
8 | Ohmystream demo - Watch Video
9 |
10 |
11 |
12 | Check out the app [HERE](https://ohmystream.co/)
13 |
14 | Web based software to record a livestream in the browser and stream simultaneously to Youtube, Facebook, and Twitch. Software is similar in functionality to OBS and streamlabs.
15 |
16 | Frontend is built using React JS. Backend is build using Node + Express. Database is postgres. The livestreaming is enabled using websockets and ffmpeg.
17 |
18 | On the client the code that sends the livestream to the server is in the client/containers/Broadcast/Broadcast.js file. On the server the websockets are enabled in server/server.js file.
19 |
20 | ## Todo
21 |
22 | - [Trello](https://trello.com/b/W8LZ83oV/ohmystream)
23 |
24 | ## Tech/framework used
25 |
26 | Frontend tech stack:
27 |
28 | - React JS
29 | - CSS
30 | - React Router
31 | - React Icons
32 | - [react-hot-toast](https://github.com/timolins/react-hot-toast)
33 |
34 | Backend tech stack:
35 |
36 | - Node JS
37 | - Express
38 | - [node-media-server](https://github.com/illuspas/Node-Media-Server)
39 | - [ffmpeg](http://ffmpeg.org/)
40 | - [ws](https://github.com/websockets/ws)
41 | - [nodemailer](https://nodemailer.com/about/)
42 |
43 | ## Installation
44 |
45 | To run this project, install it locally using npm:
46 |
47 | ```
48 | $ cd client
49 | $ npm install
50 | $ npm start
51 | ```
52 |
53 | For the server create a .env file with the variables from env.example
54 |
55 | ```
56 | $ cd server
57 | $ touch .env
58 | $ npm install
59 | $ node server.js
60 | ```
61 |
62 | ## Contribute
63 |
64 | Feel free to use code however you would like.
65 |
66 | Feel free to also reach out to me on twitter at @t0xsh
67 |
68 | ## License
69 |
70 | MIT
71 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .env
16 | .vscode
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 | package-lock.json
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | #gitignore
29 | .gitignore
30 |
31 | #src
32 | /src/fonts
33 | /src/assets
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "livestreaming",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.9",
7 | "@testing-library/react": "^11.2.5",
8 | "@testing-library/user-event": "^12.7.1",
9 | "axios": "^0.21.1",
10 | "react": "^17.0.1",
11 | "react-dom": "^17.0.1",
12 | "react-ga": "^3.3.0",
13 | "react-ga4": "^1.0.4",
14 | "react-helmet": "^6.1.0",
15 | "react-hot-toast": "^2.1.1",
16 | "react-icons": "^4.3.1",
17 | "react-modal": "^3.14.3",
18 | "react-router": "^5.2.0",
19 | "react-router-dom": "^5.2.0",
20 | "react-scripts": "^5.0.0",
21 | "react-select": "^5.1.0",
22 | "react-tooltip": "^4.2.21",
23 | "socket.io-client": "^4.4.1",
24 | "web-vitals": "^1.1.0"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
16 |
21 |
24 |
25 |
29 |
30 |
31 |
46 |
47 |
48 |
52 |
61 |
62 |
63 |
67 |
68 |
69 |
73 |
74 |
83 | ohmystream
84 |
85 |
86 |
87 |
88 |
98 |
99 |
100 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/client/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 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, Suspense, lazy } from 'react'
2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
3 | import ReactGA from 'react-ga'
4 | import Login from './containers/Login/Login'
5 | import Register from './containers/Register/Register'
6 | import Code from './containers/Code/Code'
7 | import getCookie from './utils/getCookie'
8 | import PageNotFound from './containers/PageNotFound/PageNotFound'
9 | import Broadcast from './containers/Broadcast/Broadcast'
10 | import ProtectedRoute from './ProtectedRoute'
11 | import AuthRoute from './AuthRoute'
12 | import Destinations from './containers/Destinations/Destinations'
13 | import Referral from './containers/Referral/Referral'
14 | import Studio from './containers/Studio/Studio'
15 | import Settings from './containers/Settings/Settings'
16 | import Billing from './containers/Billing/Billing'
17 | import Spinner from './website/Spinner/Spinner'
18 | import Terms from './website/Terms/Terms'
19 | import Privacy from './website/Privacy/Privacy'
20 | import Discount from './containers/Discount/Discount'
21 | import './variables.css'
22 | const Website = lazy(() => import('./website/Website/Website'))
23 |
24 | function App() {
25 | const [isLoggedIn, setisLoggedIn] = useState('')
26 |
27 | useEffect(() => {
28 | let login = getCookie('isLoggedIn')
29 | setisLoggedIn(login)
30 | }, [isLoggedIn])
31 |
32 | useEffect(() => {
33 | ReactGA.initialize(process.env.REACT_APP_GOOGLE_ANALYTICS_ID)
34 | ReactGA.pageview(window.location.pathname + window.location.search)
35 | ReactGA.set({
36 | username: getCookie('userEmail'),
37 | // Other relevant user information
38 | })
39 | }, [])
40 |
41 | return (
42 | <>
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {/* LANDING PAGE ROUTES */}
60 |
61 |
62 | }>
63 |
64 |
65 |
66 |
67 |
68 | >
69 | )
70 | }
71 |
72 | export default App
73 |
--------------------------------------------------------------------------------
/client/src/AuthRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Redirect } from 'react-router-dom'
3 | import getCookie from './utils/getCookie'
4 |
5 | function AuthRoute({ component: Component, ...rest }) {
6 | const isLoggedIn = getCookie('isLoggedIn')
7 | return (
8 | {
11 | if (!isLoggedIn) {
12 | return
13 | } else {
14 | return (
15 |
18 | )
19 | }
20 | }}
21 | />
22 | )
23 | }
24 |
25 | export default AuthRoute
26 |
--------------------------------------------------------------------------------
/client/src/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Redirect } from 'react-router-dom'
3 | import getCookie from './utils/getCookie'
4 |
5 | const ProtectedRoute = ({ component: Component, ...rest }) => {
6 | const isLoggedIn = getCookie('isLoggedIn')
7 | return (
8 | {
11 | if (isLoggedIn) {
12 | return
13 | } else {
14 | return (
15 |
18 | )
19 | }
20 | }}
21 | />
22 | )
23 | }
24 |
25 | export default ProtectedRoute
26 |
--------------------------------------------------------------------------------
/client/src/api/api.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | const baseURL =
4 | process.env.NODE_ENV === 'production'
5 | ? 'https://www.ohmystream.xyz/api'
6 | : 'http://localhost:5001/api'
7 |
8 | const API = axios.create({
9 | baseURL,
10 | })
11 |
12 | export default API
13 |
--------------------------------------------------------------------------------
/client/src/assets/RTMP.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/src/assets/RTMP.png
--------------------------------------------------------------------------------
/client/src/assets/ohmystream.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/src/assets/ohmystream.png
--------------------------------------------------------------------------------
/client/src/components/Authentication/FbAuth.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/src/components/Authentication/FbAuth.css
--------------------------------------------------------------------------------
/client/src/components/Authentication/FbAuth.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import toastSuccessMessage from '../../utils/toastSuccessMessage'
3 | import DisabledBroadcastAvatar from '../../components/Avatars/DisabledBroadcastAvatar'
4 | import ReactTooltip from 'react-tooltip'
5 | import API from '../../api/api'
6 | import getCookie from '../../utils/getCookie'
7 | import * as FaIcons from 'react-icons/fa'
8 |
9 | /* global FB */
10 |
11 | const FbAuth = () => {
12 | let userId = getCookie('userId')
13 |
14 | const facebookAuth = () => {
15 | FB.getLoginStatus(function (response) {
16 | console.log(response)
17 | })
18 | FB.login(
19 | function (response) {
20 | console.log(response)
21 | console.log('FB access token:' + response.authResponse.accessToken)
22 | let facebookAccessToken = response.authResponse.accessToken
23 | let facebookUserId = response.authResponse.userID
24 |
25 | saveFacebookDataToDB(userId, facebookAccessToken, facebookUserId)
26 | facebookAuthBooleanDB()
27 | toastSuccessMessage('Facebook added as destination')
28 | },
29 | {
30 | scope: 'email, publish_video, public_profile',
31 | auth_type: 'rerequest',
32 | }
33 | )
34 | }
35 |
36 | const saveFacebookDataToDB = (
37 | userId,
38 | facebookAccessToken,
39 | facebookUserId
40 | ) => {
41 | API.post('/authorize/facebook', {
42 | userId,
43 | facebookAccessToken,
44 | facebookUserId,
45 | })
46 | }
47 |
48 | const facebookAuthBooleanDB = () => {
49 | let data = { facebookAuthBool: true, userId }
50 | API.put('/user/destinations', data)
51 | }
52 | return (
53 |
54 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default FbAuth
65 |
--------------------------------------------------------------------------------
/client/src/components/Authentication/TwitchAuth.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/src/components/Authentication/TwitchAuth.css
--------------------------------------------------------------------------------
/client/src/components/Authentication/TwitchAuth.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import {
3 | TWITCH_SCOPE,
4 | TWITCH_REDIRECT_URL_BROADCAST,
5 | } from '../../constants/constants'
6 | import {
7 | twitchAuthBooleanDB,
8 | sendCodeToTwitch,
9 | validateTwitchRequest,
10 | saveTwitchDataToDB,
11 | getTwitchStreamKey,
12 | } from '../../utils/twitchDestinationUtils'
13 | import getUrlParams from '../../utils/getUrlParams'
14 | import { useHistory } from 'react-router-dom'
15 | import getCookie from '../../utils/getCookie'
16 | import toastSuccessMessage from '../../utils/toastSuccessMessage'
17 | import DisabledBroadcastAvatar from '../../components/Avatars/DisabledBroadcastAvatar'
18 | import ReactTooltip from 'react-tooltip'
19 | import * as FaIcons from 'react-icons/fa'
20 |
21 | const TwitchAuth = () => {
22 | const twitchURL = `https://id.twitch.tv/oauth2/authorize?client_id=${process.env.REACT_APP_TWITCH_CLIENT_ID}&redirect_uri=${TWITCH_REDIRECT_URL_BROADCAST}&response_type=code&scope=${TWITCH_SCOPE}&force_verify=true`
23 |
24 | return (
25 | (window.location.href = twitchURL)}>
26 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default TwitchAuth
37 |
--------------------------------------------------------------------------------
/client/src/components/Avatars/BroadcastAvatar.css:
--------------------------------------------------------------------------------
1 | .broadcast-avatar {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 50%;
5 | border: 1px solid #ddd;
6 | margin-right: 1rem;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | box-sizing: border-box;
11 | }
12 |
13 | .broadcast-avatar:hover {
14 | cursor: pointer;
15 | background-color: #f8f8f8;
16 | /* border: 2px solid #03a9f4; */
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/components/Avatars/BroadcastAvatar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './BroadcastAvatar.css'
3 |
4 | const BroadcastAvatar = (props) => {
5 | return (
6 |
11 | {props.children}
12 |
13 | )
14 | }
15 |
16 | export default BroadcastAvatar
17 |
--------------------------------------------------------------------------------
/client/src/components/Avatars/DisabledBroadcastAvatar.css:
--------------------------------------------------------------------------------
1 | .disabled-broadcast-avatar {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 50%;
5 | border: 1px solid #ddd;
6 | margin-right: 1rem;
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | box-sizing: border-box;
11 | background-color: #ddd;
12 | }
13 |
--------------------------------------------------------------------------------
/client/src/components/Avatars/DisabledBroadcastAvatar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './DisabledBroadcastAvatar.css'
3 |
4 | const DisabledBroadcastAvatar = (props) => {
5 | return (
6 |
11 | {props.children}
12 |
13 | )
14 | }
15 |
16 | export default DisabledBroadcastAvatar
17 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/BroadcastButton.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-font: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
3 | 'Helvetica Neue', Ubuntu, Arial, sans-serif;
4 | }
5 |
6 | button > * {
7 | vertical-align: middle;
8 | }
9 |
10 | .broadcast-button {
11 | /* background-color: #03a9f4; */
12 | background-color: #dddddd;
13 | color: black;
14 | letter-spacing: 2px;
15 | outline: none;
16 | border: none;
17 | padding: 12px 20px;
18 | font-size: 14px;
19 | border-radius: 5px;
20 | text-transform: uppercase;
21 | font-family: var(--main-font);
22 | margin-right: 10px;
23 | vertical-align: middle;
24 | }
25 |
26 | .broadcast-button:hover {
27 | background-color: #0298db;
28 | color: white;
29 | cursor: pointer;
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/BroadcastButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './BroadcastButton.css'
3 |
4 | function BroadcastButton(props) {
5 | return (
6 | <>
7 |
17 | >
18 | )
19 | }
20 |
21 | export default BroadcastButton
22 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/Button.css:
--------------------------------------------------------------------------------
1 | .button {
2 | background-color: #03a9f4;
3 | /* background-color: #3cc764; */
4 | color: white;
5 | /* letter-spacing: 2px; */
6 | outline: none;
7 | border: none;
8 | padding: 16px 80px;
9 | font-size: 16px;
10 | border-radius: 5px;
11 | /* text-transform: uppercase; */
12 | display: flex;
13 | justify-content: center;
14 | font-family: var(--main-font);
15 | }
16 |
17 | @media only screen and (max-width: 600px) {
18 | .button {
19 | font-size: 16px;
20 | padding: 15px 50px;
21 | }
22 | }
23 |
24 | .button:hover {
25 | background-color: #0298db;
26 | color: white;
27 | cursor: pointer;
28 | }
29 |
30 | .loader {
31 | border: 2px solid #fff;
32 | border-radius: 50%;
33 | border-top: 2px solid transparent;
34 | /* border-bottom: 16px solid white; */
35 | width: 1rem;
36 | height: 1rem;
37 | -webkit-animation: spin 2s linear infinite;
38 | animation: spin 2s linear infinite;
39 | margin-right: 0.5rem;
40 | }
41 |
42 | @-webkit-keyframes spin {
43 | 0% {
44 | -webkit-transform: rotate(0deg);
45 | }
46 | 100% {
47 | -webkit-transform: rotate(360deg);
48 | }
49 | }
50 |
51 | @keyframes spin {
52 | 0% {
53 | transform: rotate(0deg);
54 | }
55 | 100% {
56 | transform: rotate(360deg);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Button.css'
3 |
4 | function Button(props) {
5 | return (
6 | <>
7 |
17 | >
18 | )
19 | }
20 |
21 | export default Button
22 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/DestinationButton.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-font: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
3 | 'Helvetica Neue', Ubuntu, Arial, sans-serif;
4 | }
5 |
6 | button > * {
7 | vertical-align: middle;
8 | }
9 |
10 | .destination-button {
11 | /* background-color: #dddddd; */
12 | background-color: var(--card-background-color);
13 | color: black;
14 | letter-spacing: 2px;
15 | outline: none;
16 | border: none;
17 | padding: 12px 16px;
18 | font-size: 14px;
19 | border-radius: 5px;
20 | text-transform: uppercase;
21 | font-family: var(--main-font);
22 | margin-right: 8px;
23 | vertical-align: middle;
24 | }
25 |
26 | .destination-button:hover {
27 | background-color: #dddddd;
28 | color: white;
29 | cursor: pointer;
30 | }
31 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/DestinationButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './DestinationButton.css'
3 |
4 | function DestinationButton(props) {
5 | return (
6 | <>
7 |
17 | >
18 | )
19 | }
20 |
21 | export default DestinationButton
22 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/ReferralButton.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-font: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
3 | 'Helvetica Neue', Ubuntu, Arial, sans-serif;
4 | }
5 |
6 | .referral-button {
7 | background-color: transparent;
8 | /* background-color: #f1f1f1; */
9 | border: 1px solid #ccc;
10 | color: grey;
11 | letter-spacing: 2px;
12 | padding: 12px;
13 | outline: none;
14 | /* padding: 15px 80px; */
15 | font-size: 16px;
16 | border-radius: 5px;
17 | text-transform: uppercase;
18 | font-family: var(--main-font);
19 | width: 100%;
20 | margin-top: 1rem;
21 | }
22 |
23 | /* Add this to align vertically */
24 | button > img,
25 | button > span {
26 | vertical-align: middle;
27 | }
28 |
29 | .referral-button-title-span {
30 | margin-left: 10px;
31 | }
32 |
33 | @media only screen and (max-width: 600px) {
34 | .referral-button {
35 | font-size: 16px;
36 | padding: 15px 50px;
37 | }
38 | }
39 |
40 | .referral-button:hover {
41 | background-color: #f1f1f1;
42 | color: black;
43 | cursor: pointer;
44 | }
45 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/ReferralButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './ReferralButton.css'
3 |
4 | function ReferralButton(props) {
5 | return (
6 | <>
7 |
15 | >
16 | )
17 | }
18 |
19 | export default ReferralButton
20 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/StudioButton.css:
--------------------------------------------------------------------------------
1 | button > * {
2 | vertical-align: middle;
3 | }
4 |
5 | /* .studio-button-container {
6 | margin-right: 1rem;
7 | margin-left: 1rem;
8 | } */
9 |
10 | .studio-button {
11 | /* background-color: #03a9f4; */
12 | background-color: var(--card-background-color);
13 | color: grey;
14 | letter-spacing: 2px;
15 | outline: none;
16 | border: none;
17 | padding: 20px 20px;
18 | font-size: 14px;
19 | border-radius: 50%;
20 | text-transform: uppercase;
21 | font-family: var(--main-font-light);
22 | /* margin-right: 30px; */
23 | vertical-align: middle;
24 | margin-right: 1.5rem;
25 | margin-left: 1.5rem;
26 | }
27 |
28 | .studio-button:hover {
29 | /* background-color: #0298db; */
30 | background-color: #dddddd;
31 | background-color: #2a2a2a;
32 | cursor: pointer;
33 | }
34 |
35 | .studio-button-label {
36 | color: grey;
37 | font-family: var(--main-font-light);
38 | }
39 |
40 | @media screen and (max-width: 600px) {
41 | .studio-button-label {
42 | display: none;
43 | }
44 |
45 | .studio-button {
46 | margin-right: 1rem;
47 | margin-left: 1rem;
48 | padding: 25px 25px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/StudioButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './StudioButton.css'
3 |
4 | function StudioButton(props) {
5 | return (
6 |
7 |
16 |
{props.label}
17 |
18 | )
19 | }
20 |
21 | export default StudioButton
22 |
--------------------------------------------------------------------------------
/client/src/components/Canvas/Canvas.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, createRef } from 'react'
2 |
3 | const Canvas = ({ videoRef, width, height }) => {
4 | const canvasRef = createRef(null)
5 |
6 | useEffect(() => {
7 | if (canvasRef.current && videoRef.current) {
8 | const interval = setInterval(() => {
9 | const ctx = canvasRef.current.getContext('2d')
10 | ctx.drawImage(videoRef.current, 0, 0, 250, 188)
11 | }, 60)
12 | return () => clearInterval(interval)
13 | }
14 | })
15 |
16 | return
17 | }
18 |
19 | export default Canvas
20 |
--------------------------------------------------------------------------------
/client/src/components/Card/Card.css:
--------------------------------------------------------------------------------
1 | .card-styles {
2 | height: 150px;
3 | min-width: 160px;
4 | border-radius: 5px;
5 | /* border: 1px solid #ccc; */
6 | background-color: var(--card-background-color);
7 | /* transition: 0.5s; */
8 | font-family: var(--main-font);
9 | /* margin-right: 2rem; */
10 | }
11 |
12 | .inner-card-content {
13 | text-align: center;
14 | margin-top: 1.5rem;
15 | }
16 |
17 | .card-title {
18 | margin-top: 0;
19 | padding-top: 0;
20 | margin-bottom: 0;
21 | padding-bottom: 0;
22 | font-size: 20px;
23 | color: grey;
24 | }
25 |
26 | .remove-button {
27 | padding: 0.25rem 1rem;
28 | margin-top: 0.5rem;
29 | }
30 |
31 | .card-styles:hover {
32 | /* box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); */
33 | box-shadow: none;
34 | border: 1px solid var(--green-color);
35 | cursor: pointer;
36 | /* background-color: #f8f8f8; */
37 | }
38 |
39 | .card-styles:hover p {
40 | color: #fff;
41 | /* transition: 0.5s; */
42 | }
43 |
44 | @media screen and (max-width: 600px) {
45 | .inner-card-content {
46 | /* padding-top: 1rem; */
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/components/Card/Card.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './Card.css'
3 | // import * as FaIcons from 'react-icons/fa'
4 |
5 | function Card(props) {
6 | const [displayButton, setdisplayButton] = useState(false)
7 | return (
8 | setdisplayButton(true)}
13 | onMouseLeave={() => setdisplayButton(false)}
14 | className='card-styles'
15 | >
16 |
17 | {props.children}
18 |
19 | {props.title}
20 |
21 | {/* DISPLAY THE REMOVE BUTTON */}
22 | {displayButton && props.selected && (
23 |
26 | )}
27 |
28 |
29 | )
30 | }
31 |
32 | export default React.memo(Card)
33 |
--------------------------------------------------------------------------------
/client/src/components/ChatMessage/ChatMessage.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/toshvelaga/livestream/eb6f05417a0db5cf4c68f8835daedd1fc61be51e/client/src/components/ChatMessage/ChatMessage.css
--------------------------------------------------------------------------------
/client/src/components/ChatMessage/ChatMessage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './ChatMessage.css'
3 |
4 | const ChatMessage = (props) => {
5 | return {props.message}
6 | }
7 |
8 | export default ChatMessage
9 |
--------------------------------------------------------------------------------
/client/src/components/Messages/NoDestinationsMessage.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --main-font: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto,
3 | 'Helvetica Neue', Ubuntu, Arial, sans-serif;
4 | }
5 |
6 | .connect-button {
7 | /* background-color: grey; */
8 | background: #fff;
9 | color: black;
10 | padding: 0.5em 0.75em;
11 |
12 | /* padding: 0.75rem; */
13 | border-radius: 5px;
14 | border: 1px solid grey;
15 | cursor: pointer;
16 | font-family: var(--main-font);
17 | }
18 |
19 | .connect-button:hover {
20 | background-color: #f8f8f8;
21 | /* background-color: #dddddd; */
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/components/Messages/NoDestinationsMessage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../../styles/styles'
3 | import { Link } from 'react-router-dom'
4 | import './NoDestinationsMessage.css'
5 |
6 | const NoDestinationsMessage = () => {
7 | return (
8 | <>
9 |
15 | Connect YouTube, Twitch, and FB:
16 | {/*
17 | destination
18 | {' '} */}
19 |
20 |
21 |
22 |
23 | >
24 | )
25 | }
26 |
27 | export default NoDestinationsMessage
28 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/Navbar.css:
--------------------------------------------------------------------------------
1 | #top-navbar {
2 | overflow: hidden;
3 | background-color: #fff;
4 | position: fixed;
5 | top: 0;
6 | height: 60px;
7 | width: 100%;
8 | }
9 |
10 | #top-navbar span {
11 | float: right;
12 | display: block;
13 | color: black;
14 | text-align: center;
15 | padding: 14px 16px;
16 | text-decoration: none;
17 | font-size: 17px;
18 | }
19 |
20 | #top-navbar span:hover {
21 | background: #ddd;
22 | color: black;
23 | }
24 |
25 | .top-nav-user-button {
26 | background-color: transparent;
27 | width: 5rem;
28 | height: 2rem;
29 | border-radius: 5px;
30 | border: 1px solid #dddddd;
31 | }
32 |
33 | .top-nav-user-button:hover {
34 | /* background-color: #dddddd; */
35 | cursor: pointer;
36 | }
37 |
38 | .notification-icon,
39 | .hamburger-icon {
40 | margin-top: 4px;
41 | }
42 |
43 | #top-navbar .hamburger-icon {
44 | float: left;
45 | margin-left: 5px;
46 | }
47 |
48 | /* mobile navbar for small screens */
49 |
50 | .nav-menu {
51 | width: 180px;
52 | height: 100vh;
53 | display: flex;
54 | justify-content: center;
55 | position: fixed;
56 | top: 0;
57 | left: -100%;
58 | transition: 850ms;
59 | }
60 |
61 | .nav-menu.active {
62 | left: 0;
63 | transition: 350ms;
64 | }
65 |
66 | .nav-text {
67 | display: flex;
68 | justify-content: start;
69 | align-items: center;
70 | list-style: none;
71 | height: 60px;
72 | }
73 |
74 | .nav-text a {
75 | text-decoration: none;
76 | color: black;
77 | font-size: 18px;
78 | width: 95%;
79 | height: 100%;
80 | display: flex;
81 | align-items: center;
82 | padding: 0 16px;
83 | }
84 |
85 | .nav-text a:hover {
86 | background-color: #ddd;
87 | }
88 |
89 | .nav-menu-items {
90 | width: 180px;
91 | }
92 |
93 | .navbar-toggle {
94 | margin-left: 1rem;
95 | height: 60px;
96 | display: flex;
97 | justify-content: start;
98 | align-items: center;
99 | }
100 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useLayoutEffect } from 'react'
2 | import * as FaIcons from 'react-icons/fa'
3 | import { Link } from 'react-router-dom'
4 | import './Navbar.css'
5 | import SideNavbar from './SideNavbar'
6 | import TopNavbar from './TopNavbar'
7 | import { SidebarData } from './SidebarData'
8 | import { IconContext } from 'react-icons'
9 |
10 | function Navbar(props) {
11 | const [sidebar, setSidebar] = useState(false)
12 | const [width, setWidth] = useState(0)
13 |
14 | const showSidebar = () => {
15 | setSidebar(!sidebar)
16 | }
17 |
18 | useLayoutEffect(() => {
19 | const updateWindowDimensions = () => {
20 | const newWidth = window.innerWidth
21 | setWidth(newWidth)
22 | console.log('updating width')
23 | }
24 |
25 | window.addEventListener('resize', updateWindowDimensions)
26 |
27 | return () => window.removeEventListener('resize', updateWindowDimensions)
28 | }, [])
29 |
30 | const closeSideNav = () => {
31 | if (sidebar === true && width > 555) {
32 | setSidebar(false)
33 | }
34 | }
35 |
36 | return (
37 | <>
38 | {/* sticky top navbar */}
39 |
40 | {/* {sidebar
41 | ? (document.body.style = 'background: grey')
42 | : (document.body.style = 'background: #fff')}
43 |
49 |
50 |
51 |
52 |
*/}
53 |
54 | {/* fixed side navbar with buttons */}
55 |
56 |
57 | {props.children}
58 |
59 | {/* Mobile navbar overlay for small screen size */}
60 |
61 |
80 |
81 | >
82 | )
83 | }
84 |
85 | export default Navbar
86 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/SideNavbar.css:
--------------------------------------------------------------------------------
1 | .side-navbar {
2 | float: left;
3 | position: fixed;
4 | top: 0;
5 | border-right: 1px solid var(--card-background-color);
6 | background-color: var(--background-color);
7 | /* background-color: #1a161e; */
8 | width: 200px;
9 | z-index: 100;
10 | height: 100vh;
11 | padding-top: 50px;
12 | }
13 |
14 | .side-navbar-hidden {
15 | display: none;
16 | }
17 |
18 | .broadcast-li-navbar {
19 | margin-top: 100rem;
20 | }
21 |
22 | /* Style the buttons inside the tab */
23 | .side-navbar li {
24 | display: block;
25 | background-color: inherit;
26 | padding: 14px 16px;
27 | margin: 0.4rem 0;
28 | margin-left: 8px;
29 | width: 90%;
30 | border: none;
31 | outline: none;
32 | text-align: left;
33 | cursor: pointer;
34 | transition: 0.3s;
35 | font-size: 17px;
36 | border-radius: 5px;
37 | box-sizing: border-box;
38 | }
39 |
40 | /* Change background color of buttons on hover */
41 | .side-navbar li:hover {
42 | background-color: var(--navbar-background-color-hover);
43 | /* background-color: #212122; */
44 | /* background-color: #272728; */
45 | }
46 |
47 | #side-navbar-broadcasts-li {
48 | margin-top: 1.5rem;
49 | }
50 |
51 | .side-navbar-title {
52 | margin-left: 10px;
53 | font-family: var(--main-font-light);
54 | /* letter-spacing: 0.5px; */
55 | }
56 |
57 | .side-navbar-icon,
58 | .side-navbar-title {
59 | display: inline-block;
60 | vertical-align: top;
61 | text-align: center;
62 | color: grey;
63 | }
64 |
65 | @media screen and (max-width: 1200px) {
66 | .side-navbar li {
67 | margin-left: 0;
68 | display: flex;
69 | align-self: center;
70 | width: 100%;
71 | border-radius: 0;
72 | }
73 |
74 | .side-navbar-title {
75 | display: none;
76 | }
77 |
78 | .side-navbar {
79 | width: 60px;
80 | transition: 0.3s;
81 | }
82 |
83 | .side-navbar-icon,
84 | .side-navbar-icon-active {
85 | margin-left: 5px;
86 | }
87 | }
88 |
89 | @media screen and (max-width: 592px) {
90 | .side-navbar {
91 | display: none;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/SideNavbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useHistory } from 'react-router-dom'
3 | import * as FaIcons from 'react-icons/fa'
4 | import './SideNavbar.css'
5 | import styles from '../../styles/styles'
6 | import eventTrack from '../../utils/eventTrack'
7 |
8 | function SideNavbar(props) {
9 | const history = useHistory()
10 | const url = window.location.pathname
11 |
12 | return (
13 |
16 | {/* Broadcasts */}
17 | - history.push('/broadcast')}
20 | style={
21 | window.location.pathname === '/broadcast'
22 | ? { backgroundColor: styles.sideNavbarHoverColor }
23 | : null
24 | }
25 | >
26 |
32 |
33 |
34 |
40 | Broadcast
41 |
42 |
43 | {/* Destinations */}
44 | - {
46 | history.push('/destinations')
47 | eventTrack('App', 'Destinations Tab Clicked', 'Button')
48 | }}
49 | style={
50 | window.location.pathname === '/destinations'
51 | ? { backgroundColor: styles.sideNavbarHoverColor }
52 | : null
53 | }
54 | >
55 |
63 |
64 |
65 |
73 | Destinations
74 |
75 |
76 | {/* Referrals */}
77 |
78 | {/* - history.push('/referrals')}
80 | style={
81 | window.location.pathname === '/referrals'
82 | ? { backgroundColor: styles.sideNavbarHoverColor }
83 | : null
84 | }
85 | className='tablinks'
86 | >
87 |
88 |
89 |
90 | Referrals
91 | */}
92 |
93 | )
94 | }
95 |
96 | export default SideNavbar
97 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/SidebarData.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import * as FaIcons from 'react-icons/fa'
3 |
4 | export const SidebarData = [
5 | {
6 | title: 'Broadcast',
7 | path: '/broadcast',
8 | icon: ,
9 | cName: 'nav-text',
10 | },
11 | {
12 | title: 'Destinations',
13 | path: '/destinations',
14 | icon: ,
15 | cName: 'nav-text',
16 | },
17 | ]
18 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/TopNavbar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react'
2 | import * as FaIcons from 'react-icons/fa'
3 | import navbarStyles from './TopNavbar.module.css'
4 | import deleteAllCookies from '../../utils/deleteAllCookies'
5 | import { Link, useHistory, useLocation, history } from 'react-router-dom'
6 | import styles from '../../styles/styles'
7 |
8 | function TopNavbar(props) {
9 | const history = useHistory()
10 |
11 | const handleClick = () => {
12 | history.push('/billing')
13 | }
14 | const url = window.location.pathname
15 |
16 | return (
17 |
18 | {/* } /> */}
19 | {props.children}
20 | {!url.includes('/studio') && (
21 | <>
22 |
25 |
31 | }
32 | >
33 |
34 |
35 | >
36 | )}
37 |
38 | )
39 | }
40 |
41 | function Navbar(props) {
42 | return (
43 |
46 | )
47 | }
48 |
49 | function NavItem(props) {
50 | const [open, setOpen] = useState(false)
51 | const naviconRef = useRef(null)
52 | useOutsideAlerter(naviconRef)
53 |
54 | function useOutsideAlerter(ref) {
55 | useEffect(() => {
56 | /**
57 | * close the topnavbar dropdown if it is open
58 | */
59 | function handleClickOutside(event) {
60 | if (ref.current && !ref.current.contains(event.target) && open) {
61 | setOpen(false)
62 | }
63 | }
64 |
65 | // Bind the event listener
66 | document.addEventListener('mousedown', handleClickOutside)
67 | return () => {
68 | // Unbind the event listener on clean up
69 | document.removeEventListener('mousedown', handleClickOutside)
70 | }
71 | }, [ref, open])
72 | }
73 |
74 | const closeNavbar = () => {
75 | setOpen(!open)
76 | }
77 |
78 | return (
79 |
80 |
81 | {props.icon}
82 |
83 |
84 | {open && props.children}
85 |
86 | )
87 | }
88 |
89 | function DropdownMenu() {
90 | const [activeMenu, setActiveMenu] = useState('main')
91 | const dropdownRef = useRef(null)
92 |
93 | const history = useHistory()
94 |
95 | function DropdownItem(props) {
96 | return (
97 |
98 | {props.children}
99 |
100 | )
101 | }
102 |
103 | const onLogout = () => {
104 | deleteAllCookies()
105 | history.push('/login')
106 | }
107 |
108 | return (
109 |
110 |
111 | history.push('/broadcast')}>
112 | Broadcast
113 |
114 | history.push('/destinations')}>
115 | Destinations
116 |
117 | history.push('/billing')}>
118 | Billing
119 |
120 | history.push('/settings')}>
121 | Settings
122 |
123 | Logout
124 |
125 |
126 | )
127 | }
128 |
129 | export default TopNavbar
130 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/TopNavbar.module.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | :root {
6 | /* --bg: #2c2b30; */
7 | --bg: var(--main-background-color);
8 | --bg-accent: #f1f1f1;
9 | --nav-size: 60px;
10 | --border: 1px solid var(--card-background-color);
11 | --border-radius: 8px;
12 | --speed: 500ms;
13 | }
14 |
15 | ul {
16 | list-style: none;
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | a {
22 | color: var(--text-color);
23 | text-decoration: none;
24 | font-family: var(--main-font);
25 | }
26 |
27 | /* Top Navigation Bar */
28 |
29 | /*