Skip to content

Commit

Permalink
Merge pull request #44 from rajnishdargan/generic-editor
Browse files Browse the repository at this point in the history
Issue PS-2230 feat: Implement review flow for content
  • Loading branch information
itsvick authored Oct 18, 2024
2 parents d61f042 + 28f0d61 commit b2802d6
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 4 deletions.
31 changes: 31 additions & 0 deletions src/components/ConfirmActionPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';

interface ConfirmActionPopupProps {
open: boolean;
onClose: () => void;
onConfirm: () => void;
actionType: 'publish' | 'delete' | '';
}

const ConfirmActionPopup: React.FC<ConfirmActionPopupProps> = ({
open,
onClose,
onConfirm,
actionType,
}) => {
return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{`Confirm ${actionType.charAt(0).toUpperCase() + actionType.slice(1)}`}</DialogTitle>
<DialogContent>
<p>{`Are you sure you want to ${actionType} this content?`}</p>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">No</Button>
<Button onClick={onConfirm} color="primary">Yes</Button>
</DialogActions>
</Dialog>
);
};

export default ConfirmActionPopup;
10 changes: 6 additions & 4 deletions src/components/CourseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ const CourseCard: React.FC<ContentCardProps> = ({
}) => {
const theme = useTheme<any>();

const openEditor = () => {
const onContentClick = () => {
if (mimeType === MIME_TYPE.QUESTIONSET_MIME_TYPE) {
router.push({ pathname: `/editor`, query: { identifier, mode } });
} else if (mimeType && MIME_TYPE.GENERIC_MIME_TYPE.includes(mimeType)) {
} else if (mimeType && MIME_TYPE.GENERIC_MIME_TYPE.includes(mimeType) && mode === 'review') {
router.push({pathname: `/workspace/content/review` , query: { identifier, mode } });
} else if (mimeType && MIME_TYPE.GENERIC_MIME_TYPE.includes(mimeType) && mode !== 'review') {
router.push({ pathname: `/upload-editor`, query: { identifier } });
}
};
Expand Down Expand Up @@ -73,7 +75,7 @@ const CourseCard: React.FC<ContentCardProps> = ({
width: "250px",
}}
>
<Box position="relative" onClick={openEditor}>
<Box position="relative" onClick={onContentClick}>
<CardMedia
component="div"
sx={{
Expand Down Expand Up @@ -102,7 +104,7 @@ const CourseCard: React.FC<ContentCardProps> = ({
}}
/>
</Box>
<CardContent sx={{ flex: 1 }} onClick={openEditor}>
<CardContent sx={{ flex: 1 }} onClick={onContentClick}>
<Typography variant="h6">{title}</Typography>
<Typography variant="body2" color="text.secondary">
{description}
Expand Down
44 changes: 44 additions & 0 deletions src/components/ReviewCommentPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState } from 'react';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField } from '@mui/material';

interface ReviewCommentPopupProps {
open: boolean;
onClose: () => void;
onSubmit: (comment: string) => void;
title: string;
}

const ReviewCommentPopup: React.FC<ReviewCommentPopupProps> = ({ open, onClose, onSubmit, title }) => {
const [comment, setComment] = useState<string>('');

const handleSubmit = () => {
if (comment.trim()) {
onSubmit(comment);
setComment('');
onClose();
}
};

return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<TextField
label="Write your comment"
fullWidth
multiline
rows={4}
variant="outlined"
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">Cancel</Button>
<Button onClick={handleSubmit} color="primary">Submit Comment</Button>
</DialogActions>
</Dialog>
);
};

export default ReviewCommentPopup;
175 changes: 175 additions & 0 deletions src/pages/workspace/content/review/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React, { useEffect, useState } from "react";
import {
Box,
Button,
Card,
CardContent,
CardActions,
Typography,
IconButton
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { fetchContent } from "@/services/PlayerService";
import { useRouter } from "next/router";
import ConfirmActionPopup from '../../../../components/ConfirmActionPopup';
import ReviewCommentPopup from '../../../../components/ReviewCommentPopup';
import { publishContent, submitComment } from "@/services/ContentService";

const ReviewContentSubmissions = () => {
const router = useRouter();
const { identifier } = router.query;

const [contentDetails, setContentDetails] = useState<any>(undefined);
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
const [confirmationActionType, setConfirmationActionType] = useState<'publish' | ''>('');
const [openCommentPopup, setOpenCommentPopup] = useState<boolean>(false);

useEffect(() => {
const loadContent = async () => {
try {
if (identifier) {
const data = await fetchContent(identifier);
console.log('data ==>', data);
setContentDetails(data);
}
} catch (error) {
console.error('Failed to fetch content:', error);
}
};

if (identifier) {
loadContent();
}
}, [identifier]);

const redirectToReviewPage = () => {
router.push({ pathname: `/workspace/content/submitted` });
};

const closePublishPopup = () => {
setOpenConfirmationPopup(false);
};

const closeCommentPopup = () => {
setOpenCommentPopup(false);
};

const handleReject = () => {
console.log("Reject button clicked");
setOpenCommentPopup(true);
};

const handlePublish = () => {
console.log("Publish button clicked");
setConfirmationActionType('publish');
setOpenConfirmationPopup(true);
};

const confirmPublishContent = async () => {
try {
const response = await publishContent(identifier);
console.log('Published successfully:', response);
// Add toaster success message here
setOpenConfirmationPopup(false);
router.push({ pathname: `/workspace/content/submitted` });
} catch (error) {
console.error('Error during publishing:', error);
// Add toaster error message here
}
};

const handleSubmitComment = async (comment: string) => {
try {
const response = await submitComment(identifier, comment);
console.log('Comment submitted successfully:', response);
// Add toaster success message here
setOpenCommentPopup(false);
} catch (error) {
console.error('Error submitting comment:', error);
// Add toaster error message here
}
};

const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
};

return (
<Card sx={{ padding: 2, backgroundColor: 'white' }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h5" color="primary">Review Content Submissions</Typography>
<IconButton onClick={redirectToReviewPage}>
<CloseIcon />
</IconButton>
</Box>

{contentDetails ? (
<>
<Box sx={{ border: '1px solid #ccc', borderRadius: 1, padding: 2, backgroundColor: 'white', mb: 2 }}>
<Typography variant="h6" color="primary">{contentDetails.name}</Typography>
<Box sx={{
border: '1px solid #000', height: '500px', marginBottom: '16px',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}>
<Typography align="center">Player will be loaded here</Typography>
</Box>
</Box>

<CardActions disableSpacing sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
<Button variant="contained" color="primary" onClick={handlePublish} sx={{ marginRight: 1 }}>
Publish
</Button>
<Button variant="contained" color="primary" onClick={handleReject}>
Request Changes
</Button>
</CardActions>

<CardContent sx={{ border: '1px solid #ccc', borderRadius: 1, backgroundColor: 'white' }}>
<Typography variant="h6" color="primary">Content Details</Typography>
<Typography>
<strong>Name:</strong> {contentDetails.name}
</Typography>
<Typography>
<strong>Creator:</strong> {contentDetails.creator}
</Typography>
<Typography>
<strong>Description:</strong> {contentDetails.description}
</Typography>
<Typography>
<strong>Primary Category:</strong> {contentDetails.primaryCategory}
</Typography>
<Typography>
<strong>Created On:</strong> {formatDate(contentDetails.createdOn)}
</Typography>
<Typography>
<strong>Last Update:</strong> {formatDate(contentDetails.lastUpdatedOn)}
</Typography>
</CardContent>
</>
) : (
<Typography>No content details available</Typography>
)}

<ConfirmActionPopup
open={openConfirmationPopup}
onClose={closePublishPopup}
actionType={confirmationActionType}
onConfirm={confirmPublishContent}
/>

<ReviewCommentPopup
open={openCommentPopup}
onClose={closeCommentPopup}
onSubmit={handleSubmitComment}
title="Submit Your Comment"
/>
</Card>
);
};

export default ReviewContentSubmissions;
21 changes: 21 additions & 0 deletions src/services/ContentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,24 @@ export const deleteContent = async (identifier: string, mimeType: string) => {
throw error;
}
};


export const publishContent = async (identifier: any) => {
try {
const response = await axios.post('/api/publish', { identifier });
return response.data;
} catch (error) {
console.error('Error during publishing:', error);
throw error;
}
};

export const submitComment = async (identifier: any, comment: any) => {
try {
const response = await axios.post('/api/submit-comment', {identifier, comment });
return response.data;
} catch (error) {
console.error('Error submitting comment:', error);
throw error;
}
};
17 changes: 17 additions & 0 deletions src/services/PlayerService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { URL_CONFIG } from "../utils/url.config";
import axios from "axios";

export const fetchContent = async (identifier: any) => {
try {
const API_URL = URL_CONFIG.API.CONTENT_READ + identifier;
const FIELDS = URL_CONFIG.PARAMS.CONTENT_GET;
const LICENSE_DETAILS = URL_CONFIG.PARAMS.LICENSE_DETAILS;
const MODE = "edit";
const response = await axios.get(`${API_URL}?fields=${FIELDS}&mode=${MODE}&licenseDetails=${LICENSE_DETAILS}`);
console.log('response =====>', response)
return response.data.result.content;
} catch (error) {
console.error('Error fetching content:', error);
throw error;
}
};
9 changes: 9 additions & 0 deletions src/utils/url.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const URL_CONFIG = {
"PARAMS": {
"CONTENT_GET": "transcripts,ageGroup,appIcon,artifactUrl,attributions,attributions,audience,author,badgeAssertions,body,channel,code,concepts,contentCredits,contentType,contributors,copyright,copyrightYear,createdBy,createdOn,creator,creators,description,displayScore,domain,editorState,flagReasons,flaggedBy,flags,framework,identifier,itemSetPreviewUrl,keywords,language,languageCode,lastUpdatedOn,license,mediaType,mimeType,name,originData,osId,owner,pkgVersion,publisher,questions,resourceType,scoreDisplayConfig,status,streamingUrl,template,templateId,totalQuestions,totalScore,versionKey,visibility,year,primaryCategory,additionalCategories,interceptionPoints,interceptionType",
"LICENSE_DETAILS": "name,description,url"
},
"API": {
"CONTENT_READ": "/api/content/v1/read/"
}
}

0 comments on commit b2802d6

Please sign in to comment.