diff --git a/package.json b/package.json index 12f9fb06b4edc360666001146a680e50da80361b..f70128ae289cee26401b58b0b74ac6aae94a81fc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "axios": "^0.21.1", "http-proxy-middleware": "^1.0.6", "moment": "^2.29.1", + "moment-duration-format": "^2.3.2", "nodemon": "^2.0.7", "prettier": "^2.2.1", "react": "^17.0.1", diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000000000000000000000000000000000000..7797f7c6a7356b0d451d11a49925df854c22e978 --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/src/components/survey/AddChoiceInput.jsx b/src/components/survey/AddChoiceInput.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/components/survey/ChoiceBox.jsx b/src/components/survey/ChoiceBox.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3d07fb1ceb8f9bd640c8e65055ba895df38ed115 --- /dev/null +++ b/src/components/survey/ChoiceBox.jsx @@ -0,0 +1,33 @@ +import '../../styles/survey/ChoiceBox.css'; + +export const ChoiceBoxVoted = ({ choice }) => { + const { name, votes } = choice; + const isUserVoted = Math.random() < 0.5; + + return ( + <div + className="choice-box__voted" + style={{ + background: isUserVoted + ? '#FEA02F' + : '#FFFFFF', + color: isUserVoted + ? '#FFFFFF' + : '#000000', + }} + > + <h6 className="name">{name}</h6> + <h6 className="votes">{votes}</h6> + </div> + ); +}; + +export const ChoiceBoxUnvoted = ({ choice, isPreview=false }) => { + const { name } = choice; + + return ( + <div className={`choice-box__unvoted ${isPreview && 'is_preview'}`}> + <h6 className="name">{name}</h6> + </div> + ); +}; diff --git a/src/components/survey/CreateSurvey.jsx b/src/components/survey/CreateSurvey.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ee6063565cb621571fa548cf3a9b3cf9a6fe36c4 --- /dev/null +++ b/src/components/survey/CreateSurvey.jsx @@ -0,0 +1,216 @@ +import axios from 'axios'; +import React, { useState, useEffect } from 'react'; +import { useInput } from '../../helpers/hooks/input-hook'; +import { authHeader } from '../../helpers/services/auth.service'; +import { API_URL } from '../../config/keys'; + +import '../../styles/thread/Form.css'; +import '../../styles/survey/Form.css'; +import { datetimeToUTC } from '../../helpers/time-util'; + +import AddChoiceSvg from '../../images/add-choice.svg'; +import DelChoiceSvg from '../../images/del-choice.svg'; + +const INITIAL_CHOICES = [ + { + "choice": "", + }, + { + "choice": "", + }, + { + "choice": "", + } +] + +export default function CreateSurvey(props) { + const { history } = props; + + const back = () => { + history.goBack(); + }; + + const redirect = (path) => { + history.push(path); + }; + + const { value: title, bind: bindTitle, reset: resetTitle } = useInput(''); + const { value: topicId, bind: bindTopicId, reset: resetTopicId } = useInput(0); + const { value: endDate, bind: bindEndDate, reset: resetEndDate } = useInput(null); + const { value: endTime, bind: bindEndTime, reset: resetEndTime } = useInput(null); + + const [choices, setChoices] = useState(INITIAL_CHOICES); + const [listTopic, setlistTopic] = useState([]); + const [message, setMessage] = useState(''); + + useEffect(() => { + async function getListTopic() { + const allTopic = await (await axios.get(`${API_URL}/topics`))?.data?.data; + setlistTopic(allTopic); + } + getListTopic(); + }, []); + + const handleChoiceInput = id => e => { + let updatedChoices = [...choices]; + updatedChoices[id].choice = e.target.value; + setChoices(updatedChoices); + } + + const handleAddChoiceBelow = id => { + let updatedChoices = [...choices]; + updatedChoices.splice(id+1, 0, {"choice": ""}); + setChoices(updatedChoices); + } + + const handleDeleteChoice = id => { + let updatedChoices = [...choices]; + updatedChoices.splice(id, 1); + setChoices(updatedChoices); + } + + const handleSubmit = async (event) => { + event.preventDefault(); + try { + const thisThread = await axios.post( + `${API_URL}/surveys`, + { + thread: { + points: 0, + title: title, + topic_id: topicId, + datetime: datetimeToUTC(endDate, endTime), + }, + }, + { + headers: authHeader(), + } + ); + const { data } = thisThread?.data; + redirect(`/topic/${data?.topic_name}/${data?.id}/page/1`); + } catch (error) { + setMessage("Thread's title already exist in the selected topic"); + } + resetTitle(); + resetTopicId(); + resetEndDate(); + resetEndTime(); + }; + + return ( + <div className="formComponentContainer"> + <div className="back" onClick={back}> + <i className="fas fa-angle-left"></i> + <h5>Back</h5> + </div> + <div className="header"> + <h1> + <b>Create a Survey</b> + </h1> + </div> + <div className="formSection"> + <div> + {message && ( + <div> + <div className="alert alert-danger" role="alert"> + <p>{message}</p> + </div> + </div> + )} + <form onSubmit={handleSubmit}> + <div className="formContainer"> + <label for="title">Title</label> + <input + type="text" + className="title" + name="title" + placeholder="Your survey's title" + required="false" + {...bindTitle} + /> + <label>Topic</label> + <select + className="topic" + name="topic" + required="false" + {...bindTopicId} + > + <option value="" /> + {listTopic.map((topic) => ( + <option value={topic.id}>{topic.name}</option> + ))} + </select> + <label for="choices">Choices</label> + <div className="choices"> + {choices.length ? ( + choices.map((_, id) => ( + <div className="choice"> + <input + className="choice__input" + name="choice" + placeholder={`Choice ${id + 1}`} + required="false" + onChange={handleChoiceInput(id)} + value={choices[id].choice} + /> + <div className="choice__navs"> + <div + className="choice__navs-item add" + onClick={() => handleAddChoiceBelow(id)} + > + <img src={AddChoiceSvg} alt="add-choice" /> + </div> + <div + className="choice__navs-item del" + onClick={() => handleDeleteChoice(id)} + > + <img src={DelChoiceSvg} alt="del-choice" /> + </div> + </div> + </div> + )) + ) : ( + <div className="choice-empty"> + Add Choices + <div + className="choice__navs-item add" + onClick={() => handleAddChoiceBelow(-1)} + > + <img src={AddChoiceSvg} alt="add-choice" /> + </div> + </div> + )} + </div> + <label for="duration">Duration</label> + <div className="d-flex justify-content-between"> + <input + className="end-date w-100" + name="end-date" + placeholder="End Date" + required="false" + type="date" + {...bindEndDate} + /> + <input + className="end-time w-100" + name="end-time" + placeholder="End Time" + required="false" + type="time" + {...bindEndTime} + /> + </div> + <div className="buttonContainer"> + <input + type="submit" + className="buttonSubmit" + value="Create Survey" + /> + </div> + </div> + </form> + </div> + </div> + </div> + ); +} diff --git a/src/components/survey/DetailChoices.jsx b/src/components/survey/DetailChoices.jsx new file mode 100644 index 0000000000000000000000000000000000000000..dd2d0c001583a169d3cfebb42dfc2fb3491671cf --- /dev/null +++ b/src/components/survey/DetailChoices.jsx @@ -0,0 +1,45 @@ +import '../../styles/survey/DetailChoices.css'; +import { getCountdown } from '../../helpers/time-util'; +import { ChoiceBoxUnvoted, ChoiceBoxVoted } from './ChoiceBox'; +import { sortChoicesByVoteDescending } from '../../helpers/survey-util'; + + +export default function DetailChoices({ choices, endTime }) { + const COUNTDOWN_STRING = { + withCountdown: (countdown) => `Poll ends in ${countdown}`, + withoutCountdown: 'Poll has ended' + }; + const isOverEndTime = new Date() > new Date(endTime); + const isUserHasVoted = Math.random() < 0.5; + const countdown = getCountdown(endTime); + + return ( + <div className="detail-choices__container"> + <div className="countdown"> + { + isOverEndTime + ? COUNTDOWN_STRING.withoutCountdown + : COUNTDOWN_STRING.withCountdown(countdown) + } + </div> + <div className="choiceboxes"> + {sortChoicesByVoteDescending(choices).map((choice, idx) => ( + <> + {isUserHasVoted ? ( + <ChoiceBoxVoted + choice={choice} + key={idx} + /> + ) : ( + <ChoiceBoxUnvoted + choice={choice} + key={idx} + isPreview + /> + )} + </> + ))} + </div> + </div> + ) +} diff --git a/src/components/survey/ListSurveys.jsx b/src/components/survey/ListSurveys.jsx new file mode 100644 index 0000000000000000000000000000000000000000..85aea631bf0c378e5801425705519b1207828e9e --- /dev/null +++ b/src/components/survey/ListSurveys.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import '../../styles/survey/ListSurveys.css'; +import Button from '../utility/Button'; +import TopSurveys from './TopSurveys'; +import RecentSurveys from './RecentSurveys'; +import { loggedIn } from '../../helpers/services/auth.service'; +import { Link } from 'react-router-dom'; + +export default function ListSurveys(props) { + return ( + <div className="listSurveysContainer"> + <div className="header"> + <h1> + <b>Surveys</b> + </h1> + </div> + + {props.isRecent ? ( + <div className="listSurveysSection"> + <div className="subHeaderListSurveys"> + <div className="tab"> + <Link to="/surveys/page/1" style={{ textDecoration: 'none' }}> + <h3 className="active">Recent</h3> + </Link> + <Link to="/surveys/top/page/1" style={{ textDecoration: 'none' }}> + <h3>Top</h3> + </Link> + </div> + {loggedIn && ( + <Button text="Create Surveys" color="orange" url="create/survey" /> + )} + </div> + <RecentSurveys + pageNumber={props.pageNumber} + history={props.history} + /> + </div> + ) : ( + <div className="listSurveysSection"> + <div className="subHeaderListSurveys"> + <div className="tab"> + <Link to="/surveys/page/1" style={{ textDecoration: 'none' }}> + <h3>Recent</h3> + </Link> + <Link to="/surveys/top/page/1" style={{ textDecoration: 'none' }}> + <h3 className="active">Top</h3> + </Link> + </div> + {loggedIn && ( + <Button text="Create Surveys" color="orange" url="create/survey" /> + )} + </div> + <TopSurveys pageNumber={props.pageNumber} history={props.history} /> + </div> + )} + </div> + ); +} diff --git a/src/components/survey/PreviewChoices.jsx b/src/components/survey/PreviewChoices.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f403d46b9e35f449749dba30ff59791aa00f60e6 --- /dev/null +++ b/src/components/survey/PreviewChoices.jsx @@ -0,0 +1,50 @@ +import '../../styles/survey/PreviewChoices.css'; +import { getCountdown } from '../../helpers/time-util'; +import { ChoiceBoxUnvoted, ChoiceBoxVoted } from './ChoiceBox'; +import { sortChoicesByVoteDescending } from '../../helpers/survey-util'; + + +export default function PreviewChoices({ choices, endTime }) { + const COUNTDOWN_STRING = { + withCountdown: (countdown) => `Poll ends in ${countdown}`, + withoutCountdown: 'Poll has ended' + }; + const isOverEndTime = new Date() > new Date(endTime); + const isUserHasVoted = Math.random() < 0.5; + const countdown = getCountdown(endTime); + const numOfLeftoverChoices = choices.length - 3; + + return ( + <div className="preview-choices__container"> + <div className="countdown"> + { + isOverEndTime + ? COUNTDOWN_STRING.withoutCountdown + : COUNTDOWN_STRING.withCountdown(countdown) + } + </div> + <div className="choiceboxes"> + {sortChoicesByVoteDescending(choices).slice(0, 3).map((choice, idx) => ( + <> + {isUserHasVoted ? ( + <ChoiceBoxVoted + choice={choice} + key={idx} + /> + ) : ( + <ChoiceBoxUnvoted + choice={choice} + key={idx} + isPreview + /> + )} + </> + ))} + </div> + <h6 className="choices-left"> + {numOfLeftoverChoices > 0 && `${numOfLeftoverChoices} More Choice`} + {numOfLeftoverChoices > 1 && 's'} + </h6> + </div> + ) +} diff --git a/src/components/survey/PreviewSurvey.jsx b/src/components/survey/PreviewSurvey.jsx new file mode 100644 index 0000000000000000000000000000000000000000..74ba5e89db648910cc81c74ee6c52d3a52bd9ee7 --- /dev/null +++ b/src/components/survey/PreviewSurvey.jsx @@ -0,0 +1,42 @@ +import '../../styles/survey/PreviewSurvey.css'; +import { translate } from '../../helpers/time-util'; +import { Link } from 'react-router-dom'; +import PreviewChoices from './PreviewChoices'; + +export default function PreviewSurvey({ content }) { + const { + title, + choices, + end_time, + topic_name, + username, + points, + } = content; + const time = translate(content.inserted_at); + + return ( + <div className="surveyCard"> + <div className="surveyCardHeader"> + <h2 className="previewSurveyTitle"> + <b>{title}</b> + </h2> + </div> + <PreviewChoices + choices={choices} + endTime={end_time} + /> + <p className="previewSurveyTopic">{topic_name}</p> + <div className="surveyCardContent"> + <p> + By{' '} + <Link to={`/profile/${username}/1`}>{username}</Link>{' '} + {', '} + {window.innerWidth < 780 && ( + <br></br> + )} + {time} - <i className="far fa-thumbs-up" /> {points} + </p> + </div> + </div> + ); +} diff --git a/src/components/survey/RecentSurveys.jsx b/src/components/survey/RecentSurveys.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a498d50b6f0444f7669151f5c7d2e80661aa7128 --- /dev/null +++ b/src/components/survey/RecentSurveys.jsx @@ -0,0 +1,58 @@ +/* eslint-disable eqeqeq */ +import React, { useEffect, useState, Fragment } from 'react'; +import axios from 'axios'; +import SurveyList from './SurveyList'; +import { API_URL } from '../../config/keys'; +import Pagination from '../thread/Pagination'; +import DUMMY_SURVEYS from './dummy'; + +export default function RecentSurveys(props) { + const [threads, setThreads] = useState([]); + const [totalItems, setTotalItems] = useState(0); + + const currentPage = props.pageNumber; + + if (currentPage != props.pageNumber) { + window.location.reload(); + } + + useEffect(() => { + const fetch = async () => { + const responseThreads = await axios.get( + `${API_URL}/threads/pages/recent/?page=${currentPage}` + ); + const { data } = responseThreads; + console.log(data) + + setThreads(DUMMY_SURVEYS); + setTotalItems(DUMMY_SURVEYS.length); + }; + fetch(); + }, [currentPage]); + + function switchPage(pageNumber) { + props.history.push(`/surveys/page/${pageNumber}`); + window.location.reload(); + } + + return ( + <div className="recentSurveys"> + {totalItems == 0 ? ( + <div className="noSurveysMessage"> + <p>There is no surveys yet.</p> + </div> + ) : ( + <Fragment> + <SurveyList thread={threads} /> + <div className="paginationContainer"> + <Pagination + activePage={currentPage} + totalItems={totalItems} + switchPage={switchPage} + /> + </div> + </Fragment> + )} + </div> + ); +} diff --git a/src/components/survey/Survey.jsx b/src/components/survey/Survey.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5b0a8b4dfd3a681485c77264de57281cd8f69153 --- /dev/null +++ b/src/components/survey/Survey.jsx @@ -0,0 +1,157 @@ +/* eslint-disable eqeqeq */ +import axios from 'axios'; +import React, { useState, useEffect, useCallback } from 'react'; + +import '../../styles/survey/Survey.css'; +import CommentList from '../thread/CommentList'; +import SurveyPost from './SurveyPost'; +import { useInput } from '../../helpers/hooks/input-hook'; +import AuthService, { + authHeader, + loggedIn, +} from '../../helpers/services/auth.service'; +import { API_URL } from '../../config/keys'; +import Pagination from '../thread/Pagination'; +import DUMMY_SURVEYS from './dummy'; + +export default function Survey(props) { + const currentUserId = AuthService.getCurrentUserId(); + const redirect = (url) => { + props.history.push(url); + }; + + const [survey, setSurvey] = useState(null); + const [comment, setComment] = useState([ + { + id: '', + message: '', + points: 0, + survey_id: '', + user_id: '', + updated_at: '', + inserted_at: '', + username: '', + }, + ]); + + const { params } = props.match; + const topicParm = params.topic; + const surveyParm = params.survey; + const currentPage = params.pageNumber; + + const { value: input, bind: bindInput, reset: resetInput } = useInput(''); + const [totalComments, setTotalComments] = useState(0); + + useEffect(() => { + setSurvey(DUMMY_SURVEYS[1]); + }, []) + + const refreshComment = useCallback(() => { + const fetch = async () => { + const responseComment = await axios.get( + `${API_URL}/post/pages/survey/${surveyParm}/?page=${currentPage}` + ); + const { data } = responseComment; + setComment(data?.data); + setTotalComments(data?.metadata?.total_data); + }; + fetch(); + }, [surveyParm, currentPage]); + + useEffect(() => { + const fetch = async () => { + const responseSurvey = await axios.get( + `${API_URL}/surveys/${surveyParm}` + ); + + const { data } = responseSurvey; + console.log(data.data) + + setSurvey(DUMMY_SURVEYS[0]); + refreshComment(); + }; + fetch(); + }, [surveyParm, refreshComment, currentUserId, survey]); + + const handleSubmit = async (event) => { + event.preventDefault(); + + try { + await axios.post( + `${API_URL}/post`, + { + post: { + message: input, + points: 0, + survey_id: surveyParm, + user_id: localStorage.getItem('id'), + }, + }, + { + headers: authHeader(), + } + ); + refreshComment(); + } catch (error) {} + resetInput(); + }; + + function switchPage(pageNumber) { + props.history.push(`/topic/${topicParm}/${surveyParm}/page/${pageNumber}`); + window.location.reload(); + } + + return ( + <div className="surveyContainer"> + <div className="survey_section"> + {survey && <SurveyPost content={survey} redirect={redirect} />} + </div> + + {loggedIn && ( + <div className="addCommentSection"> + <h3>Write a Comment</h3> + <form onSubmit={handleSubmit}> + <div className="commentFormContainer"> + <textarea + className="commentBox" + placeholder="Write your comment here" + required="false" + {...bindInput} + /> + <div> + <button + className="submitComment addCommentButton" + type="submit" + > + Post Comment + </button> + </div> + </div> + </form> + </div> + )} + <h2 className="commentText">Comments</h2> + + {totalComments == 0 ? ( + <div className="noCommentLabel"> + <p>There are no comments yet.</p> + </div> + ) : ( + <div> + <CommentList + comment={comment} + survey_className={surveyParm} + topic_className={topicParm} + /> + <div className="paginationContainer"> + <Pagination + activePage={currentPage} + totalItems={totalComments} + switchPage={switchPage} + /> + </div> + </div> + )} + </div> + ); +} diff --git a/src/components/survey/SurveyList.jsx b/src/components/survey/SurveyList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..88fe5e739b6753f78aa33d24b3ac5660cb8906c0 --- /dev/null +++ b/src/components/survey/SurveyList.jsx @@ -0,0 +1,18 @@ +import PreviewSurvey from './PreviewSurvey'; +import { Link } from 'react-router-dom'; + +export default function SurveyList(props) { + return ( + <div className="list_threads"> + {props.thread.map((value) => ( + <Link + key={value.id} + to={`/survey/topic/${value.topic_name}/${value.id}/page/1`} + style={{ textDecoration: 'none' }} + > + <PreviewSurvey content={value} /> + </Link> + ))} + </div> + ); +} diff --git a/src/components/survey/SurveyPost.jsx b/src/components/survey/SurveyPost.jsx new file mode 100644 index 0000000000000000000000000000000000000000..1d336440df2227c9b75108dc70a0e6594cc8dcd0 --- /dev/null +++ b/src/components/survey/SurveyPost.jsx @@ -0,0 +1,158 @@ +/* eslint-disable eqeqeq */ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import axios from 'axios'; + +import '../../styles/thread/Post.css'; +import AuthService, { + authHeader, + loggedIn, +} from '../../helpers/services/auth.service'; +import { translate } from '../../helpers/time-util'; +import { API_URL } from '../../config/keys'; +import Button from '../utility/Button'; +import DetailChoices from './DetailChoices'; + +export default function SurveyPost({ content }) { + const [points, setPoints] = useState(0); + const [isLiked, setIsLiked] = useState(false); + const [user, setUser] = useState({ + id: '', + name: '', + username: '', + }); + + const deletePost = async () => { + try { + await axios.delete(`${API_URL}/survey/${content.id}`, { + headers: authHeader(), + }); + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + const fetch = async () => { + try { + setPoints(content.points); + if (content.username != '') { + const getUser = await axios.get( + `${API_URL}/users/name/${content.username}` + ); + setUser(getUser?.data?.data); + } + await axios.get(`${API_URL}/$survey/checklike/${content.id}`, { + headers: authHeader(), + }); + setIsLiked(true); + } catch (error) { + setIsLiked(false); + } + }; + fetch(); + }, [content.id, content.points, content.username]); + + const handleLike = async () => { + try { + await axios.get(`${API_URL}/survey/checklike/${content.id}`, { + headers: authHeader(), + }); + await axios.post( + `${API_URL}/survey/dislike/${content.id}`, + {}, + { + headers: authHeader(), + } + ); + setPoints(points - 1); + setIsLiked(false); + } catch (error) { + await axios.post( + `${API_URL}/survey/like/${content.id}`, + {}, + { + headers: authHeader(), + } + ); + setPoints(points + 1); + setIsLiked(true); + } + }; + + useEffect(() => { + console.log(content) + }, [content]) + + const time = translate(content.inserted_at); + + return ( + <div className="post"> + <div className="postHeader"> + <div className="headerData"> + <div className="userImage"> + {user.picture ? ( + <i className="far fa-user-circle" /> + ) : ( + <img alt="profile" src={user.picture} /> + )} + </div> + <div className="creator"> + <p> + By{' '} + <Link to={`/profile/${content.username}/1`}> + {content.username} + </Link>{', '} + {window.innerWidth < 780 && ( + <br></br> + )} + {time}{' '} + </p> + </div> + </div> + {content.user_id == AuthService.getCurrentUserId() && ( + <div className="headerButton"> + <div className="postButtonContainer"> + <Button + type="button" + text="Edit" + color="none-green" + url={`thread/edit/${content.id}`} + /> + <button + type="button" + className="deleteButton" + onClick={() => deletePost(user.username)} + > + Delete + </button> + </div> + </div> + )} + </div> + <div className="postContent"> + <h1 className="postTitle">{content.title}</h1> + <DetailChoices + choices={content.choices} + endTime={content.end_time} + /> + <div className="likeSection"> + {loggedIn && ( + <button className="likeButton" onClick={handleLike}> + <i + className={`far fa-thumbs-up ${ + isLiked ? 'active' : 'inactive' + }`} + /> + </button> + )} + <div className="pointContainer"> + <div className="point"> + <p>{points} likes</p> + </div> + </div> + </div> + </div> + </div> + ); +} diff --git a/src/components/survey/TopSurveys.jsx b/src/components/survey/TopSurveys.jsx new file mode 100644 index 0000000000000000000000000000000000000000..50c9b34d3cddfa01069aa025a7fd0917243a2e67 --- /dev/null +++ b/src/components/survey/TopSurveys.jsx @@ -0,0 +1,57 @@ +/* eslint-disable eqeqeq */ +import React, { useEffect, useState, Fragment } from 'react'; +import axios from 'axios'; +import SurveyList from './SurveyList'; +import { API_URL } from '../../config/keys'; +import Pagination from '../thread/Pagination'; +import DUMMY_SURVEYS from './dummy'; + +export default function TopSurveys(props) { + const [threads, setThreads] = useState([]); + const currentPage = props.pageNumber; + const [totalItems, setTotalItems] = useState(0); + + if (currentPage != props.pageNumber) { + window.location.reload(); + } + + useEffect(() => { + const fetch = async () => { + const responseThreads = await axios.get( + `${API_URL}/threads/pages/top/?page=${currentPage}` + ); + const { data } = responseThreads; + console.log(data) + + setThreads(DUMMY_SURVEYS); + setTotalItems(DUMMY_SURVEYS.length); + }; + fetch(); + }, [currentPage]); + + function switchPage(pageNumber) { + props.history.push(`/surveys/top/page/${pageNumber}`); + window.location.reload(); + } + + return ( + <div className="topThreads"> + {totalItems == 0 ? ( + <div className="noThreadsMessage"> + <p>There is no surveys yet.</p> + </div> + ) : ( + <Fragment> + <SurveyList thread={threads} /> + <div className="paginationContainer"> + <Pagination + activePage={currentPage} + totalItems={totalItems} + switchPage={switchPage} + /> + </div> + </Fragment> + )} + </div> + ); +} diff --git a/src/components/survey/dummy.js b/src/components/survey/dummy.js new file mode 100644 index 0000000000000000000000000000000000000000..455efa7989f3b892e24b6bd449b2388a0deb6030 --- /dev/null +++ b/src/components/survey/dummy.js @@ -0,0 +1,66 @@ +const DUMMY_SURVEYS = [ + { + "id": 251, + "title": "You snap your finger and one of the following are erased for ever, what do you choose?", + "is_user_has_voted": true, + "choices": [ + { + "name": "World Hunger", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "Poverty", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "Famine", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + ], + "end_time": "2021-11-30T12:34:22", + "inserted_at": "2021-11-26T12:34:22", + "updated_at": "2021-11-26T12:34:22", + "points": 0, + "topic_id": 22, + "topic_name": "Ok Google", + "user_id": 15, + "username": "Ahmad Haulian Yoga Pratama" + }, + { + "id": 252, + "title": "Kamu suka pakai bahasa pemrograman fungsional yang mana?", + "is_user_has_voted": false, + "choices": [ + { + "name": "Scala", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "Haskell", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "Mercury", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "Clojure", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + { + "name": "OCaml", + "votes": Math.floor(Math.random() * 1000) + 1 + }, + ], + "end_time": "2021-12-31T18:34:22", + "inserted_at": "2021-11-26T12:34:22", + "updated_at": "2021-11-26T12:34:22", + "points": 0, + "topic_id": 22, + "topic_name": "Ok Google", + "user_id": 15, + "username": "Ahmad Haulian Yoga Pratama" + } +] + +export default DUMMY_SURVEYS; \ No newline at end of file diff --git a/src/components/thread/ListThreads.jsx b/src/components/thread/ListThreads.jsx index 665c5ef7c3e7e6d6574e20a7676bdc143618b99e..b2de847dcdf5235829af0c816493e988592ce465 100644 --- a/src/components/thread/ListThreads.jsx +++ b/src/components/thread/ListThreads.jsx @@ -19,10 +19,10 @@ export default function ListThreads(props) { <div className="listThreadsSection"> <div className="subHeaderListThreads"> <div className="tab"> - <Link to="/page/1" style={{ textDecoration: 'none' }}> + <Link to="/threads/page/1" style={{ textDecoration: 'none' }}> <h3 className="active">Recent</h3> </Link> - <Link to="/top/page/1" style={{ textDecoration: 'none' }}> + <Link to="/threads/top/page/1" style={{ textDecoration: 'none' }}> <h3>Top</h3> </Link> </div> @@ -39,10 +39,10 @@ export default function ListThreads(props) { <div className="listThreadsSection"> <div className="subHeaderListThreads"> <div className="tab"> - <Link to="/page/1" style={{ textDecoration: 'none' }}> + <Link to="/threads/page/1" style={{ textDecoration: 'none' }}> <h3>Recent</h3> </Link> - <Link to="/top/page/1" style={{ textDecoration: 'none' }}> + <Link to="/threads/top/page/1" style={{ textDecoration: 'none' }}> <h3 className="active">Top</h3> </Link> </div> diff --git a/src/components/thread/RecentThreads.jsx b/src/components/thread/RecentThreads.jsx index ea47f67c793f39d2002605140d9ce1d2874b9081..b1fa230ebc6d8f8359aa978dc8b02751e2c085fa 100644 --- a/src/components/thread/RecentThreads.jsx +++ b/src/components/thread/RecentThreads.jsx @@ -28,7 +28,7 @@ export default function RecentThreads(props) { }, [currentPage]); function switchPage(pageNumber) { - props.history.push(`/page/${pageNumber}`); + props.history.push(`/threads/page/${pageNumber}`); window.location.reload(); } diff --git a/src/components/thread/ThreadList.jsx b/src/components/thread/ThreadList.jsx index 574a18cdd4819f00de78f5c790c4472d699777a2..7d65c1539859ab5545756933154099af1671d8f8 100644 --- a/src/components/thread/ThreadList.jsx +++ b/src/components/thread/ThreadList.jsx @@ -7,7 +7,7 @@ export default function ThreadList(props) { {props.thread.map((value) => ( <Link key={value.id} - to={`/topic/${value.topic_name}/${value.id}/page/1`} + to={`/thread/topic/${value.topic_name}/${value.id}/page/1`} style={{ textDecoration: 'none' }} > <PreviewThread content={value} /> diff --git a/src/components/thread/TopThreads.jsx b/src/components/thread/TopThreads.jsx index c33b302b7f3d80641bda3b7b990f0045ca806107..e0757522227b09a791ba6b4408ab8dd4c110e67e 100644 --- a/src/components/thread/TopThreads.jsx +++ b/src/components/thread/TopThreads.jsx @@ -27,7 +27,7 @@ export default function TopThreads(props) { }, [currentPage]); function switchPage(pageNumber) { - props.history.push(`/top/page/${pageNumber}`); + props.history.push(`/threads/top/page/${pageNumber}`); window.location.reload(); } diff --git a/src/components/utility/Navbar.jsx b/src/components/utility/Navbar.jsx index a6f8038a0399df75a9be0443f43aae423d1012d2..a295ba29748bb6541d1486480e7213b5c9035486 100644 --- a/src/components/utility/Navbar.jsx +++ b/src/components/utility/Navbar.jsx @@ -62,11 +62,21 @@ const Navbar = (props) => { exact activeClassName="navbar--active" className="nav-link" - to="/page/1" + to="/threads/page/1" > <b>Threads</b> </NavLink> </li> + <li className="nav-item mr-auto"> + <NavLink + exact + activeClassName="navbar--active" + className="nav-link" + to="/surveys/page/1" + > + <b>Surveys</b> + </NavLink> + </li> <li className="nav-item mr-auto"> <NavLink exact diff --git a/src/helpers/survey-util.js b/src/helpers/survey-util.js new file mode 100644 index 0000000000000000000000000000000000000000..4b3090d7b8d738b046248f7769201e2d48857652 --- /dev/null +++ b/src/helpers/survey-util.js @@ -0,0 +1,3 @@ +export const sortChoicesByVoteDescending = (choices) => ( + choices.sort((a, b) => parseFloat(b.votes) - parseFloat(a.votes)) +); diff --git a/src/helpers/time-util.js b/src/helpers/time-util.js index 73b16ed2455e103e9d9c0e58ab6ed43f8a4d92d2..ba03c53f66b326a7d38afdf2efaeb57db3d0fc63 100644 --- a/src/helpers/time-util.js +++ b/src/helpers/time-util.js @@ -1,3 +1,4 @@ +import 'moment-duration-format'; import moment from 'moment'; function translate(time) { @@ -10,4 +11,27 @@ function translate(time) { } catch (error) {} } -export { translate }; +const datetimeToUTC = (endDate, endTime) => { + const date = new Date(`${endDate} ${endTime}`); + return date.toISOString(); +} + +const getCountdown = endTime => { + const now = moment() + const end = moment(endTime).local() + const duration = moment.duration(end.diff(now)) + + return ( + duration + .format( + [ + moment.duration(1, "minute"), + moment.duration(1, "hour"), + moment.duration(1, "day") + ], + "d [days], h [hours], m [minutes]" + ) + ); +} + +export { translate, datetimeToUTC, getCountdown }; diff --git a/src/images/add-choice.svg b/src/images/add-choice.svg new file mode 100644 index 0000000000000000000000000000000000000000..0a054ca034e1ec2ce9bd79d646ff8ab239fec98c --- /dev/null +++ b/src/images/add-choice.svg @@ -0,0 +1,21 @@ +<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g filter="url(#filter0_d_226_946)"> +<rect x="2" width="30" height="30" fill="url(#pattern0)" shape-rendering="crispEdges"/> +</g> +<defs> +<filter id="filter0_d_226_946" x="0" y="0" width="34" height="34" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="1"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_226_946"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_226_946" result="shape"/> +</filter> +<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_226_946" transform="scale(0.0333333)"/> +</pattern> +<image id="image0_226_946" width="30" height="30" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAAsElEQVRIie2VsQ3CMBBF37EDFNAwDxVDgJC8gTOAMwEIwRY0jJMUmcOmCZJd5iBRBPer85f8n2xL32CaSKLa5f0ekRspJeBIXT+GRixUYJEzsERkhchFE6EDwzqbN1OCP5aBDfx74LK5vN/15bD9MqcFHCE830Z5YpHrCFD6zHtuzOaNT0AzAqchxkNu6H6nqkrFOoTBObO5agMb+H/BbTarCkcLdkAHdMTolBmmafQCluEdELXKEHIAAAAASUVORK5CYII="/> +</defs> +</svg> diff --git a/src/images/del-choice.svg b/src/images/del-choice.svg new file mode 100644 index 0000000000000000000000000000000000000000..e95672484a763a35a91108b332151b6837f04ca5 --- /dev/null +++ b/src/images/del-choice.svg @@ -0,0 +1,21 @@ +<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g filter="url(#filter0_d_226_947)"> +<rect x="2" width="30" height="30" fill="url(#pattern0)" shape-rendering="crispEdges"/> +</g> +<defs> +<filter id="filter0_d_226_947" x="0" y="0" width="34" height="34" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="2"/> +<feGaussianBlur stdDeviation="1"/> +<feComposite in2="hardAlpha" operator="out"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_226_947"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_226_947" result="shape"/> +</filter> +<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_226_947" transform="scale(0.0333333)"/> +</pattern> +<image id="image0_226_947" width="30" height="30" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAAhUlEQVRIie3SQQqAIBCF4TdzGztBRLXyLJ3SlQbdoA5g1wjbRwWmBsL8ywH9RAWkn6LYBZseFwDtZbwoY7tkeNWDI1Afe6i7AsLcGDdc55xj87cYFEobdfT4uTY9ZrkiZeytUfyNnxJYYIEFFljgmmDCnro5AT4a5oOmFJwAj8DT1/VS/Z1SDxeJl/d4WwAAAABJRU5ErkJggg=="/> +</defs> +</svg> diff --git a/src/routes/App.jsx b/src/routes/App.jsx index 6f72f0c440ac21071c66a3ae9de361f63a4959cf..1c277d6e9aee094b49a25dcf383b75b6b6430cbb 100644 --- a/src/routes/App.jsx +++ b/src/routes/App.jsx @@ -13,12 +13,15 @@ import EditThread from '../components/thread/EditThread'; import Footer from '../components/utility/Footer'; import UpdateProfileForm from '../components/profile/UpdateProfileForm.jsx'; import EditComment from '../components/thread/EditComment'; +import CreateSurvey from '../components/survey/CreateSurvey'; import { BrowserRouter as Router, Route, Redirect, Switch, } from 'react-router-dom'; +import ListSurveys from '../components/survey/ListSurveys'; +import Survey from '../components/survey/Survey'; function App() { return ( @@ -27,7 +30,7 @@ function App() { <Switch> <Route exact - path="/page/:pageNumber" + path="/threads/page/:pageNumber" component={(props) => ( <ListThreads isRecent={true} @@ -38,7 +41,7 @@ function App() { /> <Route exact - path="/top/page/:pageNumber" + path="/threads/top/page/:pageNumber" component={(props) => ( <ListThreads isRecent={false} @@ -47,23 +50,52 @@ function App() { /> )} /> + <Route + exact + path="/surveys/page/:pageNumber" + component={(props) => ( + <ListSurveys + isRecent={true} + pageNumber={props.match.params.pageNumber} + history={props.history} + /> + )} + /> + <Route + exact + path="/surveys/top/page/:pageNumber" + component={(props) => ( + <ListSurveys + isRecent={false} + pageNumber={props.match.params.pageNumber} + history={props.history} + /> + )} + /> <Route exact path="/topic" component={TopicList} /> <Route exact path="/profile/:user/page/:pageNumber" component={Profile} /> <Route exact path="/login" component={LoginForm} /> <Route exact path="/create/topic" component={CreateTopicForm} /> <Route exact path="/create/thread" component={CreateThread} /> + <Route exact path="/create/survey" component={CreateSurvey} /> <Route exact path="/search/:input/page/:pageNumber" component={Search} /> <Route exact - path="/topic/:topic/:thread/page/:pageNumber" + path="/thread/topic/:topic/:thread/page/:pageNumber" component={Thread} /> + <Route + exact + path="/survey/topic/:topic/:survey/page/:pageNumber" + component={Survey} + /> <Route exact path="/topic/:topic/page/:pageNumber" component={Topic} /> <Route exact path="/threads" component={ListThreads} /> + <Route exact path="/surveys" component={ListSurveys} /> <Route exact path="/thread/edit/:thread" component={EditThread} /> <Route exact path="/profile/update" component={UpdateProfileForm} /> <Route exact path="/comment/edit/:comment" component={EditComment} /> - <Redirect exact from="" to="page/1" /> + <Redirect exact from="" to="/threads/page/1" /> </Switch> <Footer /> </Router> diff --git a/src/styles/survey/ChoiceBox.css b/src/styles/survey/ChoiceBox.css new file mode 100644 index 0000000000000000000000000000000000000000..711fe3493cd77de66e227fd5f2b50c12a7a3f50f --- /dev/null +++ b/src/styles/survey/ChoiceBox.css @@ -0,0 +1,48 @@ +.choice-box__voted, +.choice-box__unvoted { + display: flex; + align-items: center; + margin: 8px 0px; + padding: 8px; + + background: #FFFFFF; + border: 2px solid #FEA02F; + box-sizing: border-box; + border-radius: 25px; +} + +.choice-box__voted h6, +.choice-box__unvoted h6 { + font-family: DM Sans; + font-style: normal; + font-weight: bold; + padding-bottom: 0px; + margin-bottom: 0px; +} + +.choice-box__unvoted:hover { + background: #FEA02F; +} + +.choice-box__unvoted:hover h6 { + color: #FFFFFF; +} + +.is_preview.choice-box__unvoted:hover { + background: #FFFFFF; +} + +.is_preview.choice-box__unvoted:hover h6 { + color: #000000; +} + +.choice-box__voted { + justify-content: space-between; + padding-left: 12px; + padding-right: 12px; +} + +.choice-box__unvoted { + justify-content: center; + color: #000000 +} diff --git a/src/styles/survey/DetailChoices.css b/src/styles/survey/DetailChoices.css new file mode 100644 index 0000000000000000000000000000000000000000..61c1848e21699a8585963a7d4c52d8bb797c80b2 --- /dev/null +++ b/src/styles/survey/DetailChoices.css @@ -0,0 +1,10 @@ +.detail-choices__container { + border-radius: 8px; + margin: 1.5rem 0px; + cursor: pointer; +} + +.detail-choices__container .countdown { + color: rgba(0, 0, 0, 0.5); + font-weight: bold; +} diff --git a/src/styles/survey/Form.css b/src/styles/survey/Form.css new file mode 100644 index 0000000000000000000000000000000000000000..64b937f78045851a128f03a80365270602ac4ff7 --- /dev/null +++ b/src/styles/survey/Form.css @@ -0,0 +1,71 @@ +input.end-date, +input.end-time { + padding: 12px 16px; + border-radius: 8px; + border: 0; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); + margin-bottom: 32px; + font-family: "DM Sans"; +} + +input.end-date { + margin-right: 12px; +} + +input.end-time { + margin-left: 12px; +} + +.choices { + padding: 12px 24px; + border-radius: 8px; + border: 0; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); + margin-bottom: 32px; + font-family: "DM Sans"; +} + +.choice { + width: 100%; + display: flex; + align-items: center; +} + +.choice-empty { + text-align: center; + font-family: "DM Sans"; + font-size: 18px; + color: #000000; + font-weight: bold; +} + +.choice-empty > .choice__navs-item.add { + width: fit-content; + padding: 5px; + margin: 12px auto; + border-radius: 8px; + border: 0; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); +} + +input.choice__input { + width: 100%; + padding: 12px 16px; + margin: 12px 0px; + border-radius: 8px; + border: 0; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); +} + +.choice__navs { + display: flex; + margin-left: 24px; +} + +.choice__navs-item { + cursor: pointer; +} + +.choice__navs-item:last-child { + margin-left: 7px; +} diff --git a/src/styles/survey/ListSurveys.css b/src/styles/survey/ListSurveys.css new file mode 100644 index 0000000000000000000000000000000000000000..ad41acb0b6bf4ca58e54021251f8a78b0be60e4b --- /dev/null +++ b/src/styles/survey/ListSurveys.css @@ -0,0 +1,78 @@ +.listSurveysSection { + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 56px; +} + +.subHeaderListSurveys { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 28px; +} + +.tab { + display: flex; + align-items: center; +} + +.tab h3 { + font-family: "DM Sans"; + font-size: 18px; + color: #000000; + margin-right: 24px; + margin-top: 4px; + opacity: 0.5; +} + +.tab .active { + color: #DE6600 !important; + font-weight: bold; + opacity: 1!important; +} + +.listSurveysContainer { + margin-top: 36px; + margin-left: 72px; + margin-right: 72px; + min-height: 100vh; + margin-bottom: 72px; +} + +@media only screen and (min-device-width : 320px) and (max-device-width : 540px) { + .listSurveysContainer { + margin-top: 24px; + margin-left: 36px; + margin-right: 36px; + } + + .listSurveysSection { + margin-top: 36px; + } + + .subHeaderListSurveys { + margin-bottom: 28px; + flex-wrap: wrap; + justify-content: center; + flex-direction: column; + justify-content: center; + } + + .tab .active { + color: #DE6600 !important; + font-weight: bold; + opacity: 1!important; + } + + .tab h3 { + font-size: 16px; + margin-right: 28px; + margin-left: 28px; + margin-top: 4px; + } + + .noSurveysMessage p{ + text-align: center; + } +} diff --git a/src/styles/survey/PreviewChoices.css b/src/styles/survey/PreviewChoices.css new file mode 100644 index 0000000000000000000000000000000000000000..11c635bc99e849d60501f5a57cf8aceefc11da4e --- /dev/null +++ b/src/styles/survey/PreviewChoices.css @@ -0,0 +1,21 @@ +.preview-choices__container { + box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.5); + border-radius: 8px; + padding: 0.75rem 1rem; + margin: 1.5rem 0px 1rem; + cursor: pointer; +} + +.preview-choices__container .countdown { + color: rgba(0, 0, 0, 0.5); + font-weight: bold; + margin-left: calc(1rem - 2px); +} + +.preview-choices__container .choices-left { + color: rgba(0, 0, 0, 0.5); + font-weight: bold; + text-align: center; + margin-top: 0.75rem; + margin-bottom: 0rem; +} diff --git a/src/styles/survey/PreviewSurvey.css b/src/styles/survey/PreviewSurvey.css new file mode 100644 index 0000000000000000000000000000000000000000..8986fb5f0f81017ccedddfc6a4cde2ee2b255ff5 --- /dev/null +++ b/src/styles/survey/PreviewSurvey.css @@ -0,0 +1,79 @@ +.surveyCard { + min-width: 303px; + max-width: 3696px; + margin-bottom: 20px; + padding:20px; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); + border-radius: 8px; + font-family: "DM Sans"; +} + +.previewSurveyTopic { + background: #DE6600; + color:#ffffff; + padding: 4px; + border-radius: 4px; + width: max-content; + margin: 24px 0px 0px 0px; + border-radius: 4px; + font-size: 14px; +} + +.newSurveyButton { + margin-top: 20px; + margin-left: 20px; +} + +.fa-thumbs-up { + height: 16px; + color: #DE6600; +} + +.previewSurveyTitle { + font-size: 24px; + color: #000000; +} + +.surveyCardContent { + margin-top: 8px; +} + +.surveyCardContent p{ + margin-bottom: 0px; + color: #000000; +} + +@media only screen and (min-device-width : 320px) and (max-device-width : 540px) { + .surveyCard { + min-width: 248px; + max-width: 468px; + margin-bottom: 20px; + padding: 16px; + } + + .previewSurveyTopic { + font-size: 14px; + } + + .newSurveyButton { + margin-top: 20px; + margin-left: 20px; + } + + .previewSurveyTitle { + font-size: 24px; + color: #000000; + } + + .surveyCardContent { + margin-top: 8px; + } + + .surveyCardContent p{ + margin-bottom: 0px; + } + + .surveyCardContent p{ + text-align: left; + } +} \ No newline at end of file diff --git a/src/styles/survey/Survey.css b/src/styles/survey/Survey.css new file mode 100644 index 0000000000000000000000000000000000000000..058ae95fe95760cf206d848a471e230f8da646ae --- /dev/null +++ b/src/styles/survey/Survey.css @@ -0,0 +1,116 @@ +.surveyContainer { + display: flex; + flex-direction: column; + margin-top: 36px; + font-family: "DM Sans"; + margin-left: 72px; + margin-right: 72px; + min-height: 100vh; + margin-bottom: 72px; +} + +.commentText { + color: #FEA02F; + font-size: 24px; + font-weight: bold; + margin-bottom: 12px; +} + +.addCommentSection { + margin-top: 40px; + display: flex; + flex-direction: column; + margin-top: 20px; +} + +.addCommentSection h3 { + color: #FEA02F; + font-size: 24px; + font-weight: bold; + margin-bottom: 12px; +} + +.addCommentButton { + margin-top: 20px; + margin-left: auto; + margin-right: auto; + margin-bottom: 40px; +} + +.commentBox { + min-width: 468px; + max-width: 3696px; + font-family: "DM Sans"; + height: 120px; + padding: 12px 16px; + border-radius: 8px; + border: 0; + box-shadow: 0px 0px 8px 1px rgba(0,0,0,0.25); +} + +.submitComment { + border-radius: 4px; + border-width: 0px; + padding: 8px; + font-family: "DM Sans"; + color: #ffffff; + border-style: none; + background-color: #DE6600; + border-color: #DE6600; +} + +.noCommentLabel p { + font-size: 16px; + margin-bottom: 0px; +} + +.commentFormContainer { + display: flex; + flex-direction: column; +} + +@media only screen and (min-device-width : 320px) and (max-device-width : 540px) { + .surveyContainer { + margin-top: 24px; + margin-bottom: 24px; + margin-left: 36px; + margin-right: 36px; + } + + .addCommentSection h3 { + font-size: 18px; + text-align: center; + } + + .addCommentSection div { + display: flex; + justify-content: center; + } + + .commentBox { + min-width: 248px; + max-width: 468px; + height: 120px; + font-size: 14px; + } + + .submitComment { + font-size: 16px; + } + + .commentText { + font-size: 18px; + text-align: center; + } + + .noCommentLabel p { + font-size: 14px; + margin-bottom: 0px; + text-align: center; + } + + .buttonContainer .button { + margin-top: 0px; + } +} + diff --git a/src/styles/thread/Form.css b/src/styles/thread/Form.css index a8909a541cee929f13d38b3d3f01e5ed16c42623..7d1aa25e75d0b0f393ac7fa3607a23d0ab716af0 100644 --- a/src/styles/thread/Form.css +++ b/src/styles/thread/Form.css @@ -57,7 +57,7 @@ label { .topic { min-width: 303px; - max-width: 400px; + max-width: 3696px; font-family: "DM Sans"; height: 45px; padding: 12px 16px;