├── public
├── robots.txt
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── images
│ ├── lounge.jpg
│ └── homepage.jpg
├── setupTests.js
├── App.test.js
├── utility
│ └── auth.js
├── components
│ ├── Footer.jsx
│ ├── Button.jsx
│ ├── Input.jsx
│ ├── TextBackground.jsx
│ ├── WelcomeContainer.jsx
│ ├── Message.jsx
│ ├── Navbar.jsx
│ └── Form.jsx
├── screens
│ ├── Show.jsx
│ ├── Welcome.jsx
│ ├── Edit.jsx
│ ├── Signin.jsx
│ ├── TopicEdit.jsx
│ ├── CreateTopic.jsx
│ ├── Signup.jsx
│ ├── TopicDetails.jsx
│ └── Lounge.jsx
├── reportWebVitals.js
├── index.js
├── App.js
└── App.css
├── .gitignore
├── README.md
└── package.json
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LatoyaHead/The-Teachers-Lounge/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/images/lounge.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LatoyaHead/The-Teachers-Lounge/HEAD/src/images/lounge.jpg
--------------------------------------------------------------------------------
/src/images/homepage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LatoyaHead/The-Teachers-Lounge/HEAD/src/images/homepage.jpg
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/utility/auth.js:
--------------------------------------------------------------------------------
1 | export const authenticated = (setIsAuth) => { //authenticating whether or not the user is logged in by checking token saved on localstorage
2 | const token = localStorage.getItem('token')
3 | if(token) {
4 | setIsAuth(true)
5 | return true
6 | }
7 | setIsAuth(false)
8 | return false
9 | }
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Footer = () => {
4 | return (
5 |
6 |
9 |
10 | )
11 | }
12 |
13 | export default Footer
14 |
--------------------------------------------------------------------------------
/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Button = ({text, backgroundColor, width, onClick}) => {
4 | return (
5 |
6 |
8 |
9 | )
10 | }
11 |
12 | export default Button
--------------------------------------------------------------------------------
/src/screens/Show.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Show = ({lounge}) => {
4 | return (
5 |
6 |
Lounge
7 |
{lounge.title}
8 |
{lounge.body}
9 |

10 |
{lounge.author}
11 |
12 | )
13 | }
14 |
15 |
16 | export default Show
--------------------------------------------------------------------------------
/src/components/Input.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Input = ({text, type, onChange, name, placeholder, defaultValue}) => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default Input
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | .env
--------------------------------------------------------------------------------
/src/components/TextBackground.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const TextBackground = ({children, title, style}) => {
4 | return (
5 |
6 |
7 |
{title}
8 |
9 | {children}
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default TextBackground
17 |
--------------------------------------------------------------------------------
/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/components/WelcomeContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const WelcomeContainer = ({width, children}) => {
4 | return (
5 |
6 |
7 |
8 |
The
Teacher's
Lounge
9 |
10 | {children}
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default WelcomeContainer
18 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 | import reportWebVitals from './reportWebVitals';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 |
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/screens/Welcome.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import WelcomeContainer from '../components/WelcomeContainer'
3 | import Signin from './Signin'
4 | import { useNavigate } from 'react-router-dom'
5 | import { authenticated } from '../utility/auth'
6 |
7 | const Welcome = () => {
8 | const [isAuth, setIsAuth] = useState(false)
9 | const navigate = useNavigate()
10 |
11 |
12 | useEffect(() => {
13 | if(authenticated(setIsAuth)) {
14 | navigate('/lounge')
15 | }
16 | }, [isAuth])
17 |
18 | return (
19 |
20 |
21 |
22 | )
23 |
24 | }
25 |
26 | export default Welcome
27 |
--------------------------------------------------------------------------------
/src/components/Message.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | const Message = ({message, avatar, user_id, myId, username}) => {
5 | console.log(username);
6 | return (
7 |
8 |
9 |
10 |
{username}
11 | {/*

*/}
12 |
13 |
{message}
14 |
15 |
16 | )
17 | }
18 |
19 | export default Message
--------------------------------------------------------------------------------
/src/screens/Edit.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Navbar from '../components/Navbar'
3 | import TextBackground from '../components/TextBackground'
4 |
5 | const Edit = ({LoungeModel}) => {
6 | return (
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default Edit
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, {useContext, useEffect} from 'react'
2 | import "../../src/App.css"
3 | import { UserContext } from '../App'
4 | import { NavLink } from "react-router-dom"
5 |
6 |
7 | const Navbar = () => {
8 | const context = useContext(UserContext)
9 | const handleSignOut = () => {
10 | localStorage.removeItem('token')
11 | context.setIsAuth(true)
12 | }
13 | useEffect(() => {
14 |
15 | }, [context.isAuth])
16 |
17 |
18 | return (
19 |
26 | )
27 | }
28 |
29 |
30 | export default Navbar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Teacher's Lounge
2 |
3 | ## Description
4 |
5 |
6 | The Teacher's Lounge is known for being the most important room for educators. Here in The Teacher's Lounge you can relax, talk to other educators, discuss issues, and most of all socialize. With that being said, this app is designed for teacher's worldwide to gather and talk about their day whatever that may entail: "The good, the bad, and the downright UGLY." The Teacher's Lounge allows you to enter a username and gives you a random avatar for all your posts and comments made within the app. Have fun!
7 |
8 |
9 | ## Tech Stack
10 | - MongoDB/Mongoose
11 | - Express
12 | - React
13 | - Node
14 | - CSS
15 |
16 |
17 | ## Upcoming Features
18 |
19 | - Update Avatar to user for messaging
20 | - Allow users to choose Lounge Rooms
21 | - Create a Delete Modal
22 |
23 | ## Github Repos and Sites
24 | - https://github.com/LatoyaHead/The-Teachers-Lounge.git
25 | - https://github.com/LatoyaHead/The-Teachers-Lounge-API.git
26 | - https://trello.com/b/ExtBl3uI/the-teachers-lounge
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "the-teachers-lounge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@dicebear/avatars": "^4.10.5",
7 | "@dicebear/avatars-avataaars-sprites": "^4.10.5",
8 | "@testing-library/jest-dom": "^5.16.5",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "pusher-js": "^7.4.1",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-jwt": "^1.1.7",
15 | "react-router-dom": "^6.4.3",
16 | "react-scripts": "5.0.1",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/Form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import TextBackground from './TextBackground'
3 | import Input from './Input'
4 |
5 | const Form = ({title, btnText, handleOnChange, submit, defaultValue}) => {
6 | return (
7 |
8 |
34 |
35 | )
36 | }
37 |
38 | export default Form
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {createContext, useState, useEffect } from 'react'
2 | import './App.css';
3 | import Signup from './screens/Signup';
4 | import Welcome from './screens/Welcome';
5 | import New from './screens/CreateTopic';
6 | import TopicEdit from './screens/TopicEdit';
7 | import TopicDetails from './screens/TopicDetails';
8 | import { decodeToken } from "react-jwt"
9 | import {
10 | createBrowserRouter,
11 | RouterProvider,
12 | } from "react-router-dom";
13 | import Lounge from './screens/Lounge';
14 |
15 | export const UserContext = createContext()
16 | const router = createBrowserRouter([ //Creating router
17 | { // This object let's the router know what url to go to and what element to present on the url
18 | path: "/",
19 | element: ,
20 | },
21 | {
22 | path: '/signup',
23 | element:
24 | },
25 | {
26 | path: '/lounge',
27 | element:
28 | },
29 | {
30 | path: '/new',
31 | element:
32 | },
33 | {
34 | path: '/topic/:id',
35 | element:
36 | },
37 | {
38 | path: 'topic-details/:id',
39 | element:
40 | }
41 | ]);
42 |
43 | function App() {
44 | const [user, setUser] = useState(null)
45 | const [isAuth, setIsAuth] = useState(false)
46 |
47 | const authenticated = () => {
48 | const token = localStorage.getItem('token')
49 | if(token) {
50 | const decode = decodeToken(token)
51 | setUser(decode?.user)
52 | setIsAuth(true)
53 | }
54 |
55 | }
56 | useEffect(() => {
57 | authenticated()
58 | }, [isAuth])
59 | return (
60 |
61 |
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | export default App;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/screens/Signin.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import Input from '../components/Input'
3 | import Button from '../components/Button'
4 | import { NavLink, useNavigate } from 'react-router-dom'
5 |
6 | const Login = () => {
7 | const navigate = useNavigate()
8 | const apiUrl = process.env.REACT_APP_URL || 'http://localhost:3001'
9 | const [user, setUser] = useState({email:'', password:''})
10 | const handleOnChange = (e) => {
11 | setUser({...user, [e.target.name]:e.target.value})
12 | }
13 | const handleLogin = () => {
14 | if(user.email === '' || user.password === ''){
15 | return
16 | }
17 | fetch(`${apiUrl}/signin`, {
18 | method: 'post',
19 | headers: {
20 | 'Accept': 'application/json',
21 | 'Content-Type': 'application/json'
22 | },
23 |
24 | //make sure to serialize your JSON body
25 | body: JSON.stringify({
26 | email: user.email,
27 | password: user.password,
28 | })
29 |
30 | })
31 | .then(async(data) => {
32 | const res = await data.json()
33 | localStorage.setItem('token', res.token)
34 | if(data.status === 400) return
35 | navigate('/lounge')
36 | setUser({email:'', password:''})
37 | })
38 | .catch((error) => {
39 | console.log("Failed", error);
40 | })
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
54 |
55 | New to The Teacher's Lounge?Sign Up
56 |
57 |
58 |
59 |
60 | )
61 | }
62 |
63 |
64 |
65 | export default Login
66 |
--------------------------------------------------------------------------------
/src/screens/TopicEdit.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import {useParams, useNavigate} from 'react-router-dom'
3 | import Navbar from '../components/Navbar'
4 | import Footer from '../components/Footer'
5 | import Form from '../components/Form'
6 |
7 | const TopicEdit = () => {
8 | const [topic, setTopic] = useState(null)
9 | const params = useParams()
10 | const navigate = useNavigate()
11 | const apiUrl = process.env.REACT_APP_URL || 'http://localhost:3001'
12 |
13 | const getTopic = async () => {
14 | try {
15 | const res = await fetch(`http:///${params.id}`)
16 | const topic = await res.json()
17 | setTopic(topic)
18 | }catch(error) {
19 | console.log("Topic Not Found",error);
20 | }
21 |
22 | }
23 | useEffect(() => {
24 | getTopic()
25 | }, [])
26 |
27 | const handleOnChange = (e) => {
28 | setTopic({...topic, [e.target.name]:e.target.value})
29 | }
30 | const editTopic = async (e) => {
31 | e.preventDefault()
32 | try {
33 | const res = await fetch(`${apiUrl}/${params.id}`,{ //send id of the post we want to edit
34 | method: 'put',
35 | headers: {
36 | 'Accept': 'application/json',
37 | 'Content-Type': 'application/json'
38 | },
39 |
40 | //make sure to serialize your JSON body
41 | body: JSON.stringify({
42 | title: topic.title,
43 | body: topic.body
44 | })
45 | })
46 |
47 | const updatedTopic = await res.json()
48 | setTopic(updatedTopic)
49 | navigate('/lounge')
50 | }catch(error) {
51 | console.log("Topic Not Found",error);
52 | }
53 | }
54 | return (
55 |
56 |
57 |
The Teacher's Lounge
58 |
59 |
60 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | export default TopicEdit
74 |
--------------------------------------------------------------------------------
/src/screens/CreateTopic.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useContext } from 'react'
2 | import Navbar from '../components/Navbar'
3 | import { useNavigate } from 'react-router-dom';
4 | import { UserContext } from '../App';
5 | import Footer from '../components/Footer';
6 | import Form from '../components/Form';
7 |
8 | const CreateTopic = () => {
9 | const navigate = useNavigate()
10 | const user = useContext(UserContext)
11 | console.log("user",user);
12 | const apiUrl = process.env.REACT_APP_URL || 'http://localhost:3001'
13 | const [topic, setTopic] = useState({title:"", body:""})
14 | const createTopic = (e) => {
15 | e.preventDefault()
16 | if(topic.title === '' || topic.body === ''){
17 | return
18 | }
19 | fetch(`${apiUrl}/topic`, { //
20 | method: 'post',
21 | headers: {
22 | 'Accept': 'application/json',
23 | 'Content-Type': 'application/json'
24 | },
25 |
26 | //make sure to serialize your JSON body
27 | body: JSON.stringify({ //data sent with post req
28 | title: topic.title,
29 | body: topic.body,
30 | author: user?.user.username,
31 | avatar: user?.user.avatar,
32 | author_id: user?.user._id
33 | })
34 |
35 | })
36 | .then(async(data) => {
37 | const res = await data.json()
38 | if(data.status === 400) return
39 | navigate('/lounge')
40 | setTopic({title:'', body:''})
41 | })
42 | .catch((error) => {
43 | console.log("Failed", error);
44 | })
45 | }
46 | const handleOnChange = (e) => {
47 | setTopic({...topic, [e.target.name]:e.target.value})
48 | }
49 | return (
50 |
51 |
52 |
The Teacher's Lounge
53 |
54 |
55 |
62 |
63 |
64 |
65 | )
66 | }
67 |
68 | export default CreateTopic
--------------------------------------------------------------------------------
/src/screens/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React,{ useState, useEffect } from 'react'
2 | import Input from '../components/Input'
3 | import Button from '../components/Button'
4 | import Navbar from '../components/Navbar'
5 | import { NavLink, useNavigate } from 'react-router-dom'
6 | import WelcomeContainer from '../components/WelcomeContainer'
7 | import { createAvatar } from '@dicebear/avatars';
8 | import * as style from '@dicebear/avatars-avataaars-sprites';
9 |
10 | const Signup = () => {
11 | const [isAuth, setIsAuth] = useState(false)
12 | const navigate = useNavigate()
13 | const [user, setUser]= useState({username:'', email:'', password:''})
14 | const handleOnChange = (e) => {
15 | setUser({...user, [e.target.name]:e.target.value})
16 | }
17 | const authenticated = () => { //authenticating whether or not the user is logged in by checking token saved on localstorage
18 | const token = localStorage.getItem('token')
19 | if(token) {
20 | setIsAuth(true)
21 | return true
22 | }
23 | setIsAuth(false)
24 | return false
25 | }
26 | useEffect(() => {
27 | if(authenticated()) {
28 | navigate('/lounge')
29 | }
30 | }, [isAuth])
31 |
32 | const avatar = createAvatar(style, {
33 | dataUri: true
34 | });
35 |
36 | const handleSignup = () => {
37 | if(user.username === '' || user.email === '' || user.password === ''){
38 | console.log('data needed');
39 | return
40 | }
41 | fetch('http://localhost:3001/signup', {
42 | method: 'post',
43 | headers: {
44 | 'Accept': 'application/json',
45 | 'Content-Type': 'application/json'
46 | },
47 |
48 | //make sure to serialize your JSON body
49 | body: JSON.stringify({
50 | username: user.username,
51 | email: user.email,
52 | password: user.password,
53 | avatar: avatar
54 | })
55 |
56 | })
57 | .then(async(data) => {
58 | const res = await data.json()
59 | localStorage.setItem('token', res.token)
60 | if(data.status === 400) return
61 | navigate('/lounge')
62 | setUser({username:'', email:'', password:''})
63 | })
64 | .catch((error) => {
65 | console.log("Failed", error);
66 | })
67 | }
68 |
69 | return (
70 |
71 |
90 |
91 | )
92 | }
93 |
94 |
95 | export default Signup
--------------------------------------------------------------------------------
/src/screens/TopicDetails.jsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState, useContext} from 'react'
2 | import {useParams, useNavigate} from 'react-router-dom'
3 | import Message from '../components/Message'
4 | import Input from '../components/Input'
5 | import Button from '../components/Button'
6 | import Pusher from 'pusher-js'
7 | import { UserContext } from '../App';
8 | import Navbar from '../components/Navbar'
9 | import Footer from '../components/Footer'
10 | import lounge from '../images/lounge.jpg'
11 |
12 | const pusher = new Pusher(process.env.REACT_APP_PUSHER_KEY, {
13 | cluster: 'mt1'
14 | });
15 |
16 | const TopicDetails = () => {
17 | const user = useContext(UserContext)
18 | const apiUrl = process.env.REACT_APP_URL || 'http://localhost:3001'
19 | const [topic, setTopic] = useState(null)
20 | const [pushedMessage, setPushedMessage] = useState([])
21 | const [message, setMessage] = useState({message: ''})
22 | const params = useParams()
23 |
24 | const getTopic = async () => {
25 | try {
26 | const res = await fetch(`${apiUrl}/${params.id}`)
27 | const topic = await res.json()
28 | setTopic(topic)
29 | }catch(error) {
30 | console.log("Topic Not Found",error);
31 | }
32 |
33 | }
34 | const handleMessage = (e) => {
35 | setMessage({...message, [e.target.name]:e.target.value})
36 | }
37 |
38 | const channel = pusher.subscribe('my-channel');
39 | channel.bind('my-event', function(data) {
40 | setPushedMessage([...pushedMessage, data]) //...(spread operator) adds previous messages
41 | });
42 |
43 | const sendMessage = () => {
44 | if(message.message === ''){
45 | return
46 | }
47 | fetch(`${apiUrl}/message`, {
48 | method: 'post',
49 | headers: {
50 | 'Accept': 'application/json',
51 | 'Content-Type': 'application/json'
52 | },
53 |
54 | //make sure to serialize your JSON body
55 | body: JSON.stringify({
56 | message: message.message,
57 | author: user?.user.username,
58 | author_id: user?.user._id,
59 | avatar: user?.user.avatar
60 | })
61 |
62 | })
63 | .then(async(data) => {
64 | const res = await data.json()
65 | if(data.status === 400) return
66 | setMessage({message:''})
67 | })
68 | .catch((error) => {
69 | console.log("Failed", error);
70 | })
71 | }
72 |
73 | useEffect(() => {
74 | getTopic()
75 | }, [])
76 | console.log(pushedMessage);
77 | return (
78 |
79 |
80 |
The Teacher's Lounge
81 |
82 |
83 |
84 |
85 |

86 |
{topic?.title}
87 |
Written By: {topic?.author}
88 |
{topic?.body}
89 |
90 |
91 |
92 | {
93 | pushedMessage.map(message => {
94 | return
95 | })
96 | }
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | )
114 | }
115 |
116 | export default TopicDetails
--------------------------------------------------------------------------------
/src/screens/Lounge.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useContext } from 'react'
2 | import Navbar from '../components/Navbar';
3 | import { UserContext } from '../App';
4 | import {Link} from 'react-router-dom'
5 | import { useNavigate } from 'react-router-dom';
6 | import { decodeToken } from "react-jwt"
7 | import Footer from '../components/Footer';
8 |
9 | const Lounge = () => {
10 | const user = useContext(UserContext)
11 | const navigate = useNavigate()
12 | const [topics, setTopics] = useState([])
13 | const apiUrl = process.env.REACT_APP_URL || 'http://localhost:3001'
14 |
15 | const getTopics = async () => { //get topics, fetch gets data back and sets it to setsTopics
16 | try {
17 | const response = await fetch(`${apiUrl}/topics`)
18 | const topics = await response.json()
19 | setTopics(topics)
20 | }catch(error) {
21 | console.log('Topic Errors', error);
22 | }
23 | }
24 | const authenticated = () => { //authenticating whether or not the user is logged in by checking token saved on localstorage
25 | const token = localStorage.getItem('token')
26 | if(token) {
27 | user?.setIsAuth(false)
28 | return false
29 | }
30 | user?.setIsAuth(true)
31 | return true
32 | }
33 | const decodeUser = () => {
34 | const token = localStorage.getItem('token')
35 | if(token) {
36 | const decode = decodeToken(token)
37 | user?.setUser(decode?.user)
38 | user?.setIsAuth(true)
39 | }
40 | }
41 |
42 | const handleDelete = (topicId) => {
43 | fetch(`${apiUrl}/remove/${topicId}`, {
44 | method: 'delete'
45 | })
46 | .then(res => {
47 | setTopics(topics.filter(topic => topic._id !== topicId))
48 | }).catch(error => {
49 | console.log(error);
50 | })
51 | }
52 |
53 | useEffect(() => { //runs the first render when going to lounge page
54 | if(authenticated()) {
55 | navigate('/')
56 | }
57 | getTopics()
58 | decodeUser()
59 | }, [user?.isAuth]) //if isAuth is not here just thr ? instead of error
60 |
61 | return (
62 |
63 |
64 |
65 |
The Teacher's Lounge
66 |
67 |
68 | || Latest Lounge Post
69 |
70 |
71 | {topics.map((topic) => (
72 |
73 |
74 |
75 | {" "}
76 |
{topic.title}
77 |
78 |
81 |
82 |
83 |

84 |
85 |
{topic.author}
86 | {topic.created_at}
87 |
88 |
89 |
90 |
91 |
92 | {user?.user?._id === topic.author_id ? ( //conditional statement that allows only the user to delete and edit their posts
93 |
94 |
95 |
96 |
97 |
98 |
99 | Edit
100 |
101 |
102 | ) : null}
103 |
104 |
105 | ))}
106 |
107 |
108 |
109 |
110 | );
111 | }
112 |
113 |
114 | export default Lounge
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | .welcome {
6 | background: url('https://images.unsplash.com/photo-1552819401-700b5e342b9d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80');
7 | background-size: cover;
8 | font-family: "Comfortaa", cursive;
9 | background-repeat: no-repeat;
10 | background-attachment: fixed;
11 | background-position: center;
12 | height: 100vh;
13 | padding: 25px;
14 | }
15 | .nav-container {
16 | display: flex;
17 | justify-content: right;
18 | font-size: 15px;
19 | padding-right: 20px;
20 | }
21 | a {
22 | color: white;
23 | text-decoration: none;
24 | text-shadow: 1px 1px 1px black;
25 | font-size: 25px;
26 | font-weight: bold;
27 | padding: 5px;
28 | display: flex;
29 | justify-content: center;
30 | border-radius: 10%;
31 | font-family: Georgia, 'Times New Roman', Times, serif;
32 | }
33 | a:hover {
34 | color: rgb(136,0,0);
35 | cursor: pointer;
36 | }
37 | .signin .signup {
38 | display: flex;
39 | justify-content: center;
40 | font-size: 20px;
41 | }
42 | .signin {
43 | display: flex;
44 | flex-direction: row;
45 | align-items: center;
46 | padding-top: 45px;
47 | padding-left: 295px;
48 | gap: 20px;
49 | justify-content: center;
50 | }
51 | .signup {
52 | margin-top: 30px;
53 | }
54 | .header {
55 | color: #fff;
56 | text-shadow:
57 | 0 0 7px #fff,
58 | 0 0 10px #fff,
59 | 0 0 21px #fff,
60 | 0 0 42px red,
61 | 0 0 82px red,
62 | 0 0 92px red,
63 | 0 0 102px red,
64 | 0 0 151px red;
65 | font-size: 115px;
66 | padding-left: 295px;
67 | text-align: center;
68 | animation: flicker 1.5s infinite alternate;
69 | }
70 | @keyframes flicker {
71 |
72 | 0%, 18%, 22%, 25%, 53%, 57%, 100% {
73 |
74 | text-shadow:
75 | 0 0 4px #fff,
76 | 0 0 11px #fff,
77 | 0 0 19px #fff,
78 | 0 0 40px red,
79 | 0 0 80px red,
80 | 0 0 90px red,
81 | 0 0 100px red,
82 | 0 0 150px red;
83 | }
84 | 20%, 24%, 55% {
85 | text-shadow: none;
86 | }
87 | }
88 | .btn {
89 | width: 220px;
90 | height: 50px;
91 | font-size: 25px;
92 | font-family: Georgia, 'Times New Roman', Times, serif;
93 | font-weight: bold;
94 | border: none;
95 | outline: none;
96 | color: white;
97 | background: black;
98 | cursor: pointer;
99 | border-radius: 10px;
100 | padding-left: 10px;
101 | padding-right: 10px;
102 | }
103 | .input-style {
104 | padding: 10px;
105 | width: 100%;
106 | box-sizing: border-box;
107 | margin-bottom: 10px;
108 | border-radius: 1px;
109 | border: 1px solid;
110 | }
111 | .lounge-section {
112 | display: flex;
113 | flex-wrap: wrap;
114 | gap: 20px;
115 | padding: 45px;
116 | }
117 | .card {
118 | box-shadow: 8px 10px 10px 10px rgba(0,0,0,0.2);
119 | transition: 0.3s;
120 | width: 40%;
121 | background-color: rgb(245,245,245, 0.6);
122 | width: 300px;
123 | border-radius: 10px;
124 | }
125 |
126 | form {
127 | display: flex;
128 | flex-direction: column;
129 | align-items: center;
130 | }
131 |
132 | .parent-input-div {
133 | display: flex;
134 | justify-content: center;
135 | width: 100%;
136 | }
137 | .parent-input-div .delete-button {
138 | font-size: 20px;
139 | background: rgb(136,0,0);
140 | color: white;
141 | border: none;
142 | font-family: Georgia, 'Times New Roman', Times, serif;
143 | font-weight: bold;
144 | border-radius: 8px;
145 | display: flex;
146 | justify-content: center;
147 | }
148 | .percent-100 {
149 | width: 100%
150 | }
151 | .parent-input-div .edit-button {
152 | background: black;
153 | justify-content: center;
154 | color: white;
155 | border-radius: 8px;
156 | font-family: Georgia, 'Times New Roman', Times, serif;
157 | font-weight: bold;
158 | font-size: 20px;
159 | }
160 | .ellipsis {
161 | display: -webkit-box;
162 | -webkit-line-clamp: 3;
163 | -webkit-box-orient: vertical;
164 | overflow: hidden;
165 | }
166 | .avatar {
167 | border-radius: 65%;
168 | height: 75px;
169 | }
170 | .post {
171 | color: rgb(136,0,0);
172 | margin-bottom: 10px;
173 | margin: 15px;
174 | text-align: left;
175 | padding-left: 25px;
176 | }
177 | .center {
178 | display: flex;
179 | justify-content: center;
180 | align-items: center;
181 | margin-top: 70px;
182 | }
183 | .text-background {
184 | background-color: rgb(245,245,245, 0.6);
185 | border-radius: 5px;
186 | font-weight: bold;
187 | font-size: 18px;
188 | line-height: 25px;
189 | }
190 | input[type="submit"] {
191 | padding: 5px 25px;
192 | color: white;
193 | background: black;
194 | font-size: 15px;
195 | border-radius: 10px;
196 | border: none;
197 | font-weight: bold;
198 | transition: .4s;
199 | }
200 | .title {
201 | height: 140;
202 | width: 100%;
203 | background: black;
204 | display: flex;
205 | justify-content: center;
206 | align-items: center;
207 | padding: 20px;
208 | box-sizing: border-box;
209 | color: rgb(136,0,0);
210 | font-size: 25px;
211 | }
212 | .pages {
213 | background-image: linear-gradient(to right, rgba(0,0,0,1),rgba(56, 56, 56));
214 | height: 100vh;
215 | }
216 | .line {
217 | width: 70%;
218 | margin-left: auto;
219 | margin-right: auto;
220 | border: 2px solid rgb(245,245,245, 0.6);
221 | margin-bottom: 40px;
222 | margin-top: 20px;
223 | }
224 | .details {
225 | color: white;
226 | width: 60%;
227 | background-color: rgb(245,245,245, 0.6);
228 | display: flex;
229 | flex-direction: row;
230 | border-radius: 8px;
231 | height: 600px;
232 | }
233 | .footer {
234 | background: rgb(245,245,245, 0.6);
235 | text-align: center;
236 | position: absolute;
237 | bottom: 0;
238 | width: 100%;
239 | height: 2.5rem;
240 | }
241 | .edit-b {
242 | width: 220px;
243 | height: 50px;
244 | font-size: 25px;
245 | font-family: Georgia, 'Times New Roman', Times, serif;
246 | font-weight: bold;
247 | border: none;
248 | outline: none;
249 | color: rgb(136,0,0);
250 | background: #111;
251 | cursor: pointer;
252 | border-radius: 1px;
253 | }
--------------------------------------------------------------------------------