From 9c67d904f0b50c85a6c9fa3fef7505b857cbc0d6 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 10 Aug 2021 12:26:00 -0700 Subject: [PATCH 01/58] Backend work for search complete New search resource added that doesn't deal with any filterby/sortby parameters and only deals with the search parameter. --- server/inquire/__init__.py | 6 +- server/inquire/resources/__init__.py | 1 + server/inquire/resources/search.py | 104 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 server/inquire/resources/search.py diff --git a/server/inquire/__init__.py b/server/inquire/__init__.py index cfac0c9..beeb72f 100644 --- a/server/inquire/__init__.py +++ b/server/inquire/__init__.py @@ -21,7 +21,8 @@ def create_app(override_config=None, testing=False, include_socketio=True): # CORS app.config['CORS_HEADERS'] = 'Content-Type' - api_bp = Blueprint("api_bp", __name__, url_prefix='/' + config.ROUTING_PREFIX) + api_bp = Blueprint("api_bp", __name__, + url_prefix='/' + config.ROUTING_PREFIX) # api_bp = Blueprint("api_bp", __name__, url_prefix='/') @app.after_request @@ -76,7 +77,7 @@ def after_request(response): api_base_url='https://api.github.com/', client_kwargs={'scope': 'read:user user:email'}, ) - from inquire.resources import Demo, Me, Courses, Posts, Comments, Replies, Join, Reactions, Home, Roles, MeRole, CourseUsers, Poll, Pin, BanRemove, Images + from inquire.resources import Demo, Me, Courses, Posts, Comments, Replies, Join, Reactions, Home, Roles, MeRole, CourseUsers, Poll, Pin, BanRemove, Images, Search api = Api(api_bp) @@ -102,6 +103,7 @@ def after_request(response): api.add_resource(Join, '/join') api.add_resource(BanRemove, '/courses//ban-remove') api.add_resource(Images, '/images') + api.add_resource(Search, 'courses//search') app.register_blueprint(api_bp) if include_socketio: # Wrapping flask app in socketio wrapper diff --git a/server/inquire/resources/__init__.py b/server/inquire/resources/__init__.py index 06af1ba..a071e01 100644 --- a/server/inquire/resources/__init__.py +++ b/server/inquire/resources/__init__.py @@ -14,3 +14,4 @@ from .pin import Pin from .ban_remove import BanRemove from .images import Images +from .search import Search diff --git a/server/inquire/resources/search.py b/server/inquire/resources/search.py new file mode 100644 index 0000000..5838849 --- /dev/null +++ b/server/inquire/resources/search.py @@ -0,0 +1,104 @@ +from flask import request +from flask_restful import Resource +from inquire.auth import current_user, permission_layer +from inquire.mongo import * + + +class Search(Resource): + @permission_layer(require_joined_course=True) + def get(self, courseId=None): + """ + Retrieves posts that include the search string + --- + tags: + - Posts + parameters: + - in: path + name: courseId + required: true + description: course id from which to retrieve posts + - in: query + name: search + schema: + type: string + required: true + description: Filter posts by search + responses: + 200: + description: Responds with array of posts + schema: + type: array + items: + $ref: '#/definitions/Post' + 400: + description: Array of errors gathered from request + schema: + $ref: '#/definitions/400Response' + 403: + description: Unable to retrieve current user data + schema: + $ref: '#/definitions/403Response' + """ + # Get the search input and the current course + search = request.args.get('search') + + if search is None or search == "": + return {"errors": ["Nothing to be searched for."]}, 401 + + user_perms = current_user.permissions + + # -1 sorts newest first + sort_date = -1 + + queryParams = {"courseId": courseId} + + # If the current user can see private posts + if user_perms["privacy"]["private"]: + queryParams['$text'] = {'$search': search} + query = Post.objects.raw(queryParams).order_by( + [("isPinned", -1), ("createdDate", sort_date)]) + + # If the current user cannot see private posts + elif not user_perms["privacy"]["private"]: + queryParams['$or'] = [{'isPrivate': False}, {'postedBy._id': { + '$in': [current_user._id, current_user.anonymousId]}}] + queryParams['$text'] = {'$search': search} + query = Post.objects.raw(queryParams).order_by( + [('isPinned', -1), ('createdDate', sort_date)]) + + course = current_user.get_course(courseId) + viewed = course.viewed + # Get the json for all the posts we want to display + result = [self.serialize(post, viewed=viewed) for post in query] + + return result, 200 + + def serialize(self, post, viewed=None): + # Get the JSON format + result = post.to_son() + if viewed: + updated_date = result["updatedDate"] + result["read"] = self.calc_read( + result["_id"], updated_date, viewed) + + # Convert datetime to a string + date = str(result['createdDate']) + result['createdDate'] = date + + date = str(result['updatedDate']) + result['updatedDate'] = date + + return result + + def calc_read(self, post_id, updatedDate, viewed): + """Modifies a dictionary representing a post by setting a new key-value + pair of either "read": True or "read": False depending on the contents + of the viewed dict. + + Args: + post (dict): Dictionary representing a post + viewed (dict): Dictionary representing the last time a user viewed the comments on a post. + Keys are post id strings, values are datetime objects. If post id is not a key in the dict, + the user has never viewed the post. + """ + return post_id in viewed and viewed[post_id] > updatedDate From 6e6eb17ee42c5fa1eccad2334d2e0e45649c1be6 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 10 Aug 2021 14:08:04 -0700 Subject: [PATCH 02/58] Skeleton work for frontend search is complete LazyFetch and search component were created. Still need to populate post feed correctly. --- client/src/components/common/SearchBar.js | 22 +++-- client/src/components/posts/Options.js | 4 +- client/src/components/posts/PostView.js | 110 ++++++++++++++------- client/src/components/posts/SearchPanel.js | 99 +++++++++++++++++++ server/inquire/resources/search.py | 8 ++ 5 files changed, 197 insertions(+), 46 deletions(-) create mode 100644 client/src/components/posts/SearchPanel.js diff --git a/client/src/components/common/SearchBar.js b/client/src/components/common/SearchBar.js index 8f6c1c1..9779579 100644 --- a/client/src/components/common/SearchBar.js +++ b/client/src/components/common/SearchBar.js @@ -10,17 +10,21 @@ import Icon from "../common/Icon"; * @param {string} placeholder placeholder text in the search textfield * @returns */ -const SearchBar = ({ onChange, placeholder }) => { +const SearchBar = ({ onChange, placeholder, displayIcon }) => { return ( - + {displayIcon ? ( + + ) : ( + <> + )} ); diff --git a/client/src/components/posts/Options.js b/client/src/components/posts/Options.js index f538269..6949bd4 100644 --- a/client/src/components/posts/Options.js +++ b/client/src/components/posts/Options.js @@ -103,13 +103,13 @@ const OptionsWrapper = styled.div` flex-grow: 1; position: absolute; right: -40px; - top: 0; + top: 120px; /* border: 1px solid red; */ `; const OptionsHeader = styled.h1` - margin: 3em 0 2em 0; + margin: 3em 0 1em 0; font-size: 14px; `; diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostView.js index ffdba31..1f22476 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostView.js @@ -15,6 +15,9 @@ import { Editor } from "react-draft-wysiwyg"; import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; import PollWrapper from "./refactorComponents/PollWrapper"; import EditorWrapper from "./refactorComponents/EditorWrapper"; +import SearchPanel from "./SearchPanel"; +import LazyFetch from "../common/requests/LazyFetch"; +import LoadingDots from "../common/animation/LoadingDots"; const convertToUpper = (postType) => { var first = postType[0].toUpperCase(); @@ -147,46 +150,83 @@ const PostView = ({ userRole, highlightedSection }) => { } let posts = generateSections(data, userRole, isCondensed); + // const [posts, setPosts] = useState([]); + // useEffect(() => { + // if (initialPosts) { + // setPosts(initialPosts); + // console.log("inside useEffect:", posts); + // } + // }, []); + + const handleSearch = (e) => { + // console.log(e.target.value); + LazyFetch({ + type: "get", + endpoint: "/courses/" + courseId + "/search?search=" + e.target.value, + onSuccess: (data) => { + console.log(data); + if (data.length > 0) { + // setPosts(generateSections(data, userRole, isCondensed)); + console.log("posts:", posts); + } else { + // setPosts(initialPosts); + } + }, + onFailure: (err) => { + console.log(err.response.data.errors); + }, + }); + }; + + console.log("outside return:", posts); return ( <> - - - - - - {posts.pinned.length > 0 && ( - - - Pinned Posts - - )} - {posts.pinned} - {posts.other.length > 0 && ( - All Posts - )} - {posts.other} - - - + {posts.length <= 0 ? ( + <> + ) : ( + + + + + + {posts.pinned.length > 0 && ( + + + Pinned Posts + + )} + {posts.pinned} + {posts.other.length > 0 && ( + All Posts + )} + {posts.other} + + + + + )} diff --git a/client/src/components/posts/SearchPanel.js b/client/src/components/posts/SearchPanel.js new file mode 100644 index 0000000..53791e4 --- /dev/null +++ b/client/src/components/posts/SearchPanel.js @@ -0,0 +1,99 @@ +import React, { useContext } from "react"; +import { UserContext } from "../context/UserProvider"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import Button from "../common/Button"; +import { Link } from "react-router-dom"; +import CogIcon from "../../imgs/settings 1.svg"; +import SearchBar from "../common/SearchBar"; +import LazyFetch from "../common/requests/LazyFetch"; + +/** + * Options Component ~ Button side panel for displaying buttons for the user + * + * @param {string} courseId given to the "+ New Post" button to route to the Post form page + */ +const SearchPanel = ({ courseId, onChangeCallback }) => { + const user = useContext(UserContext); + // console.log("User Object: ", user); + // console.log("OPTIONS User Role: ", userRole); + + // const handleSearch = (e) => { + // // console.log(e.target.value); + // LazyFetch({ + // type: "get", + // endpoint: "/courses/" + courseId + "/search?search=" + e.target.value, + // onSuccess: (data) => { + // console.log(data); + // }, + // onFailure: (err) => { + // console.log(err.response.data.errors); + // }, + // }); + // }; + + return ( + + SEARCH + + + + + ); +}; + +SearchPanel.propTypes = { + courseId: PropTypes.string, +}; + +export default SearchPanel; + +const OptionsWrapper = styled.div` + width: 280px; // Need to make same width as nav + menu bar + flex-grow: 1; + position: absolute; + right: -40px; + top: 0px; + + /* border: 1px solid red; */ +`; + +const OptionsHeader = styled.h1` + margin: 3em 0 1em 0; + + font-size: 14px; +`; + +const OptionsPanel = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #fff; + width: 220px; + padding: 14px; + border-radius: 5px; + + box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); +`; + +const TextInput = styled.input` + height: 100%; + border: none; + background-color: transparent; + width: 100%; + padding-left: 11px; + font-size: 16px; + + &:focus { + outline: none; + } + + &::selection { + background: #81818150; /* WebKit/Blink Browsers */ + } +`; diff --git a/server/inquire/resources/search.py b/server/inquire/resources/search.py index 5838849..710d271 100644 --- a/server/inquire/resources/search.py +++ b/server/inquire/resources/search.py @@ -1,3 +1,11 @@ +''' +This resource deals with search functionality so that users can search for specific posts. + +Author: Brian Gunnarson +Group Name: 5 Bits in a Byte + +Last Modified Date: 08/10/2021 +''' from flask import request from flask_restful import Resource from inquire.auth import current_user, permission_layer From 8cd897e807c5ed1875a572f665fb7844d3761ed9 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 17 Aug 2021 13:28:36 -0700 Subject: [PATCH 03/58] Searching for Posts now works new resource on backend for search set up and frontend work now complete. refactored a lot of PostView.js --- client/src/components/courses/CourseCard.js | 84 +++---- client/src/components/posts/PostView.js | 242 +++++++++++--------- client/src/components/posts/SearchPanel.js | 1 - server/inquire/resources/search.py | 2 + 4 files changed, 179 insertions(+), 150 deletions(-) diff --git a/client/src/components/courses/CourseCard.js b/client/src/components/courses/CourseCard.js index 0180362..1cd40aa 100644 --- a/client/src/components/courses/CourseCard.js +++ b/client/src/components/courses/CourseCard.js @@ -66,7 +66,7 @@ class CourseCard extends React.Component { endpoint: this.endpoint, data: { courseId: this.props.id, - color: colors.hex + color: colors.hex, }, onSuccess: (data) => { console.log(data.success); @@ -81,7 +81,7 @@ class CourseCard extends React.Component { endpoint: this.endpoint, data: { courseId: this.props.id, - nickname: nickname + nickname: nickname, }, onSuccess: (data) => { console.log(data.success); @@ -96,14 +96,14 @@ class CourseCard extends React.Component { endpoint: this.endpoint, data: { courseId: this.props.id, - removeNickname: true + removeNickname: true, }, onSuccess: (data) => { console.log(data.success); this.setState({ nickname: null }); - } + }, }); - } + }; handleNicknameChange = (e) => { this.setState({ nickname: e.target.value }); @@ -113,7 +113,7 @@ class CourseCard extends React.Component { if (e.key == "Enter") { this.sendNicknameRequest(e.target.value); } - } + }; // Track when new messages come in componentDidMount() { @@ -121,9 +121,8 @@ class CourseCard extends React.Component { //const newMsgs = {}; this.setState({ numMsgs: 0 }); } - + render() { - console.log("this.state.nickname:", this.state.nickname) return ( <> @@ -189,37 +188,42 @@ class CourseCard extends React.Component { - {!this.state.nicknameActive && ()} - {!this.state.nicknameActive && this.state.nickname && ()} {!this.state.nicknameActive && ( - )} + + )} + {!this.state.nicknameActive && this.state.nickname && ( + + )} + {!this.state.nicknameActive && ( + + )} {/* {this.state.nicknameActive && this.props.nickname && } */} {this.state.nicknameActive && ( @@ -234,7 +238,9 @@ class CourseCard extends React.Component { diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostView.js index 1f22476..bcfd453 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostView.js @@ -76,6 +76,17 @@ const generateSections = (data, userRole, isCondensed) => { return posts; }; +const fetchData = (endpoint, socketPosts, setData) => { + LazyFetch({ + type: "get", + endpoint: endpoint, + onSuccess: (response) => { + // console.log("get request data:", response); + setData([...socketPosts, ...response]); + }, + }); +}; + const PostView = ({ userRole, highlightedSection }) => { const user = useContext(UserContext); const [socketPosts, setSocketPosts] = useState([]); @@ -103,130 +114,141 @@ const PostView = ({ userRole, highlightedSection }) => { const [sortByMostRecent, toggleSort] = useState(true); // Retrieves the courseId from the url parameters const { courseId } = useParams(); - let endpoint = "/courses/" + courseId + "/posts"; + const baseEndpoint = "/courses/" + courseId + "/posts"; + const [endpoint, setEndpoint] = useState(baseEndpoint); - switch (highlightedSection) { - case "Instructor": - endpoint += "?filterby=instructor"; - break; - case "My Posts": - endpoint += "?filterby=me"; - break; - case "My Upvoted": - endpoint += "?filterby=myupvoted"; - break; - case "Announcements": - endpoint += "?filterby=announcement"; - break; - case "Questions": - endpoint += "?filterby=question"; - break; - case "Polls": - endpoint += "?filterby=poll"; - break; - case "General Posts": - endpoint += "?filterby=general"; - break; - default: - // Don't add a filter to endpoint - } + useEffect(() => { + // Sorting by post type + switch (highlightedSection) { + case "Instructor": + setEndpoint(baseEndpoint + "?filterby=instructor"); + break; + case "My Posts": + setEndpoint(baseEndpoint + "?filterby=me"); + break; + case "My Upvoted": + setEndpoint(baseEndpoint + "?filterby=myupvoted"); + break; + case "Announcements": + setEndpoint(baseEndpoint + "?filterby=announcement"); + break; + case "Questions": + setEndpoint(baseEndpoint + "?filterby=question"); + break; + case "Polls": + setEndpoint(baseEndpoint + "?filterby=poll"); + break; + case "General Posts": + setEndpoint(baseEndpoint + "?filterby=general"); + break; + default: + // Don't add a filter to endpoint + setEndpoint(baseEndpoint); + break; + } - if (!sortByMostRecent) { - if (highlightedSection !== "All Posts") { - endpoint += "&sortby=oldest"; - } else { - endpoint += "?sortby=oldest"; + // Sorting by date + if (!sortByMostRecent) { + if (highlightedSection !== "All Posts") { + setEndpoint(endpoint + "&sortby=oldest"); + } else { + setEndpoint(baseEndpoint + "?sortby=oldest"); + } } - } + }, [highlightedSection, sortByMostRecent]); - // Load posts from course - let { data, errors, loading } = Fetch({ - type: "get", - endpoint: endpoint, - }); - if (data) { - // console.log("DATA: ", data); - data = [...socketPosts, ...data]; - } + const [data, setData] = useState(null); + + // Initial Fetch of Data + useEffect(() => { + if (!data) { + fetchData(endpoint, socketPosts, setData); + } + }, []); + + // Fetch for endpoint change + useEffect(() => { + if (data) { + fetchData(endpoint, socketPosts, setData); + } + }, [endpoint]); - let posts = generateSections(data, userRole, isCondensed); - // const [posts, setPosts] = useState([]); - // useEffect(() => { - // if (initialPosts) { - // setPosts(initialPosts); - // console.log("inside useEffect:", posts); - // } - // }, []); + const [posts, setPosts] = useState(null); + const [initialPosts, setInitialPosts] = useState(posts); + + // Populate posts and store state of initial posts + useEffect(() => { + // console.log("data:", data); + if (data) { + let initialGeneratedPosts = generateSections(data, userRole, isCondensed); + setPosts(initialGeneratedPosts); + setInitialPosts(initialGeneratedPosts); + } + }, [data]); const handleSearch = (e) => { // console.log(e.target.value); - LazyFetch({ - type: "get", - endpoint: "/courses/" + courseId + "/search?search=" + e.target.value, - onSuccess: (data) => { - console.log(data); - if (data.length > 0) { - // setPosts(generateSections(data, userRole, isCondensed)); - console.log("posts:", posts); - } else { - // setPosts(initialPosts); - } - }, - onFailure: (err) => { - console.log(err.response.data.errors); - }, - }); + if (e.target.value != "") { + LazyFetch({ + type: "get", + endpoint: "/courses/" + courseId + "/search?search=" + e.target.value, + onSuccess: (searchData) => { + // console.log("onSuccess data:", searchData); + setPosts(generateSections(searchData, userRole, isCondensed)); + }, + onFailure: (err) => { + console.log(err.response.data.errors); + }, + }); + } else { + // console.log("initialPosts:", initialPosts); + setPosts(initialPosts); + } }; - console.log("outside return:", posts); - return ( <> - {posts.length <= 0 ? ( - <> - ) : ( - - - - - - {posts.pinned.length > 0 && ( - - - Pinned Posts - - )} - {posts.pinned} - {posts.other.length > 0 && ( - All Posts - )} - {posts.other} - - - - - )} + + + + + + {posts && posts.pinned.length > 0 && ( + + + Pinned Posts + + )} + {posts ? posts.pinned : <>} + {posts && posts.other.length > 0 && ( + All Posts + )} + {posts ? posts.other : <>} + + + + diff --git a/client/src/components/posts/SearchPanel.js b/client/src/components/posts/SearchPanel.js index 53791e4..0571f59 100644 --- a/client/src/components/posts/SearchPanel.js +++ b/client/src/components/posts/SearchPanel.js @@ -14,7 +14,6 @@ import LazyFetch from "../common/requests/LazyFetch"; * @param {string} courseId given to the "+ New Post" button to route to the Post form page */ const SearchPanel = ({ courseId, onChangeCallback }) => { - const user = useContext(UserContext); // console.log("User Object: ", user); // console.log("OPTIONS User Role: ", userRole); diff --git a/server/inquire/resources/search.py b/server/inquire/resources/search.py index 710d271..abe4db6 100644 --- a/server/inquire/resources/search.py +++ b/server/inquire/resources/search.py @@ -60,6 +60,8 @@ def get(self, courseId=None): queryParams = {"courseId": courseId} + # TODO: parallel query for title text; prioritize raw text over title + # If the current user can see private posts if user_perms["privacy"]["private"]: queryParams['$text'] = {'$search': search} From c04f348666de9ddea0072181a42f660a5dfdb1f4 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 17 Aug 2021 13:53:28 -0700 Subject: [PATCH 04/58] Fixed color on remove nickname button --- client/src/imgs/remove-nickname.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/imgs/remove-nickname.svg b/client/src/imgs/remove-nickname.svg index 77bd1ed..4d3e6e3 100644 --- a/client/src/imgs/remove-nickname.svg +++ b/client/src/imgs/remove-nickname.svg @@ -1,4 +1,4 @@ - - + + From 5089e4806359b8171bfbe3c1d006bfe48a3b1144 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 17 Aug 2021 18:10:47 -0700 Subject: [PATCH 05/58] Begin work on course search Backend completed (i think) and frontend started. F U CSS --- .../src/components/configPage/ConfigPanel.js | 1 + client/src/components/courses/JoinCourse.js | 6 +-- .../courses/joinCourse/CoursePanel.js | 45 +++++++++++++++++++ .../courses/joinCourse/JoinConfirmation.js | 32 ++++++++++--- server/inquire/resources/join.py | 31 +++++++++---- 5 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 client/src/components/courses/joinCourse/CoursePanel.js diff --git a/client/src/components/configPage/ConfigPanel.js b/client/src/components/configPage/ConfigPanel.js index 922fc52..cc9daaa 100644 --- a/client/src/components/configPage/ConfigPanel.js +++ b/client/src/components/configPage/ConfigPanel.js @@ -98,6 +98,7 @@ const createRoleObject = (itemId) => { * Generates a list of Role Components for State Management */ const GenerateRoleList = (roles, setRoles, userList, setUserList) => { + console.log("roles:", roles); return roles.map((role, index) => ( { const [modalIsShown, toggleModal] = useState(false); - const [course, joinCourse] = useState(null); + const [courses, joinCourse] = useState(null); const [display, toggleDisplay] = useState("flex"); return ( @@ -31,11 +31,11 @@ const JoinCourse = ({ courseList, setCourseList }) => { width={"724px"} data-testid={"join-course-modal"} > - {!course ? ( + {!courses ? ( ) : ( { + let displayBorder = false; + if (selectedCourse == courseId) { + displayBorder = true; + } + return ( + + Course:  + {courseName} +
+ Instructor:  + {instructorName} +
+ ); +}; + +export default CoursePanel; + +const CoursePanelWrapper = styled.div` + display: flex; + justify-content: space-evenly; + margin: 0.5rem 0; + background-color: white; + border: 2px solid + ${(props) => (props.displayBorder ? css`#4CAF50` : css`none`)}; + border-radius: 0.2rem; +`; + +const TextContent = styled.div` + font-weight: 400; + font-style: normal; +`; + +const Descriptor = styled.div` + opacity: 70%; +`; diff --git a/client/src/components/courses/joinCourse/JoinConfirmation.js b/client/src/components/courses/joinCourse/JoinConfirmation.js index 8c707a0..47eeb4b 100644 --- a/client/src/components/courses/joinCourse/JoinConfirmation.js +++ b/client/src/components/courses/joinCourse/JoinConfirmation.js @@ -7,6 +7,7 @@ import Errors from "../../common/Errors"; import CourseCard from "../CourseCard"; import { UserContext, UserDispatchContext } from "../../context/UserProvider"; import LazyFetch from "../../common/requests/LazyFetch"; +import CoursePanel from "./CoursePanel"; const AddNewCourseToList = (newCourse, courseList) => { if (newCourse === null) return; @@ -42,14 +43,27 @@ const AddNewCourseToList = (newCourse, courseList) => { return ret; }; +const GenerateCoursesList = (courses) => { + return courses.map((course, index) => ( + + )); +}; + const JoinConfirmation = ({ - course, + courses, joinCourse, display, toggleDisplay, courseList, setCourseList, }) => { + console.log("courseList:", courseList); const [loading, toggleLoading] = useState(false); const [errors, toggleErrors] = useState(null); const [success, toggleSuccess] = useState(null); @@ -65,7 +79,7 @@ const JoinConfirmation = ({ LazyFetch({ type: "put", endpoint: "/join", - data: { courseId: course.courseId }, + data: { courseId: courses.courseId }, onSuccess: (data) => { let newCourseList = AddNewCourseToList(data.course, courseList); if (newCourseList != null) setCourseList(newCourseList); @@ -97,6 +111,8 @@ const JoinConfirmation = ({ }, 1000); }; + let potentialCourses = GenerateCoursesList(courses); + return ( @@ -110,16 +126,17 @@ const JoinConfirmation = ({ - -
Course: 
{" "} - {course.course} + {potentialCourses} + {/* +
Course: 
+ {courses.course}
Instructor: 
- {course.first} {course.last} + {courses.first} {courses.last} -
+ */}
@@ -192,6 +209,7 @@ const HighlightedSection = styled.div` padding: 15px; border-radius: 4px; width: 100%; + overflow: auto; display: flex; justify-content: center; `; diff --git a/server/inquire/resources/join.py b/server/inquire/resources/join.py index db8d4c9..2c42ba8 100644 --- a/server/inquire/resources/join.py +++ b/server/inquire/resources/join.py @@ -41,8 +41,8 @@ def post(self): 200: description: Returns additional course information schema: - type: object - properties: + type: list of objects + object properties: courseName: type: string description: Name of the course @@ -80,11 +80,8 @@ def post(self): if count == 0: return {'errors': [f"Course with the name {args['courseName']} does not exist"]}, 400 else: - course = query.first() - instructor = User.objects.raw( - {"_id": course.instructorID}).first() - - return {"courseId": course._id, "course": course.course, "first": instructor.first, "last": instructor.last}, 200 + result = self.format(query) + return result # Access code sent to backend else: @@ -96,7 +93,7 @@ def post(self): instructor = User.objects.raw({"_id": course.instructorID}).first() - return {"courseId": course._id, "course": course.course, "first": instructor.first, "last": instructor.last}, 200 + return [{"courseId": course._id, "course": course.course, "first": instructor.first, "last": instructor.last}], 200 def put(self): """ @@ -167,3 +164,21 @@ def put(self): result = user_course.to_son() return {"success": ["Course joined successfully"], "course": result}, 200 + + def format(self, query): + ''' + courseID + course (the name) + first + last + ''' + result = [] + for course in query: + data = {"courseId": course._id, "course": course.course, + "first": None, "last": None} + instructor = User.objects.raw( + {"_id": course.instructorID}).first() + data["first"] = instructor.first + data["last"] = instructor.last + result.append(data) + return result From 1ff745e979929a04545c8bcb11ce65b9cd31d27e Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Tue, 17 Aug 2021 18:37:28 -0700 Subject: [PATCH 06/58] Frontend functionality complete, just need styling Can now join a course from a list of courses but the styling for that list is still not right. --- .../courses/joinCourse/CoursePanel.js | 25 +++++++++++++++---- .../courses/joinCourse/JoinConfirmation.js | 16 +++++++++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/client/src/components/courses/joinCourse/CoursePanel.js b/client/src/components/courses/joinCourse/CoursePanel.js index 027923d..4a5ecd4 100644 --- a/client/src/components/courses/joinCourse/CoursePanel.js +++ b/client/src/components/courses/joinCourse/CoursePanel.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import styled, { css } from "styled-components"; const CoursePanel = ({ @@ -6,14 +6,24 @@ const CoursePanel = ({ courseName, instructorName, selectedCourse, + setSelectedCourse, ...props }) => { let displayBorder = false; if (selectedCourse == courseId) { displayBorder = true; } + + const handleCourseSelect = () => { + console.log("you pressed " + courseName + " with courseId: " + courseId); + setSelectedCourse(courseId); + }; + return ( - + Course:  {courseName}
@@ -25,13 +35,18 @@ const CoursePanel = ({ export default CoursePanel; -const CoursePanelWrapper = styled.div` +const CoursePanelWrapper = styled.button` display: flex; justify-content: space-evenly; margin: 0.5rem 0; background-color: white; - border: 2px solid - ${(props) => (props.displayBorder ? css`#4CAF50` : css`none`)}; + border: none; + cursor: pointer; + ${(props) => + props.displayBorder && + css` + border: 2px solid #4caf50; + `} border-radius: 0.2rem; `; diff --git a/client/src/components/courses/joinCourse/JoinConfirmation.js b/client/src/components/courses/joinCourse/JoinConfirmation.js index 47eeb4b..70ee1f7 100644 --- a/client/src/components/courses/joinCourse/JoinConfirmation.js +++ b/client/src/components/courses/joinCourse/JoinConfirmation.js @@ -43,14 +43,16 @@ const AddNewCourseToList = (newCourse, courseList) => { return ret; }; -const GenerateCoursesList = (courses) => { +const GenerateCoursesList = (courses, selectedCourse, setSelectedCourse) => { + // const [selectedCourse, setSelectedCourse] = useState(null); return courses.map((course, index) => ( )); }; @@ -79,8 +81,9 @@ const JoinConfirmation = ({ LazyFetch({ type: "put", endpoint: "/join", - data: { courseId: courses.courseId }, + data: { courseId: selectedCourse }, onSuccess: (data) => { + console.log("RESPONSE LENGTH:", data.length); let newCourseList = AddNewCourseToList(data.course, courseList); if (newCourseList != null) setCourseList(newCourseList); joinCourse(data.course); @@ -111,7 +114,12 @@ const JoinConfirmation = ({ }, 1000); }; - let potentialCourses = GenerateCoursesList(courses); + const [selectedCourse, setSelectedCourse] = useState(null); + + // Only call GenerateCoursesList if the return value is an array + let potentialCourses = courses.length + ? GenerateCoursesList(courses, selectedCourse, setSelectedCourse) + : null; return ( From 01ab28f4472b994ef06dd540f49c2cada1eeb65b Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Wed, 18 Aug 2021 10:42:20 -0700 Subject: [PATCH 07/58] Icon on course cards now takes user to drafting posts for that specific course --- client/src/components/comments/CommentView.js | 9 +++---- client/src/components/courses/CourseCard.js | 25 ++++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/client/src/components/comments/CommentView.js b/client/src/components/comments/CommentView.js index 753913d..30dfbeb 100644 --- a/client/src/components/comments/CommentView.js +++ b/client/src/components/comments/CommentView.js @@ -1,9 +1,6 @@ import React, { useContext, useEffect, useState, useMemo, useRef } from "react"; import { Link, useHistory, useLocation, useParams } from "react-router-dom"; -import { - UserRoleContext, - UserRoleDispatchContext, -} from "../context/UserRoleProvider"; +import { UserRoleContext } from "../context/UserRoleProvider"; import styled from "styled-components"; import PostWrapper from "../posts/refactorComponents/PostWrapper"; import Sidebar from "../posts/Sidebar"; @@ -239,7 +236,9 @@ const CommentView = ({ classroomName }) => { )} - {postid === "newQorA" && } + {postid === "newQorA" && userRole && ( + + )} {/* {postid === "newPoll" && } */} {postid === "newPoll" && } {postExists && diff --git a/client/src/components/courses/CourseCard.js b/client/src/components/courses/CourseCard.js index 1cd40aa..aca2e8d 100644 --- a/client/src/components/courses/CourseCard.js +++ b/client/src/components/courses/CourseCard.js @@ -128,21 +128,16 @@ class CourseCard extends React.Component { - - alert( - 'You clicked the Unread Messages icon for "' + - this.props.courseName + - '".\nThis feature is a work in progress.' - ) - } - > + + + {this.state.numMsgs > 0 && this.state.numMsgs ? (

{this.state.numMsgs} From 391111a0773f4fdae56cecf474b01f67cb4c9376 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Wed, 18 Aug 2021 11:10:32 -0700 Subject: [PATCH 08/58] Course search now fully functional --- .../courses/joinCourse/CoursePanel.js | 17 +++++-- .../courses/joinCourse/JoinConfirmation.js | 47 ++----------------- server/inquire/resources/join.py | 3 ++ 3 files changed, 21 insertions(+), 46 deletions(-) diff --git a/client/src/components/courses/joinCourse/CoursePanel.js b/client/src/components/courses/joinCourse/CoursePanel.js index 4a5ecd4..871631b 100644 --- a/client/src/components/courses/joinCourse/CoursePanel.js +++ b/client/src/components/courses/joinCourse/CoursePanel.js @@ -15,7 +15,6 @@ const CoursePanel = ({ } const handleCourseSelect = () => { - console.log("you pressed " + courseName + " with courseId: " + courseId); setSelectedCourse(courseId); }; @@ -23,11 +22,11 @@ const CoursePanel = ({ Course:  {courseName} -
- Instructor:  + Instructor:  {instructorName}
); @@ -37,11 +36,13 @@ export default CoursePanel; const CoursePanelWrapper = styled.button` display: flex; - justify-content: space-evenly; margin: 0.5rem 0; background-color: white; border: none; + width: 600px; + height: 50px; cursor: pointer; + padding: 0em 6em; ${(props) => props.displayBorder && css` @@ -51,10 +52,18 @@ const CoursePanelWrapper = styled.button` `; const TextContent = styled.div` + display: flex; + align-items: center; + justify-content: center; font-weight: 400; font-style: normal; + height: 50px; `; const Descriptor = styled.div` + display: flex; + align-items: center; + justify-content: center; opacity: 70%; + height: 50px; `; diff --git a/client/src/components/courses/joinCourse/JoinConfirmation.js b/client/src/components/courses/joinCourse/JoinConfirmation.js index 70ee1f7..c9ee9b6 100644 --- a/client/src/components/courses/joinCourse/JoinConfirmation.js +++ b/client/src/components/courses/joinCourse/JoinConfirmation.js @@ -133,19 +133,7 @@ const JoinConfirmation = ({ {success} - - {potentialCourses} - {/* -
Course: 
- {courses.course} -
- -
Instructor: 
- - {courses.first} {courses.last} - -
*/} -
+ {potentialCourses}
+ + + {/*

*/} + + + ); +}; + +export default UserCourseCard; + +const CardWrapper = styled.div` + min-width: 8rem; + min-height: 6rem; + + margin: 1em; + /* padding: 1em; */ + + box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); + border-left: 4px solid + ${(props) => + css` + ${props?.courseColor} + `}; + border-radius: 5px; +`; + +const CourseTitle = styled.h4` + display: flex; + /* justify-content: center; */ + align-items: center; + flex-direction: row; + + width: 100%; + height: 100%; + + text-align: left; + + text-shadow: 0px 1px 4px rgba(0, 0, 0, 0.25); +`; + +const CourseSubtitle = styled.h6` + color: #979797; + text-align: left; +`; + +const UpperSection = styled.div` + display: flex; + justify-content: center; + /* align-items: center; */ + flex-direction: column; + + width: 100%; + /* height: 50%; */ + + padding: 1em 1em; + + /* border: 1px solid red; */ + border-radius: 5px 5px 5px 5px; + /* background-color: ${(props) => + css` + ${props?.courseColor} + `}; */ +`; + +const LowerSection = styled.div` + width: 100%; + /* height: 50%; */ + + padding: 0em 1em 1em 1em; + + /* border: 1px solid green; */ + border-radius: 5px 5px 5px 5px; +`; diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js new file mode 100644 index 0000000..6ea216d --- /dev/null +++ b/client/src/components/userProfile/UserCourses.js @@ -0,0 +1,54 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import UserCourseCard from "./UserCourseCard"; + +const UserCourses = ({ userObject, ...props }) => { + const generateUserCourseCards = (courseList) => { + console.log(courseList); + + let userCourseCards = courseList.map((course, index) => ( + + )); + + return userCourseCards; + }; + + return ( + <> + +

Courses:

+ + {generateUserCourseCards(userObject.courses)} + +
+ + ); +}; + +export default UserCourses; + +const SectionWrapper = styled.div` + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + min-height: 100px; + margin: 1em; + padding: 1em 2em; + + background-color: #fff; + border-radius: 10px; + + box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); +`; + +const CardsContainer = styled.div` + display: flex; + align-items: center; + /* justify-content: center; */ + flex-wrap: wrap; + + overflow-y: scroll; + + transition: 150ms ease-out; +`; diff --git a/client/src/components/userProfile/UserProfile.js b/client/src/components/userProfile/UserProfile.js index c0c661d..ea1c736 100644 --- a/client/src/components/userProfile/UserProfile.js +++ b/client/src/components/userProfile/UserProfile.js @@ -2,6 +2,7 @@ import React, { useContext } from "react"; import styled, { css } from "styled-components"; import { UserContext } from "../context/UserProvider"; import AboutUser from "./AboutUser"; +import UserCourses from "./UserCourses"; const UserProfile = ({ props }) => { const user = useContext(UserContext); @@ -10,6 +11,7 @@ const UserProfile = ({ props }) => { + {/*

{JSON.stringify(user, null, 2)}

*/} {/* KEEP THE OVERFLOW COUNTER IT HELPS WITH OVERFLOW at the bottom of the scrolling div. */} From 55fac775241cd60a6901b721198f2d18fc520814 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Wed, 18 Aug 2021 13:32:18 -0700 Subject: [PATCH 12/58] About section can now be updated --- .../src/components/userProfile/AboutUser.js | 137 +++++++++++++++--- .../src/components/userProfile/UserCourses.js | 2 +- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index 361e67c..bffd6d9 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -1,10 +1,61 @@ -import React, { useContext } from "react"; +import React, { useContext, useState, useEffect } from "react"; import styled, { css } from "styled-components"; import Button from "../common/Button"; import { UserContext } from "../context/UserProvider"; import DraftTextArea from "../common/DraftTextArea"; +import LazyFetch from "../common/requests/LazyFetch"; const AboutUser = ({ userObject, ...props }) => { + const user = useContext(UserContext); + const [editingProfile, toggleEdit] = useState(false); + const [aboutMe, setAboutMe] = useState(null); + const [initialAboutMe, setInitialAboutMe] = useState(null); + const [initialBannerColor, setInitialBannerColor] = useState(null); + const [bannerColor, setBannerColor] = useState(null); + let endpoint = "/userProfiles"; + + useEffect(() => { + LazyFetch({ + type: "get", + endpoint: endpoint, + onSuccess: (response) => { + console.log("response:", response); + setAboutMe(response.profileData.about); + setInitialAboutMe(response.profileData.about); + setBannerColor(response.profileData.bannerColor); + setInitialBannerColor(response.profileData.bannerColor); + }, + }); + }, []); + + const handleAboutSubmit = () => { + LazyFetch({ + type: "put", + endpoint: endpoint, + data: { + userId: user._id, + about: aboutMe, + }, + onSuccess: (response) => { + console.log("response:", response); + }, + }); + }; + + const handleColorChange = (colors) => { + LazyFetch({ + type: "put", + endpoint: endpoint, + data: { + userId: "", + color: colors.hex, + }, + onSuccess: (data) => { + console.log(data.success); + this.setState({ courseColor: colors.hex }); + }, + }); + }; return ( <> @@ -18,31 +69,67 @@ const AboutUser = ({ userObject, ...props }) => { /> - + {editingProfile ? ( + + ) : ( + + )} {userObject.first + " " + userObject.last}

About

- + {editingProfile ? ( +
+ { + setAboutMe(e.target.value); + }} + > + + + +
+ ) : ( + {aboutMe} + )}
- +
); @@ -55,7 +142,6 @@ const Wrapper = styled.div` overflow: hidden; display: flex; align-items: center; - /* width: 100%; */ height: 300px; margin: 1em; padding: 1em; @@ -66,6 +152,16 @@ const Wrapper = styled.div` box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); `; +const AboutText = styled.div` + width: 150%; + white-space: initial; +`; + +const ButtonWrapper = styled.div` + display: flex; + justify-content: flex-end; +`; + const ContentWrapper = styled.div` z-index: 9998; @@ -80,7 +176,8 @@ const CustomColorSection = styled.div` width: 100%; height: 112px; - background-color: #4a86fa; + background-color: ${(props) => + props.bannerColor ? props.bannerColor : css`#4A86FA`}; `; const UserInfoWrapper = styled.div` diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js index 6ea216d..08f9e30 100644 --- a/client/src/components/userProfile/UserCourses.js +++ b/client/src/components/userProfile/UserCourses.js @@ -16,7 +16,7 @@ const UserCourses = ({ userObject, ...props }) => { return ( <> -

Courses:

+

Courses

{generateUserCourseCards(userObject.courses)} From 19b67bd58731f36862a255dda1f5304ffe646dbb Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Wed, 18 Aug 2021 14:55:41 -0700 Subject: [PATCH 13/58] Color selector in user profile now works The color selector now sets the banner color for your profile and dynamically changes color based on light/dark backgrounds. --- .../src/components/userProfile/AboutUser.js | 95 ++++++++++++++++--- .../src/components/userProfile/UserCourses.js | 9 +- client/src/imgs/color-palette-white.svg | 3 + 3 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 client/src/imgs/color-palette-white.svg diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index bffd6d9..05b56f1 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -4,14 +4,18 @@ import Button from "../common/Button"; import { UserContext } from "../context/UserProvider"; import DraftTextArea from "../common/DraftTextArea"; import LazyFetch from "../common/requests/LazyFetch"; +import { ChromePicker } from "react-color"; +import Icon from "../common/Icon"; +import LightColorImg from "../../imgs/color-palette-white.svg"; +import DarkColorImg from "../../imgs/color-palette.svg"; const AboutUser = ({ userObject, ...props }) => { const user = useContext(UserContext); const [editingProfile, toggleEdit] = useState(false); const [aboutMe, setAboutMe] = useState(null); const [initialAboutMe, setInitialAboutMe] = useState(null); - const [initialBannerColor, setInitialBannerColor] = useState(null); const [bannerColor, setBannerColor] = useState(null); + const [displayColorSelector, toggleColorDisplay] = useState(false); let endpoint = "/userProfiles"; useEffect(() => { @@ -23,7 +27,6 @@ const AboutUser = ({ userObject, ...props }) => { setAboutMe(response.profileData.about); setInitialAboutMe(response.profileData.about); setBannerColor(response.profileData.bannerColor); - setInitialBannerColor(response.profileData.bannerColor); }, }); }, []); @@ -43,23 +46,53 @@ const AboutUser = ({ userObject, ...props }) => { }; const handleColorChange = (colors) => { + setBannerColor(colors.hex); + }; + + const submitColorChange = (colors) => { LazyFetch({ type: "put", endpoint: endpoint, data: { - userId: "", - color: colors.hex, + userId: user._id, + bannerColor: colors.hex, }, onSuccess: (data) => { console.log(data.success); - this.setState({ courseColor: colors.hex }); + setBannerColor(colors.hex); }, }); }; + + // This code was obtained from https://awik.io/determine-color-bright-dark-using-javascript/ + const lightOrDark = () => { + var r, g, b, colorVal; + var color = bannerColor; + + // Convert hex value to integer + color = +("0x" + color.slice(1).replace(color.length < 5 && /./g, "$&$&")); + + // Bit manipulation to obtain rgb values + r = color >> 16; + g = (color >> 8) & 255; + b = color & 255; + + // Get a value between 0 and 255 using rgb values + colorVal = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); + + // Determine if the color is light or dark + if (colorVal > 127.5) { + return "light"; + } else { + return "dark"; + } + }; + + var background = bannerColor ? lightOrDark() : null; + return ( <> - {/*

{JSON.stringify(userObject, null, 2)}

*/} @@ -77,7 +110,7 @@ const AboutUser = ({ userObject, ...props }) => { onClick={() => { toggleEdit(false); setAboutMe(initialAboutMe); - setBannerColor(initialBannerColor); + toggleColorDisplay(false); }} > Cancel @@ -88,7 +121,6 @@ const AboutUser = ({ userObject, ...props }) => { buttonWidth={"10em"} buttonHeight={"2em"} onClick={() => { - // alert("This feature is in progress."); toggleEdit(!editingProfile); }} > @@ -98,12 +130,13 @@ const AboutUser = ({ userObject, ...props }) => { {userObject.first + " " + userObject.last} -

About

+

About

{editingProfile ? (
{ setAboutMe(e.target.value); @@ -129,7 +162,43 @@ const AboutUser = ({ userObject, ...props }) => { - + + {displayColorSelector && ( + + )} + {background == "dark" ? ( + { + toggleColorDisplay(!displayColorSelector); + }} + /> + ) : ( + { + toggleColorDisplay(!displayColorSelector); + }} + /> + )} + ); @@ -160,6 +229,7 @@ const AboutText = styled.div` const ButtonWrapper = styled.div` display: flex; justify-content: flex-end; + padding: 0.5em 0 0 0; `; const ContentWrapper = styled.div` @@ -175,6 +245,9 @@ const CustomColorSection = styled.div` left: 0; width: 100%; height: 112px; + display: flex; + justify-content: flex-end; + align-items: flex-start; background-color: ${(props) => props.bannerColor ? props.bannerColor : css`#4A86FA`}; diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js index 08f9e30..be07475 100644 --- a/client/src/components/userProfile/UserCourses.js +++ b/client/src/components/userProfile/UserCourses.js @@ -13,12 +13,19 @@ const UserCourses = ({ userObject, ...props }) => { return userCourseCards; }; + var courses = generateUserCourseCards(userObject.courses); + return ( <>

Courses

- {generateUserCourseCards(userObject.courses)} + {courses.length > 0 + ? courses + : userObject.first + + " " + + userObject.last + + " is not currently enrolled in any courses."}
diff --git a/client/src/imgs/color-palette-white.svg b/client/src/imgs/color-palette-white.svg new file mode 100644 index 0000000..de2b0da --- /dev/null +++ b/client/src/imgs/color-palette-white.svg @@ -0,0 +1,3 @@ + + + From 33ec43808b7191a8b23fcc10eea5292c0fe60784 Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Fri, 20 Aug 2021 12:49:45 -0700 Subject: [PATCH 14/58] Removed refactor folder from ./posts/ --- client/src/components/comments/Comment.js | 48 ++- .../src/components/comments/CommentReply.js | 62 ++- client/src/components/comments/CommentView.js | 10 +- .../MaterialDropDownGroup.js | 1 - .../roleConfigComponents/RolePanel.js | 3 - client/src/components/home/Home.js | 8 +- client/src/components/posts/ClassView.js | 2 - .../posts/{refactorComponents => }/Draft.js | 54 +-- .../{refactorComponents => }/EditorWrapper.js | 66 ++-- .../{refactorComponents => }/OptionsPanel.js | 2 +- .../{refactorComponents => }/PollConfig.js | 16 +- .../{refactorComponents => }/PollWrapper.js | 4 +- client/src/components/posts/Post.js | 371 ------------------ .../{refactorComponents => }/PostRefactor.js | 2 +- client/src/components/posts/PostView.js | 7 +- .../{refactorComponents => }/PostWrapper.js | 18 +- 16 files changed, 180 insertions(+), 494 deletions(-) rename client/src/components/posts/{refactorComponents => }/Draft.js (89%) rename client/src/components/posts/{refactorComponents => }/EditorWrapper.js (82%) rename client/src/components/posts/{refactorComponents => }/OptionsPanel.js (98%) rename client/src/components/posts/{refactorComponents => }/PollConfig.js (96%) rename client/src/components/posts/{refactorComponents => }/PollWrapper.js (93%) delete mode 100644 client/src/components/posts/Post.js rename client/src/components/posts/{refactorComponents => }/PostRefactor.js (99%) rename client/src/components/posts/{refactorComponents => }/PostWrapper.js (95%) diff --git a/client/src/components/comments/Comment.js b/client/src/components/comments/Comment.js index e4bb6d4..a6c8e22 100644 --- a/client/src/components/comments/Comment.js +++ b/client/src/components/comments/Comment.js @@ -12,7 +12,7 @@ import Icon from "../common/Icon"; import OptionDots from "../../imgs/option-dots.svg"; import { UserContext } from "../context/UserProvider"; import { UserRoleContext } from "../context/UserRoleProvider"; -import EditorWrapper from "../posts/refactorComponents/EditorWrapper"; +import EditorWrapper from "../posts/EditorWrapper"; import { Editor } from "react-draft-wysiwyg"; import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; import Checkbox from "../common/Checkbox"; @@ -47,22 +47,20 @@ const Comment = ({ comment, isDraft, callback }) => { }; const imageCallback = async (file) => { - return new Promise( - (resolve, reject) => { - const formData = new FormData(); - formData.append("imageFile", file) - - LazyFetch({ - type: "post", - endpoint: "/images", - data: formData, - onSuccess: (data) => { - resolve({ data: { link: data.data.link } }); - } - }); - } - ); - } + return new Promise((resolve, reject) => { + const formData = new FormData(); + formData.append("imageFile", file); + + LazyFetch({ + type: "post", + endpoint: "/images", + data: formData, + onSuccess: (data) => { + resolve({ data: { link: data.data.link } }); + }, + }); + }); + }; const renderContent = () => { if (isDraft) { @@ -77,8 +75,20 @@ const Comment = ({ comment, isDraft, callback }) => { }} onEditorStateChange={handleContentChange} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} /> ); diff --git a/client/src/components/comments/CommentReply.js b/client/src/components/comments/CommentReply.js index 70c3c09..362ad38 100644 --- a/client/src/components/comments/CommentReply.js +++ b/client/src/components/comments/CommentReply.js @@ -9,7 +9,7 @@ import Dropdown from "../common/dropdown/Dropdown"; import Icon from "../common/Icon"; import OptionDots from "../../imgs/option-dots.svg"; import { Editor } from "react-draft-wysiwyg"; -import EditorWrapper from "../posts/refactorComponents/EditorWrapper"; +import EditorWrapper from "../posts/EditorWrapper"; import { EditorState } from "draft-js"; import Checkbox from "../common/Checkbox"; import { UserRoleContext } from "../context/UserRoleProvider"; @@ -48,22 +48,20 @@ const CommentReply = ({ }; const imageCallback = async (file) => { - return new Promise( - (resolve, reject) => { - const formData = new FormData(); - formData.append("imageFile", file) + return new Promise((resolve, reject) => { + const formData = new FormData(); + formData.append("imageFile", file); - LazyFetch({ - type: "post", - endpoint: "/images", - data: formData, - onSuccess: (data) => { - resolve({ data: { link: data.data.link } }); - } - }); - } - ); - } + LazyFetch({ + type: "post", + endpoint: "/images", + data: formData, + onSuccess: (data) => { + resolve({ data: { link: data.data.link } }); + }, + }); + }); + }; if (isDraft) { reply = { @@ -80,8 +78,20 @@ const CommentReply = ({ }} onEditorStateChange={handleContentChange} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} /> ), @@ -169,8 +179,20 @@ const CommentReply = ({ }} onEditorStateChange={handleContentChange} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} /> ); diff --git a/client/src/components/comments/CommentView.js b/client/src/components/comments/CommentView.js index 30dfbeb..9b3ad56 100644 --- a/client/src/components/comments/CommentView.js +++ b/client/src/components/comments/CommentView.js @@ -2,17 +2,17 @@ import React, { useContext, useEffect, useState, useMemo, useRef } from "react"; import { Link, useHistory, useLocation, useParams } from "react-router-dom"; import { UserRoleContext } from "../context/UserRoleProvider"; import styled from "styled-components"; -import PostWrapper from "../posts/refactorComponents/PostWrapper"; +import PostWrapper from "../posts/PostWrapper"; import Sidebar from "../posts/Sidebar"; import Button from "../common/Button"; import Comment from "./Comment"; import LazyFetch from "../common/requests/LazyFetch"; import { UserContext } from "../context/UserProvider"; import io from "../../services/socketio"; -import Draft from "../posts/refactorComponents/Draft"; -import PollConfig from "../posts/refactorComponents/PollConfig"; -import PollWrapper from "../posts/refactorComponents/PollWrapper"; -import EditorWrapper from "../posts/refactorComponents/EditorWrapper"; +import Draft from "../posts/Draft"; +import PollConfig from "../posts/PollConfig"; +import PollWrapper from "../posts/PollWrapper"; +import EditorWrapper from "../posts/EditorWrapper"; import { convertToRaw } from "draft-js"; const renderComments = (data, userRole) => { diff --git a/client/src/components/configPage/roleConfigComponents/MaterialDropDownGroup.js b/client/src/components/configPage/roleConfigComponents/MaterialDropDownGroup.js index c882770..ec3ffb8 100644 --- a/client/src/components/configPage/roleConfigComponents/MaterialDropDownGroup.js +++ b/client/src/components/configPage/roleConfigComponents/MaterialDropDownGroup.js @@ -1,5 +1,4 @@ import React, { useState } from "react"; -import PropTypes from "prop-types"; import styled from "styled-components"; import Button from "../../common/Button"; import Menu from "@material-ui/core/Menu"; diff --git a/client/src/components/configPage/roleConfigComponents/RolePanel.js b/client/src/components/configPage/roleConfigComponents/RolePanel.js index 5146c8e..bd65aff 100644 --- a/client/src/components/configPage/roleConfigComponents/RolePanel.js +++ b/client/src/components/configPage/roleConfigComponents/RolePanel.js @@ -5,10 +5,7 @@ import styled, { css } from "styled-components"; import Button from "../../common/Button"; import PencilIcon from "../../../imgs/pencil.svg"; import CloseButtonIcon from "../../../imgs/close.svg"; -import Input from "../../common/Input"; import DraftTextArea from "../../common/DraftTextArea"; -import Dropdown from "../../common/dropdown/Dropdown"; -import Arrow from "../../../imgs/carrot-down-secondary.svg"; import MaterialDropDownGroup from "./MaterialDropDownGroup"; import UserPanel from "../userConfigComponents/UserPanel"; diff --git a/client/src/components/home/Home.js b/client/src/components/home/Home.js index cd71856..09cead3 100644 --- a/client/src/components/home/Home.js +++ b/client/src/components/home/Home.js @@ -2,12 +2,12 @@ import React, { useState } from "react"; import styled from "styled-components"; import RecentGroup from "./RecentGroup"; import Fetch from "../common/requests/Fetch"; -import Post from "../posts/Post"; +// import Post from "../posts/Post"; import { Editor } from "react-draft-wysiwyg"; import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; -import PostWrapper from "../posts/refactorComponents/PostWrapper"; -import PollWrapper from "../posts/refactorComponents/PollWrapper"; -import EditorWrapper from "../posts/refactorComponents/EditorWrapper"; +import PostWrapper from "../posts/PostWrapper"; +import PollWrapper from "../posts/PollWrapper"; +import EditorWrapper from "../posts/EditorWrapper"; const convertToUpper = (postType) => { var first = postType[0].toUpperCase(); diff --git a/client/src/components/posts/ClassView.js b/client/src/components/posts/ClassView.js index a50823d..92b7c24 100644 --- a/client/src/components/posts/ClassView.js +++ b/client/src/components/posts/ClassView.js @@ -9,9 +9,7 @@ import PropTypes from "prop-types"; import styled from "styled-components"; import SectionTab from "./SectionTab"; import Sidebar from "./Sidebar"; -import LazyFetch from "../common/requests/LazyFetch"; import LoadingDots from "../common/animation/LoadingDots"; -import PostRefactor from "./refactorComponents/PostRefactor"; const PostView = React.lazy(() => import("./PostView")); diff --git a/client/src/components/posts/refactorComponents/Draft.js b/client/src/components/posts/Draft.js similarity index 89% rename from client/src/components/posts/refactorComponents/Draft.js rename to client/src/components/posts/Draft.js index 6a8a88b..6f5f78d 100644 --- a/client/src/components/posts/refactorComponents/Draft.js +++ b/client/src/components/posts/Draft.js @@ -1,10 +1,10 @@ import React, { useState } from "react"; import { useParams } from "react-router-dom"; import styled from "styled-components"; -import DraftTextArea from "../../common/DraftTextArea"; -import Checkbox from "../../common/Checkbox"; -import Button from "../../common/Button"; -import LazyFetch from "../../common/requests/LazyFetch"; +import DraftTextArea from "../common/DraftTextArea"; +import Checkbox from "../common/Checkbox"; +import Button from "../common/Button"; +import LazyFetch from "../common/requests/LazyFetch"; import { Editor } from "react-draft-wysiwyg"; import { convertToRaw, EditorState } from "draft-js"; import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css"; @@ -31,7 +31,7 @@ const Draft = ({ userRole }) => { const [draft, setDraft] = useState({ title: "", isAnonymous: false, - isPrivate: false + isPrivate: false, }); var defaultType; @@ -93,22 +93,20 @@ const Draft = ({ userRole }) => { }; const imageCallback = async (file) => { - return new Promise( - (resolve, reject) => { - const formData = new FormData(); - formData.append("imageFile", file) + return new Promise((resolve, reject) => { + const formData = new FormData(); + formData.append("imageFile", file); - LazyFetch({ - type: "post", - endpoint: "/images", - data: formData, - onSuccess: (data) => { - resolve({ data: { link: data.data.link } }); - } - }); - } - ); - } + LazyFetch({ + type: "post", + endpoint: "/images", + data: formData, + onSuccess: (data) => { + resolve({ data: { link: data.data.link } }); + }, + }); + }); + }; // Styling Variables var titlePlaceholder = @@ -196,8 +194,20 @@ const Draft = ({ userRole }) => { // placeholder="Details" onEditorStateChange={handleContentChange} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} /> diff --git a/client/src/components/posts/refactorComponents/EditorWrapper.js b/client/src/components/posts/EditorWrapper.js similarity index 82% rename from client/src/components/posts/refactorComponents/EditorWrapper.js rename to client/src/components/posts/EditorWrapper.js index 6f63b8f..6c7905d 100644 --- a/client/src/components/posts/refactorComponents/EditorWrapper.js +++ b/client/src/components/posts/EditorWrapper.js @@ -2,8 +2,8 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; import { Editor } from "react-draft-wysiwyg"; import { EditorState, convertFromRaw, convertToRaw } from "draft-js"; -import Button from "../../common/Button"; -import LazyFetch from "../../common/requests/LazyFetch"; +import Button from "../common/Button"; +import LazyFetch from "../common/requests/LazyFetch"; import { useParams } from "react-router"; const generateStyle = (edit, postid, messageType) => { @@ -132,22 +132,20 @@ const EditorWrapper = ({ messageData, messageType, edit, commentId }) => { }; const imageCallback = async (file) => { - return new Promise( - (resolve, reject) => { - const formData = new FormData(); - formData.append("imageFile", file) - - LazyFetch({ - type: "post", - endpoint: "/images", - data: formData, - onSuccess: (data) => { - resolve({ data: { link: data.data.link } }); - } - }); - } - ); - } + return new Promise((resolve, reject) => { + const formData = new FormData(); + formData.append("imageFile", file); + + LazyFetch({ + type: "post", + endpoint: "/images", + data: formData, + onSuccess: (data) => { + resolve({ data: { link: data.data.link } }); + }, + }); + }); + }; /** This variable is used to determine whether or not to force a maximum height for this containing element. */ var editorStyle = generateStyle(edit, postid, messageType); @@ -160,8 +158,20 @@ const EditorWrapper = ({ messageData, messageType, edit, commentId }) => { editorState={editorStateTest} editorStyle={editorStyle} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} onEditorStateChange={handleContentChange} /> @@ -173,8 +183,20 @@ const EditorWrapper = ({ messageData, messageType, edit, commentId }) => { editorState={editorStateTest} editorStyle={editorStyle} toolbar={{ - options: ["inline", "list", "link", "emoji", "history", "blockType", "image"], - image: { uploadCallback: imageCallback, uploadEnabled: true, previewImage: true } + options: [ + "inline", + "list", + "link", + "emoji", + "history", + "blockType", + "image", + ], + image: { + uploadCallback: imageCallback, + uploadEnabled: true, + previewImage: true, + }, }} /> )} diff --git a/client/src/components/posts/refactorComponents/OptionsPanel.js b/client/src/components/posts/OptionsPanel.js similarity index 98% rename from client/src/components/posts/refactorComponents/OptionsPanel.js rename to client/src/components/posts/OptionsPanel.js index 6f2b714..5958296 100644 --- a/client/src/components/posts/refactorComponents/OptionsPanel.js +++ b/client/src/components/posts/OptionsPanel.js @@ -2,7 +2,7 @@ import React from "react"; import { useParams } from "react-router-dom"; import styled, { css } from "styled-components"; import PropTypes from "prop-types"; -import Button from "../../common/Button"; +import Button from "../common/Button"; import { Link } from "react-router-dom"; import CogIcon from "../../../imgs/settings 1.svg"; diff --git a/client/src/components/posts/refactorComponents/PollConfig.js b/client/src/components/posts/PollConfig.js similarity index 96% rename from client/src/components/posts/refactorComponents/PollConfig.js rename to client/src/components/posts/PollConfig.js index 3f6b718..1d58b2d 100644 --- a/client/src/components/posts/refactorComponents/PollConfig.js +++ b/client/src/components/posts/PollConfig.js @@ -1,14 +1,14 @@ import React, { useContext, useState } from "react"; import styled, { css } from "styled-components"; -import InfoIcon from "../../../imgs/Info_tip.svg"; -import PencilIcon from "../../../imgs/pencil.svg"; -import CloseButtonIcon from "../../../imgs/close.svg"; -import DraftTextArea from "../../common/DraftTextArea"; -import Button from "../../common/Button"; -import Checkbox from "../../common/Checkbox"; -import LazyFetch from "../../common/requests/LazyFetch"; +import InfoIcon from "../../imgs/Info_tip.svg"; +import PencilIcon from "../../imgs/pencil.svg"; +import CloseButtonIcon from "../../imgs/close.svg"; +import DraftTextArea from "../common/DraftTextArea"; +import Button from "../common/Button"; +import Checkbox from "../common/Checkbox"; +import LazyFetch from "../common/requests/LazyFetch"; import { useHistory, useParams } from "react-router"; -import { UserRoleContext } from "../../context/UserRoleProvider"; +import { UserRoleContext } from "../context/UserRoleProvider"; const max_options = 6; const default_title = "Inquire is a great website!"; diff --git a/client/src/components/posts/refactorComponents/PollWrapper.js b/client/src/components/posts/PollWrapper.js similarity index 93% rename from client/src/components/posts/refactorComponents/PollWrapper.js rename to client/src/components/posts/PollWrapper.js index b82bc03..67c552a 100644 --- a/client/src/components/posts/refactorComponents/PollWrapper.js +++ b/client/src/components/posts/PollWrapper.js @@ -1,8 +1,8 @@ import React, { useContext, useState } from "react"; import Poll from "react-polls"; import { useParams } from "react-router"; -import LazyFetch from "../../common/requests/LazyFetch"; -import { UserRoleContext } from "../../context/UserRoleProvider"; +import LazyFetch from "../common/requests/LazyFetch"; +import { UserRoleContext } from "../context/UserRoleProvider"; const PollWrapper = ({ post }) => { const { courseId } = useParams(); diff --git a/client/src/components/posts/Post.js b/client/src/components/posts/Post.js deleted file mode 100644 index 2679bb7..0000000 --- a/client/src/components/posts/Post.js +++ /dev/null @@ -1,371 +0,0 @@ -import React, { useContext, useEffect, useState } from "react"; -import styled, { css } from "styled-components"; -import PinImg from "../../imgs/pin.svg"; -import { useParams } from "react-router-dom"; -import DraftTextArea from "../common/DraftTextArea"; -import { UserContext } from "../context/UserProvider"; -import { useHistory } from "react-router-dom"; -import Reaction from "../common/Reaction"; -import Button from "../common/Button"; -import LazyFetch from "../common/requests/LazyFetch"; -import InstructorIcon from "../../imgs/instructor.svg"; -import CommentImg from "../../imgs/comment.svg"; -import Checkbox from "../common/Checkbox"; -import Icon from "../common/Icon"; -import OptionDots from "../../imgs/option-dots.svg"; -import Dropdown from "../common/dropdown/Dropdown"; -import { UserRoleContext } from "../context/UserRoleProvider"; - -// Checks props to determine if the post is a draft, isPinned, etc. -const generatePostContent = ( - user, - userRole, - post, - isDraft, - handleChange, - handleSubmit, - postid, - isAnon, - isPriv -) => { - // If a post is passed, set all dynamic content accordingly, otherwise render a draft - if (isDraft) { - return { - title: ( - - ), - content: ( - - ), - to: null, - isInstructor: false, - isPinned: false, - picture: user.picture, - postedBy: user.first + " " + user.last, - meta: ( - - ), - isAnonymous: - userRole && userRole.privacy.anonymous ? ( - - ) : ( - <> - ), - isPrivate: ( - - ), - }; - } else { - return { - title: post.title, - content: post.content, - to: { - pathname: "/course/" + post.courseId + "/post/" + post._id, - state: { post }, - }, - isInstructor: post.isInstructor, - isPinned: post.isPinned, - picture: post.postedBy.picture, - postedBy: post.postedBy.first + " " + post.postedBy.last, - meta: ( - <> - - -
{post.comments}
- - ), - }; - } -}; - -/** - * Post ~ Blueprint for displaying Posts of various types. - * - * @param {string} post idk what this does tbh - * @param {bool} isCondensed tells the post whether to display completely or condensed - * @param {bool} isDraft tells the post whether to display as a form to fill out or not - */ -const Post = ({ userRole, post, isCondensed, isDraft }) => { - const history = useHistory(); - const user = useContext(UserContext); - // const userRole = useContext(UserRoleContext); - const { courseId, postid } = useParams(); - let endpoint = "/courses/" + courseId + "/posts"; - - // State and handler for drafting posts - const [draft, setDraft] = useState({ - title: "", - content: "", - isAnonymous: false, - isPrivate: false, - }); - - const handleChange = (e) => { - setDraft({ - ...draft, - [e.target.name]: - e.target.type === "checkbox" ? e.target.checked : e.target.value, - }); - }; - - const handleSubmit = (e) => { - LazyFetch({ - type: "post", - endpoint: endpoint, - data: { - title: draft.title, - content: draft.content, - isPrivate: draft.isPrivate, - isAnonymous: draft.isAnonymous, - }, - onSuccess: (data) => { - /* data.new is used after the redirect to prevent - a request for comments (new posts have 0 comments)*/ - data.new = true; - history.push({ - pathname: "/course/" + data.courseId + "/post/" + data._id, - state: { post: data }, - }); - }, - }); - }; - - if (draft != null) { - var isAnon = draft.isAnonymous; - var isPriv = draft.isPrivate; - } else { - isAnon = false; - isPriv = false; - } - - // Determines if post is a draft or not and renders accordingly: - let render = generatePostContent( - user, - userRole, - post, - isDraft, - handleChange, - handleSubmit, - postid, - isAnon, - isPriv - ); - - // Handles redirect if the post is not a draft - const navigateToPost = () => { - if (render.to) { - history.push(render.to); - } - }; - - const handleDelete = () => { - LazyFetch({ - type: "delete", - endpoint: endpoint, - data: { _id: postid }, - onSuccess: (data) => { - let path = "/course/" + courseId; - history.push(path); - }, - }); - }; - - const handleEdit = () => { - alert("This feature is still a work in progress. Check back soon!"); - }; - - const options = [ - { onClick: handleDelete, label: "Delete post" }, - { onClick: handleEdit, label: "Edit post" }, - ]; - - // Initialize viewOptions to see if a user should be able to see dropdown options - var viewOptions = false; - // Check to see if the user is an admin - for (let i = 0; i < user.courses.length; i++) { - if (user?.courses[i].courseId == courseId) { - viewOptions = user.courses[i].admin; - } - } - // Check to see if the user made the post - if ( - !isDraft && - (post.postedBy._id == user._id || post.postedBy._id == user.anonymousId) - ) { - viewOptions = true; - } - - return ( - - - {render.title} - {!isDraft && viewOptions && ( - - - - )} - - - {!isCondensed && {render.content}} - {!isCondensed &&
} - - {render.picture ? : null} - - {render.isInstructor && ( - - - - )} - - Posted by {render.postedBy} - - - - {render.isAnonymous} - {render.isPrivate} - {render.meta} - - -
- ); -}; - -export default Post; - -const HRStyle = { - width: "100%", - border: "1px solid #DDDDDD", - margin: "16px 0", -}; - -const PostWrapper = styled.div` - cursor: pointer; - position: relative; - padding: 23px 30px; - display: flex; - flex-direction: column; - flex: 1; - text-decoration: none; - width: 100%; - min-height: 85px; - margin: 2em 0; - border-radius: 0.3em; - background: #fff; - box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); - transition: all 100ms ease-in-out; - ${(props) => - !props.isFocused && - css` - &:hover { - box-shadow: 0px 3px 10px 3px rgb(0 0 0 / 7%); - } - `} -`; - -const PostTitle = styled.h2` - /* margin: 1em 0 0.5em 2em; */ - font-size: ${(props) => (!props.isCondensed && "18px") || "14px"}; - flex: 1; - padding-right: 3px; - ${(props) => - !props.isCondensed ? "&:hover {text-decoration: underline}" : ""}; -`; - -const PinIcon = styled.img` - visibility: ${(props) => (props.isPinned ? "visible" : "hidden")}; - position: absolute; - top: 0; - right: 0; - width: 16px; - height: 16px; - margin: 1.1em 2em 0 0; -`; - -const PostContent = styled.p` - margin-top: 10px; - font-size: 16px; - color: #979797; -`; - -const PostMetaContentWrapper = styled.div` - display: flex; - flex-direction: row; - align-items: center; - height: 100%; -`; - -const NameWrapper = styled.div` - display: flex; - align-items: center; -`; - -const UserIcon = styled.img` - float: left; - width: 30px; - height: 30px; - margin-right: 0.5em; - border-radius: 50%; - user-select: none; -`; - -const UserDescription = styled.h5` - user-select: none; - color: ${(props) => props.isInstructor && "#FF9900"}; -`; - -const MetaIconWrapper = styled.div` - display: inline-flex; - margin-left: auto; - height: 100%; -`; - -const TopSection = styled.div` - display: flex; - align-items: center; -`; diff --git a/client/src/components/posts/refactorComponents/PostRefactor.js b/client/src/components/posts/PostRefactor.js similarity index 99% rename from client/src/components/posts/refactorComponents/PostRefactor.js rename to client/src/components/posts/PostRefactor.js index 0eee1c6..e7cef72 100644 --- a/client/src/components/posts/refactorComponents/PostRefactor.js +++ b/client/src/components/posts/PostRefactor.js @@ -5,7 +5,7 @@ import OptionsPanel from "./OptionsPanel"; import PostWrapper from "./PostWrapper"; import Poll from "react-polls"; import PollConfig from "./PollConfig"; -import Button from "../../common/Button"; +import Button from "../common/Button"; import LineWidthImg from "../../../imgs/line-width.svg"; import { Editor } from "react-draft-wysiwyg"; import { convertToRaw, EditorState } from "draft-js"; diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostView.js index bcfd453..c8566c2 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostView.js @@ -1,7 +1,6 @@ import React, { useContext, useState, useEffect } from "react"; import styled, { css } from "styled-components"; import Options from "./Options"; -import Post from "./Post"; import Button from "../common/Button"; import LineWidthImg from "../../imgs/line-width.svg"; import HollowPinImg from "../../imgs/pin-hollow.svg"; @@ -9,12 +8,12 @@ import { useParams } from "react-router-dom"; import Fetch from "../common/requests/Fetch"; import { UserContext } from "../context/UserProvider"; import io from "../../services/socketio"; -import PostWrapper from "./refactorComponents/PostWrapper"; +import PostWrapper from "./PostWrapper"; import Poll from "react-polls"; import { Editor } from "react-draft-wysiwyg"; import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; -import PollWrapper from "./refactorComponents/PollWrapper"; -import EditorWrapper from "./refactorComponents/EditorWrapper"; +import PollWrapper from "./PollWrapper"; +import EditorWrapper from "./EditorWrapper"; import SearchPanel from "./SearchPanel"; import LazyFetch from "../common/requests/LazyFetch"; import LoadingDots from "../common/animation/LoadingDots"; diff --git a/client/src/components/posts/refactorComponents/PostWrapper.js b/client/src/components/posts/PostWrapper.js similarity index 95% rename from client/src/components/posts/refactorComponents/PostWrapper.js rename to client/src/components/posts/PostWrapper.js index 5ae9400..8e7d778 100644 --- a/client/src/components/posts/refactorComponents/PostWrapper.js +++ b/client/src/components/posts/PostWrapper.js @@ -1,18 +1,18 @@ import React, { useState, useContext, useEffect } from "react"; import styled, { css } from "styled-components"; -import { UserContext } from "../../context/UserProvider"; +import { UserContext } from "../context/UserProvider"; import { UserRoleContext, UserRoleDispatchContext, -} from "../../context/UserRoleProvider"; -import Dropdown from "../../common/dropdown/Dropdown"; -import Icon from "../../common/Icon"; -import OptionDots from "../../../imgs/option-dots.svg"; -import Reaction from "../../common/Reaction"; -import CommentImg from "../../../imgs/comment.svg"; +} from "../context/UserRoleProvider"; +import Dropdown from "../common/dropdown/Dropdown"; +import Icon from "../common/Icon"; +import OptionDots from "../../imgs/option-dots.svg"; +import Reaction from "../common/Reaction"; +import CommentImg from "../../imgs/comment.svg"; import { useHistory, useParams } from "react-router"; -import LazyFetch from "../../common/requests/LazyFetch"; -import PinIcon from "../../../imgs/pin.svg"; +import LazyFetch from "../common/requests/LazyFetch"; +import PinIcon from "../../imgs/pin.svg"; const accentColor = (type) => { switch (type) { From e5de51f6289ed9111c221f11b41ee8941bc44c1e Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Fri, 20 Aug 2021 12:59:13 -0700 Subject: [PATCH 15/58] Restructured ./posts/ * Draft.js now called PostDraft.js * Wrapper components in their own subdirectory --- client/src/components/comments/CommentView.js | 8 +++---- client/src/components/home/Home.js | 9 +++----- .../posts/{Draft.js => PostDraft.js} | 4 ++-- client/src/components/posts/PostRefactor.js | 2 +- client/src/components/posts/PostView.js | 8 ++----- .../posts/{ => wrappers}/PollWrapper.js | 4 ++-- .../posts/{ => wrappers}/PostWrapper.js | 23 ++++++++----------- 7 files changed, 24 insertions(+), 34 deletions(-) rename client/src/components/posts/{Draft.js => PostDraft.js} (99%) rename client/src/components/posts/{ => wrappers}/PollWrapper.js (93%) rename client/src/components/posts/{ => wrappers}/PostWrapper.js (95%) diff --git a/client/src/components/comments/CommentView.js b/client/src/components/comments/CommentView.js index 9b3ad56..448354c 100644 --- a/client/src/components/comments/CommentView.js +++ b/client/src/components/comments/CommentView.js @@ -2,16 +2,16 @@ import React, { useContext, useEffect, useState, useMemo, useRef } from "react"; import { Link, useHistory, useLocation, useParams } from "react-router-dom"; import { UserRoleContext } from "../context/UserRoleProvider"; import styled from "styled-components"; -import PostWrapper from "../posts/PostWrapper"; +import PostWrapper from "../posts/wrappers/PostWrapper"; import Sidebar from "../posts/Sidebar"; import Button from "../common/Button"; import Comment from "./Comment"; import LazyFetch from "../common/requests/LazyFetch"; import { UserContext } from "../context/UserProvider"; import io from "../../services/socketio"; -import Draft from "../posts/Draft"; +import PostDraft from "../posts/PostDraft"; import PollConfig from "../posts/PollConfig"; -import PollWrapper from "../posts/PollWrapper"; +import PollWrapper from "../posts/wrappers/PollWrapper"; import EditorWrapper from "../posts/EditorWrapper"; import { convertToRaw } from "draft-js"; @@ -237,7 +237,7 @@ const CommentView = ({ classroomName }) => { )} {postid === "newQorA" && userRole && ( - + )} {/* {postid === "newPoll" && } */} {postid === "newPoll" && } diff --git a/client/src/components/home/Home.js b/client/src/components/home/Home.js index 09cead3..3e0ac71 100644 --- a/client/src/components/home/Home.js +++ b/client/src/components/home/Home.js @@ -1,12 +1,9 @@ -import React, { useState } from "react"; +import React from "react"; import styled from "styled-components"; import RecentGroup from "./RecentGroup"; import Fetch from "../common/requests/Fetch"; -// import Post from "../posts/Post"; -import { Editor } from "react-draft-wysiwyg"; -import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; -import PostWrapper from "../posts/PostWrapper"; -import PollWrapper from "../posts/PollWrapper"; +import PostWrapper from "../posts/wrappers/PostWrapper"; +import PollWrapper from "../posts/wrappers/PollWrapper"; import EditorWrapper from "../posts/EditorWrapper"; const convertToUpper = (postType) => { diff --git a/client/src/components/posts/Draft.js b/client/src/components/posts/PostDraft.js similarity index 99% rename from client/src/components/posts/Draft.js rename to client/src/components/posts/PostDraft.js index 6f5f78d..0aceaf6 100644 --- a/client/src/components/posts/Draft.js +++ b/client/src/components/posts/PostDraft.js @@ -24,7 +24,7 @@ const accentColor = (type) => { } }; -const Draft = ({ userRole }) => { +const PostDraft = ({ userRole }) => { const { courseId } = useParams(); const history = useHistory(); // State and handler for drafting posts @@ -238,7 +238,7 @@ const Draft = ({ userRole }) => { ); }; -export default Draft; +export default PostDraft; const Wrapper = styled.div` margin: 2em; diff --git a/client/src/components/posts/PostRefactor.js b/client/src/components/posts/PostRefactor.js index e7cef72..2deb724 100644 --- a/client/src/components/posts/PostRefactor.js +++ b/client/src/components/posts/PostRefactor.js @@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext, useParams } from "react"; import styled, { css } from "styled-components"; import Options from "../Options"; import OptionsPanel from "./OptionsPanel"; -import PostWrapper from "./PostWrapper"; +import PostWrapper from "./wrappers/PostWrapper"; import Poll from "react-polls"; import PollConfig from "./PollConfig"; import Button from "../common/Button"; diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostView.js index c8566c2..7cabf19 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostView.js @@ -5,14 +5,10 @@ import Button from "../common/Button"; import LineWidthImg from "../../imgs/line-width.svg"; import HollowPinImg from "../../imgs/pin-hollow.svg"; import { useParams } from "react-router-dom"; -import Fetch from "../common/requests/Fetch"; import { UserContext } from "../context/UserProvider"; import io from "../../services/socketio"; -import PostWrapper from "./PostWrapper"; -import Poll from "react-polls"; -import { Editor } from "react-draft-wysiwyg"; -import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; -import PollWrapper from "./PollWrapper"; +import PostWrapper from "./wrappers/PostWrapper"; +import PollWrapper from "./wrappers/PollWrapper"; import EditorWrapper from "./EditorWrapper"; import SearchPanel from "./SearchPanel"; import LazyFetch from "../common/requests/LazyFetch"; diff --git a/client/src/components/posts/PollWrapper.js b/client/src/components/posts/wrappers/PollWrapper.js similarity index 93% rename from client/src/components/posts/PollWrapper.js rename to client/src/components/posts/wrappers/PollWrapper.js index 67c552a..b82bc03 100644 --- a/client/src/components/posts/PollWrapper.js +++ b/client/src/components/posts/wrappers/PollWrapper.js @@ -1,8 +1,8 @@ import React, { useContext, useState } from "react"; import Poll from "react-polls"; import { useParams } from "react-router"; -import LazyFetch from "../common/requests/LazyFetch"; -import { UserRoleContext } from "../context/UserRoleProvider"; +import LazyFetch from "../../common/requests/LazyFetch"; +import { UserRoleContext } from "../../context/UserRoleProvider"; const PollWrapper = ({ post }) => { const { courseId } = useParams(); diff --git a/client/src/components/posts/PostWrapper.js b/client/src/components/posts/wrappers/PostWrapper.js similarity index 95% rename from client/src/components/posts/PostWrapper.js rename to client/src/components/posts/wrappers/PostWrapper.js index 8e7d778..c745f2b 100644 --- a/client/src/components/posts/PostWrapper.js +++ b/client/src/components/posts/wrappers/PostWrapper.js @@ -1,18 +1,15 @@ -import React, { useState, useContext, useEffect } from "react"; +import React, { useState, useContext } from "react"; import styled, { css } from "styled-components"; -import { UserContext } from "../context/UserProvider"; -import { - UserRoleContext, - UserRoleDispatchContext, -} from "../context/UserRoleProvider"; -import Dropdown from "../common/dropdown/Dropdown"; -import Icon from "../common/Icon"; -import OptionDots from "../../imgs/option-dots.svg"; -import Reaction from "../common/Reaction"; -import CommentImg from "../../imgs/comment.svg"; +import { UserContext } from "../../context/UserProvider"; +import { UserRoleContext } from "../../context/UserRoleProvider"; +import Dropdown from "../../common/dropdown/Dropdown"; +import Icon from "../../common/Icon"; +import OptionDots from "../../../imgs/option-dots.svg"; +import Reaction from "../../common/Reaction"; +import CommentImg from "../../../imgs/comment.svg"; import { useHistory, useParams } from "react-router"; -import LazyFetch from "../common/requests/LazyFetch"; -import PinIcon from "../../imgs/pin.svg"; +import LazyFetch from "../../common/requests/LazyFetch"; +import PinIcon from "../../../imgs/pin.svg"; const accentColor = (type) => { switch (type) { From 7a637029efa50abf45186b94d952f6873e20abb8 Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Fri, 20 Aug 2021 13:08:20 -0700 Subject: [PATCH 16/58] Renamed Options.js to OptionsPanel.js --- client/src/components/posts/ClassView.js | 1 - client/src/components/posts/Options.js | 128 -------------- client/src/components/posts/OptionsPanel.js | 117 +++++++----- client/src/components/posts/PostRefactor.js | 186 -------------------- client/src/components/posts/PostView.js | 4 +- 5 files changed, 74 insertions(+), 362 deletions(-) delete mode 100644 client/src/components/posts/Options.js delete mode 100644 client/src/components/posts/PostRefactor.js diff --git a/client/src/components/posts/ClassView.js b/client/src/components/posts/ClassView.js index 92b7c24..e3c27d7 100644 --- a/client/src/components/posts/ClassView.js +++ b/client/src/components/posts/ClassView.js @@ -67,7 +67,6 @@ const ClassView = ({ props }) => { } > - {/* */} ); diff --git a/client/src/components/posts/Options.js b/client/src/components/posts/Options.js deleted file mode 100644 index 6949bd4..0000000 --- a/client/src/components/posts/Options.js +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useContext } from "react"; -import { UserContext } from "../context/UserProvider"; -import PropTypes from "prop-types"; -import styled from "styled-components"; -import Button from "../common/Button"; -import { Link } from "react-router-dom"; -import CogIcon from "../../imgs/settings 1.svg"; - -/** - * Options Component ~ Button side panel for displaying buttons for the user - * - * @param {string} courseId given to the "+ New Post" button to route to the Post form page - */ -const Options = ({ userRole, courseId }) => { - const user = useContext(UserContext); - // console.log("User Object: ", user); - // console.log("OPTIONS User Role: ", userRole); - - // Will be used to conditionally render the config page button and draft post button - var userIsAdmin = false; - var userCanBan = false; - var userCanRemove = false; - var displayDraftPost = false; - var displayDraftPoll = false; - if (userRole) { - userIsAdmin = userRole.admin.configure; - userCanBan = userRole.admin.banUsers; - userCanRemove = userRole.admin.removeUsers; - displayDraftPost = - userRole.publish.question || - userRole.publish.announcement || - userRole.publish.general; - displayDraftPoll = userRole.publish.poll; - } - - return ( - - OPTIONS - - {displayDraftPost && ( - - - - )} - {displayDraftPoll && ( - - - - )} - - {/* The Config page conditionally renders based on whether or not - the user has ADMIN priviledges for this course */} - {(userIsAdmin || userCanBan || userCanRemove) && ( - - - - )} - - - ); -}; - -Options.propTypes = { - courseId: PropTypes.string, -}; - -export default Options; - -const OptionsWrapper = styled.div` - width: 280px; // Need to make same width as nav + menu bar - flex-grow: 1; - position: absolute; - right: -40px; - top: 120px; - - /* border: 1px solid red; */ -`; - -const OptionsHeader = styled.h1` - margin: 3em 0 1em 0; - - font-size: 14px; -`; - -const OptionsPanel = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: #fff; - width: 220px; - padding: 14px; - border-radius: 5px; - - box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.07); -`; diff --git a/client/src/components/posts/OptionsPanel.js b/client/src/components/posts/OptionsPanel.js index 5958296..03659e3 100644 --- a/client/src/components/posts/OptionsPanel.js +++ b/client/src/components/posts/OptionsPanel.js @@ -1,52 +1,70 @@ -import React from "react"; -import { useParams } from "react-router-dom"; -import styled, { css } from "styled-components"; +import React, { useContext } from "react"; +import { UserContext } from "../context/UserProvider"; import PropTypes from "prop-types"; +import styled from "styled-components"; import Button from "../common/Button"; import { Link } from "react-router-dom"; -import CogIcon from "../../../imgs/settings 1.svg"; +import CogIcon from "../../imgs/settings 1.svg"; -const OptionsPanel = ({ userRole, ...props }) => { - var { courseId } = useParams(); - var userIsAdmin = userRole ? userRole.admin.config : false; +/** + * Options Component ~ Button side panel for displaying buttons for the user + * + * @param {string} courseId given to the "+ New Post" button to route to the Post form page + */ +const OptionsPanel = ({ userRole, courseId }) => { + // Will be used to conditionally render the config page button and draft post button + var userIsAdmin = false; + var userCanBan = false; + var userCanRemove = false; + var displayDraftPost = false; + var displayDraftPoll = false; + if (userRole) { + userIsAdmin = userRole.admin.configure; + userCanBan = userRole.admin.banUsers; + userCanRemove = userRole.admin.removeUsers; + displayDraftPost = + userRole.publish.question || + userRole.publish.announcement || + userRole.publish.general; + displayDraftPoll = userRole.publish.poll; + } return ( - - Options - - - - - - + + )} + {displayDraftPoll && ( + - Draft Poll - - + + + )} {/* The Config page conditionally renders based on whether or not the user has ADMIN priviledges for this course */} - {userIsAdmin && ( + {(userIsAdmin || userCanBan || userCanRemove) && ( { )} - - + + ); }; +OptionsPanel.propTypes = { + courseId: PropTypes.string, +}; + export default OptionsPanel; -const Wrapper = styled.div` - width: 100%; +const OptionsWrapper = styled.div` + width: 280px; // Need to make same width as nav + menu bar + flex-grow: 1; + position: absolute; + right: -40px; + top: 120px; + /* border: 1px solid red; */ `; const OptionsHeader = styled.h1` - margin: 3em 0 2em 0; + margin: 3em 0 1em 0; font-size: 14px; `; -const Panel = styled.div` +const OptionsPanelWrapper = styled.div` display: flex; flex-direction: column; align-items: center; diff --git a/client/src/components/posts/PostRefactor.js b/client/src/components/posts/PostRefactor.js deleted file mode 100644 index 2deb724..0000000 --- a/client/src/components/posts/PostRefactor.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useState, useEffect, useContext, useParams } from "react"; -import styled, { css } from "styled-components"; -import Options from "../Options"; -import OptionsPanel from "./OptionsPanel"; -import PostWrapper from "./wrappers/PostWrapper"; -import Poll from "react-polls"; -import PollConfig from "./PollConfig"; -import Button from "../common/Button"; -import LineWidthImg from "../../../imgs/line-width.svg"; -import { Editor } from "react-draft-wysiwyg"; -import { convertToRaw, EditorState } from "draft-js"; - -const pollAnswers = [ - { option: "Yes", votes: 8 }, - { option: "No", votes: 2 }, -]; - -const createPost = (postType, postData, userRole) => { - return ; -}; - -const PostRefactor = ({ userRole, highlightedSection, ...props }) => { - const [pollAns, setPollAns] = useState(pollAnswers); - const [condensed, setCondensed] = useState(false); - - const [content, setContent] = useState({ - type: "Question", - raw: EditorState.createEmpty(), - plainText: EditorState.createEmpty(), - }); - - const handleContentChange = (e) => { - const plainText = e.getCurrentContent().getPlainText(); - setContent({ ...content, raw: e, plainText: plainText }); - console.log(content); - }; - - const handleVote = (voteAnswer) => { - var pa = pollAns; - const newPollAnswers = pa.map((answer) => { - if (answer.option === voteAnswer) answer.votes++; - return answer; - }); - setPollAns(newPollAnswers); - }; - - var testAnonymousPostObject = { - _id: "987654321", - postedBy: { - firstName: "Anonymous", - lastName: "", - isAnonymous: true, - _id: "608dd55e87f76c3cdbf52745", - }, - comments: 11, - reactions: { likes: [], goods: [], helpfuls: [] }, - isInstructor: true, - }; - var testPostObject = { - _id: "123456789", - postedBy: { - firstName: "Brian", - lastName: "Gunnarson", - picture: - "https://lh3.googleusercontent.com/a-/AOh14Ggopr1ZffPC5y-S8yZzvlkTYZanP0iDYBg0JnKU2Q=s96-c", - isAnonymous: false, - _id: "108734863236913803139", - }, - comments: 7, - reactions: { likes: ["108734863236913803139"], goods: [], helpfuls: [] }, - isInstructor: false, - }; - - var pollDraftDemo = { - _id: "000111222", - postedBy: { - firstName: "Aaron", - lastName: "Van Cleave", - picture: "https://avatars.githubusercontent.com/u/46456517?v=4", - isAnonymous: false, - _id: "108734863236913803139", - }, - comments: 0, - reactions: { likes: ["108734863236913803139"], goods: [], helpfuls: [] }, - isInstructor: false, - }; - - return ( - <> - - - - - - - - - {/* {false ? createPost("Question") : TestPosts} */} - - - - - - - - ); -}; - -export default PostRefactor; - -const FlexWrapper = styled.div` - display: flex; - flex-direction: column; - - width: 100%; - height: 100vh; - - /* border: 1px solid orange; */ -`; - -const Wrapper = styled.div` - display: flex; - flex-direction: row; - min-width: 100%; - height: 100%; - overflow: auto; - - /* border: 1px solid green; */ -`; - -/** THIS ACCOUNTS FOR WEIRD SCROLLING DIV STUFF */ -const OverflowCounter = styled.div` - width: 100%; - ${(props) => - props.offsetAmount && - css` - padding: ${props.offsetAmount}; - `}/* border: 3px solid black; */ -`; - -const PostFeedWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - padding: 1rem; - /* border: 1px solid green; */ -`; - -const CenterWrapper = styled.div` - margin: 0 auto; - max-width: 1200px; - width: 100%; - /* padding-left: 10rem; */ - /* position: relative; */ -`; - -const SortingOptions = styled.div` - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - width: 100%; - margin: 1.5em 0 1em 0; - /* position: absolute; */ - padding-right: 0em; -`; diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostView.js index 7cabf19..1321358 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostView.js @@ -1,6 +1,6 @@ import React, { useContext, useState, useEffect } from "react"; import styled, { css } from "styled-components"; -import Options from "./Options"; +import OptionsPanel from "./OptionsPanel"; import Button from "../common/Button"; import LineWidthImg from "../../imgs/line-width.svg"; import HollowPinImg from "../../imgs/pin-hollow.svg"; @@ -241,7 +241,7 @@ const PostView = ({ userRole, highlightedSection }) => { )} {posts ? posts.other : <>} - + From dbd8a7e242cb68413ecd8ff4fe6b4001295d04ec Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Fri, 20 Aug 2021 13:14:11 -0700 Subject: [PATCH 17/58] PostView now called PostFeed --- client/src/components/posts/ClassView.js | 5 +++-- .../components/posts/{PostView.js => PostFeed.js} | 14 ++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) rename client/src/components/posts/{PostView.js => PostFeed.js} (97%) diff --git a/client/src/components/posts/ClassView.js b/client/src/components/posts/ClassView.js index e3c27d7..e5486f8 100644 --- a/client/src/components/posts/ClassView.js +++ b/client/src/components/posts/ClassView.js @@ -10,8 +10,9 @@ import styled from "styled-components"; import SectionTab from "./SectionTab"; import Sidebar from "./Sidebar"; import LoadingDots from "../common/animation/LoadingDots"; +import PostFeed from "./PostFeed"; -const PostView = React.lazy(() => import("./PostView")); +// const PostView = React.lazy(() => import("./PostView")); /** * ClassView Component ~ Blueprint for displaying a specific course post feed, as well as @@ -66,7 +67,7 @@ const ClassView = ({ props }) => {
} > - + ); diff --git a/client/src/components/posts/PostView.js b/client/src/components/posts/PostFeed.js similarity index 97% rename from client/src/components/posts/PostView.js rename to client/src/components/posts/PostFeed.js index 1321358..687edb5 100644 --- a/client/src/components/posts/PostView.js +++ b/client/src/components/posts/PostFeed.js @@ -12,7 +12,7 @@ import PollWrapper from "./wrappers/PollWrapper"; import EditorWrapper from "./EditorWrapper"; import SearchPanel from "./SearchPanel"; import LazyFetch from "../common/requests/LazyFetch"; -import LoadingDots from "../common/animation/LoadingDots"; +// import LoadingDots from "../common/animation/LoadingDots"; const convertToUpper = (postType) => { var first = postType[0].toUpperCase(); @@ -82,7 +82,7 @@ const fetchData = (endpoint, socketPosts, setData) => { }); }; -const PostView = ({ userRole, highlightedSection }) => { +const PostFeed = ({ userRole, highlightedSection }) => { const user = useContext(UserContext); const [socketPosts, setSocketPosts] = useState([]); @@ -203,7 +203,7 @@ const PostView = ({ userRole, highlightedSection }) => { return ( <> - + @@ -245,21 +245,19 @@ const PostView = ({ userRole, highlightedSection }) => { - + ); }; -PostView.propTypes = {}; - -export default PostView; +export default PostFeed; const MarginLeftRight = { marginLeft: "1em", marginRight: "1em", }; -const PostFeed = styled.div` +const PostFeedWrapper = styled.div` width: 100%; position: relative; display: flex; From 4b2570040ff7558ebad57d91067515569ef692c4 Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Fri, 20 Aug 2021 13:37:59 -0700 Subject: [PATCH 18/58] Various changes to ./posts/ structure --- client/src/components/comments/Comment.js | 2 +- client/src/components/comments/CommentReply.js | 2 +- client/src/components/comments/CommentView.js | 4 ++-- client/src/components/home/Home.js | 2 +- client/src/components/posts/ClassView.js | 4 ++-- client/src/components/posts/PostFeed.js | 2 +- .../posts/{ => leftSideBar}/SectionTab.js | 3 +-- .../posts/{ => leftSideBar}/Sidebar.js | 18 +++++++++--------- .../posts/{ => wrappers}/EditorWrapper.js | 4 ++-- 9 files changed, 20 insertions(+), 21 deletions(-) rename client/src/components/posts/{ => leftSideBar}/SectionTab.js (93%) rename client/src/components/posts/{ => leftSideBar}/Sidebar.js (89%) rename client/src/components/posts/{ => wrappers}/EditorWrapper.js (98%) diff --git a/client/src/components/comments/Comment.js b/client/src/components/comments/Comment.js index a6c8e22..c2abed1 100644 --- a/client/src/components/comments/Comment.js +++ b/client/src/components/comments/Comment.js @@ -12,7 +12,7 @@ import Icon from "../common/Icon"; import OptionDots from "../../imgs/option-dots.svg"; import { UserContext } from "../context/UserProvider"; import { UserRoleContext } from "../context/UserRoleProvider"; -import EditorWrapper from "../posts/EditorWrapper"; +import EditorWrapper from "../posts/wrappers/EditorWrapper"; import { Editor } from "react-draft-wysiwyg"; import { convertFromRaw, convertToRaw, EditorState } from "draft-js"; import Checkbox from "../common/Checkbox"; diff --git a/client/src/components/comments/CommentReply.js b/client/src/components/comments/CommentReply.js index 362ad38..c08971e 100644 --- a/client/src/components/comments/CommentReply.js +++ b/client/src/components/comments/CommentReply.js @@ -9,7 +9,7 @@ import Dropdown from "../common/dropdown/Dropdown"; import Icon from "../common/Icon"; import OptionDots from "../../imgs/option-dots.svg"; import { Editor } from "react-draft-wysiwyg"; -import EditorWrapper from "../posts/EditorWrapper"; +import EditorWrapper from "../posts/wrappers/EditorWrapper"; import { EditorState } from "draft-js"; import Checkbox from "../common/Checkbox"; import { UserRoleContext } from "../context/UserRoleProvider"; diff --git a/client/src/components/comments/CommentView.js b/client/src/components/comments/CommentView.js index 448354c..ba3a04f 100644 --- a/client/src/components/comments/CommentView.js +++ b/client/src/components/comments/CommentView.js @@ -3,7 +3,7 @@ import { Link, useHistory, useLocation, useParams } from "react-router-dom"; import { UserRoleContext } from "../context/UserRoleProvider"; import styled from "styled-components"; import PostWrapper from "../posts/wrappers/PostWrapper"; -import Sidebar from "../posts/Sidebar"; +import Sidebar from "../posts/leftSideBar/Sidebar"; import Button from "../common/Button"; import Comment from "./Comment"; import LazyFetch from "../common/requests/LazyFetch"; @@ -12,7 +12,7 @@ import io from "../../services/socketio"; import PostDraft from "../posts/PostDraft"; import PollConfig from "../posts/PollConfig"; import PollWrapper from "../posts/wrappers/PollWrapper"; -import EditorWrapper from "../posts/EditorWrapper"; +import EditorWrapper from "../posts/wrappers/EditorWrapper"; import { convertToRaw } from "draft-js"; const renderComments = (data, userRole) => { diff --git a/client/src/components/home/Home.js b/client/src/components/home/Home.js index 3e0ac71..696e107 100644 --- a/client/src/components/home/Home.js +++ b/client/src/components/home/Home.js @@ -4,7 +4,7 @@ import RecentGroup from "./RecentGroup"; import Fetch from "../common/requests/Fetch"; import PostWrapper from "../posts/wrappers/PostWrapper"; import PollWrapper from "../posts/wrappers/PollWrapper"; -import EditorWrapper from "../posts/EditorWrapper"; +import EditorWrapper from "../posts/wrappers/EditorWrapper"; const convertToUpper = (postType) => { var first = postType[0].toUpperCase(); diff --git a/client/src/components/posts/ClassView.js b/client/src/components/posts/ClassView.js index e5486f8..b68c644 100644 --- a/client/src/components/posts/ClassView.js +++ b/client/src/components/posts/ClassView.js @@ -7,8 +7,8 @@ import { } from "../context/UserRoleProvider"; import PropTypes from "prop-types"; import styled from "styled-components"; -import SectionTab from "./SectionTab"; -import Sidebar from "./Sidebar"; +import SectionTab from "./leftSideBar/SectionTab"; +import Sidebar from "./leftSideBar/Sidebar"; import LoadingDots from "../common/animation/LoadingDots"; import PostFeed from "./PostFeed"; diff --git a/client/src/components/posts/PostFeed.js b/client/src/components/posts/PostFeed.js index 687edb5..01e1f97 100644 --- a/client/src/components/posts/PostFeed.js +++ b/client/src/components/posts/PostFeed.js @@ -9,7 +9,7 @@ import { UserContext } from "../context/UserProvider"; import io from "../../services/socketio"; import PostWrapper from "./wrappers/PostWrapper"; import PollWrapper from "./wrappers/PollWrapper"; -import EditorWrapper from "./EditorWrapper"; +import EditorWrapper from "./wrappers/EditorWrapper"; import SearchPanel from "./SearchPanel"; import LazyFetch from "../common/requests/LazyFetch"; // import LoadingDots from "../common/animation/LoadingDots"; diff --git a/client/src/components/posts/SectionTab.js b/client/src/components/posts/leftSideBar/SectionTab.js similarity index 93% rename from client/src/components/posts/SectionTab.js rename to client/src/components/posts/leftSideBar/SectionTab.js index f18adea..c6051cd 100644 --- a/client/src/components/posts/SectionTab.js +++ b/client/src/components/posts/leftSideBar/SectionTab.js @@ -1,8 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import styled, { css } from "styled-components"; -import Icon from "../common/Icon"; -import LazyFetch from "../common/requests/LazyFetch"; +import Icon from "../../common/Icon"; const SectionTab = ({ setHighlightedSection, diff --git a/client/src/components/posts/Sidebar.js b/client/src/components/posts/leftSideBar/Sidebar.js similarity index 89% rename from client/src/components/posts/Sidebar.js rename to client/src/components/posts/leftSideBar/Sidebar.js index 6845e87..925f5e4 100644 --- a/client/src/components/posts/Sidebar.js +++ b/client/src/components/posts/leftSideBar/Sidebar.js @@ -2,15 +2,15 @@ import React, { useContext } from "react"; import { Link, useParams } from "react-router-dom"; import styled from "styled-components"; import SectionTab from "./SectionTab"; -import UserImg from "../../imgs/user.svg"; -import { UserContext } from "../context/UserProvider"; -import GlassesImg from "../../imgs/glasses.svg"; -import NoteImg from "../../imgs/note.svg"; -import HeartImg from "../../imgs/heart.svg"; -import AnnouncementsImg from "../../imgs/announcements.svg"; -import QuestionsImg from "../../imgs/questions.svg"; -import PollsImg from "../../imgs/polls.svg"; -import GeneralImg from "../../imgs/general.svg"; +import UserImg from "../../../imgs/user.svg"; +import { UserContext } from "../../context/UserProvider"; +import GlassesImg from "../../../imgs/glasses.svg"; +import NoteImg from "../../../imgs/note.svg"; +import HeartImg from "../../../imgs/heart.svg"; +import AnnouncementsImg from "../../../imgs/announcements.svg"; +import QuestionsImg from "../../../imgs/questions.svg"; +import PollsImg from "../../../imgs/polls.svg"; +import GeneralImg from "../../../imgs/general.svg"; /* Sidebar view shows tabs of different post feeds and shows which one is selected */ const Sidebar = ({ userRole, setHighlightedSection, highlightedSection }) => { diff --git a/client/src/components/posts/EditorWrapper.js b/client/src/components/posts/wrappers/EditorWrapper.js similarity index 98% rename from client/src/components/posts/EditorWrapper.js rename to client/src/components/posts/wrappers/EditorWrapper.js index 6c7905d..08e045c 100644 --- a/client/src/components/posts/EditorWrapper.js +++ b/client/src/components/posts/wrappers/EditorWrapper.js @@ -2,8 +2,8 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; import { Editor } from "react-draft-wysiwyg"; import { EditorState, convertFromRaw, convertToRaw } from "draft-js"; -import Button from "../common/Button"; -import LazyFetch from "../common/requests/LazyFetch"; +import Button from "../../common/Button"; +import LazyFetch from "../../common/requests/LazyFetch"; import { useParams } from "react-router"; const generateStyle = (edit, postid, messageType) => { From 34f0470ddbf175839b4bcb897e01aaa8164dd5b6 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Fri, 20 Aug 2021 14:08:56 -0700 Subject: [PATCH 19/58] Fixed edit profile so the color selector isn't displayed at all times --- client/src/components/userProfile/AboutUser.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index 05b56f1..bc92fcf 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -16,6 +16,7 @@ const AboutUser = ({ userObject, ...props }) => { const [initialAboutMe, setInitialAboutMe] = useState(null); const [bannerColor, setBannerColor] = useState(null); const [displayColorSelector, toggleColorDisplay] = useState(false); + const [changesMade, toggleChangesMade] = useState(false); let endpoint = "/userProfiles"; useEffect(() => { @@ -23,7 +24,7 @@ const AboutUser = ({ userObject, ...props }) => { type: "get", endpoint: endpoint, onSuccess: (response) => { - console.log("response:", response); + // console.log("response:", response); setAboutMe(response.profileData.about); setInitialAboutMe(response.profileData.about); setBannerColor(response.profileData.bannerColor); @@ -41,12 +42,14 @@ const AboutUser = ({ userObject, ...props }) => { }, onSuccess: (response) => { console.log("response:", response); + toggleChangesMade(true); }, }); }; const handleColorChange = (colors) => { setBannerColor(colors.hex); + toggleChangesMade(true); }; const submitColorChange = (colors) => { @@ -60,6 +63,7 @@ const AboutUser = ({ userObject, ...props }) => { onSuccess: (data) => { console.log(data.success); setBannerColor(colors.hex); + toggleChangesMade(true); }, }); }; @@ -113,7 +117,7 @@ const AboutUser = ({ userObject, ...props }) => { toggleColorDisplay(false); }} > - Cancel + {changesMade ? "Done" : "Cancel"} ) : ( + ); + } else { + return ( + + ); + } +}; + +const renderColorIcon = ( + background, + toggleColorDisplay, + displayColorSelector +) => { + if (background == "dark") { + return ( + { + toggleColorDisplay(!displayColorSelector); + }} + /> + ); + } else { + return ( + { + toggleColorDisplay(!displayColorSelector); + }} + /> + ); + } +}; -const AboutUser = ({ userObject, ...props }) => { - const user = useContext(UserContext); +const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { + // const { profileId } = useParams(); + // Compare useParams ID value with userObject ID value to see if we should display edit profile buttons + // const isMyProfile = profileId == userObject._id ? true : false; + console.log("profileId:", profileId); + console.log("userObject._id:", userObject._id); const [editingProfile, toggleEdit] = useState(false); const [aboutMe, setAboutMe] = useState(null); const [initialAboutMe, setInitialAboutMe] = useState(null); const [bannerColor, setBannerColor] = useState(null); const [displayColorSelector, toggleColorDisplay] = useState(false); - const [changesMade, toggleChangesMade] = useState(false); + const [profilePicture, setProfilePicture] = useState(null); let endpoint = "/userProfiles"; useEffect(() => { LazyFetch({ type: "get", - endpoint: endpoint, + endpoint: endpoint + "?profileId=" + profileId, + // data: { profileId: profileId }, onSuccess: (response) => { - // console.log("response:", response); + console.log("response:", response); setAboutMe(response.profileData.about); setInitialAboutMe(response.profileData.about); setBannerColor(response.profileData.bannerColor); + setProfilePicture(response.picture); }, }); }, []); @@ -37,19 +119,17 @@ const AboutUser = ({ userObject, ...props }) => { type: "put", endpoint: endpoint, data: { - userId: user._id, + userId: userObject._id, about: aboutMe, }, onSuccess: (response) => { console.log("response:", response); - toggleChangesMade(true); }, }); }; const handleColorChange = (colors) => { setBannerColor(colors.hex); - toggleChangesMade(true); }; const submitColorChange = (colors) => { @@ -57,42 +137,17 @@ const AboutUser = ({ userObject, ...props }) => { type: "put", endpoint: endpoint, data: { - userId: user._id, + userId: userObject._id, bannerColor: colors.hex, }, onSuccess: (data) => { console.log(data.success); setBannerColor(colors.hex); - toggleChangesMade(true); }, }); }; - // This code was obtained from https://awik.io/determine-color-bright-dark-using-javascript/ - const lightOrDark = () => { - var r, g, b, colorVal; - var color = bannerColor; - - // Convert hex value to integer - color = +("0x" + color.slice(1).replace(color.length < 5 && /./g, "$&$&")); - - // Bit manipulation to obtain rgb values - r = color >> 16; - g = (color >> 8) & 255; - b = color & 255; - - // Get a value between 0 and 255 using rgb values - colorVal = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); - - // Determine if the color is light or dark - if (colorVal > 127.5) { - return "light"; - } else { - return "dark"; - } - }; - - var background = bannerColor ? lightOrDark() : null; + var background = bannerColor ? ContrastDetector(bannerColor) : null; return ( <> @@ -101,40 +156,26 @@ const AboutUser = ({ userObject, ...props }) => { - - {editingProfile ? ( - + {isMyProfile ? ( + renderEditButton( + toggleEdit, + setAboutMe, + editingProfile, + initialAboutMe + ) ) : ( - + <> )} + - {userObject.first + " " + userObject.last} + + {userObject.first + " " + userObject.last} +

