diff --git a/cypress/e2e/app/api/dashboard/requestedBadge.cy.ts b/cypress/e2e/app/api/dashboard/requestedBadge.cy.ts new file mode 100644 index 00000000..8d015dc4 --- /dev/null +++ b/cypress/e2e/app/api/dashboard/requestedBadge.cy.ts @@ -0,0 +1,69 @@ +import { TIMEOUT } from '../../../../utils/constants' + +describe('test requested submission required API', () => { + + context('Logged out', () => { + it('should reject any request if not logged in', () => { + cy.request({ + url: '/api/submission-box/requestedsubmissions/require-submission', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + failOnStatusCode: false, + }).then(async (response) => { + expect(response.status).to.eq(401) + expect(response.body.error).to.eq('Unauthorized') + }) + }) + }) + + context('Logged in', () => { + const email = 'noRequests@user.com' + beforeEach(() => { + cy.task('clearDB') + const password = 'Pass1234' + cy.task('createUser', { email, password }) + cy.visit('/login') + cy.get('[data-cy=email]').type(email) + cy.get('[data-cy=password]').type(password) + cy.get('[data-cy=submit]').click() + cy.url({ timeout: TIMEOUT.EXTRA_LONG }).should('not.contain', 'login') + cy.reload() + }) + + it('should return nothing when user does not have any requests', () => { + cy.request({ + url: '/api/submission-box/requestedsubmissions/require-submission', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then(async (response) => { + expect(response.status).to.eq(200) + expect(response.body.requiredCount).to.eq(0) + }) + }) + + it('should return the number of requested submissions needing to be submitted to', () => { + const submissionBoxTitle = 'multiple submissions' + cy.task('getUserId', email).then((userId) => { + cy.task('createRequestSubmissionForUser', { userId, submissionBoxTitle }) + cy.task('createRequestSubmissionForUser', { userId, submissionBoxTitle }) + cy.task('createRequestSubmissionForUser', { userId, submissionBoxTitle }) + cy.task('createRequestSubmissionForUser', { userId, submissionBoxTitle }) + }) + cy.reload() + cy.request({ + url: '/api/submission-box/requestedsubmissions/require-submission', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then(async (response) => { + expect(response.status).to.eq(200) + expect(response.body.requiredCount).to.eq(4) + }) + }) + }) +}) diff --git a/cypress/e2e/app/dashboard/requestedBadge.cy.ts b/cypress/e2e/app/dashboard/requestedBadge.cy.ts new file mode 100644 index 00000000..7c39adc7 --- /dev/null +++ b/cypress/e2e/app/dashboard/requestedBadge.cy.ts @@ -0,0 +1,121 @@ +import { TIMEOUT } from '../../../utils/constants' + +describe('Dashboard requested submission badge count tests', () => { + // User information + const email = 'requested@badge.test' + const password = 'Pass1234' + beforeEach(() => { + cy.task('clearDB') + cy.task('createUser', { email, password }) + cy.visit('/login') + cy.get('[data-cy=email]').type(email) + cy.get('[data-cy=password]').type(password) + cy.get('[data-cy=submit]').click() + cy.url({ timeout: TIMEOUT.EXTRA_LONG }).should('not.contain', 'login') + cy.reload() + cy.visit('/dashboard') + }) + + it('should not have any badge when the user has no requested submissions', () => { + // Wait for api fetch + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('exist').should('contain', '') + }) + + it('should increment count when the user has a submission not yet submitted to and decrement it after submission', () => { + const submissionBoxTitle = 'Badge testing simulator' + const videoTitle = 'Get rid of the badge' + cy.task('getUserId', email).then((userId) => { + cy.task('createRequestSubmissionForUser', {userId, submissionBoxTitle}).then((requestedSubmissionId) => { + cy.reload() + // wait for api fetch + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '1') + cy.task('createOneVideoAndRetrieveVideoId', { + title: videoTitle, + }).then((videoId) => { + cy.task('submitVideoToSubmissionBox', { + requestedSubmissionId, + videoId, + }).then(() => { + cy.reload() + cy.get('[data-cy=requested-badge]').should('exist').should('contain', '') + }) + }) + }) + }) + }) + + it('should increment the count after users remove the submission from a requested submission', () => { + const submissionBoxTitle = 'Badge testing simulator' + const videoTitle = 'Get rid of the badge' + cy.task('getUserId', email).then((userId) => { + cy.task('createRequestSubmissionForUser', {userId, submissionBoxTitle}).then((requestedSubmissionId) => { + cy.reload() + // wait for api fetch + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '1') + cy.task('createOneVideoAndRetrieveVideoId', { + title: videoTitle, + ownerId: userId, + }).then((videoId) => { + cy.task('submitVideoToSubmissionBox', { + requestedSubmissionId, + videoId, + }).then(() => { + cy.reload() + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '') + cy.visit('/dashboard?tab=my-invitations') + cy.get('[data-cy="My Invitations"]') + cy.wait(1000) + cy.get('[data-cy="My Invitations"]').click() + cy.get(`[data-cy="${ submissionBoxTitle }"]`) + cy.wait(1000) + cy.get(`[data-cy="${ submissionBoxTitle }"]`).click() + cy.wait(1000) + cy.get('[data-cy="unsubmit-button"]').should('be.visible').click() + cy.get('button').last().should('be.visible').and('have.text', 'Yes').click() + + cy.visit('/dashboard') + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '1') + }) + }) + }) + }) + }) + + it('should increment the count after users deletes the video submission of a requested submission', () => { + const submissionBoxTitle = 'Badge testing simulator' + const videoTitle = 'Get rid of the badge' + cy.task('getUserId', email).then((userId) => { + cy.task('createRequestSubmissionForUser', {userId, submissionBoxTitle}).then((requestedSubmissionId) => { + cy.reload() + // wait for api fetch + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '1') + cy.task('createOneVideoAndRetrieveVideoId', { + title: videoTitle, + ownerId: userId, + }).then((videoId) => { + cy.task('submitVideoToSubmissionBox', { + requestedSubmissionId, + videoId, + }).then(() => { + cy.reload() + cy.wait(1000) + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '') + cy.get('[data-cy="video-list"]').children().first().should('contain', videoTitle).click() + cy.wait(1000) + cy.get('[data-cy="edit-icon"]').click() + cy.get('[data-cy="detail-video-delete-button"]').click() + cy.get('[data-cy="detail-video-delete-confirm-button"]').click() + + cy.visit('/dashboard') + cy.get('[data-cy=requested-badge]').should('be.visible').should('contain', '1') + }) + }) + }) + }) + }) +}) diff --git a/src/app/api/submission-box/requestedsubmissions/require-submission/route.ts b/src/app/api/submission-box/requestedsubmissions/require-submission/route.ts new file mode 100644 index 00000000..ce756bcc --- /dev/null +++ b/src/app/api/submission-box/requestedsubmissions/require-submission/route.ts @@ -0,0 +1,41 @@ +import { NextResponse } from 'next/server' +import { getServerSession } from 'next-auth' +import logger from '@/utils/logger' +import prisma from '@/lib/prisma' + +export async function GET() { + // This API will return the count of requested submissions that the user has not yet submitted to, call with GET + try { + // Check if a user is logged in + const session = await getServerSession() + if (!session || !session.user?.email) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + // Get userId + const userId: string = ( + await prisma.user.findUniqueOrThrow({ + where: { + email: session.user.email, + }, + select: { + id: true, + }, + }) + ).id + // This query returns the count of requested submissions with a videoVersions of none + const requiredCount = await prisma.requestedSubmission.aggregate({ + where: { + userId: userId, + videoVersions: { none: {} }, + }, + _count: { + id: true, + }, + }) + + return NextResponse.json({ requiredCount: requiredCount._count.id }, { status: 200 }) + } catch (error) { + logger.error(error) + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }) + } +} diff --git a/src/components/DashboardSidebarSubmissionBoxes/index.tsx b/src/components/DashboardSidebarSubmissionBoxes/index.tsx index c9fe16f7..d7ec5c39 100644 --- a/src/components/DashboardSidebarSubmissionBoxes/index.tsx +++ b/src/components/DashboardSidebarSubmissionBoxes/index.tsx @@ -4,9 +4,11 @@ import DashboardSidePanelOption from '@/components/DashboardSidePanelOption' import AddCircleIcon from '@mui/icons-material/AddCircle' import SendIcon from '@mui/icons-material/Send' import Box from '@mui/material/Box' -import React from 'react' +import { useEffect, useState } from 'react' import EditIcon from '@mui/icons-material/Edit' import { SidebarOption } from '@/types/dashboard/sidebar' +import { Badge } from '@mui/material' +import { toast } from 'react-toastify' export type DashboardSidebarSubmissionBoxesProps = { onCreateNewClick: () => void @@ -18,6 +20,17 @@ export type DashboardSidebarSubmissionBoxesProps = { export default function DashboardSidebarSubmissionBoxes(props: DashboardSidebarSubmissionBoxesProps) { const { sidebarSelectedOption } = props + const [count, setCount] = useState() + useEffect(() => { + fetch('/api/submission-box/requestedsubmissions/require-submission') + .then(async (res) => { + const { requiredCount } = await res.json() + setCount(requiredCount) + }) + .catch(() => { + toast.error('An error occurred trying to access requested submission count') + }) + }) return ( <> @@ -47,6 +60,14 @@ export default function DashboardSidebarSubmissionBoxes(props: DashboardSidebarS Submission Invitations + }