diff --git a/zubhub_backend/zubhub/activities/views.py b/zubhub_backend/zubhub/activities/views.py index 7a7f015b7..8e371b94e 100644 --- a/zubhub_backend/zubhub/activities/views.py +++ b/zubhub_backend/zubhub/activities/views.py @@ -1,29 +1,28 @@ -from django.shortcuts import render -from django.utils.translation import ugettext_lazy as _ -from rest_framework.decorators import api_view -from rest_framework.response import Response -from rest_framework.generics import ( - ListAPIView, CreateAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView) -from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny -from .permissions import IsStaffOrModeratorOrEducator, IsOwner, IsStaffOrModerator -from django.shortcuts import get_object_or_404 -from .models import * -from .serializers import * -from django.db import transaction -from django.core.exceptions import PermissionDenied from django.contrib.auth.models import AnonymousUser +from django.db import transaction +from django.shortcuts import get_object_or_404 +from rest_framework.generics import ( + CreateAPIView, + DestroyAPIView, + ListAPIView, + RetrieveAPIView, + UpdateAPIView, +) +from rest_framework.permissions import AllowAny, IsAuthenticated +from .models import Activity +from .permissions import IsOwner, IsStaffOrModerator, IsStaffOrModeratorOrEducator +from .serializers import ActivitySerializer class ActivityListAPIView(ListAPIView): - serializer_class = ActivitySerializer permission_classes = [AllowAny] def get_queryset(self): all = Activity.objects.all() return all - + class UserActivitiesAPIView(ListAPIView): """ @@ -33,10 +32,11 @@ class UserActivitiesAPIView(ListAPIView): serializer_class = ActivitySerializer permission_classes = [IsAuthenticated, IsOwner] - + def get_queryset(self): return self.request.user.activities_created.all() + class ActivityDetailsAPIView(RetrieveAPIView): """ Fetch Activity details. @@ -47,28 +47,29 @@ class ActivityDetailsAPIView(RetrieveAPIView): queryset = Activity.objects.all() serializer_class = ActivitySerializer - permission_classes = [IsAuthenticated] + permission_classes = [AllowAny] def get_object(self): queryset = self.get_queryset() pk = self.kwargs.get("pk") obj = get_object_or_404(queryset, pk=pk) - + if obj: with transaction.atomic(): if isinstance(self.request.user, AnonymousUser): obj.views_count += 1 obj.save() else: - if not self.request.user in obj.views.all(): + if self.request.user not in obj.views.all(): obj.views.add(self.request.user) obj.views_count += 1 obj.save() return obj - + else: raise Exception() + class PublishedActivitiesAPIView(ListAPIView): """ Fetch list of published activities by any user. @@ -77,17 +78,18 @@ class PublishedActivitiesAPIView(ListAPIView): serializer_class = ActivitySerializer permission_classes = [AllowAny] - + def get_queryset(self): - limit = self.request.query_params.get('limit', 10000) + limit = self.request.query_params.get("limit", 10000) try: limit = int(limit) except ValueError: limit = 10 - return Activity.objects.filter(publish= True)[:limit] - + return Activity.objects.filter(publish=True)[:limit] + + class UnPublishedActivitiesAPIView(ListAPIView): """ Fetch list of unpublished activities by authenticated staff member. @@ -100,24 +102,29 @@ class UnPublishedActivitiesAPIView(ListAPIView): permission_classes = [IsAuthenticated, IsStaffOrModerator] def get_queryset(self): - return Activity.objects.filter(publish= False) + return Activity.objects.filter(publish=False) + class ActivityCreateAPIView(CreateAPIView): """ Create new Activity.\n """ + queryset = Activity.objects.all() serializer_class = ActivitySerializer permission_classes = [IsAuthenticated, IsStaffOrModeratorOrEducator] + class ActivityUpdateAPIView(UpdateAPIView): """ Update activity. """ + queryset = Activity.objects.all() serializer_class = ActivitySerializer permission_classes = [IsAuthenticated, IsOwner] + class ActivityDeleteAPIView(DestroyAPIView): """ Delete a activity and related objects from database. @@ -126,9 +133,10 @@ class ActivityDeleteAPIView(DestroyAPIView): Requires activity id. Returns {details: "ok"} """ + queryset = Activity.objects.all() serializer_class = ActivitySerializer - permission_classes = [IsAuthenticated, IsOwner] + permission_classes = [IsAuthenticated, IsOwner, IsStaffOrModeratorOrEducator] def delete(self, request, *args, **kwargs): activity = self.get_object() @@ -150,11 +158,11 @@ class ToggleSaveAPIView(RetrieveAPIView): queryset = Activity.objects.all() serializer_class = ActivitySerializer permission_classes = [IsAuthenticated] - + def get_object(self): pk = self.kwargs.get("pk") obj = get_object_or_404(self.get_queryset(), pk=pk) - + if self.request.user in obj.saved_by.all(): obj.saved_by.remove(self.request.user) obj.save() @@ -170,15 +178,14 @@ class togglePublishActivityAPIView(RetrieveAPIView): Requires activity id. Returns updated activity. """ + queryset = Activity.objects.all() serializer_class = ActivitySerializer permission_classes = [IsAuthenticated, IsStaffOrModerator] - def get_object(self): - pk = self.kwargs.get("pk") - obj = get_object_or_404(self.get_queryset(), pk=pk) + obj = get_object_or_404(self.get_queryset(), pk=pk) obj.publish = not obj.publish obj.save() return obj diff --git a/zubhub_frontend/zubhub/public/locales/en/translation.json b/zubhub_frontend/zubhub/public/locales/en/translation.json index 983199552..29bc747bb 100644 --- a/zubhub_frontend/zubhub/public/locales/en/translation.json +++ b/zubhub_frontend/zubhub/public/locales/en/translation.json @@ -1098,7 +1098,8 @@ "mediaServerError": "Sorry media server is down we couldn't upload your files! try again later", "uploadError": "error occurred while downloading file : " } - } + }, + "tooltipMore": " more" }, "activityDetails": { @@ -1112,11 +1113,18 @@ "contributors": "CONTRIBUTORS" }, "made": "Made by", + "inspired": { + "recreated": "Re-created", + "times": "times" + }, "activity": { "creator": { "follow": "Follow", "unfollow": "Unfollow" }, + "introduction": "Introduction", + "categories": "Categories", + "classGrade": "Class Grade", "description": "Description", "materials": "Materials Used", "category": "Category", @@ -1124,7 +1132,10 @@ "none": "None", "build": "Let's Make This Project", - "pdf": "Download Pdf", + "pdf": { + "downloading": "Downloading...", + "download": "Download PDF" + }, "create": { "dialog": { "primary": "Create Activity", @@ -1132,6 +1143,10 @@ "proceed": "Proceed", "success": "activity Created successfully", "forbidden": "You must be staff monitor ao educator to be able to create a new activity " + }, + "modal": { + "success": "Congratulations your Activity has been successfully created!", + "share": "Share your activity with the world. Post it on the following platforms:" } }, "edit": { @@ -1159,14 +1174,19 @@ "label": "Publish" }, "unpublish": { - "label": "UnPublish" + "label": "Unpublish" } }, "saveButton": { "label": "save button", "save": "save", "unsave": "unsave" - } + }, + "footer": { + "introductionText": "Did you like this activity?", + "buttonLabel": "Create it!", + "moreActivitiesTitle": "More Activities" + } }, "breadCrumb":{ "link":{ diff --git a/zubhub_frontend/zubhub/src/assets/js/colors.js b/zubhub_frontend/zubhub/src/assets/js/colors.js index 0cdb70aa2..96477fcf4 100644 --- a/zubhub_frontend/zubhub/src/assets/js/colors.js +++ b/zubhub_frontend/zubhub/src/assets/js/colors.js @@ -1,22 +1,23 @@ export const colors = { - primary: '#02B7C4', - "primary-01": "#E5F8F9", - secondary: '#DC3545', - tertiary: '#FECB00', - 'tertiary-dark': '#C18D30', - black: '#292535', - gray: '#7E7E7E', - light: '#C4C4C4', - white: '#fff', - green: '#22C55E', - red: '#f44336', - 'blue-light': '#00B8C433', - 'blue-dark': '#7BA8AB', - 'blue-pale': '#DBECFF' -} + primary: '#02B7C4', + 'primary-01': '#E5F8F9', + secondary: '#DC3545', + tertiary: '#FECB00', + 'tertiary-dark': '#C18D30', + black: '#292535', + gray: '#7E7E7E', + light: '#C4C4C4', + white: '#fff', + green: '#22C55E', + red: '#f44336', + 'blue-light': '#00B8C433', + 'blue-dark': '#7BA8AB', + 'blue-pale': '#DBECFF', + border: '#7E5B4B', +}; export const borders = { - borderRadius: 20, - borderRadiusMd: 8, - borderRadiusSm: 4, -} \ No newline at end of file + borderRadius: 20, + borderRadiusMd: 8, + borderRadiusSm: 4, +}; diff --git a/zubhub_frontend/zubhub/src/assets/js/muiTheme.js b/zubhub_frontend/zubhub/src/assets/js/muiTheme.js index 53022e34f..048e7cc86 100644 --- a/zubhub_frontend/zubhub/src/assets/js/muiTheme.js +++ b/zubhub_frontend/zubhub/src/assets/js/muiTheme.js @@ -32,4 +32,17 @@ export const theme = createTheme({ }, }, }, + categoryColors: { + Animations: '#FCB07F', + Art: '#F8D991', + Science: '#FBC9B3', + Coding: '#65B4BD', + Electronics: '#F1D27C', + Toys: '#FAC5C2', + Games: '#6065A4', + Mechanical: '#F571AE', + Music: '#F1FC73', + Robotics: '#A66CA9', + Structures: '#FAE393', + }, }); diff --git a/zubhub_frontend/zubhub/src/assets/js/styles/components/activity/activityStyle.js b/zubhub_frontend/zubhub/src/assets/js/styles/components/activity/activityStyle.js index bbbeaf037..db0a820c8 100644 --- a/zubhub_frontend/zubhub/src/assets/js/styles/components/activity/activityStyle.js +++ b/zubhub_frontend/zubhub/src/assets/js/styles/components/activity/activityStyle.js @@ -1,15 +1,14 @@ -export const style = theme => ({ +export const style = () => ({ activityCardContainer: { position: 'relative', - // maxWidth: '350px', - // minWidth: '300px', - height: '95%', + width: '100%', + textAlign: 'left', }, activityCard: { maxWidth: '100%', - borderRadius: '15px', + height: '33em', + borderRadius: '20px', position: 'relative!important', - height: '100%', }, opacity: { backgroundColor: 'black', @@ -29,55 +28,21 @@ export const style = theme => ({ }, mediaBoxStyle: { width: '100%', - height: '17em', + height: '13em', position: 'relative', padding: '2%', display: 'flex', justifyContent: 'center', alignItems: 'center', }, - activityTagsBox: { - position: 'absolute', - top: '10px', - right: '10%', - display: 'flex', - }, - activityTagPill: { - backgroundColor: 'white', - color: 'var(--text-color2)', - border: '1px solid var(--text-color2)', - '&:hover': { - backgroundColor: 'var(--text-color2)', - color: 'white', - border: '1px solid white', - }, - }, - activityTagsShowMore: { - '&:hover': { - backgroundColor: 'white', - color: 'var(--text-color2)', - border: '1px solid white', - }, - }, - tagsShowMoreIconContainer: { - //position: 'absolute', - }, - tagsShowMoreList: { - position: 'absolute', - right: '0%', - backgroundColor: 'white', - maxHeight: '12em', - overflow: 'auto', - borderRadius: '10px', - }, - activityCardContent: { width: '100%', - position: 'relative', - }, - activityCardInfoBox: { - height: '100%', + padding: '16px', + '&:last-child': { + paddingBottom: '12px', + }, display: 'flex', + flexDirection: 'column', justifyContent: 'space-between', }, projectsCount: { @@ -92,10 +57,67 @@ export const style = theme => ({ marginLeft: '5px', }, activityTitle: { - fontSize: '1.1rem', - fontWeight: '900', + fontSize: '1.3rem', + fontWeight: 700, color: 'var(--text-color1)', - // width: '80%', - textAlign: '-webkit-auto', + }, + activityDescription: { + height: '48px', + margin: '8px 0', + textOverflow: 'ellipsis', + overflow: 'hidden', + display: '-webkit-box', + '-webkit-line-clamp': 2, + '-webkit-box-orient': 'vertical', + }, + activityCategoryContainer: { + margin: '12px 0', + display: 'flex', + flexWrap: 'nowrap', + gap: '8px', + }, + activityCategory: { + overflow: 'hidden', + textOverflow: 'ellipsis', + fontSize: '0.9em', + padding: '2px 10px', + border: '1px solid #7E5B4B', + borderRadius: '10em', + background: '#F1D27C', + }, + footer: { + marginTop: 10, + display: 'flex', + flexDirection: 'row', + flexWrap: 'nowrap', + overflow: 'hidden', + }, + captionStyle: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + captionIconStyle: { + backgroundColor: '#eee', + padding: '2px 7px', + borderRadius: 25, + justifyContent: 'space-between', + fontWeight: '600', + display: 'flex', + alignItems: 'center', + marginRight: '1em', + '& svg': { + fill: 'rgba(0,0,0,0.54)', + marginRight: '0.5em', + fontSize: '1.1rem', + }, + }, + date: { + fontSize: '0.9rem', + fontWeight: '600', + marginLeft: 'auto', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', }, }); diff --git a/zubhub_frontend/zubhub/src/assets/js/utils/constants.js b/zubhub_frontend/zubhub/src/assets/js/utils/constants.js index 5476630d4..c89f13c01 100644 --- a/zubhub_frontend/zubhub/src/assets/js/utils/constants.js +++ b/zubhub_frontend/zubhub/src/assets/js/utils/constants.js @@ -7,6 +7,12 @@ */ export const BASE_TAGS = ['staff', 'moderator', 'group', 'creator']; +export const USER_TAGS = { + staff: 'staff', + creator: 'creator', + educator: 'educator', +}; + export const site_mode = { PUBLIC: 1, PRIVATE: 2, diff --git a/zubhub_frontend/zubhub/src/components/activity/activity.jsx b/zubhub_frontend/zubhub/src/components/activity/activity.jsx index 870e9665c..99fc2c93b 100644 --- a/zubhub_frontend/zubhub/src/components/activity/activity.jsx +++ b/zubhub_frontend/zubhub/src/components/activity/activity.jsx @@ -1,48 +1,27 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import clsx from 'clsx'; import { makeStyles } from '@mui/styles'; -import BookmarkIcon from '@mui/icons-material/Bookmark'; -import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; - -import { - Card, - CardActions, - CardContent, - CardMedia, - Typography, - Box, - List, - ListItem, - ListItemText, - Grid, - Fab, -} from '@mui/material'; - +import { Card, CardActions, CardContent, CardMedia, Typography, Box } from '@mui/material'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import EmojiObjectsIcon from '@mui/icons-material/EmojiObjects'; +import { style } from '../../assets/js/styles/components/activity/activityStyle'; import { getActivities, activityToggleSave, setActivity } from '../../store/actions/activityActions'; import commonStyles from '../../assets/js/styles'; -import Creator from '../creator/creator'; -import { toggleSave } from './activityScripts'; -import { style } from '../../assets/js/styles/components/activity/activityStyle'; +import { dFormatter } from '../../assets/js/utils/scripts'; +import Categories from '../categories/Categories'; +import Creators from '../creators/Creators'; const useCommonStyles = makeStyles(commonStyles); const useStyles = makeStyles(style); function Activity(props) { const { activity, t } = { ...props }; - const [tagsShowMore, setTagsShowMore] = useState(false); const classes = useStyles(); const common_classes = useCommonStyles(); - const topMarginCoefficient = activity.creator?.length < 6 ? 2 : 1; - return (
- {activity.creators?.length > 0 - ? activity.creators.map((creator, index) => ( - - )) - : ''} - - - {activity.tags?.length > 0 - ? activity.tags.slice(0, 3).map(tag => ( - - {tag.name} - - )) - : ''} - {activity.tags?.length > 3 ? ( -
setTagsShowMore(true)} - onMouseOut={() => setTagsShowMore(false)} - > - - {['+', activity.tags.length - 3].join('')} - -
- ) : ( - '' - )} -
- - {tagsShowMore ? ( - setTagsShowMore(true)} - onMouseLeave={() => setTagsShowMore(false)} - > - - {activity.tags?.map(tag => ( - - - - ))} - - - ) : ( - '' - )} - toggleSave(e, activity.id, props.auth, props.navigate, props.activityToggleSave, t)} + + {activity.title} + + - {props.auth && activity.saved_by.includes(props.auth.id) ? ( - - ) : ( - - )} - - - - - {activity.title} + {activity.introduction.replace(/(<([^>]+)>)/gi, '')} + + {activity.category.length > 0 && } + + + + + {activity.views_count} - - - props.setActivity(activity)} + - - {`${t('activities.LinkedProjects')} `}{' '} - {` ${activity.inspired_projects.length}`} - - - - + {activity.inspired_projects.length} + + + + {` + ${dFormatter(activity.created_on).value} + ${t(`date.${dFormatter(activity.created_on).key}`)} + ${t('date.ago')} + `} + + diff --git a/zubhub_frontend/zubhub/src/components/categories/Categories.jsx b/zubhub_frontend/zubhub/src/components/categories/Categories.jsx new file mode 100644 index 000000000..7d90ddebc --- /dev/null +++ b/zubhub_frontend/zubhub/src/components/categories/Categories.jsx @@ -0,0 +1,24 @@ +import { makeStyles, useTheme } from '@mui/styles'; +import Chip from '@mui/material/Chip'; +import { styles } from './categories.styles'; + +const Categories = ({ categories }) => { + const classes = makeStyles(styles)(); + const theme = useTheme(); + return ( +
+ {categories?.map(category => ( + + ))} +
+ ); +}; + +export default Categories; diff --git a/zubhub_frontend/zubhub/src/components/categories/categories.styles.js b/zubhub_frontend/zubhub/src/components/categories/categories.styles.js new file mode 100644 index 000000000..1f562a4ba --- /dev/null +++ b/zubhub_frontend/zubhub/src/components/categories/categories.styles.js @@ -0,0 +1,18 @@ +import { colors } from '../../assets/js/colors'; + +export const styles = () => ({ + container: { + border: '2px olid red', + margin: '12px 0', + display: 'flex', + flexWrap: 'nowrap', + gap: '8px', + }, + chip: { + overflow: 'hidden', + textOverflow: 'ellipsis', + fontSize: '0.9em', + border: `1px solid ${colors.border}`, + borderRadius: '10em', + }, +}); diff --git a/zubhub_frontend/zubhub/src/components/creators/Creators.jsx b/zubhub_frontend/zubhub/src/components/creators/Creators.jsx new file mode 100644 index 000000000..e0cdc80cd --- /dev/null +++ b/zubhub_frontend/zubhub/src/components/creators/Creators.jsx @@ -0,0 +1,88 @@ +import { makeStyles } from '@mui/styles'; +import { Link } from 'react-router-dom'; +import { Typography, Box, Avatar, Tooltip } from '@mui/material'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import commonStyles from '../../assets/js/styles'; +import { creatorsStyles } from './creators.styles'; + +const useCommonStyles = makeStyles(commonStyles); +const useStyles = makeStyles(creatorsStyles); + +const Creators = ({ creators }) => { + const classes = useStyles(); + const common_classes = useCommonStyles(); + const { t } = useTranslation(); + + return ( + <> + {creators.length === 1 ? ( + creators.map((creator, index) => ( + + + + + + + {creator.username} + + {creator.tags.map((tag, index) => ( + + {tag} + + ))} + + + + + )) + ) : ( + + + {creators.slice(0, 3).map((creator, index) => ( + 3 + ? `${creators.length - 2} ${t('activities.tooltipMore')}` + : creator.username + } + placement="bottom" + arrow + className={classes.creatorUsernameTooltip} + style={{ zIndex: index }} + > + {index === 2 && creators.length > 3 ? ( + {`+${creators.length - 2}`} + ) : ( + + )} + + ))} + + + + {creators[creators.length - 1].username} + + {creators[creators.length - 1].tags.map((tag, index) => ( + + {tag} + + ))} + + + )} + + ); +}; + +export default Creators; diff --git a/zubhub_frontend/zubhub/src/components/creators/creators.styles.js b/zubhub_frontend/zubhub/src/components/creators/creators.styles.js new file mode 100644 index 000000000..9cf879e44 --- /dev/null +++ b/zubhub_frontend/zubhub/src/components/creators/creators.styles.js @@ -0,0 +1,50 @@ +export const creatorsStyles = () => ({ + creatorBox: { + margin: '15px 0 0.5em 0', + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + flexWrap: 'nowrap', + gap: '15px', + overflow: 'hidden', + }, + twoCreatorBox: { + minWidth: '4em', + transition: '0.4s', + display: 'grid', + gridTemplateColumns: '1.5em 1.5em', + '&:hover': { + gridTemplateColumns: '3.2em 3.2em', + }, + }, + multipleCreatorBox: { + minWidth: '5.5em', + transition: '0.4s', + display: 'grid', + gridTemplateColumns: '1.5em 1.5em 1.5em', + '&:hover': { + gridTemplateColumns: '3.2em 3.2em 3.2em', + }, + }, + creatorAvatar: { + boxShadow: `0 3px 5px 2px rgba(0, 0, 0, .12)`, + background: 'white', + color: 'black', + }, + creatorUsernameTooltip: { + marginRight: 'auto', + fontWeight: '400', + }, + creatorUsername: { + flex: 1, + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + fontWeight: '600', + fontSize: '1.1em', + }, + creatorTag: { + fontWeight: '500', + fontSize: '0.9rem', + }, +}); diff --git a/zubhub_frontend/zubhub/src/components/social_share_buttons/socialShareButtons.jsx b/zubhub_frontend/zubhub/src/components/social_share_buttons/socialShareButtons.jsx index c0e1b4b3e..ea5ee3222 100644 --- a/zubhub_frontend/zubhub/src/components/social_share_buttons/socialShareButtons.jsx +++ b/zubhub_frontend/zubhub/src/components/social_share_buttons/socialShareButtons.jsx @@ -13,7 +13,7 @@ import { colors } from '../../assets/js/colors'; import styles from '../../assets/js/styles/components/social_share_buttons/socialShareButtonsStyles'; const useStyles = makeStyles(styles); -const SocialButtons = ({ facebook, whatsapp, link, withColor, containerStyle = {} }) => { +const SocialButtons = ({ facebook, whatsapp, link, withColor, containerStyle = {}, styleOverrides }) => { const { t } = useTranslation(); const classes = useStyles(); const url = window.location.href; @@ -21,10 +21,10 @@ const SocialButtons = ({ facebook, whatsapp, link, withColor, containerStyle = { return ( <> - + {(showAll || facebook) && ( window.open( `https://www.facebook.com/sharer/sharer.php?u=${url}"e=${t('projectDetails.socialShare.fbwa')}`, @@ -38,7 +38,7 @@ const SocialButtons = ({ facebook, whatsapp, link, withColor, containerStyle = { {(showAll || whatsapp) && ( window.open(`https://api.whatsapp.com/send?text=${t('projectDetails.socialShare.fbwa')} ${url}`) } @@ -50,7 +50,7 @@ const SocialButtons = ({ facebook, whatsapp, link, withColor, containerStyle = { {(showAll || link) && ( { navigator.clipboard .writeText(url) diff --git a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetails.styles.js b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetails.styles.js index 8367438cd..423ab2aff 100644 --- a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetails.styles.js +++ b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetails.styles.js @@ -1,87 +1,135 @@ import { colors } from '../../assets/js/colors'; -export const activityDefailsStyles = theme => ({ - container: { - margin: '2em 24px', - [theme.breakpoints.down('378')]: { - marginTop: '3em 24px', +export const activityDetailsStyles = theme => ({ + mainContainer: { + [theme.breakpoints.down('sm')]: { + margin: '0 24px', }, - // borderRadius: 8, - // backgroundColor: colors.white, - // padding: 24 - }, - descriptionBodyStyle: { - marginBottom: '0.7em', - color: 'rgba(0, 0, 0, 0.54)', - '& .ql-editor': { - fontSize: '1.01rem', - fontFamily: 'Raleway,Roboto,sans-serif', - padding: '4px 0', - lineHeight: 1.9, + [theme.breakpoints.down('xs')]: { + margin: '0 12px', }, }, - socialButtons: { - backgroundColor: colors.primary, - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - margin: '16px 0', - borderRadius: 8, - }, - moreTextTitle: { - marginTop: 50, - marginBottom: 30, - fontSize: 22, - fontWeight: 'bold', - color: colors.black, + signedOutMainContainer: { + padding: '2em 12em', + [theme.breakpoints.down('sm')]: { + padding: '2em 0', + }, + [theme.breakpoints.down('xs')]: { + padding: '3em 0', + }, }, card: { borderRadius: 8, backgroundColor: colors.white, padding: 24, + marginBottom: 40, + [theme.breakpoints.down('xs')]: { + padding: '24px 16px', + }, + display: 'flex', + flexDirection: 'column', + gap: 16, }, - creatorProfileStyle: { - width: '100%', + headerFlex: { display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', alignItems: 'center', - marginBottom: '1em', - '& a': { - display: 'flex', - alignItems: 'center', - }, - [theme.breakpoints.down('500')]: { - width: '100%', - // justifyContent: 'space-between', - }, }, - actionBoxButtonStyle: { - color: 'white', - '& MuiFab-root:hover': { - color: '#F2F2F2', + headerTitle: { + fontWeight: 700, + }, + headerButton: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 4, + padding: '0 1.5em', + height: '2.5em', + '& .MuiButton-label': { + gap: '0.5em', }, - '& svg': { - fill: 'white', + }, + headerIconBox: { + display: 'flex', + alignItems: 'center', + gap: '1em', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + [theme.breakpoints.down('400')]: { + gap: '0.5em', + fontSize: '0.9em', }, - '& svg:hover': { - fill: '#F2F2F2', + }, + headerIconText: { + display: 'flex', + alignItems: 'center', + gap: '0.5em', + fontSize: '1em', + [theme.breakpoints.down('400')]: { + '& .MuiSvgIcon-root': { + fontSize: '1.2em', + }, }, }, - closed: { - height: 0, - transition: '0.4s', - overflow: 'hidden', + creatorBox: { + display: 'flex', + alignItems: 'center', + gap: 8, + }, + avatar: { + boxShadow: `0 3px 5px 2px rgba(0, 0, 0, .12)`, + }, + creatorUsername: { + fontWeight: 500, + fontSize: 16, + textTransform: 'capitalize', + color: colors.black, + }, + cardTitle: { + fontSize: '1.2em', + fontWeight: 600, + }, + descriptionBodyStyle: { + '& .ql-editor': { + fontSize: '1.1em', + fontFamily: 'Raleway,Roboto,sans-serif', + lineHeight: 1.9, + margin: 0, + padding: 0, + '& ol': { + padding: 0, + }, + }, }, - expandableMargin: { - // marginBottom: 10, - marginTop: 30, + classGrade: { + width: 'fit-content', + border: `1px solid ${colors.primary}`, }, - expanded: { - transition: '0.4s', - height: 'fit-content', + footer: { display: 'flex', flexDirection: 'column', - gap: 32, - marginTop: 20, + gap: '2em', + textAlign: 'center', + }, + footerTitle: { + fontSize: 22, + fontWeight: 700, + }, + menuItemIcon: { + minWidth: '2em', + }, + dangerButton: { + color: colors.secondary, + }, +}); + +export const socialButtonsStyleOverrides = () => ({ + containerStyle: { + display: 'flex', + }, + outlined: { + border: 'none', }, }); diff --git a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx index 636033227..e842eb11d 100644 --- a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx +++ b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx @@ -10,36 +10,46 @@ import { Menu, MenuItem, Typography, + Chip, + ListItemIcon, + ListItemText, } from '@mui/material'; -import { CloseOutlined, MoreVert } from '@mui/icons-material'; -import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; -import VisibilityIcon from '@mui/icons-material/Visibility'; +import { + CloseOutlined, + MoreVert, + Visibility as VisibilityIcon, + EmojiObjects as EmojiObjectsIcon, + Share as ShareIcon, +} from '@mui/icons-material'; import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; import ReactConfetti from 'react-confetti'; import { useTranslation } from 'react-i18next'; -import { FiShare } from 'react-icons/fi'; +import { FiShare, FiDownload, FiEdit, FiTrash2 } from 'react-icons/fi'; +import { MdPublish, MdFileDownloadOff } from 'react-icons/md'; import ReactQuill from 'react-quill'; import { useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; import { useReactToPrint } from 'react-to-print'; import Html2Pdf from 'html2pdf.js'; - import ZubHubAPI from '../../api'; import { colors } from '../../assets/js/colors'; -import { ClapBorderIcon } from '../../assets/js/icons/ClapIcon'; import styles from '../../assets/js/styles/index'; -import { Collapsible, CustomButton, Gallery, Modal, Pill } from '../../components'; +import { CustomButton, Gallery, Modal } from '../../components'; import Activity from '../../components/activity/activity'; import SocialButtons from '../../components/social_share_buttons/socialShareButtons'; import { getUrlQueryObject } from '../../utils.js'; -import { activityDefailsStyles } from './ActivityDetails.styles'; +import { dFormatter } from '../../assets/js/utils/scripts'; +import { activityDetailsStyles, socialButtonsStyleOverrides } from './ActivityDetails.styles'; +import Categories from '../../components/categories/Categories.jsx'; +import { USER_TAGS } from '../../assets/js/utils/constants'; const API = new ZubHubAPI(); // TODO: move api request to redux action const authenticatedUserActivitiesGrid = { xs: 12, sm: 6, md: 6 }; const unauthenticatedUserActivitiesGrid = { xs: 12, sm: 6, md: 3 }; export default function ActivityDetailsV2(props) { - const classes = makeStyles(activityDefailsStyles)(); + const classes = makeStyles(activityDetailsStyles)(); const commonClasses = makeStyles(styles)(); const { t } = useTranslation(); @@ -84,7 +94,11 @@ export default function ActivityDetailsV2(props) { }; const handleEdit = () => { - props.navigate(`${props.location.pathname}/edit`); + props.history.push(`${props.location.pathname}/edit`); + }; + + const handlePublish = () => { + API.activityTogglePublish({ token: auth.token, id: activity.id }).then(() => window.location.reload()); }; const handleDownload = useReactToPrint({ @@ -112,174 +126,151 @@ export default function ActivityDetailsV2(props) { }); return ( -
+
{open ? : null} -
- - {activity?.title} - - -
-
- -
- - {creator?.username} - -
- - Educator - +
+
+ +
+ +
+ + {creator?.username} + +
+ {creator?.tags.includes(USER_TAGS.educator) && ( + + {USER_TAGS.educator} + + )} +
-
- -
- -
- - Create this Project + + + {t('activityDetails.activity.create.dialog.primary')} +
+ +
+ + {activity?.title} + + {(activity.creators?.some(creator => creator.id === auth.id) || auth.tags.includes(USER_TAGS.staff)) && ( + + )} +
+
+ + + {activity.views_count} + + + + + {t('activityDetails.inspired.recreated')} + {activity.views_count} + {t('activityDetails.inspired.times')} + + + + {` + ${dFormatter(activity.created_on).value} + ${t(`date.${dFormatter(activity.created_on).key}`)} + ${t('date.ago')} + `} + +
+ {activity.images?.length > 0 && } +
- {isDownloading ? 'Downloading...' : 'Download PDF'} + + {isDownloading ? t('activityDetails.activity.pdf.downloading') : t('activityDetails.activity.pdf.download')} +
-
- handleSetState(toggleLike(e, props, project.id))} - > - {/* {project.likes.includes(props.auth.id) ? ( - - ) : ( */} - - {/* )} */} - - handleSetState(toggleSave(e, props, project.id))} - > - {/* {project.saved_by.includes(props.auth.id) ? ( - - ) : ( */} - - {/* )} */} - - - - - - - -
- - - {activity.images?.length > 0 && img.image?.file_url)} />} +
+ + {t('activityDetails.activity.introduction')} + - - - {activity.category?.length > 0 && ( - -
- {activity.category?.map((cat, index) => ( - - ))} -
-
- )} - - {activity.class_grade && ( - -
- -
-
- )} - - {activity.materials_used && ( - - {activity.materials_used_image && } + {activity.images?.length > 0 && img.image?.file_url)} />} + + + {t('activityDetails.activity.categories')} + + {activity.category?.length > 0 && } + + + {t('activityDetails.activity.classGrade')} + + {activity.class_grade && } + + + {t('activityDetails.activity.materials')} + + {activity.materials_used && ( - - )} - - {activity.making_steps?.map((step, index) => ( - - {step.image?.length > 0 && img?.file_url)} />} - {step.description && ( + )} + {activity.materials_used_image && } + {activity.making_steps?.map(step => ( + <> + + + {`Step ${step?.step_order}: ${step.title}`} + - )} - - ))} - -
-
- - Did you like this activity? - - Be the first to create it - - Create It! - -
- - - More Activities + {step.image?.length > 0 && img?.file_url)} />} + + ))} +
+
+ + + {t('activityDetails.footer.moreActivitiesTitle')} - - - {moreActivities.map((activity, index) => ( + + {moreActivities.slice(0, 2).map((activity, index) => ( - handleSetState(updateProjects(res, state, props, toast))} - {...props} - /> + ))} - - {/* */}
} maxWidth="xs" open={open} onClose={toggleDialog}> @@ -290,14 +281,12 @@ export default function ActivityDetailsV2(props) {
- - Congratulations your Activity has been successfully created! + + {t('activityDetails.activity.create.modal.success')} - - Share your activity with the world. Post it on the following platforms: - + {t('activityDetails.activity.create.modal.share')}
@@ -307,8 +296,10 @@ export default function ActivityDetailsV2(props) { ); } -const AnchorElemt = ({ onEdit, onDelete, isLoading = false }) => { +const AnchorElemt = ({ onEdit, onDelete, onPublish, isLoading = false, activity, auth }) => { const [anchorEl, setAnchorEl] = useState(null); + const classes = makeStyles(activityDetailsStyles)(); + const { t } = useTranslation(); const open = Boolean(anchorEl); const handleClick = event => { @@ -329,6 +320,37 @@ const AnchorElemt = ({ onEdit, onDelete, isLoading = false }) => { handleClose(); }; + const handlePublish = () => { + onPublish(); + handleClose(); + }; + + const ActionButtons = () => { + if (activity.creators?.some(creator => creator.id === auth.id)) { + return ( + + + + + {t('activityDetails.activity.edit.label')} + + ); + } else if (auth.tags.includes(USER_TAGS.staff)) { + return ( + + + {activity?.publish ? : } + + + {activity?.publish + ? t('activityDetails.activity.unpublish.label') + : t('activityDetails.activity.publish.label')} + + + ); + } + }; + return ( <> { 'aria-labelledby': `basic-button`, }} > - Edit - - Delete + + + + + + {t('activityDetails.activity.delete.label')} + + + + ); +}; + +const ShareButton = () => { + const socialClasses = makeStyles(socialButtonsStyleOverrides)(); + const [anchorEl, setAnchorEl] = useState(null); + + const open = Boolean(anchorEl); + const handleClick = event => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + ); diff --git a/zubhub_frontend/zubhub/src/views/profile/Profile.jsx b/zubhub_frontend/zubhub/src/views/profile/Profile.jsx index e4498a5f8..8bbe16c18 100644 --- a/zubhub_frontend/zubhub/src/views/profile/Profile.jsx +++ b/zubhub_frontend/zubhub/src/views/profile/Profile.jsx @@ -53,6 +53,7 @@ import styles from '../../assets/js/styles/views/profile/profileStyles'; import commonStyles from '../../assets/js/styles'; import ProjectsDraftsGrid from '../../components/projects_drafts/ProjectsDraftsGrid'; import UserActivitylog from '../../components/user_activitylog/UserActivitylog'; +import { USER_TAGS } from '../../assets/js/utils/constants.js'; const useStyles = makeStyles(styles); const useCommonStyles = makeStyles(commonStyles); @@ -129,7 +130,7 @@ function Profile(props) { setNextPage(nextPageExist); }); } catch (error) { - console.log(error); + console.log(error); // eslint-disable-line no-console } }, [page]); @@ -184,17 +185,28 @@ function Profile(props) { {profile.username} - {sortTags(profile.tags).map(tag => ( + {props.location.state?.prevPath.split('/')[1] === 'activities' ? ( - {tag} + {USER_TAGS.educator} - ))} + ) : ( + sortTags(profile.tags).map(tag => ( + + {tag} + + )) + )} {props.auth.username === profile.username ? ( <>