About

{editingProfile ? ( @@ -176,33 +217,14 @@ const AboutUser = ({ userObject, ...props }) => { disableAlpha /> )} - {background == "dark" && editingProfile && ( - { - toggleColorDisplay(!displayColorSelector); - }} - /> - )}{" "} - {background == "light" && editingProfile && ( - { - toggleColorDisplay(!displayColorSelector); - }} - /> + {isMyProfile ? ( + renderColorIcon( + background, + toggleColorDisplay, + displayColorSelector + ) + ) : ( + <> )}
@@ -271,7 +293,8 @@ const UserName = styled.h1` margin-top: 1em; font-size: 36px; - color: #fff; + color: ${(props) => + props.backgroundColor == "dark" ? css`#fff` : css`#162B55`}; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); `; diff --git a/client/src/components/userProfile/UserCourseCard.js b/client/src/components/userProfile/UserCourseCard.js index 073b050..6cbffd7 100644 --- a/client/src/components/userProfile/UserCourseCard.js +++ b/client/src/components/userProfile/UserCourseCard.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; import Button from "../common/Button"; -const UserCourseCard = ({ userCourseObject, ...props }) => { +const UserCourseCard = ({ userCourseObject, isMyProfile, ...props }) => { let title = userCourseObject?.courseName; let subtitle = ""; if (userCourseObject.nickname) { @@ -17,16 +17,20 @@ const UserCourseCard = ({ userCourseObject, ...props }) => { {title} {subtitle} - - - + {isMyProfile ? ( + + + + ) : ( + <> + )} {/*

*/} @@ -38,7 +42,7 @@ export default UserCourseCard; const CardWrapper = styled.div` min-width: 8rem; - min-height: 6rem; + /* min-height: 6rem; */ margin: 1em; /* padding: 1em; */ diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js index be07475..5be18fa 100644 --- a/client/src/components/userProfile/UserCourses.js +++ b/client/src/components/userProfile/UserCourses.js @@ -1,19 +1,35 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import styled, { css } from "styled-components"; +import LazyFetch from "../common/requests/LazyFetch"; import UserCourseCard from "./UserCourseCard"; -const UserCourses = ({ userObject, ...props }) => { +const UserCourses = ({ userObject, isMyProfile, profileId, ...props }) => { + const [courses, setCourses] = useState([]); const generateUserCourseCards = (courseList) => { console.log(courseList); let userCourseCards = courseList.map((course, index) => ( - + )); return userCourseCards; }; - var courses = generateUserCourseCards(userObject.courses); + useEffect(() => { + LazyFetch({ + type: "get", + endpoint: "/userProfiles?profileId=" + profileId, + onSuccess: (response) => { + // setCourseList(response.courses); + setCourses(generateUserCourseCards(response.courses)); + }, + }); + }, []); return ( <> diff --git a/client/src/components/userProfile/UserProfile.js b/client/src/components/userProfile/UserProfile.js index ea1c736..d6bdf9b 100644 --- a/client/src/components/userProfile/UserProfile.js +++ b/client/src/components/userProfile/UserProfile.js @@ -1,4 +1,5 @@ import React, { useContext } from "react"; +import { useParams } from "react-router-dom"; import styled, { css } from "styled-components"; import { UserContext } from "../context/UserProvider"; import AboutUser from "./AboutUser"; @@ -6,16 +7,22 @@ import UserCourses from "./UserCourses"; const UserProfile = ({ props }) => { const user = useContext(UserContext); + const { profileId } = useParams(); + const isMyProfile = user._id == profileId ? true : false; return ( <> - - - {/*

{JSON.stringify(user, null, 2)}

*/} - {/* KEEP THE OVERFLOW COUNTER IT HELPS WITH OVERFLOW - at the bottom of the scrolling div. */} - {/* */} + +
diff --git a/server/inquire/resources/user_profiles.py b/server/inquire/resources/user_profiles.py index 59f679d..658a6ce 100644 --- a/server/inquire/resources/user_profiles.py +++ b/server/inquire/resources/user_profiles.py @@ -1,12 +1,28 @@ from flask_restful import Resource, reqparse +from flask import request from inquire.auth import current_user, permission_layer from inquire.mongo import * class UserProfiles(Resource): def get(self): - if current_user.userProfileData is not None: - return {"profileData": current_user.userProfileData}, 200 + print("inside GET request") + profileId = request.args.get('profileId') + # print("args['profileId']:", args['profileId']) + print("profileId:", profileId) + + try: + user = User.objects.get({"_id": profileId}) + except User.DoesNotExist: + return {"errors": "Error: User with id " + profileId + " does not exist."}, 400 + except User.MultipleObjectsReturned: + return {"errors": "Error: Multiple objects with id " + profileId + " were found."}, 400 + + serialized_user = self.__serialize(user).to_dict() + print("serialized_user:", serialized_user) + + if user.userProfileData is not None: + return {"profileData": serialized_user['userProfileData'], "picture": serialized_user['picture'], "courses": serialized_user['courses']}, 200 else: return {"errors": ["No user profile data was found"]}, 400 @@ -35,3 +51,9 @@ def put(self): user.save() return {"success": "User profile data was updated successfully"}, 200 + + def __serialize(self, user): + result = user.to_son() + for course in result["courses"]: + course.pop("viewed") + return result From 34542670f4cb5bb0b88b95daefd79a6575937999 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Fri, 20 Aug 2021 16:12:32 -0700 Subject: [PATCH 21/58] Remove/comment out console logs --- client/src/components/userProfile/AboutUser.js | 7 +------ client/src/components/userProfile/UserCourses.js | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index 4041973..755ef89 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -86,11 +86,6 @@ const renderColorIcon = ( }; const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { - // const { profileId } = useParams(); - // Compare useParams ID value with userObject ID value to see if we should display edit profile buttons - // const isMyProfile = profileId == userObject._id ? true : false; - console.log("profileId:", profileId); - console.log("userObject._id:", userObject._id); const [editingProfile, toggleEdit] = useState(false); const [aboutMe, setAboutMe] = useState(null); const [initialAboutMe, setInitialAboutMe] = useState(null); @@ -105,7 +100,7 @@ const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { endpoint: endpoint + "?profileId=" + profileId, // data: { profileId: profileId }, onSuccess: (response) => { - console.log("response:", response); + // console.log("response:", response); setAboutMe(response.profileData.about); setInitialAboutMe(response.profileData.about); setBannerColor(response.profileData.bannerColor); diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js index 5be18fa..bd51977 100644 --- a/client/src/components/userProfile/UserCourses.js +++ b/client/src/components/userProfile/UserCourses.js @@ -6,8 +6,6 @@ import UserCourseCard from "./UserCourseCard"; const UserCourses = ({ userObject, isMyProfile, profileId, ...props }) => { const [courses, setCourses] = useState([]); const generateUserCourseCards = (courseList) => { - console.log(courseList); - let userCourseCards = courseList.map((course, index) => ( Date: Fri, 20 Aug 2021 17:52:49 -0700 Subject: [PATCH 22/58] Created backend resource for leaving a course --- .../src/components/userProfile/AboutUser.js | 1 - server/inquire/__init__.py | 3 +- server/inquire/resources/__init__.py | 1 + server/inquire/resources/leave_course.py | 46 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 server/inquire/resources/leave_course.py diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index 755ef89..62a9382 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -98,7 +98,6 @@ const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { LazyFetch({ type: "get", endpoint: endpoint + "?profileId=" + profileId, - // data: { profileId: profileId }, onSuccess: (response) => { // console.log("response:", response); setAboutMe(response.profileData.about); diff --git a/server/inquire/__init__.py b/server/inquire/__init__.py index ea1471a..00669d3 100644 --- a/server/inquire/__init__.py +++ b/server/inquire/__init__.py @@ -77,7 +77,7 @@ def after_request(response): api_base_url='https://api.github.com/', client_kwargs={'scope': 'read:user user:email'}, ) - from inquire.resources import Demo, Me, Courses, Posts, Comments, Replies, Join, Reactions, Home, Roles, MeRole, CourseUsers, Poll, Pin, BanRemove, Images, Search, UserProfiles + from inquire.resources import Demo, Me, Courses, Posts, Comments, Replies, Join, Reactions, Home, Roles, MeRole, CourseUsers, Poll, Pin, BanRemove, Images, Search, UserProfiles, LeaveCourse api = Api(api_bp) @@ -105,6 +105,7 @@ def after_request(response): api.add_resource(Images, '/images') api.add_resource(Search, 'courses//search') api.add_resource(UserProfiles, '/userProfiles') + api.add_resource(LeaveCourse, '/leaveCourse') app.register_blueprint(api_bp) if include_socketio: # Wrapping flask app in socketio wrapper diff --git a/server/inquire/resources/__init__.py b/server/inquire/resources/__init__.py index 93675bf..3e89269 100644 --- a/server/inquire/resources/__init__.py +++ b/server/inquire/resources/__init__.py @@ -16,3 +16,4 @@ from .images import Images from .search import Search from .user_profiles import UserProfiles +from .leave_course import LeaveCourse diff --git a/server/inquire/resources/leave_course.py b/server/inquire/resources/leave_course.py new file mode 100644 index 0000000..2de88fb --- /dev/null +++ b/server/inquire/resources/leave_course.py @@ -0,0 +1,46 @@ +from flask import request +from flask_restful import Resource, reqparse +from inquire.auth import current_user, permission_layer +from inquire.mongo import * + + +class LeaveCourse(Resource): + def put(self): + # Parse arguments + parser = reqparse.RequestParser() + parser.add_argument('courseId') + args = parser.parse_args() + + # Get the course you want to leave + try: + course_to_leave = Course.objects.get({'_id': args['courseId']}) + except Course.DoesNotExist: + return {'deleted': False, 'errors': f"No course with id {args['courseId']}"}, 403 + except Course.MultipleObjectsReturned: + return {'deleted': False, 'errors': f"Duplicate course detected, multiple courses in database with id {args['courseId']}"}, 400 + + # Creator of a course should delete instead + # TODO: maybe check to see if the creator passed off the admin role to another user in the course? + if current_user._id == course_to_leave.instructorID: + return {"errors": ["Creators of a course cannot leave. Instead delete the course."]}, 400 + + # Leave the course itself + enrolled_in_course = False + for course in current_user.courses: + if course.courseId == args['courseId']: + enrolled_in_course = True + current_user.courses.remove(course) + break + + # Trying to leave a course you're not even in + if not enrolled_in_course: + return {"errors": [f"You are not enrolled in any course with ID {args['courseId']}"]}, 400 + + # Delete yourself from the roles list in the course + course_to_leave.roles[course.role].remove(current_user._id) + + # Save the changes we've made + current_user.save() + course_to_leave.save() + + return {"success": [f"Successfully left {course.courseName}"]}, 200 From 862bb5f24a232c854d8d087ecdd0462e9a93db10 Mon Sep 17 00:00:00 2001 From: Brian Gunnarson Date: Fri, 20 Aug 2021 20:48:56 -0700 Subject: [PATCH 23/58] Added modal to confirm leaving course and functionality now works Modal now pops up when you click the "leave course" button to confirm if you want to leave the course. Clicking confirm works and updates the state of the courses you're currently in on your profile. Still having issues with styling that come from z-index in AboutUser.js in the ContentWrapper. --- .../src/components/userProfile/AboutUser.js | 69 ++++++++----- .../components/userProfile/UserCourseCard.js | 19 +++- .../src/components/userProfile/UserCourses.js | 19 +++- .../src/components/userProfile/UserProfile.js | 99 ++++++++++++++++++- 4 files changed, 173 insertions(+), 33 deletions(-) diff --git a/client/src/components/userProfile/AboutUser.js b/client/src/components/userProfile/AboutUser.js index 62a9382..3487a15 100644 --- a/client/src/components/userProfile/AboutUser.js +++ b/client/src/components/userProfile/AboutUser.js @@ -15,18 +15,24 @@ const renderEditButton = ( toggleEdit, setAboutMe, editingProfile, - initialAboutMe + initialAboutMe, + modalIsShown ) => { + var opacity = "100%"; + if (modalIsShown) { + opacity = "60%"; + } if (editingProfile) { return ( @@ -40,6 +46,7 @@ const renderEditButton = ( onClick={() => { toggleEdit(!editingProfile); }} + style={{ opacity: opacity }} > Edit Profile @@ -85,7 +92,13 @@ const renderColorIcon = ( } }; -const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { +const AboutUser = ({ + userObject, + isMyProfile, + profileId, + modalIsShown, + ...props +}) => { const [editingProfile, toggleEdit] = useState(false); const [aboutMe, setAboutMe] = useState(null); const [initialAboutMe, setInitialAboutMe] = useState(null); @@ -146,9 +159,28 @@ const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { return ( <> + + {displayColorSelector && ( + + )} + {isMyProfile ? ( + renderColorIcon( + background, + toggleColorDisplay, + displayColorSelector + ) + ) : ( + <> + )} + - + { toggleEdit, setAboutMe, editingProfile, - initialAboutMe + initialAboutMe, + modalIsShown ) ) : ( <> @@ -167,7 +200,7 @@ const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => { - + {userObject.first + " " + userObject.last}

About

@@ -202,25 +235,6 @@ const AboutUser = ({ userObject, isMyProfile, profileId, ...props }) => {
- - {displayColorSelector && ( - - )} - {isMyProfile ? ( - renderColorIcon( - background, - toggleColorDisplay, - displayColorSelector - ) - ) : ( - <> - )} -
); @@ -290,6 +304,8 @@ const UserName = styled.h1` color: ${(props) => props.backgroundColor == "dark" ? css`#fff` : css`#162B55`}; + opacity: ${(props) => (props.modalActive ? css`60%` : css`100%`)}; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); `; @@ -315,7 +331,8 @@ const ImageWrapper = styled.div` width: 200px; height: 200px; - background-color: #f8f8f8; + background-color: ${(props) => + props.modalActive ? css`#CBCFD7` : css`#F8F8F8`}; border-radius: 50%; box-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.15); diff --git a/client/src/components/userProfile/UserCourseCard.js b/client/src/components/userProfile/UserCourseCard.js index 6cbffd7..b95ca6b 100644 --- a/client/src/components/userProfile/UserCourseCard.js +++ b/client/src/components/userProfile/UserCourseCard.js @@ -1,8 +1,17 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; import Button from "../common/Button"; - -const UserCourseCard = ({ userCourseObject, isMyProfile, ...props }) => { +import Modal from "../common/Modal"; +import LazyFetch from "../common/requests/LazyFetch"; + +const UserCourseCard = ({ + userCourseObject, + isMyProfile, + toggleModal, + setCourseToLeave, + setName, + ...props +}) => { let title = userCourseObject?.courseName; let subtitle = ""; if (userCourseObject.nickname) { @@ -24,6 +33,11 @@ const UserCourseCard = ({ userCourseObject, isMyProfile, ...props }) => { autoWidth buttonColor={`#DC2B2B`} style={{ color: `#DC2B2B` }} + onClick={() => { + toggleModal(true); + setCourseToLeave(userCourseObject.courseId); + setName(userCourseObject.courseName); + }} > Leave Course @@ -31,7 +45,6 @@ const UserCourseCard = ({ userCourseObject, isMyProfile, ...props }) => { ) : ( <> )} - {/*

