diff --git a/src/components/CourseFinder/CourseItem.jsx b/src/components/CourseFinder/CourseItem.jsx index e82f09b1..25c44a76 100644 --- a/src/components/CourseFinder/CourseItem.jsx +++ b/src/components/CourseFinder/CourseItem.jsx @@ -36,7 +36,16 @@ const HighlightMatches = ({ content }) => { } const CourseItemMain = ({ courseData }) => { - const { code, credits, department, title, description, tags } = courseData + const { + code, + credits, + department, + title, + description, + tags, + favoritedByCount, + } = courseData + const departmentList = useSelector(selectDepartments) const colorPicker = useColorPicker() @@ -80,7 +89,7 @@ const CourseItemMain = ({ courseData }) => { ))} - + @@ -108,10 +117,15 @@ const CourseItemMain = ({ courseData }) => { const CourseItemSub = ({ courseData }) => { const { isMobile, isMobileS } = useResponsive() - const { code, title, semester, reviews, resources } = courseData - - const reviewCount = reviews?.length - const resourceCount = resources?.length + const { + code, + title, + semester, + reviewsCount, + resourcesCount, + reviewRequestersCount, + resourceRequestersCount, + } = courseData return ( <> @@ -129,14 +143,14 @@ const CourseItemSub = ({ courseData }) => { style={{ width: '100%', borderRadius: '0.5rem 0 0 0.5rem' }} > - Reviews {reviewCount > 0 && `(${reviewCount})`} + Reviews {reviewsCount > 0 && `(${reviewsCount})`} @@ -146,14 +160,14 @@ const CourseItemSub = ({ courseData }) => { to={`${coursePageUrl(code, title)}#resources`} > - Resources {resourceCount > 0 && `(${resourceCount})`} + Resources {resourcesCount > 0 && `(${resourcesCount})`} diff --git a/src/components/CoursePage/CourseContentRequest.jsx b/src/components/CoursePage/CourseContentRequest.jsx index 16b21dc8..f550ab13 100644 --- a/src/components/CoursePage/CourseContentRequest.jsx +++ b/src/components/CoursePage/CourseContentRequest.jsx @@ -2,14 +2,15 @@ import { UserGroup } from '@styled-icons/heroicons-outline' import { useState } from 'react' import { useSelector } from 'react-redux' -import { ButtonIcon, ButtonSwitch, toast } from 'components/shared' +import { Badge, ButtonIcon, ButtonSwitch, toast } from 'components/shared' import { API } from 'config/api' import { selectUserProfile } from 'store/userSlice' -export const useCourseContentRequest = ({ code, type }) => { +export const useCourseContentRequest = ({ code, type, initialCount }) => { const profile = useSelector(selectUserProfile) const [loading, setLoading] = useState(false) + const [count, setCount] = useState(initialCount) const [requestStatus, setRequestStatus] = useState( profile[`${type}Requested`]?.includes(code) ?? false ) @@ -19,8 +20,10 @@ export const useCourseContentRequest = ({ code, type }) => { setLoading(true) if (requestStatus) { await API[type].request.remove({ code }) + setCount(count - 1) } else { await API[type].request.add({ code }) + setCount(count + 1) } setRequestStatus(!requestStatus) @@ -35,6 +38,7 @@ export const useCourseContentRequest = ({ code, type }) => { requestStatus, handleRequest, loading, + count, } } @@ -59,21 +63,33 @@ export const CourseContentRequestSquare = ({ code, type, ...props }) => { ) } -export const CourseContentRequestIcon = ({ code, type, ...props }) => { - const { requestStatus, handleRequest, loading } = useCourseContentRequest({ - code, - type, - }) +export const CourseContentRequestIcon = ({ + code, + type, + initialCount, + ...props +}) => { + const { requestStatus, handleRequest, loading, count } = + useCourseContentRequest({ code, type, initialCount }) return ( } + icon={ + initialCount ? ( + + + + ) : ( + + ) + } $active={requestStatus} onClick={handleRequest} loading={loading} + style={{ width: '2.5rem', borderRadius: '0 0.5rem 0.5rem 0' }} {...props} /> ) diff --git a/src/components/CoursePage/CoursePageBody.jsx b/src/components/CoursePage/CoursePageBody.jsx index a90f871b..7d849672 100644 --- a/src/components/CoursePage/CoursePageBody.jsx +++ b/src/components/CoursePage/CoursePageBody.jsx @@ -8,15 +8,23 @@ import { device, fontSize } from 'styles/responsive' import CourseWorkload from './CourseWorkload' const CoursePageBody = ({ courseData }) => { - const { code, title, department, credits, description, workload, semester } = - courseData + const { + code, + title, + department, + credits, + description, + workload, + semester, + favoritedByCount, + } = courseData return (

{code}

- +

{title}

diff --git a/src/components/CourseResource/CourseResourceContainer.jsx b/src/components/CourseResource/CourseResourceContainer.jsx index 20306b49..ced73160 100644 --- a/src/components/CourseResource/CourseResourceContainer.jsx +++ b/src/components/CourseResource/CourseResourceContainer.jsx @@ -3,15 +3,20 @@ import { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import styled from 'styled-components/macro' -import { CourseContentRequest } from 'components/CoursePage' +import { + CourseContentRequest, + CourseContentRequestIcon, +} from 'components/CoursePage' import { ButtonSquare, LoaderAnimation, toast } from 'components/shared' import { API } from 'config/api' +import { useResponsive } from 'hooks' import { CourseResourceGrid } from './CourseResourceItem' const CourseResourceContainer = () => { const { code } = useParams() const navigate = useNavigate() + const { isMobileS } = useResponsive() const [resources, setResources] = useState([]) const [loading, setLoading] = useState(false) @@ -42,11 +47,19 @@ const CourseResourceContainer = () => {

Resources

- + {isMobileS ? ( + + ) : ( + + )} theme.textColor}; - font-weight: 400; + margin-top: 0.5rem; + + a { + color: #239afc; + + &:hover { + text-decoration: underline; + } + } ` diff --git a/src/components/CourseReview/Editor.jsx b/src/components/CourseReview/Editor.jsx index 92e1b32e..2c9a76d3 100644 --- a/src/components/CourseReview/Editor.jsx +++ b/src/components/CourseReview/Editor.jsx @@ -1,4 +1,3 @@ -import { DocumentText } from '@styled-icons/heroicons-outline' import { useState } from 'react' import ReactQuill from 'react-quill' import { useSelector } from 'react-redux' @@ -44,10 +43,14 @@ const modules = { const CustomToolbar = ({ templateHandler }) => ( - e.persist()} + > @@ -168,4 +171,9 @@ const Toolbar = styled.div` select { background: none; } + + button.ql-loadTemplate { + color: ${({ theme }) => theme.textColor}; + width: fit-content; + } ` diff --git a/src/components/Favourites/FavoriteToggle.jsx b/src/components/Favourites/FavoriteToggle.jsx index f83c3c11..cf96c2f0 100644 --- a/src/components/Favourites/FavoriteToggle.jsx +++ b/src/components/Favourites/FavoriteToggle.jsx @@ -2,22 +2,26 @@ import { Bookmark, BookmarkOutline } from '@styled-icons/zondicons' import { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { ButtonIcon, toast } from 'components/shared' +import { Badge, ButtonIcon, toast } from 'components/shared' import { API } from 'config/api' import { selectFavouriteStatus, updateFavourite } from 'store/userSlice' -const FavoriteToggle = ({ code }) => { +const FavoriteToggle = ({ code, initialCount }) => { const dispatch = useDispatch() const favourite = useSelector(selectFavouriteStatus(code)) + const [loading, setLoading] = useState(false) + const [count, setCount] = useState(initialCount) const handleToggle = async () => { try { setLoading(true) if (favourite) { await API.courses.favorite.remove({ code }) + setCount(count - 1) } else { await API.courses.favorite.add({ code }) + setCount(count + 1) } dispatch(updateFavourite(code)) @@ -32,7 +36,11 @@ const FavoriteToggle = ({ code }) => { : } + icon={ + + {favourite ? : } + + } color="white" loading={loading} /> diff --git a/src/components/shared/Avatar.jsx b/src/components/shared/Avatar.jsx index d28ae689..6bf59741 100644 --- a/src/components/shared/Avatar.jsx +++ b/src/components/shared/Avatar.jsx @@ -2,28 +2,40 @@ import { User } from '@styled-icons/heroicons-outline' import { Avatar } from 'antd' import styled from 'styled-components/macro' +import { useResponsive } from 'hooks' +import { useColorPicker } from 'styles/utils' + const StyledAvatar = styled(Avatar)` display: flex; align-items: center; justify-content: center; min-width: ${({ size }) => size}; min-height: ${({ size }) => size}; - border: 1px solid ${({ theme }) => theme.textColor}; + color: ${({ theme }) => theme.darksecondary}; img { - width: ${({ size }) => size}; - height: ${({ size }) => size}; + width: 100%; + height: 100%; } ` -export const UserAvatar = ({ size = '2rem', src, alt = 'Profile picture' }) => { - return src ? ( - - ) : ( +export const UserAvatar = ({ + size: initialSize, + src, + alt = 'Profile picture', +}) => { + const colorPicker = useColorPicker() + const { isMobileS } = useResponsive() + + const size = isMobileS ? '1rem' : initialSize ?? '2rem' + + if (src) return + + return ( } - style={{ backgroundColor: '#87d068' }} + style={{ backgroundColor: colorPicker() }} alt={alt} /> ) diff --git a/src/components/shared/Badge.jsx b/src/components/shared/Badge.jsx new file mode 100644 index 00000000..6269916d --- /dev/null +++ b/src/components/shared/Badge.jsx @@ -0,0 +1,13 @@ +import { Badge } from 'antd' +import styled from 'styled-components/macro' + +const StyledBadge = styled(Badge)` + transform: scale(${({ scale }) => scale}); + color: ${({ theme }) => theme.textColor}; + + .ant-badge-count { + box-shadow: none; + } +` + +export default StyledBadge diff --git a/src/components/shared/index.jsx b/src/components/shared/index.jsx index ed8c8a7b..b608dba5 100644 --- a/src/components/shared/index.jsx +++ b/src/components/shared/index.jsx @@ -6,6 +6,7 @@ export { ButtonDropdown, ButtonSwitch, } from './Buttons' +export { default as Badge } from './Badge' export { default as Card, CardSkeleton } from './Card' export { default as CardSplit, CardSplitSkeleton } from './CardSplit' export { default as Checkbox } from './Checkbox' diff --git a/src/routes.jsx b/src/routes.jsx index a3eab303..1167f60c 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -23,9 +23,9 @@ export const DashboardRoutes = () => ( } /> } /> - }> - - + + } /> + } /> } /> diff --git a/src/styles/utils.jsx b/src/styles/utils.jsx index 9c12b3c7..6a5abaf0 100644 --- a/src/styles/utils.jsx +++ b/src/styles/utils.jsx @@ -1,4 +1,5 @@ import { adjustHue } from 'polished' +import { useMemo } from 'react' import { useSelector } from 'react-redux' import { selectTheme } from 'store/settingsSlice' @@ -54,11 +55,17 @@ export const useColorPicker = () => { const theme = useSelector(selectTheme) const paletteTheme = palette[theme] + const randomizeId = useMemo( + () => Math.floor(Math.random() * paletteTheme.length), + [paletteTheme.length] + ) + if (!paletteTheme) { throw new Error(`No palette theme found for ${theme}`) } - const colorPicker = (id) => paletteTheme[id % paletteTheme.length] + const colorPicker = (id = randomizeId) => + paletteTheme[id % paletteTheme.length] return colorPicker }