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
+
}