*/} diff --git a/client/src/components/userProfile/UserCourses.js b/client/src/components/userProfile/UserCourses.js index bd51977..c8bd796 100644 --- a/client/src/components/userProfile/UserCourses.js +++ b/client/src/components/userProfile/UserCourses.js @@ -3,8 +3,18 @@ import styled, { css } from "styled-components"; import LazyFetch from "../common/requests/LazyFetch"; import UserCourseCard from "./UserCourseCard"; -const UserCourses = ({ userObject, isMyProfile, profileId, ...props }) => { - const [courses, setCourses] = useState([]); +const UserCourses = ({ + userObject, + isMyProfile, + profileId, + toggleModal, + setCourseToLeave, + setName, + setCourses, + courses, + changeMade, + ...props +}) => { const generateUserCourseCards = (courseList) => { let userCourseCards = courseList.map((course, index) => ( { id={`course-card-${index}`} userCourseObject={course} isMyProfile={isMyProfile} + toggleModal={toggleModal} + setCourseToLeave={setCourseToLeave} + setName={setName} /> )); @@ -27,7 +40,7 @@ const UserCourses = ({ userObject, isMyProfile, profileId, ...props }) => { setCourses(generateUserCourseCards(response.courses)); }, }); - }, []); + }, [changeMade]); return ( <> diff --git a/client/src/components/userProfile/UserProfile.js b/client/src/components/userProfile/UserProfile.js index d6bdf9b..4174bfd 100644 --- a/client/src/components/userProfile/UserProfile.js +++ b/client/src/components/userProfile/UserProfile.js @@ -1,6 +1,10 @@ -import React, { useContext } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import styled, { css } from "styled-components"; +import Button from "../common/Button"; +import Errors from "../common/Errors"; +import Modal from "../common/Modal"; +import LazyFetch from "../common/requests/LazyFetch"; import { UserContext } from "../context/UserProvider"; import AboutUser from "./AboutUser"; import UserCourses from "./UserCourses"; @@ -9,6 +13,37 @@ const UserProfile = ({ props }) => { const user = useContext(UserContext); const { profileId } = useParams(); const isMyProfile = user._id == profileId ? true : false; + const [courses, setCourses] = useState([]); + + // Leaving Course/Modal state variables + const [courseToLeave, setCourseToLeave] = useState(null); + const [modalIsShown, toggleModal] = useState(false); + const [success, setSuccessMessage] = useState(null); + const [errors, setErrorMessage] = useState(null); + const [display, toggleDisplay] = useState("flex"); + const [courseToLeaveName, setName] = useState(null); + const [changeMade, toggleChangeMade] = useState(false); + console.log("courses:", courses); + + const handleLeaveCourse = () => { + LazyFetch({ + type: "put", + endpoint: "/leaveCourse", + data: { courseId: courseToLeave }, + onSuccess: (response) => { + console.log(response.success); + toggleDisplay("none"); + setSuccessMessage(response.success); + toggleChangeMade(!changeMade); + }, + onFailure: (err) => { + if (err.response && err.response.data) { + setErrorMessage(err.response.data.errors); + } + }, + }); + }; + return ( <> @@ -17,14 +52,56 @@ const UserProfile = ({ props }) => { userObject={user} isMyProfile={isMyProfile} profileId={profileId} + modalIsShown={modalIsShown} />
+ {modalIsShown && ( + { + toggleModal(false); + setCourseToLeave(null); + toggleDisplay("flex"); + setName(null); + }} + width={"724px"} + data-testid={"leave-course-modal"} + > + + CONFIRM LEAVING COURSE + + {success} + + + Are you sure you want to leave {courseToLeaveName}? + + + + + + )} ); }; @@ -55,6 +132,26 @@ const ScrollingDiv = styled.div` overflow: auto; `; +const InnerModalWrapper = styled.div``; + +const Success = styled.div` + display: none; + text-align: center; + font-size: 25px; +`; + +const Title = styled.h4` + font-size: 25px; + padding: 0px 0px 10px 0px; +`; + +const ContentSection = styled.div` + background-color: #f8f8f8; + padding: 15px; + border-radius: 4px; + text-align: center; +`; + /** THIS ACCOUNTS FOR WEIRD SCROLLING DIV STUFF */ // const OverflowCounter = styled.div` // width: 100%; From 6bcb42658fe018f826bd54bcc5bf629afa9ea4eb Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Sun, 22 Aug 2021 15:16:08 -0700 Subject: [PATCH 24/58] WOW CUSOM TOOLTIP SYSTEM EZ PZ --- .../src/components/common/InquireTooltip.js | 139 ++++++++++++++++++ client/src/components/common/css/toolTip.css | 61 ++++++++ client/src/components/courses/CourseCard.js | 11 ++ client/src/components/courses/TopContent.js | 12 +- .../src/components/navigation/LeftNavBar.js | 62 +++++--- client/src/components/navigation/MenuItem.js | 4 +- .../components/navigation/ProfileDropdown.js | 1 + 7 files changed, 267 insertions(+), 23 deletions(-) create mode 100644 client/src/components/common/InquireTooltip.js create mode 100644 client/src/components/common/css/toolTip.css diff --git a/client/src/components/common/InquireTooltip.js b/client/src/components/common/InquireTooltip.js new file mode 100644 index 0000000..c53e219 --- /dev/null +++ b/client/src/components/common/InquireTooltip.js @@ -0,0 +1,139 @@ +import React, { useEffect, useState } from "react"; +import styled, { css } from "styled-components"; + +// import "./css/toolTip.css"; + +/** + * @param {*} tooltipText the text contained in the tooltip popup modal. + * @param {*} customPosition json object to set custom absolute tooltip position around the element its wrapping. + * @param {*} hoverDelayStart the time in milliseconds before the tooltip is shown + * @param {*} hoverDelayEnd the time in milliseconds before the tooltip is not shown + * @param {*} debug tells this component to print debug info to the console + * @brief The customPosition object should look like the following: + * { top: "value", right: "value", bottom: "value", left: "value" } + */ +const InquireTooltip = ({ + children, + tooltipText, + customPosition, + hoverDelay, + debug, + ...props +}) => { + const [hoverState, setHoverState] = useState(false); + + useEffect(() => { + debug && console.log(props.id, hoverState); + }, [hoverState]); + + return ( + <> + { + debug && event && console.log(event); + + if (hoverDelay) + setTimeout(() => { + setHoverState(true); + }, hoverDelay); + else setHoverState(true); + }} + onMouseLeave={(event) => { + debug && event && console.log(event); + + if (hoverDelay) + setTimeout(() => { + setHoverState(false); + }, hoverDelay); + else setHoverState(false); + }} + showTooltip={hoverState} + customPosition={customPosition} + > + {children} + {tooltipText ?

{tooltipText}

: <>} +
+ + ); +}; + +export default InquireTooltip; + +const TooltipContainer = styled.div` + position: relative; + + #tooltip-text { + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + + position: absolute; + + ${(props) => + props.customPosition?.top + ? css` + top: ${props.customPosition.top}; + ` + : css` + top: 0; + `}; + + ${(props) => + props.customPosition?.right + ? css` + right: ${props.customPosition.right}; + ` + : css` + right: auto; + `}; + + ${(props) => + props.customPosition?.bottom + ? css` + bottom: ${props.customPosition.bottom}; + ` + : css` + bottom: auto; + `}; + + ${(props) => + props.customPosition?.left + ? css` + left: ${props.customPosition.left}; + ` + : css` + left: 100%; + `}; + + width: 250px; + min-height: 25%; + padding: 0.25em; + + border-radius: 4px; + border: 2px solid #e7e7e7; + + box-shadow: 0px 0.25em 0.5em 0.125em rgb(0 0 0 / 25%); + + background-color: #f8f8f8; + color: #162b55; + visibility: ${(props) => (props.showTooltip ? css`visible` : css`hidden`)}; + + transition: 150ms ease-in-out; + + ::after { + display: inline-block; + width: 100%; + /* height: 25px; */ + position: absolute; + top: calc(50% - 8px); + left: -101%; + content: ""; + /* background-color: #f8f8f8; */ + border-top: 8px solid transparent; + border-right: 16px solid #e7e7e7; + border-bottom: 8px solid transparent; + } + } +`; diff --git a/client/src/components/common/css/toolTip.css b/client/src/components/common/css/toolTip.css new file mode 100644 index 0000000..90eae78 --- /dev/null +++ b/client/src/components/common/css/toolTip.css @@ -0,0 +1,61 @@ +/*== start of code for tooltips ==*/ +.tool { + cursor: help; + position: relative; + z-index: 9999; +} + +/*== common styles for both parts of tool tip ==*/ +.tool::before, +.tool::after { + left: 50%; + opacity: 0; + position: absolute; + z-index: -100; +} + +.tool:hover::before, +.tool:focus::before, +.tool:hover::after, +.tool:focus::after { + opacity: 1; + transform: scale(1) translateY(0); + z-index: 100; +} + +/*== pointer tip ==*/ +.tool::before { + border-style: solid; + border-width: 1em 0.75em 0 0.75em; + border-color: #3e474f transparent transparent transparent; + bottom: 100%; + content: ""; + margin-left: -0.5em; + transition: all 0.65s cubic-bezier(0.84, -0.18, 0.31, 1.26), + opacity 0.65s 0.5s; + transform: scale(0.6) translateY(-90%); +} + +.tool:hover::before, +.tool:focus::before { + transition: all 0.65s cubic-bezier(0.84, -0.18, 0.31, 1.26) 0.2s; +} + +/*== speech bubble ==*/ +.tool::after { + background: #3e474f; + border-radius: 0.25em; + bottom: 180%; + color: #edeff0; + content: attr(data-tip); + margin-left: -8.75em; + padding: 1em; + transition: all 0.65s cubic-bezier(0.84, -0.18, 0.31, 1.26) 0.2s; + transform: scale(0.6) translateY(50%); + width: 17.5em; +} + +.tool:hover::after, +.tool:focus::after { + transition: all 0.65s cubic-bezier(0.84, -0.18, 0.31, 1.26); +} diff --git a/client/src/components/courses/CourseCard.js b/client/src/components/courses/CourseCard.js index aca2e8d..2652892 100644 --- a/client/src/components/courses/CourseCard.js +++ b/client/src/components/courses/CourseCard.js @@ -11,6 +11,7 @@ import Button from "../common/Button"; import Input from "../common/Input"; import CloseButtonIcon from "../../imgs/close.svg"; import RemoveNickname from "../../imgs/remove-nickname.svg"; +import InquireTooltip from "../common/InquireTooltip"; /** Course Card * @brief Component for displaying courses the user is a part of. Component is one of many courses @@ -125,6 +126,15 @@ class CourseCard extends React.Component { render() { return ( <> + {/* */} @@ -243,6 +253,7 @@ class CourseCard extends React.Component { )} + {/* */} {this.state.displayColorSelector && ( { return ( COURSES - - + + + + + + ); }; diff --git a/client/src/components/navigation/LeftNavBar.js b/client/src/components/navigation/LeftNavBar.js index 5e62560..ce2e98b 100644 --- a/client/src/components/navigation/LeftNavBar.js +++ b/client/src/components/navigation/LeftNavBar.js @@ -6,6 +6,7 @@ import MenuItem from "./MenuItem"; import HomeImg from "../../imgs/home-white.svg"; import CourseImg from "../../imgs/courses-white.svg"; import MessagesImg from "../../imgs/messages-white.svg"; +import InquireTooltip from "../common/InquireTooltip"; /** LeftNavBar Component * @brief Wrapper containing MenuItems routing the user to the main website pages @@ -17,24 +18,38 @@ const LeftNavBar = () => { return ( ); @@ -54,6 +69,13 @@ const Nav = styled.nav` `; const Wrapper = styled.ul` - margin-top: 55px; - padding: 10px 0; + margin-top: 66px; // 66px is the height of topNavBar + /* padding: 0.5em; */ + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + /* border: 1px solid red; */ `; diff --git a/client/src/components/navigation/MenuItem.js b/client/src/components/navigation/MenuItem.js index bf20121..0da6a56 100644 --- a/client/src/components/navigation/MenuItem.js +++ b/client/src/components/navigation/MenuItem.js @@ -26,7 +26,9 @@ const MenuItem = ({ img, label, to, active }) => { export default MenuItem; const Item = styled.li` - margin: 12px 6px; + width: 64px; + height: 64px; + margin: 0.5em 0; text-align: center; background-color: ${(props) => props.active && "#0B1B3A"}; padding: 3px; diff --git a/client/src/components/navigation/ProfileDropdown.js b/client/src/components/navigation/ProfileDropdown.js index d516b76..cde2f12 100644 --- a/client/src/components/navigation/ProfileDropdown.js +++ b/client/src/components/navigation/ProfileDropdown.js @@ -48,6 +48,7 @@ export default ProfileDropdown; const Wrapper = styled.div` display: flex; justify-content: flex-end; + cursor: pointer; `; const DropdownWrapper = styled.div` From 777c9f78d0d7b1b85e96928146a379a51acbee50 Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Sun, 22 Aug 2021 21:19:11 -0700 Subject: [PATCH 25/58] Various style changes!!! --- client/src/components/common/Button.js | 16 +++- .../src/components/common/InquireTooltip.js | 31 +++--- client/src/components/courses/CourseCard.js | 2 +- client/src/components/courses/Courses.js | 6 ++ client/src/components/courses/CreateCourse.js | 49 ++++++++-- client/src/components/courses/JoinCourse.js | 39 +++++++- client/src/components/courses/TopContent.js | 49 ++++++++-- .../src/components/navigation/LeftNavBar.js | 9 +- client/src/components/navigation/MenuItem.js | 7 +- .../components/navigation/ProfileDropdown.js | 5 + client/src/components/navigation/TopNavBar.js | 96 ++++++++++++++++--- client/src/imgs/clock.svg | 3 + client/src/imgs/inquire_logo_navbar.svg | 29 ++++++ 13 files changed, 284 insertions(+), 57 deletions(-) create mode 100644 client/src/imgs/clock.svg create mode 100644 client/src/imgs/inquire_logo_navbar.svg diff --git a/client/src/components/common/Button.js b/client/src/components/common/Button.js index ed2bf51..c93524d 100644 --- a/client/src/components/common/Button.js +++ b/client/src/components/common/Button.js @@ -9,12 +9,20 @@ import LoadingDots from "./animation/LoadingDots"; * @version 1.0.0 * @author [Alec Springel](https://github.com/alecspringel) , [Seth Tal](https://github.com/Sephta) */ -const Button = ({ children, loading, onClick, ...props }) => { +const Button = ({ children, loading, onClick, customStyledCSS, ...props }) => { const clickHandler = loading ? undefined : onClick; return ( - - {loading ? : children} - + <> + {customStyledCSS ? ( + + {loading ? : children} + + ) : ( + + {loading ? : children} + + )} + ); }; diff --git a/client/src/components/common/InquireTooltip.js b/client/src/components/common/InquireTooltip.js index c53e219..dd70d32 100644 --- a/client/src/components/common/InquireTooltip.js +++ b/client/src/components/common/InquireTooltip.js @@ -6,8 +6,7 @@ import styled, { css } from "styled-components"; /** * @param {*} tooltipText the text contained in the tooltip popup modal. * @param {*} customPosition json object to set custom absolute tooltip position around the element its wrapping. - * @param {*} hoverDelayStart the time in milliseconds before the tooltip is shown - * @param {*} hoverDelayEnd the time in milliseconds before the tooltip is not shown + * @param {*} hoverDelay the time in milliseconds before the tooltip toggled * @param {*} debug tells this component to print debug info to the console * @brief The customPosition object should look like the following: * { top: "value", right: "value", bottom: "value", left: "value" } @@ -121,19 +120,19 @@ const TooltipContainer = styled.div` visibility: ${(props) => (props.showTooltip ? css`visible` : css`hidden`)}; transition: 150ms ease-in-out; - - ::after { - display: inline-block; - width: 100%; - /* height: 25px; */ - position: absolute; - top: calc(50% - 8px); - left: -101%; - content: ""; - /* background-color: #f8f8f8; */ - border-top: 8px solid transparent; - border-right: 16px solid #e7e7e7; - border-bottom: 8px solid transparent; - } } `; + +// ::after { +// display: inline-block; +// width: 50%; +// /* height: 25px; */ +// position: absolute; +// top: calc(50% - 8px); +// left: -160%; +// content: ""; +// /* background-color: #f8f8f8; */ +// border-top: 8px solid transparent; +// border-right: 16px solid #e7e7e7; +// border-bottom: 8px solid transparent; +// } diff --git a/client/src/components/courses/CourseCard.js b/client/src/components/courses/CourseCard.js index 2652892..497f164 100644 --- a/client/src/components/courses/CourseCard.js +++ b/client/src/components/courses/CourseCard.js @@ -145,7 +145,7 @@ class CourseCard extends React.Component { src={MessagesImg} alt={"Messages"} width={"25em"} - title={"Unread posts"} + title={"Create post for this course"} > {this.state.numMsgs > 0 && this.state.numMsgs ? ( diff --git a/client/src/components/courses/Courses.js b/client/src/components/courses/Courses.js index fe30df1..1dbc4cb 100644 --- a/client/src/components/courses/Courses.js +++ b/client/src/components/courses/Courses.js @@ -65,8 +65,14 @@ const WrapAll = styled.div` const WrapDisplay = styled.div` display: flex; + align-items: center; + /* justify-content: center; */ flex-wrap: wrap; margin: 1em 1em 1em 1em; padding: 0; //transition: 150ms ease-in-out; + + @media only screen and (max-width: 650px) { + justify-content: center; + } `; diff --git a/client/src/components/courses/CreateCourse.js b/client/src/components/courses/CreateCourse.js index 0187339..94e8610 100644 --- a/client/src/components/courses/CreateCourse.js +++ b/client/src/components/courses/CreateCourse.js @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; import Button from "../common/Button"; import Modal from "../common/Modal"; import CourseConfirmation from "./createCourse/CourseConfirmation"; @@ -61,15 +61,9 @@ const CreateCourse = ({ courseList, setCourseList }) => { return ( <> - + {modalIsShown && ( { @@ -98,3 +92,40 @@ const CreateCourse = ({ courseList, setCourseList }) => { }; export default CreateCourse; + +const CustomButton = styled.div` + cursor: pointer; + border: none; + border-radius: 3px; + + display: flex; + justify-content: center; + align-items: center; + + width: 9em; + margin-left: 1em; + + border-radius: 4px; + padding: 0.5em 0.125em; + background-color: #e7e7e7; + color: #162b55; + &:hover { + background-color: #dedede; + } + + transition: 150ms ease-out; + + @media only screen and (min-width: 1201px) { + width: 10em; + } + @media only screen and (max-width: 650px) { + width: 8em; + font-size: 14px; + /* margin-left: 0.5em; */ + /* margin: 0; */ + } + @media only screen and (max-width: 480px) { + font-size: 12px; + width: 8em; + } +`; diff --git a/client/src/components/courses/JoinCourse.js b/client/src/components/courses/JoinCourse.js index a9f4a1f..73dfce8 100644 --- a/client/src/components/courses/JoinCourse.js +++ b/client/src/components/courses/JoinCourse.js @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import styled, { css } from "styled-components"; import Button from "../common/Button"; import Modal from "../common/Modal"; import JoinConfirmation from "./joinCourse/JoinConfirmation"; @@ -18,9 +19,9 @@ const JoinCourse = ({ courseList, setCourseList }) => { return ( <> - + {modalIsShown && ( { @@ -50,3 +51,37 @@ const JoinCourse = ({ courseList, setCourseList }) => { }; export default JoinCourse; + +const CustomButton = styled.div` + cursor: pointer; + border: none; + border-radius: 3px; + + display: flex; + justify-content: center; + align-items: center; + + width: 9em; + + border-radius: 4px; + padding: 0.5em 0.125em; + background-color: #e7e7e7; + color: #162b55; + &:hover { + background-color: #dedede; + } + + transition: 150ms ease-out; + + @media only screen and (min-width: 1201px) { + width: 10em; + } + @media only screen and (max-width: 650px) { + font-size: 14px; + width: 8em; + } + @media only screen and (max-width: 480px) { + font-size: 12px; + width: 7em; + } +`; diff --git a/client/src/components/courses/TopContent.js b/client/src/components/courses/TopContent.js index 7a2e994..f44cc28 100644 --- a/client/src/components/courses/TopContent.js +++ b/client/src/components/courses/TopContent.js @@ -15,15 +15,32 @@ const TopContent = ({ courseList, setCourseList }) => { return ( COURSES - - - - - - + + + + + + + + ); }; @@ -37,3 +54,17 @@ const Title = styled.h3` const TopWrapper = styled.div` margin: 1em 1em 1em 1em; `; + +const FlexWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + + @media only screen and (max-width: 650px) { + width: 100%; + flex: 1; + /* align-items: left; */ + justify-content: space-evenly; + } +`; diff --git a/client/src/components/navigation/LeftNavBar.js b/client/src/components/navigation/LeftNavBar.js index ce2e98b..dec41c8 100644 --- a/client/src/components/navigation/LeftNavBar.js +++ b/client/src/components/navigation/LeftNavBar.js @@ -3,6 +3,7 @@ import styled from "styled-components"; import { useLocation } from "react-router-dom"; import MenuItem from "./MenuItem"; // Menu item images: +import ClockImg from "../../imgs/clock.svg"; import HomeImg from "../../imgs/home-white.svg"; import CourseImg from "../../imgs/courses-white.svg"; import MessagesImg from "../../imgs/messages-white.svg"; @@ -20,6 +21,7 @@ const LeftNavBar = () => { { > { export default MenuItem; const Item = styled.li` + display: flex; + align-items: center; + justify-content: center; + width: 64px; height: 64px; margin: 0.5em 0; text-align: center; background-color: ${(props) => props.active && "#0B1B3A"}; - padding: 3px; + padding-top: 0.5em; border-radius: 3px; `; const Label = styled.p` + margin: 0.5em 0; color: #fff; font-size: 13px; `; diff --git a/client/src/components/navigation/ProfileDropdown.js b/client/src/components/navigation/ProfileDropdown.js index cde2f12..5d0a5e0 100644 --- a/client/src/components/navigation/ProfileDropdown.js +++ b/client/src/components/navigation/ProfileDropdown.js @@ -77,4 +77,9 @@ const ArrowImg = styled.img` const Name = styled.h4` white-space: nowrap; margin-right: 10px; + + @media only screen and (max-width: 768px) { + width: 0; + visibility: hidden; + } `; diff --git a/client/src/components/navigation/TopNavBar.js b/client/src/components/navigation/TopNavBar.js index c76a5fb..b9b3fbd 100644 --- a/client/src/components/navigation/TopNavBar.js +++ b/client/src/components/navigation/TopNavBar.js @@ -9,6 +9,7 @@ import Dropdown from "../common/dropdown/Dropdown"; import { UserContext } from "../context/UserProvider"; import Arrow from "../../imgs/carrot-down-secondary.svg"; import Icon from "../common/Icon"; +import InquireLogoSVG from "../../imgs/inquire_logo_navbar.svg"; import "../common/css/noTextSelection.css"; /** TopNavBar Component @@ -42,19 +43,23 @@ const TopNavBar = () => { return ( ); }; export default TopNavBar; -const Wrapper = styled.div` +const FlexWrapper = styled.div` flex: 1; `; +const InquireLogo = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; + +const LogoIMG = styled.img` + width: 38px; + height: auto; + transition: 150ms ease-out; + + @media only screen and (min-width: 1201px) { + width: 38px; + } + /* @media only screen and (max-width: 1200px) { + width: 450px; + } + @media only screen and (max-width: 1024px) { + width: 300px; + } */ + @media only screen and (max-width: 768px) { + width: 24px; + } + @media only screen and (max-width: 480px) { + width: 20px; + } +`; + +const LogoText = styled.h2` + margin-left: 0.5em; + font-size: 32px; + transition: 150ms ease-out; + + @media only screen and (min-width: 1201px) { + font-size: 32px; + } + /* @media only screen and (max-width: 1200px) { + font-size: 450px; + } + @media only screen and (max-width: 1024px) { + font-size: 300px; + } */ + @media only screen and (max-width: 768px) { + font-size: 24px; + } + @media only screen and (max-width: 480px) { + font-size: 20px; + } +`; + const Nav = styled.nav` width: 100vw; height: 66px; @@ -89,7 +144,7 @@ const Nav = styled.nav` display: flex; align-items: center; padding: 0 20px; - z-index: 9999; + z-index: 9998; `; const LogoImg = styled.img` @@ -98,7 +153,6 @@ const LogoImg = styled.img` const DropdownSelector = styled.div` background-color: #e7e7e7; - width: 100vw; height: 2rem; display: flex; align-items: center; @@ -108,6 +162,24 @@ const DropdownSelector = styled.div` &:focus { outline-color: #162b55; } + + transition: 150ms ease-out; + + @media only screen and (min-width: 1201px) { + width: 100vw; + } + @media only screen and (max-width: 1200px) { + width: 450px; + } + @media only screen and (max-width: 1024px) { + width: 300px; + } + @media only screen and (max-width: 768px) { + width: 200px; + } + @media only screen and (max-width: 480px) { + width: 150px; + } `; const SelectionName = styled.p` diff --git a/client/src/imgs/clock.svg b/client/src/imgs/clock.svg new file mode 100644 index 0000000..5f84abf --- /dev/null +++ b/client/src/imgs/clock.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/src/imgs/inquire_logo_navbar.svg b/client/src/imgs/inquire_logo_navbar.svg new file mode 100644 index 0000000..9443034 --- /dev/null +++ b/client/src/imgs/inquire_logo_navbar.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3b8e52bfcbf36d97d46c79fadf53ac027b16d365 Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Tue, 24 Aug 2021 10:18:10 -0700 Subject: [PATCH 26/58] minor changes --- client/src/components/courses/CreateCourse.js | 3 + client/src/components/courses/JoinCourse.js | 4 + .../components/navigation/ProfileDropdown.js | 8 + client/src/components/navigation/TopNavBar.js | 10 +- client/src/components/posts/ClassView.js | 1 + .../components/posts/leftSideBar/Sidebar.js | 152 ++++++++++-------- 6 files changed, 106 insertions(+), 72 deletions(-) diff --git a/client/src/components/courses/CreateCourse.js b/client/src/components/courses/CreateCourse.js index 94e8610..1e694bb 100644 --- a/client/src/components/courses/CreateCourse.js +++ b/client/src/components/courses/CreateCourse.js @@ -128,4 +128,7 @@ const CustomButton = styled.div` font-size: 12px; width: 8em; } + @media only screen and (max-width: 400px) { + font-size: 8px; + } `; diff --git a/client/src/components/courses/JoinCourse.js b/client/src/components/courses/JoinCourse.js index 73dfce8..5e9fdc2 100644 --- a/client/src/components/courses/JoinCourse.js +++ b/client/src/components/courses/JoinCourse.js @@ -84,4 +84,8 @@ const CustomButton = styled.div` font-size: 12px; width: 7em; } + @media only screen and (max-width: 400px) { + font-size: 8px; + width: 8em; + } `; diff --git a/client/src/components/navigation/ProfileDropdown.js b/client/src/components/navigation/ProfileDropdown.js index 5d0a5e0..cc7b136 100644 --- a/client/src/components/navigation/ProfileDropdown.js +++ b/client/src/components/navigation/ProfileDropdown.js @@ -49,6 +49,13 @@ const Wrapper = styled.div` display: flex; justify-content: flex-end; cursor: pointer; + + @media only screen and (max-width: 480px) { + align-items: center; + justify-content: center; + /* height: 0; */ + /* visibility: hidden; */ + } `; const DropdownWrapper = styled.div` @@ -80,6 +87,7 @@ const Name = styled.h4` @media only screen and (max-width: 768px) { width: 0; + height: 0; visibility: hidden; } `; diff --git a/client/src/components/navigation/TopNavBar.js b/client/src/components/navigation/TopNavBar.js index b9b3fbd..fa3ed9b 100644 --- a/client/src/components/navigation/TopNavBar.js +++ b/client/src/components/navigation/TopNavBar.js @@ -116,6 +116,9 @@ const LogoText = styled.h2` font-size: 32px; transition: 150ms ease-out; + @import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap"); + font-family: "Poppins", sans-serif; + @media only screen and (min-width: 1201px) { font-size: 32px; } @@ -145,6 +148,10 @@ const Nav = styled.nav` align-items: center; padding: 0 20px; z-index: 9998; + + @media only screen and (max-width: 480px) { + padding: 0 0.5em; + } `; const LogoImg = styled.img` @@ -178,7 +185,8 @@ const DropdownSelector = styled.div` width: 200px; } @media only screen and (max-width: 480px) { - width: 150px; + width: 125px; + font-size: 14px; } `; diff --git a/client/src/components/posts/ClassView.js b/client/src/components/posts/ClassView.js index b68c644..dcb75d8 100644 --- a/client/src/components/posts/ClassView.js +++ b/client/src/components/posts/ClassView.js @@ -51,6 +51,7 @@ const ClassView = ({ props }) => { return ( { //var nameRatio = 1; return ( - - - - {classroomName} - - -
-
- - - - - - -
- - My Posts - -
- - - {/* */} -
-
-
+ <> + + + + {classroomName} + + +
+ + Course Filters + +
+ + + + + + +
+ + My Filters + +
+ + +
+
+
+ ); }; @@ -116,6 +115,12 @@ const HR = styled.hr` const FlexWrapper = styled.div` flex: 0 0 200px; + transition: 150ms ease-out; + @media only screen and (max-width: 768px) { + width: 0; + overflow: hidden; + flex: 0 0 0; + } `; const Container = styled.div` @@ -123,6 +128,11 @@ const Container = styled.div` box-shadow: 5px 2px 6px -2px rgba(0, 0, 0, 0.15); background-color: #fff; max-width: 200px; + transition: 150ms ease-out; + @media only screen and (max-width: 768px) { + width: 0; + overflow: hidden; + } `; const ClassTitle = styled.h1` @@ -139,7 +149,7 @@ const ClassSubtitle = styled.h2` line-height: 2.5em; font-size: 1.25rem; text-align: left; - color: #b8b8b8; + color: #7e7e7e; // will find better grey later but for now this is for contrast user-select: none; `; From bffcb164541e341efbbf1b7e1121588100da7f2c Mon Sep 17 00:00:00 2001 From: Seth Tal Date: Tue, 24 Aug 2021 10:34:40 -0700 Subject: [PATCH 27/58] minor change --- client/src/components/navigation/LeftNavBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/navigation/LeftNavBar.js b/client/src/components/navigation/LeftNavBar.js index dec41c8..337b066 100644 --- a/client/src/components/navigation/LeftNavBar.js +++ b/client/src/components/navigation/LeftNavBar.js @@ -20,7 +20,7 @@ const LeftNavBar = () => {