Repository: SuperteamDAO/earn
Pull request #1031: Staging
Author: login: JayeshVP24
Created: 2025-04-07T05:53:10Z | Merged: 2025-04-07T07:42:15Z

## What does this PR do?

## Where should the reviewer start?

## How should this be manually tested?

## Any background context you want to provide?

## What are the relevant issues?

## Screenshots (if appropriate)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Style**
  - Upgraded dropdown and list components with scrollable content and visibly styled scrollbars for improved navigation.
  - Enhanced image presentation in a fun interactive element to maintain proper aspect ratios.
  - Refined global styling for better consistency in colors, spacing, and scrollbar appearance.

- **New Features**
  - Simplified the activation of a hidden interactive feature, broadening its accessibility.
  - Introduced superteam status indicators with tooltips and icons in application details.
  - Adjusted submission panel displays based on submission status and related conditions.
  - Added a new modal for assigning bonuses to multiple submissions, enhancing the submission handling process.
  - Implemented a new state variable to manage multi-selection capabilities in submission lists.
  - Enhanced the dynamic image sourcing based on region parameters for improved visual context.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/app/api/dynamic-og/region/route.tsx b/src/app/api/dynamic-og/region/route.tsx
index f83522592..b0d34a708 100644
--- a/src/app/api/dynamic-og/region/route.tsx
+++ b/src/app/api/dynamic-og/region/route.tsx
@@ -1,6 +1,6 @@
 import { ImageResponse } from 'next/og';
 
-import { ASSET_URL } from '@/constants/ASSET_URL';
+import { Superteams } from '@/constants/Superteam';
 import { loadGoogleFont } from '@/utils/ogHelpers';
 
 import 'flag-icons/css/flag-icons.min.css';
@@ -23,6 +23,8 @@ export async function GET(request: Request) {
       loadGoogleFont('Inter:wght@700', allText),
     ]);
 
+    const superteam = Superteams.find((s) => s.displayValue === region);
+
     return new ImageResponse(
       (
         <div
@@ -142,7 +144,7 @@ export async function GET(request: Request) {
                 objectFit: 'cover',
               }}
               alt="logo"
-              src={ASSET_URL + `/superteams/banners/${region}.png`}
+              src={superteam?.banner || ''}
             />
           </div>
         </div>
diff --git a/src/components/shared/RegionCombobox.tsx b/src/components/shared/RegionCombobox.tsx
index 96ef87e06..e1bf89729 100644
--- a/src/components/shared/RegionCombobox.tsx
+++ b/src/components/shared/RegionCombobox.tsx
@@ -235,7 +235,7 @@ export function RegionCombobox({
       >
         <Command>
           <CommandInput placeholder="Search..." />
-          <CommandList className="max-h-[200px] md:max-h-[300px]">
+          <CommandList className="scrollbar-visible max-h-[200px] overflow-auto md:max-h-[300px]">
             <CommandEmpty>No region found.</CommandEmpty>
             <CommandGroup>
               {!!value && unset && (
diff --git a/src/components/shared/SuperteamCombobox.tsx b/src/components/shared/SuperteamCombobox.tsx
index 1a968f1f9..c170bf388 100644
--- a/src/components/shared/SuperteamCombobox.tsx
+++ b/src/components/shared/SuperteamCombobox.tsx
@@ -133,7 +133,7 @@ export function SuperteamCombobox({
       >
         <Command>
           <CommandInput placeholder="Search..." />
-          <CommandList className="max-h-[200px] md:max-h-[300px]">
+          <CommandList className="scrollbar-visible max-h-[200px] overflow-auto md:max-h-[300px]">
             <CommandEmpty>No superteam found.</CommandEmpty>
             {!!value && unset && (
               <CommandGroup>
diff --git a/src/components/ui/datetime-picker.tsx b/src/components/ui/datetime-picker.tsx
index 122eba039..41912c3bc 100644
--- a/src/components/ui/datetime-picker.tsx
+++ b/src/components/ui/datetime-picker.tsx
@@ -375,7 +375,7 @@ export function DateTimePicker({
                           ? minDateTooltipContent
                           : maxDateTooltipContent
                       }
-                      contentProps={{ className: 'max-w-56 text-center' }}
+                      contentProps={{ className: 'max-w-56 text-center z-100' }}
                     >
                       <div {...props} />
                     </Tooltip>
diff --git a/src/components/ui/multi-select.tsx b/src/components/ui/multi-select.tsx
index ceffc714a..6f6dfdc99 100644
--- a/src/components/ui/multi-select.tsx
+++ b/src/components/ui/multi-select.tsx
@@ -562,7 +562,7 @@ const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
         <div className="relative">
           {open && (
             <CommandList
-              className="bg-popover text-popover-foreground animate-in absolute top-1 z-10 w-full rounded-md border shadow-md outline-hidden"
+              className="bg-popover text-popover-foreground animate-in scrollbar-visible absolute top-1 z-10 w-full rounded-md border shadow-md outline-hidden"
               onMouseLeave={() => {
                 setOnScrollbar(false);
               }}
diff --git a/src/features/listings/components/Submission/EasterEgg.tsx b/src/features/listings/components/Submission/EasterEgg.tsx
index e4ad0fbd3..7f3f31479 100644
--- a/src/features/listings/components/Submission/EasterEgg.tsx
+++ b/src/features/listings/components/Submission/EasterEgg.tsx
@@ -65,7 +65,7 @@ export const EasterEgg = ({ isOpen, onClose, isProject }: Props) => {
             <ExternalImage
               src={'/icons/celebration.png'}
               alt="celebration icon"
-              className="h-100 w-100"
+              className="object-contain"
             />
           </div>
           <p className="text-center text-3xl font-medium text-white md:text-4xl">
diff --git a/src/features/listings/components/Submission/SubmissionDrawer.tsx b/src/features/listings/components/Submission/SubmissionDrawer.tsx
index e4015a735..8223b00d1 100644
--- a/src/features/listings/components/Submission/SubmissionDrawer.tsx
+++ b/src/features/listings/components/Submission/SubmissionDrawer.tsx
@@ -165,17 +165,8 @@ export const SubmissionDrawer = ({
         telegram: data.telegram || user?.telegram || '',
       });
 
-      const hideEasterEggFromSponsorIds = [
-        '53cbd2eb-14e5-4b8a-b6fe-e18e0c885145', // network schoool
-      ];
-
       const latestSubmissionNumber = (user?.Submission?.length ?? 0) + 1;
-      if (
-        !editMode &&
-        latestSubmissionNumber === 1 &&
-        !hideEasterEggFromSponsorIds.includes(listing.sponsorId || '')
-      )
-        showEasterEgg();
+      if (!editMode) showEasterEgg();
       if (!editMode && latestSubmissionNumber % 3 !== 0) onSurveyOpen();
 
       form.reset();
diff --git a/src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx b/src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx
index 90da26f6c..3c9c473f1 100644
--- a/src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx
+++ b/src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx
@@ -3,13 +3,14 @@ import dayjs from 'dayjs';
 import { useAtom } from 'jotai';
 import { ArrowRight, Check, Copy, X } from 'lucide-react';
 import Link from 'next/link';
-import React from 'react';
+import React, { useMemo } from 'react';
 import { MdOutlineAccountBalanceWallet, MdOutlineMail } from 'react-icons/md';
 import { toast } from 'sonner';
 
 import { Button } from '@/components/ui/button';
 import { CircularProgress } from '@/components/ui/progress';
 import { Tooltip } from '@/components/ui/tooltip';
+import { Superteams } from '@/constants/Superteam';
 import { tokenList } from '@/constants/tokenList';
 import { useClipboard } from '@/hooks/use-clipboard';
 import { cn } from '@/utils/cn';
@@ -122,6 +123,15 @@ export const ApplicationDetails = ({
     }
   };
 
+  const superteam = useMemo(() => {
+    const isSuperteamMember =
+      selectedApplication?.user.superteamLevel?.includes('Superteam') || false;
+    return isSuperteamMember
+      ? Superteams.find(
+          (s) => s.name === selectedApplication?.user.superteamLevel,
+        )
+      : undefined;
+  }, [selectedApplication]);
   return (
     <div className="w-full rounded-r-xl bg-white">
       {applications?.length ? (
@@ -135,9 +145,24 @@ export const ApplicationDetails = ({
                   avatar={selectedApplication?.user?.photo || undefined}
                 />
                 <div>
-                  <p className="w-full text-base font-medium whitespace-nowrap text-slate-900">
-                    {`${selectedApplication?.user?.firstName}`}
-                  </p>
+                  <span className="flex gap-2">
+                    <p className="w-fit text-base font-medium whitespace-nowrap text-slate-900">
+                      {`${selectedApplication?.user?.firstName}`}
+                    </p>
+                    {superteam && (
+                      <Tooltip
+                        content={
+                          selectedApplication?.user?.superteamLevel + ' Member'
+                        }
+                      >
+                        <img
+                          src={superteam.icons}
+                          alt="Superteam Member"
+                          className="size-4 rounded-full"
+                        />
+                      </Tooltip>
+                    )}
+                  </span>
                   <Link
                     href={`/t/${selectedApplication?.user?.username}`}
                     className="text-brand-purple flex w-full items-center gap-1 text-xs font-medium whitespace-nowrap"
diff --git a/src/features/sponsor-dashboard/components/GrantApplications/ApplicationList.tsx b/src/features/sponsor-dashboard/components/GrantApplications/ApplicationList.tsx
index 4cbe0d03c..daa31f956 100644
--- a/src/features/sponsor-dashboard/components/GrantApplications/ApplicationList.tsx
+++ b/src/features/sponsor-dashboard/components/GrantApplications/ApplicationList.tsx
@@ -24,6 +24,8 @@ import {
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu';
 import { Input } from '@/components/ui/input';
+import { Tooltip } from '@/components/ui/tooltip';
+import { Superteams } from '@/constants/Superteam';
 import { cn } from '@/utils/cn';
 
 import { EarnAvatar } from '@/features/talent/components/EarnAvatar';
@@ -207,6 +209,11 @@ export const ApplicationList = ({
         const { bg: statusBg, color: statusColor } =
           colorMap[applicationStatus as GrantApplicationStatus];
         const { bg: labelBg, color: labelColor } = colorMap[applicationLabel];
+        const isSuperteamMember =
+          application?.user.superteamLevel?.includes('Superteam') || false;
+        const superteam = isSuperteamMember
+          ? Superteams.find((s) => s.name === application?.user.superteamLevel)
+          : undefined;
         return (
           <div
             key={application?.id}
@@ -238,9 +245,22 @@ export const ApplicationList = ({
                 <p className="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-slate-700">
                   {application?.projectTitle}
                 </p>
-                <p className="overflow-hidden text-xs font-medium text-ellipsis whitespace-nowrap text-slate-500">
-                  {`${application?.user?.firstName} ${application?.user?.lastName}`}
-                </p>
+                <span className="flex items-center gap-2">
+                  <p className="overflow-hidden text-xs font-medium text-ellipsis whitespace-nowrap text-slate-500">
+                    {`${application?.user?.firstName} ${application?.user?.lastName}`}
+                  </p>
+                  {superteam && (
+                    <Tooltip
+                      content={application?.user?.superteamLevel + ' Member'}
+                    >
+                      <img
+                        src={superteam.icons}
+                        alt="Superteam Member"
+                        className="size-3 rounded-full"
+                      />
+                    </Tooltip>
+                  )}
+                </span>
               </div>
             </div>
 
diff --git a/src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx b/src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
index 0c9ced7a7..e41fcbdbf 100644
--- a/src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
+++ b/src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
@@ -1,7 +1,7 @@
 import { useQuery } from '@tanstack/react-query';
 import { Check, InfoIcon, Wand2, XCircle } from 'lucide-react';
 import { usePostHog } from 'posthog-js/react';
-import { useCallback, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
 import { toast } from 'sonner';
 
 import { Button } from '@/components/ui/button';
@@ -62,6 +62,10 @@ export default function AiReviewModal({ applications, grant }: Props) {
     refetchOnWindowFocus: false,
   });
 
+  useEffect(() => {
+    refetchUnreviewedApplications();
+  }, [applications, refetchUnreviewedApplications]);
+
   const { mutateAsync: reviewApplication } = useReviewApplication(
     grant?.slug || '',
   );
diff --git a/src/features/sponsor-dashboard/components/PublishResults.tsx b/src/features/sponsor-dashboard/components/PublishResults.tsx
index 10864b2ca..3738baf33 100644
--- a/src/features/sponsor-dashboard/components/PublishResults.tsx
+++ b/src/features/sponsor-dashboard/components/PublishResults.tsx
@@ -1,7 +1,7 @@
 import { useAtomValue } from 'jotai';
 import { AlertTriangle, CheckCircle2 } from 'lucide-react';
 import { usePostHog } from 'posthog-js/react';
-import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
 
 import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
 import { Button } from '@/components/ui/button';
@@ -12,7 +12,6 @@ import {
   DialogHeader,
   DialogTitle,
 } from '@/components/ui/dialog';
-import { type SubmissionWithUser } from '@/interface/submission';
 import { api } from '@/lib/api';
 import { dayjs } from '@/utils/dayjs';
 import { cleanRewards } from '@/utils/rank';
@@ -30,11 +29,6 @@ interface Props {
   totalPaymentsMade: number;
   bounty: Listing | undefined;
   remainings: { podiums: number; bonus: number } | null;
-  submissions: SubmissionWithUser[];
-  usedPositions: number[];
-  setRemainings: Dispatch<
-    SetStateAction<{ podiums: number; bonus: number } | null>
-  >;
 }
 
 export function PublishResults({
@@ -44,9 +38,6 @@ export function PublishResults({
   totalPaymentsMade,
   bounty,
   remainings,
-  submissions,
-  usedPositions,
-  setRemainings,
 }: Props) {
   const [isPublishingResults, setIsPublishingResults] = useState(false);
   const [isWinnersAnnounced, setIsWinnersAnnounced] = useState(
@@ -93,12 +84,7 @@ export function PublishResults({
   }
 
   const selectedSubmission = useAtomValue(selectedSubmissionAtom);
-  const { mutateAsync: toggleWinner } = useToggleWinner(
-    bounty,
-    submissions,
-    setRemainings,
-    usedPositions,
-  );
+  const { mutateAsync: toggleWinner } = useToggleWinner(bounty);
 
   const publishResults = async () => {
     if (!bounty?.id) return;
@@ -106,11 +92,13 @@ export function PublishResults({
     try {
       if (isProject) {
         if (selectedSubmission?.id) {
-          await toggleWinner({
-            winnerPosition: 1,
-            id: selectedSubmission?.id,
-            isWinner: true,
-          });
+          await toggleWinner([
+            {
+              winnerPosition: 1,
+              id: selectedSubmission?.id,
+              isWinner: true,
+            },
+          ]);
         }
       }
       await api.post(`/api/listings/announce/${bounty?.id}/`);
@@ -119,11 +107,13 @@ export function PublishResults({
     } catch (e) {
       if (isProject) {
         if (selectedSubmission?.id) {
-          await toggleWinner({
-            winnerPosition: null,
-            id: selectedSubmission?.id,
-            isWinner: false,
-          });
+          await toggleWinner([
+            {
+              winnerPosition: null,
+              id: selectedSubmission?.id,
+              isWinner: false,
+            },
+          ]);
         }
       }
       setIsPublishingResults(false);
diff --git a/src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx b/src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx
new file mode 100644
index 000000000..657cfe6bb
--- /dev/null
+++ b/src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx
@@ -0,0 +1,84 @@
+import { Check } from 'lucide-react'; // Import Check icon
+import React, { useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog';
+import { Separator } from '@/components/ui/separator';
+
+interface BonusModalProps {
+  bonusIsOpen: boolean;
+  bonusOnClose: () => void;
+  submissionIds: string[];
+  onBonusSubmission: (submissionId: string[]) => void;
+  allSubmissionsLength: number;
+}
+
+export const MultiBonusModal = ({
+  bonusIsOpen,
+  bonusOnClose,
+  onBonusSubmission,
+  submissionIds,
+}: BonusModalProps) => {
+  const [loading, setLoading] = useState<boolean>(false);
+
+  const assignBonus = async () => {
+    setLoading(true);
+    try {
+      await onBonusSubmission(submissionIds);
+    } catch (e) {
+      console.error('Error assigning bonus:', e);
+    } finally {
+      setLoading(false);
+      bonusOnClose();
+    }
+  };
+
+  const isMultiple = submissionIds.length > 1;
+
+  return (
+    <Dialog open={bonusIsOpen} onOpenChange={bonusOnClose}>
+      <DialogContent>
+        <DialogHeader>
+          <DialogTitle className="text-md font-semibold text-slate-500">
+            {`Assign Bonus to Submission${isMultiple ? 's' : ''}?`}
+          </DialogTitle>
+        </DialogHeader>
+
+        <Separator />
+
+        <div className="text-[0.95rem] font-medium">
+          <p className="mt-3 text-slate-500">
+            {`You are about to assign bonus rank to ${submissionIds.length} submission${isMultiple ? 's' : ''}. Are you sure you want to proceed?`}
+          </p>
+
+          <br />
+
+          <Button
+            className="mb-3 w-full bg-emerald-600 text-white hover:bg-emerald-600/90"
+            disabled={loading}
+            onClick={assignBonus}
+          >
+            {loading ? (
+              <>
+                <span className="loading loading-spinner mr-2" />
+                <span>Assign Bonus</span>
+              </>
+            ) : (
+              <>
+                <div className="mr-2 rounded-full bg-white p-[3px]">
+                  <Check className="h-2 w-2 stroke-3 text-emerald-600" />
+                </div>
+                {`Assign Bonus${isMultiple ? 'es' : ''}`}
+              </>
+            )}
+          </Button>
+        </div>
+      </DialogContent>
+    </Dialog>
+  );
+};
diff --git a/src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx b/src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx
index f8cd4feff..63ae24ebb 100644
--- a/src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx
+++ b/src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx
@@ -1,6 +1,6 @@
 import { useAtom } from 'jotai';
 import { X } from 'lucide-react';
-import { type Dispatch, type SetStateAction, useMemo } from 'react';
+import { useMemo } from 'react';
 
 import { Button } from '@/components/ui/button';
 import {
@@ -11,7 +11,6 @@ import {
   SelectValue,
 } from '@/components/ui/select';
 import { useDisclosure } from '@/hooks/use-disclosure';
-import type { SubmissionWithUser } from '@/interface/submission';
 import { cn } from '@/utils/cn';
 import { cleanRewards, getRankLabels, sortRank } from '@/utils/rank';
 
@@ -25,21 +24,15 @@ import { RejectSubmissionModal } from './Modals/RejectModal';
 
 interface Props {
   bounty: Listing | undefined;
-  submissions: SubmissionWithUser[];
   usedPositions: number[];
   isHackathonPage?: boolean;
-  setRemainings: Dispatch<
-    SetStateAction<{ podiums: number; bonus: number } | null>
-  >;
   onWinnersAnnounceOpen: () => void;
   isMultiSelectOn: boolean;
 }
 
 export const SelectWinner = ({
   bounty,
-  submissions,
   usedPositions,
-  setRemainings,
   isHackathonPage,
   onWinnersAnnounceOpen,
   isMultiSelectOn,
@@ -58,12 +51,7 @@ export const SelectWinner = ({
     onClose: rejectedOnClose,
   } = useDisclosure();
 
-  const { mutateAsync: toggleWinner } = useToggleWinner(
-    bounty,
-    submissions,
-    setRemainings,
-    usedPositions,
-  );
+  const { mutateAsync: toggleWinner } = useToggleWinner(bounty);
 
   const rejectSubmissions = useRejectSubmissions(bounty?.slug || '');
 
@@ -74,11 +62,13 @@ export const SelectWinner = ({
 
   const selectWinner = async (position: number, id: string | undefined) => {
     if (!id) return;
-    toggleWinner({
-      id,
-      isWinner: !!position,
-      winnerPosition: position || null,
-    });
+    toggleWinner([
+      {
+        id,
+        isWinner: !!position,
+        winnerPosition: position || null,
+      },
+    ]);
   };
 
   const isValueSelected = useMemo(
@@ -118,7 +108,11 @@ export const SelectWinner = ({
         ) : (
           <div className="relative">
             <Select
-              disabled={!!bounty?.isWinnersAnnounced || isHackathonPage}
+              disabled={
+                !!bounty?.isWinnersAnnounced ||
+                isHackathonPage ||
+                isMultiSelectOn
+              }
               onValueChange={(value) =>
                 selectWinner(Number(value), selectedSubmission?.id)
               }
@@ -168,17 +162,19 @@ export const SelectWinner = ({
               </SelectContent>
             </Select>
             {isValueSelected && (
-              <div
-                className="absolute top-1/2 right-8 z-10 -translate-y-1/2 cursor-pointer"
+              <button
+                className="absolute top-1/2 right-8 z-10 -translate-y-1/2 cursor-pointer disabled:cursor-not-allowed"
                 onClick={(e) => {
                   e.stopPropagation();
+                  if (isMultiSelectOn) return;
                   selectWinner(0, selectedSubmission?.id);
                 }}
+                disabled={isMultiSelectOn}
               >
                 <div className="flex h-4 w-4 items-center justify-center rounded-full bg-red-100 text-red-400 hover:bg-red-200">
                   <X className="h-2.5 w-2.5" />
                 </div>
-              </div>
+              </button>
             )}
           </div>
         )}
diff --git a/src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx b/src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx
index 266494d1e..5ef027b6f 100644
--- a/src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx
+++ b/src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx
@@ -42,6 +42,7 @@ interface Props {
   isToggled?: (id: string) => boolean;
   toggleAllSubmissions?: () => void;
   isAllToggled?: boolean;
+  isMultiSelectDisabled: boolean;
 }
 
 export const SubmissionList = ({
@@ -55,6 +56,7 @@ export const SubmissionList = ({
   isToggled,
   toggleAllSubmissions,
   isAllToggled,
+  isMultiSelectDisabled,
 }: Props) => {
   const [selectedSubmission, setSelectedSubmission] = useAtom(
     selectedSubmissionAtom,
@@ -109,11 +111,11 @@ export const SubmissionList = ({
     <div className="h-full w-full rounded-l-xl border border-slate-200 bg-white">
       <div className="flex cursor-pointer flex-col items-center justify-between gap-4 border-b border-slate-200 px-4 py-3">
         <div className="flex w-full items-center justify-between gap-4 py-[3px]">
-          {listing?.type === 'project' && (
+          {!isMultiSelectDisabled && (
             <Checkbox
               className="data-[state=checked]:border-brand-purple data-[state=checked]:bg-brand-purple"
               checked={isAllToggled}
-              disabled={listing?.isWinnersAnnounced}
+              disabled={isMultiSelectDisabled}
               onCheckedChange={() =>
                 toggleAllSubmissions && toggleAllSubmissions()
               }
@@ -233,13 +235,14 @@ export const SubmissionList = ({
             }}
           >
             <div className="flex items-center">
-              {listing?.type === 'project' && (
+              {!isMultiSelectDisabled && (
                 <Checkbox
                   className="data-[state=checked]:border-brand-purple data-[state=checked]:bg-brand-purple mr-2 disabled:invisible"
                   checked={isToggled && isToggled(submission.id)}
                   disabled={
-                    listing?.isWinnersAnnounced ||
-                    submission?.status !== 'Pending'
+                    submission?.status !== 'Pending' ||
+                    !!submission?.winnerPosition ||
+                    isMultiSelectDisabled
                   }
                   onCheckedChange={() =>
                     toggleSubmission && toggleSubmission(submission.id)
diff --git a/src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx b/src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
index 39e33513d..a77e39460 100644
--- a/src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
+++ b/src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
@@ -2,7 +2,7 @@ import { TooltipArrow } from '@radix-ui/react-tooltip';
 import { useAtom } from 'jotai';
 import { AlertTriangle, ArrowRight, ExternalLink } from 'lucide-react';
 import Link from 'next/link';
-import React, { type Dispatch, type SetStateAction } from 'react';
+import React from 'react';
 import { MdOutlineAccountBalanceWallet, MdOutlineMail } from 'react-icons/md';
 import { toast } from 'sonner';
 
@@ -37,9 +37,6 @@ interface Props {
   isHackathonPage?: boolean;
   onWinnersAnnounceOpen: () => void;
   remainings: { podiums: number; bonus: number } | null;
-  setRemainings: Dispatch<
-    SetStateAction<{ podiums: number; bonus: number } | null>
-  >;
   isMultiSelectOn?: boolean;
 }
 
@@ -50,7 +47,6 @@ export const SubmissionPanel = ({
   isHackathonPage,
   onWinnersAnnounceOpen,
   remainings,
-  setRemainings,
   isMultiSelectOn,
 }: Props) => {
   const afterAnnounceDate =
@@ -145,9 +141,11 @@ export const SubmissionPanel = ({
                       </Button>
                     </Tooltip>
                   ))}
-                {!isHackathonPage && (
-                  <SelectLabel listingSlug={bounty?.slug!} />
-                )}
+                {!isHackathonPage &&
+                  selectedSubmission?.status === 'Pending' &&
+                  !bounty?.isWinnersAnnounced && (
+                    <SelectLabel listingSlug={bounty?.slug!} />
+                  )}
                 {selectedSubmission?.isWinner &&
                   selectedSubmission?.winnerPosition &&
                   selectedSubmission?.isPaid && (
@@ -173,8 +171,6 @@ export const SubmissionPanel = ({
                       isMultiSelectOn={!!isMultiSelectOn}
                       bounty={bounty}
                       usedPositions={usedPositions}
-                      setRemainings={setRemainings}
-                      submissions={submissions}
                       isHackathonPage={isHackathonPage}
                     />
                     {!isProject && (
diff --git a/src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts b/src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts
index 1b37c5db0..0f8b5c436 100644
--- a/src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts
+++ b/src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts
@@ -15,7 +15,7 @@ export const useRejectSubmissions = (slug: string) => {
 
   return useMutation({
     mutationFn: async (submissionIds: string[]) => {
-      const batchSize = 10;
+      const batchSize = 50;
       for (let i = 0; i < submissionIds.length; i += batchSize) {
         const batch = submissionIds.slice(i, i + batchSize);
         await api.post(`/api/sponsor-dashboard/submission/reject`, {
diff --git a/src/features/sponsor-dashboard/mutations/useToggleWinner.ts b/src/features/sponsor-dashboard/mutations/useToggleWinner.ts
index c4bcf43c2..366d043fe 100644
--- a/src/features/sponsor-dashboard/mutations/useToggleWinner.ts
+++ b/src/features/sponsor-dashboard/mutations/useToggleWinner.ts
@@ -1,136 +1,97 @@
 import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useAtom, useSetAtom } from 'jotai';
+import { useAtom } from 'jotai';
 import { toast } from 'sonner';
 
 import { type SubmissionWithUser } from '@/interface/submission';
 import { api } from '@/lib/api';
 
-import { BONUS_REWARD_POSITION } from '@/features/listing-builder/constants';
 import { type Listing, type Rewards } from '@/features/listings/types';
 
-import { isStateUpdatingAtom, selectedSubmissionAtom } from '../atoms';
+import { selectedSubmissionAtom } from '../atoms';
 
-export const useToggleWinner = (
-  bounty: Listing | undefined,
-  submissions: SubmissionWithUser[],
-  setRemainings: React.Dispatch<
-    React.SetStateAction<{ podiums: number; bonus: number } | null>
-  >,
-  usedPositions: number[],
-) => {
-  const queryClient = useQueryClient();
-  const [, setSelectedSubmission] = useAtom(selectedSubmissionAtom);
+interface SubmissionUpdatePayload {
+  id: string;
+  isWinner: boolean;
+  winnerPosition: number | null;
+}
+
+const BATCH_UPDATE_LIMIT = 50;
 
-  const setPositionUpdating = useSetAtom(isStateUpdatingAtom);
+export const useToggleWinner = (bounty: Listing | undefined) => {
+  const queryClient = useQueryClient();
+  const [selectedSubmission, setSelectedSubmission] = useAtom(
+    selectedSubmissionAtom,
+  );
 
   return useMutation({
-    mutationFn: async ({
-      id,
-      isWinner,
-      winnerPosition,
-    }: {
-      id: string;
-      isWinner: boolean;
-      winnerPosition: number | null;
-    }) => {
-      const response = await api.post(
-        `/api/sponsor-dashboard/submission/toggle-winner/`,
-        {
-          id,
-          isWinner,
-          winnerPosition,
-        },
+    mutationFn: async (allUpdates: SubmissionUpdatePayload[]) => {
+      const updateBatches: SubmissionUpdatePayload[][] = [];
+      for (let i = 0; i < allUpdates.length; i += BATCH_UPDATE_LIMIT) {
+        updateBatches.push(allUpdates.slice(i, i + BATCH_UPDATE_LIMIT));
+      }
+
+      const batchPromises = updateBatches.map((batch) =>
+        api.post(`/api/sponsor-dashboard/submission/toggle-winner/`, {
+          submissions: batch,
+        }),
       );
-      if (!response.data) throw new Error('Failed to toggle winner');
-      return response.data;
+
+      await Promise.all(batchPromises);
+      return { success: true };
     },
-    onSuccess: (_, variables) => {
-      setPositionUpdating(false);
-      queryClient.setQueryData<SubmissionWithUser[]>(
-        ['sponsor-submissions', bounty?.slug],
-        (old) =>
-          old?.map((submission) =>
-            submission.id === variables.id
-              ? {
-                  ...submission,
-                  isWinner: variables.isWinner,
-                  winnerPosition: variables.winnerPosition ?? undefined,
-                }
-              : submission,
-          ),
+    onError: (error: any) => {
+      queryClient.invalidateQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
+      toast.error(
+        'An error occurred while assigning bonus spots. Please try again.',
       );
+      console.error('Failed to toggle winners:', error);
+    },
+    onMutate: async (variables) => {
+      await queryClient.cancelQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
 
-      const submissionIndex = submissions.findIndex(
-        (s) => s.id === variables.id,
-      );
-      if (submissionIndex >= 0) {
-        const oldPosition: number =
-          Number(submissions[submissionIndex]?.winnerPosition) || 0;
-        const newPosition = variables.winnerPosition || 0;
+      const previousSubmissions = queryClient.getQueryData<
+        SubmissionWithUser[]
+      >(['sponsor-submissions', bounty?.slug]);
 
-        let newUsedPositions = [...usedPositions];
-        if (oldPosition && oldPosition !== newPosition) {
-          newUsedPositions = newUsedPositions.filter(
-            (pos) => pos !== oldPosition,
+      queryClient.setQueryData<SubmissionWithUser[]>(
+        ['sponsor-submissions', bounty?.slug],
+        (oldData) => {
+          if (!oldData) return oldData;
+          const updatesMap = new Map(
+            variables.map((update) => [update.id, update]),
           );
-        }
-
-        if (newPosition) {
-          if (
-            newPosition === BONUS_REWARD_POSITION &&
-            newUsedPositions.filter((n) => n === BONUS_REWARD_POSITION).length <
-              (bounty?.maxBonusSpots ?? 0)
-          ) {
-            newUsedPositions.push(newPosition);
-          } else if (!newUsedPositions.includes(newPosition)) {
-            newUsedPositions.push(newPosition);
-          }
-        }
-
-        const updatedSubmission: SubmissionWithUser = {
-          ...submissions[submissionIndex],
-          isWinner: variables.isWinner,
-          winnerPosition: variables.winnerPosition as keyof Rewards | undefined,
-        } as SubmissionWithUser;
-
-        setSelectedSubmission(updatedSubmission);
-
-        setRemainings((prevRemainings) => {
-          if (!prevRemainings) return prevRemainings;
-
-          const newRemainings = { ...prevRemainings };
-          const isNewPositionValid = !isNaN(newPosition) && newPosition !== 0;
-          const isOldPositionValid = !isNaN(oldPosition) && oldPosition !== 0;
-
-          if (isNewPositionValid) {
-            if (newPosition === BONUS_REWARD_POSITION) {
-              newRemainings.bonus--;
-            } else {
-              newRemainings.podiums--;
-            }
-          }
-
-          if (isOldPositionValid) {
-            if (oldPosition === BONUS_REWARD_POSITION) {
-              newRemainings.bonus++;
-            } else {
-              newRemainings.podiums++;
+          return oldData.map((submission) => {
+            const update = updatesMap.get(submission.id);
+            if (update) {
+              if (selectedSubmission && update.id === selectedSubmission.id) {
+                setSelectedSubmission({
+                  ...submission,
+                  isWinner: update.isWinner ?? submission.isWinner,
+                  winnerPosition:
+                    (update.winnerPosition as
+                      | keyof Rewards
+                      | undefined
+                      | null) ?? undefined,
+                });
+              }
+              return {
+                ...submission,
+                isWinner: update.isWinner ?? submission.isWinner,
+                winnerPosition:
+                  (update.winnerPosition as keyof Rewards | undefined | null) ??
+                  undefined,
+              };
             }
-          }
+            return submission;
+          });
+        },
+      );
 
-          return newRemainings;
-        });
-      }
-    },
-    onError: (error) => {
-      console.error('Failed to toggle winner:', error);
-      toast.error('Failed to toggle winner, please try again later');
-    },
-    onMutate: () => {
-      setPositionUpdating(true);
-    },
-    onSettled: () => {
-      setPositionUpdating(false);
+      return { previousSubmissions };
     },
   });
 };
diff --git a/src/pages/api/listings/draft.ts b/src/pages/api/listings/draft.ts
index f1bc7c117..2b61ea7a4 100644
--- a/src/pages/api/listings/draft.ts
+++ b/src/pages/api/listings/draft.ts
@@ -134,10 +134,16 @@ async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
       skills: cleanedSkills as Prisma.InputJsonValue,
       language,
       sponsorId: userSponsorId,
-      pocId: userId,
       isFndnPaying,
       hackathonId,
       referredBy,
+      ...(!listing
+        ? {
+            pocId: userId,
+          }
+        : {
+            pocId: listing.pocId,
+          }),
     };
 
     const result = id
diff --git a/src/pages/api/listings/publish/[id].ts b/src/pages/api/listings/publish/[id].ts
index 8723a4648..e12eef4eb 100644
--- a/src/pages/api/listings/publish/[id].ts
+++ b/src/pages/api/listings/publish/[id].ts
@@ -349,11 +349,10 @@ async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
       rawIsFndnPaying,
     });
 
-    const data: Prisma.BountiesUncheckedCreateInput = {
+    const data: Prisma.BountiesUncheckedUpdateInput = {
       sponsorId: userSponsorId,
       title,
       usdValue,
-      pocId: userId,
       skills: correctedSkills,
       slug,
       deadline: new Date(deadline),
diff --git a/src/pages/api/listings/templates/index.ts b/src/pages/api/listings/templates/index.ts
index d49ff6c9d..0e076b260 100644
--- a/src/pages/api/listings/templates/index.ts
+++ b/src/pages/api/listings/templates/index.ts
@@ -86,6 +86,12 @@ export default async function bounties(
               },
             },
           },
+          where: {
+            isPublished: true,
+            status: 'OPEN',
+            isPrivate: false,
+            isActive: true,
+          },
         },
       },
     });
diff --git a/src/pages/api/sponsor-dashboard/submission/reject.ts b/src/pages/api/sponsor-dashboard/submission/reject.ts
index 7ff8740f2..11bf867b9 100644
--- a/src/pages/api/sponsor-dashboard/submission/reject.ts
+++ b/src/pages/api/sponsor-dashboard/submission/reject.ts
@@ -8,7 +8,7 @@ import { checkListingSponsorAuth } from '@/features/auth/utils/checkListingSpons
 import { withSponsorAuth } from '@/features/auth/utils/withSponsorAuth';
 import { queueEmail } from '@/features/emails/utils/queueEmail';
 
-const MAX_RECORDS = 10;
+const MAX_RECORDS = 50;
 
 async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
   logger.debug(`Request body: ${JSON.stringify(req.body)}`);
@@ -28,10 +28,10 @@ async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
     return res.status(400).json({ error: 'Data asked to update is empty' });
   }
   if (data.length > MAX_RECORDS) {
-    logger.warn('Only max 10 records allowed in data');
+    logger.warn('Only max 50 records allowed in data');
     return res
       .status(400)
-      .json({ error: 'Only max 10 records allowed in data' });
+      .json({ error: 'Only max 50 records allowed in data' });
   }
 
   try {
@@ -62,10 +62,10 @@ async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
         (application) => application.listingId === listingId,
       )
     ) {
-      logger.warn('All records should have same and valid grant ID');
+      logger.warn('All records should have same and valid listing ID');
       return res
         .status(404)
-        .json({ error: 'All records should have same and valid grant ID' });
+        .json({ error: 'All records should have same and valid listing ID' });
     }
 
     currentSubmissions.forEach(async (currentApplicant) => {
diff --git a/src/pages/api/sponsor-dashboard/submission/toggle-winner.ts b/src/pages/api/sponsor-dashboard/submission/toggle-winner.ts
index 7822485f8..0708bcace 100644
--- a/src/pages/api/sponsor-dashboard/submission/toggle-winner.ts
+++ b/src/pages/api/sponsor-dashboard/submission/toggle-winner.ts
@@ -6,97 +6,315 @@ import { prisma } from '@/prisma';
 import { type NextApiRequestWithSponsor } from '@/features/auth/types';
 import { checkListingSponsorAuth } from '@/features/auth/utils/checkListingSponsorAuth';
 import { withSponsorAuth } from '@/features/auth/utils/withSponsorAuth';
+import { BONUS_REWARD_POSITION } from '@/features/listing-builder/constants';
 import { fetchHistoricalTokenUSDValue } from '@/features/wallet/utils/fetchHistoricalTokenUSDValue';
 
+interface SubmissionUpdate {
+  id: string;
+  isWinner?: boolean;
+  winnerPosition?: number;
+}
+
+const MAX_SUBMISSIONS_UPDATE_LIMIT = 50;
+
 async function handler(req: NextApiRequestWithSponsor, res: NextApiResponse) {
   const userId = req.userId;
+  const userSponsorId = req.userSponsorId;
 
   logger.debug(`Request body: ${JSON.stringify(req.body)}`);
-  const { id, isWinner, winnerPosition } = req.body;
+  const { submissions: requestSubmissions } = req.body as {
+    submissions: SubmissionUpdate[];
+  };
+
+  if (!Array.isArray(requestSubmissions)) {
+    logger.warn(
+      `User ${userId} sent invalid request body: submissions is not an array.`,
+    );
+    return res
+      .status(400)
+      .json({ error: 'Request body must contain an array of submissions.' });
+  }
+
+  if (requestSubmissions.length === 0) {
+    logger.warn(`User ${userId} sent empty submissions array.`);
+    return res
+      .status(400)
+      .json({ error: 'Submissions array cannot be empty.' });
+  }
 
-  if (
-    (isWinner !== undefined && winnerPosition === undefined) ||
-    (isWinner === undefined && winnerPosition !== undefined)
-  ) {
+  if (requestSubmissions.length > MAX_SUBMISSIONS_UPDATE_LIMIT) {
+    logger.warn(
+      `User ${userId} exceeded submission update limit. Count: ${requestSubmissions.length}, Limit: ${MAX_SUBMISSIONS_UPDATE_LIMIT}`,
+    );
     return res.status(400).json({
-      error:
-        'isWinner and winnerPosition must be provided together if either is present',
+      error: `Cannot update more than ${MAX_SUBMISSIONS_UPDATE_LIMIT} submissions at once.`,
     });
   }
 
+  const submissionIdSet = new Set<string>();
+  for (const submissionUpdate of requestSubmissions) {
+    const { id, isWinner, winnerPosition } = submissionUpdate;
+    if (!id) {
+      logger.warn(
+        `User ${userId} sent submission update without an ID.`,
+        submissionUpdate,
+      );
+      return res
+        .status(400)
+        .json({ error: 'Each submission update must include an id.' });
+    }
+    if (submissionIdSet.has(id)) {
+      logger.warn(
+        `User ${userId} sent duplicate submission ID: ${id}.`,
+        requestSubmissions,
+      );
+      return res
+        .status(400)
+        .json({ error: `Duplicate submission ID found: ${id}` });
+    }
+    submissionIdSet.add(id);
+
+    if (
+      (isWinner !== undefined && winnerPosition === undefined) ||
+      (isWinner === undefined && winnerPosition !== undefined)
+    ) {
+      logger.warn(
+        `User ${userId} sent inconsistent isWinner/winnerPosition for submission ${id}.`,
+        submissionUpdate,
+      );
+      return res.status(400).json({
+        error: `For submission ${id}, isWinner and winnerPosition must be provided together if either is present`,
+      });
+    }
+    if (isWinner === false && winnerPosition !== undefined) {
+      logger.warn(
+        `Submission ${id} has winnerPosition but isWinner is false. Position will be ignored/nulled.`,
+      );
+    }
+  }
+
+  const submissionIds = Array.from(submissionIdSet);
+
   try {
-    const currentSubmission = await prisma.submission.findUnique({
-      where: { id },
+    const currentSubmissions = await prisma.submission.findMany({
+      where: {
+        id: {
+          in: submissionIds,
+        },
+      },
+      include: {
+        listing: true,
+      },
     });
 
-    if (!currentSubmission) {
-      logger.warn(`Submission with ID ${id} not found`);
+    if (currentSubmissions.length !== requestSubmissions.length) {
+      const foundIds = currentSubmissions.map((c) => c.id);
+      const missingIds = submissionIds.filter((id) => !foundIds.includes(id));
+      logger.warn(
+        `User ${userId} requested update for non-existent submissions. Provided: ${submissionIds.length}, Found: ${foundIds.length}. Missing IDs: ${missingIds.join(', ')}`,
+      );
       return res.status(404).json({
-        message: `Submission with ID ${id} not found.`,
+        error: `Some submission records were not found. Missing IDs: ${missingIds.join(', ')}`,
       });
     }
 
-    const userSponsorId = req.userSponsorId;
+    const firstListing = currentSubmissions[0]?.listing;
+    if (!firstListing) {
+      logger.error(
+        `Could not determine listing from fetched submissions for user ${userId}. Submission IDs: ${submissionIds.join(', ')}`,
+      );
+      return res
+        .status(500)
+        .json({ error: 'Internal server error: Could not verify listing.' });
+    }
+    const listingId = firstListing.id;
+
+    if (
+      !currentSubmissions.every(
+        (submission) => submission.listingId === listingId,
+      )
+    ) {
+      logger.warn(
+        `User ${userId} attempted to update submissions from multiple listings in one request. Listing ID determined: ${listingId}.`,
+        submissionIds,
+      );
+      return res
+        .status(400)
+        .json({ error: 'All submissions must belong to the same listing.' });
+    }
 
-    const { listing, error } = await checkListingSponsorAuth(
+    const { error: authError } = await checkListingSponsorAuth(
       userSponsorId,
-      currentSubmission.listingId,
+      listingId,
     );
-    if (error) {
-      return res.status(error.status).json({ error: error.message });
+    if (authError) {
+      logger.warn(
+        `User ${userId} failed authorization check for listing ${listingId}. Status: ${authError.status}, Message: ${authError.message}`,
+      );
+      return res.status(authError.status).json({ error: authError.message });
     }
 
-    if (listing.isWinnersAnnounced) {
+    if (firstListing.isWinnersAnnounced) {
+      logger.warn(
+        `User ${userId} attempted to toggle winners for listing ${listingId} after winners were announced.`,
+      );
       return res.status(400).json({
-        error: 'Submissions cant be toggled post winner announcement',
+        error: 'Submissions cannot be toggled post winner announcement.',
       });
     }
 
-    logger.debug(`Updating submission with ID: ${id}`);
-    const result = await prisma.submission.update({
-      where: { id },
-      data: {
-        isWinner,
-        winnerPosition: winnerPosition ? winnerPosition : null,
+    if (firstListing.type === 'project') {
+      const projectWinnersInRequest = requestSubmissions.filter(
+        (s) => s.isWinner === true,
+      );
+      if (projectWinnersInRequest.length > 1) {
+        logger.warn(
+          `User ${userId} attempted to set multiple winners for project listing ${listingId}. Count: ${projectWinnersInRequest.length}`,
+        );
+        return res.status(400).json({
+          error: 'For project listings, only one submission can be the winner.',
+        });
+      }
+      if (
+        projectWinnersInRequest.length === 1 &&
+        projectWinnersInRequest[0]?.winnerPosition !== 1
+      ) {
+        logger.warn(
+          `User ${userId} attempted to set winner position other than 1 for project listing ${listingId}. Position: ${projectWinnersInRequest[0]?.winnerPosition}`,
+        );
+        return res.status(400).json({
+          error: 'For project listings, the winner position must be 1.',
+        });
+      }
+    }
+
+    const otherExistingWinners = await prisma.submission.findMany({
+      where: {
+        listingId: listingId,
+        isWinner: true,
+        winnerPosition: { not: null },
+        id: { notIn: submissionIds },
       },
-      include: { listing: true },
+      select: { id: true, winnerPosition: true },
     });
+    const otherExistingWinnerPositions = new Set(
+      otherExistingWinners
+        .map((w) => w.winnerPosition)
+        .filter((p): p is number => p !== null),
+    );
+    const otherExistingBonusWinnerCount = otherExistingWinners.filter(
+      (w) => w.winnerPosition === BONUS_REWARD_POSITION,
+    ).length;
+
+    const incomingWinnerPositions = new Map<number, number>();
+    let incomingBonusWinnerCount = 0;
+
+    for (const subUpdate of requestSubmissions) {
+      if (subUpdate.isWinner && subUpdate.winnerPosition !== undefined) {
+        const pos = subUpdate.winnerPosition;
+
+        if (pos !== BONUS_REWARD_POSITION) {
+          if (otherExistingWinnerPositions.has(pos)) {
+            logger.warn(
+              `User ${userId} attempted to assign duplicate winner position ${pos} for listing ${listingId}. Position already held by submission outside this batch.`,
+            );
+            return res.status(400).json({
+              error: `Winner position ${pos} is already assigned to another submission not in this update batch.`,
+            });
+          }
+          const currentCount = incomingWinnerPositions.get(pos) || 0;
+          if (currentCount > 0) {
+            logger.warn(
+              `User ${userId} attempted to assign duplicate winner position ${pos} within the same request for listing ${listingId}.`,
+            );
+            return res.status(400).json({
+              error: `Winner position ${pos} is assigned multiple times within this request.`,
+            });
+          }
+          incomingWinnerPositions.set(pos, currentCount + 1);
+        } else {
+          incomingBonusWinnerCount++;
+        }
+      }
+    }
+
+    const totalBonusWinners =
+      otherExistingBonusWinnerCount + incomingBonusWinnerCount;
+    if (
+      firstListing.maxBonusSpots !== null &&
+      totalBonusWinners > firstListing.maxBonusSpots
+    ) {
+      logger.warn(
+        `User ${userId} attempted to exceed max bonus spots (${firstListing.maxBonusSpots}) for listing ${listingId}. Proposed total: ${totalBonusWinners}`,
+      );
+      return res.status(400).json({
+        error: `Assigning these winners would exceed the maximum allowed bonus spots (${firstListing.maxBonusSpots}). Current count (including this batch): ${totalBonusWinners}`,
+      });
+    }
 
-    const ask = result.ask || 0;
+    const currentSubmissionMap = new Map(
+      currentSubmissions.map((sub) => [sub.id, sub]),
+    );
 
-    if (currentSubmission.isWinner !== isWinner) {
-      const bountyId = result.listingId;
+    const updatePromises = requestSubmissions.map(async (submissionUpdate) => {
+      const { id, isWinner, winnerPosition } = submissionUpdate;
+      const currentSubmission = currentSubmissionMap.get(id)!;
 
-      logger.debug(`Updating bounty total winners for listing ID: ${bountyId}`);
+      const finalWinnerPosition = isWinner ? winnerPosition : null;
 
-      const listing = result.listing;
+      logger.debug(`Updating submission with ID: ${id}`);
+      const result = await prisma.submission.update({
+        where: { id },
+        data: {
+          isWinner,
+          winnerPosition: finalWinnerPosition,
+        },
+      });
 
-      if (listing.compensationType !== 'fixed') {
-        logger.debug('Fetching token USD value for variable compensation');
-        const tokenUSDValue = await fetchHistoricalTokenUSDValue(
-          listing.token!,
-          listing.publishedAt!,
+      const ask = result.ask || 0;
+
+      if (currentSubmission.isWinner !== isWinner) {
+        logger.debug(
+          `Winner status changed for submission ${id}. Updating bounty details for listing ID: ${listingId}`,
         );
-        const usdValue = tokenUSDValue * ask;
-
-        await prisma.bounties.update({
-          where: { id: bountyId },
-          data: {
-            rewards: { 1: ask },
-            rewardAmount: ask,
-            usdValue,
-          },
-        });
+
+        if (firstListing.compensationType !== 'fixed') {
+          logger.debug(
+            `Fetching token USD value for variable compensation for submission ${id}`,
+          );
+          const tokenUSDValue = await fetchHistoricalTokenUSDValue(
+            firstListing.token!,
+            firstListing.publishedAt!,
+          );
+          const usdValue = tokenUSDValue * ask;
+
+          await prisma.bounties.update({
+            where: { id: listingId },
+            data: {
+              rewards: { 1: ask },
+              rewardAmount: ask,
+              usdValue,
+            },
+          });
+        }
       }
-    }
+      return result;
+    });
 
-    logger.info(`Successfully updated submission with ID: ${id}`);
+    await Promise.all(updatePromises);
+
+    logger.info(
+      `Successfully updated ${requestSubmissions.length} submissions for listing ${listingId} by user ${userId}`,
+    );
     return res.status(200).json({ message: 'Success' });
   } catch (error: any) {
-    logger.error(`User ${userId} unable to toggle winners: ${error.message}`);
+    logger.error(
+      `User ${userId} unable to toggle winners for batch: ${error.message}`,
+      { error, submissionIds },
+    );
     return res.status(400).json({
       error: error.message,
-      message: `Error occurred while updating submission ${id}.`,
+      message: `Error occurred while updating submissions.`,
     });
   }
 }
diff --git a/src/pages/dashboard/hackathon/[listing]/submissions.tsx b/src/pages/dashboard/hackathon/[listing]/submissions.tsx
index 7f041295e..c9436b64a 100644
--- a/src/pages/dashboard/hackathon/[listing]/submissions.tsx
+++ b/src/pages/dashboard/hackathon/[listing]/submissions.tsx
@@ -173,9 +173,6 @@ export default function BountySubmissions({ listing }: Props) {
               totalWinners={totalWinners || 0}
               totalPaymentsMade={totalPaymentsMade || 0}
               bounty={bounty}
-              usedPositions={usedPositions || []}
-              setRemainings={setRemainings}
-              submissions={submissions || []}
             />
           )}
           <SubmissionHeader
@@ -230,6 +227,7 @@ export default function BountySubmissions({ listing }: Props) {
                       submissions={paginatedSubmissions}
                       setSearchText={setSearchText}
                       type={bounty?.type}
+                      isMultiSelectDisabled
                     />
                   </div>
 
@@ -255,7 +253,6 @@ export default function BountySubmissions({ listing }: Props) {
                     ) : (
                       <SubmissionPanel
                         remainings={remainings}
-                        setRemainings={setRemainings}
                         bounty={bounty}
                         submissions={paginatedSubmissions}
                         usedPositions={usedPositions || []}
diff --git a/src/pages/dashboard/listings/[slug]/submissions.tsx b/src/pages/dashboard/listings/[slug]/submissions.tsx
index 38ccf3ce4..2ea04c6e7 100644
--- a/src/pages/dashboard/listings/[slug]/submissions.tsx
+++ b/src/pages/dashboard/listings/[slug]/submissions.tsx
@@ -1,7 +1,7 @@
 import { type SubmissionLabels } from '@prisma/client';
 import { useQuery, useQueryClient } from '@tanstack/react-query';
 import { useAtom } from 'jotai';
-import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { Check, ChevronLeft, ChevronRight } from 'lucide-react';
 import type { GetServerSideProps } from 'next';
 import { useSearchParams } from 'next/navigation';
 import { useRouter } from 'next/router';
@@ -28,11 +28,13 @@ import {
 } from '@/features/sponsor-dashboard/atoms';
 import { PublishResults } from '@/features/sponsor-dashboard/components/PublishResults';
 import { ScoutTable } from '@/features/sponsor-dashboard/components/Scouts/ScoutTable';
+import { MultiBonusModal } from '@/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal';
 import { RejectAllSubmissionModal } from '@/features/sponsor-dashboard/components/Submissions/Modals/RejectAllModal';
 import { SubmissionHeader } from '@/features/sponsor-dashboard/components/Submissions/SubmissionHeader';
 import { SubmissionList } from '@/features/sponsor-dashboard/components/Submissions/SubmissionList';
 import { SubmissionPanel } from '@/features/sponsor-dashboard/components/Submissions/SubmissionPanel';
 import { useRejectSubmissions } from '@/features/sponsor-dashboard/mutations/useRejectSubmissions';
+import { useToggleWinner } from '@/features/sponsor-dashboard/mutations/useToggleWinner';
 import { sponsorDashboardListingQuery } from '@/features/sponsor-dashboard/queries/listing';
 import { scoutsQuery } from '@/features/sponsor-dashboard/queries/scouts';
 import { submissionsQuery } from '@/features/sponsor-dashboard/queries/submissions';
@@ -72,6 +74,18 @@ export default function BountySubmissions({ slug }: Props) {
     selectedSubmissionIdsAtom,
   );
 
+  useEffect(() => {
+    const handleRouteChange = () => {
+      setSelectedSubmissionIds(new Set());
+    };
+
+    router.events.on('routeChangeStart', handleRouteChange);
+
+    return () => {
+      router.events.off('routeChangeStart', handleRouteChange);
+    };
+  }, [router.events, setSelectedSubmissionIds]);
+
   const [isToggledAll, setIsToggledAll] = useState(false);
   const {
     isOpen: isTogglerOpen,
@@ -79,9 +93,9 @@ export default function BountySubmissions({ slug }: Props) {
     onClose: onTogglerClose,
   } = useDisclosure();
   const {
-    isOpen: rejectedIsOpen,
-    onOpen: rejectedOnOpen,
-    onClose: rejectedOnClose,
+    isOpen: multiActionConfrimIsOpen,
+    onOpen: multiActionConfirmOnOpen,
+    onClose: multiActionConfirmOnClose,
   } = useDisclosure();
 
   const { data: submissions, isLoading: isSubmissionsLoading } = useQuery(
@@ -92,6 +106,8 @@ export default function BountySubmissions({ slug }: Props) {
     sponsorDashboardListingQuery(slug),
   );
 
+  const isProject = useMemo(() => bounty?.type === 'project', [bounty]);
+
   useEffect(() => {
     selectedSubmissionIds.size > 0 ? onTogglerOpen() : onTogglerClose();
   }, [selectedSubmissionIds]);
@@ -102,7 +118,11 @@ export default function BountySubmissions({ slug }: Props) {
       const submissionWithId = submissions?.find(
         (submission) => submission.id === a,
       );
-      if (submissionWithId && submissionWithId.status !== 'Pending') {
+      if (
+        submissionWithId &&
+        (submissionWithId.status !== 'Pending' ||
+          submissionWithId.winnerPosition)
+      ) {
         newSet.delete(a);
       }
     });
@@ -111,7 +131,10 @@ export default function BountySubmissions({ slug }: Props) {
 
   const isAllCurrentToggled = () =>
     paginatedSubmissions
-      ?.filter((submission) => submission.status === 'Pending')
+      ?.filter(
+        (submission) =>
+          submission.status === 'Pending' && !submission.winnerPosition,
+      )
       .every((submission) => selectedSubmissionIds.has(submission.id)) || false;
 
   const toggleSubmission = (id: string) => {
@@ -135,11 +158,55 @@ export default function BountySubmissions({ slug }: Props) {
 
   const rejectSubmissions = useRejectSubmissions(slug);
 
+  const { mutateAsync: toggleWinner } = useToggleWinner(bounty);
+
   const handleRejectSubmission = (submissionIds: string[]) => {
     rejectSubmissions.mutate(submissionIds);
-    rejectedOnClose();
+    multiActionConfirmOnClose();
   };
 
+  const handleAssignBonus = useCallback(
+    async (submissionIds: string[]) => {
+      try {
+        const bonusSubmissions = submissionIds.map((s) => ({
+          id: s,
+          isWinner: true,
+          winnerPosition: BONUS_REWARD_POSITION,
+        }));
+
+        await toggleWinner(bonusSubmissions);
+        multiActionConfirmOnClose();
+        setSelectedSubmissionIds(new Set());
+      } catch (error) {
+        console.error('Error assigning bonus:', error);
+      }
+    },
+    [toggleWinner, multiActionConfirmOnClose, setSelectedSubmissionIds],
+  );
+
+  const isMultiSelectDisabled = useMemo(() => {
+    if (bounty?.isWinnersAnnounced) return true;
+    if (bounty?.type === 'project') return false;
+    else {
+      if (!bounty?.rewards?.[BONUS_REWARD_POSITION] || !bounty?.maxBonusSpots)
+        return true;
+    }
+    if (remainings?.bonus === 0) return true;
+    return false;
+  }, [bounty, selectedSubmissionIds, remainings]);
+
+  const multiBonusPodiumsOverSelected = useMemo(
+    () =>
+      Boolean(
+        bounty?.type !== 'project' &&
+          bounty?.rewards?.[BONUS_REWARD_POSITION] &&
+          bounty?.maxBonusSpots &&
+          remainings &&
+          selectedSubmissionIds.size > remainings.bonus,
+      ),
+    [bounty, selectedSubmissionIds, remainings],
+  );
+
   const { data: scouts } = useQuery({
     ...scoutsQuery({
       bountyId: bounty?.id!,
@@ -251,7 +318,10 @@ export default function BountySubmissions({ slug }: Props) {
       setSelectedSubmissionIds((prev) => {
         const newSet = new Set(prev);
         paginatedSubmissions
-          ?.filter((submission) => submission.status === 'Pending')
+          ?.filter(
+            (submission) =>
+              submission.status === 'Pending' && !submission.winnerPosition,
+          )
           .map((submission) => newSet.add(submission.id));
         return newSet;
       });
@@ -414,9 +484,6 @@ export default function BountySubmissions({ slug }: Props) {
               totalWinners={totalWinners || 0}
               totalPaymentsMade={totalPaymentsMade || 0}
               bounty={bounty}
-              usedPositions={usedPositions || []}
-              setRemainings={setRemainings}
-              submissions={submissions || []}
             />
           )}
           <SubmissionHeader
@@ -466,6 +533,7 @@ export default function BountySubmissions({ slug }: Props) {
                       toggleSubmission={toggleSubmission}
                       isAllToggled={isToggledAll}
                       toggleAllSubmissions={toggleAllSubmissions}
+                      isMultiSelectDisabled={isMultiSelectDisabled}
                     />
                   </div>
 
@@ -492,7 +560,6 @@ export default function BountySubmissions({ slug }: Props) {
                       <SubmissionPanel
                         isMultiSelectOn={selectedSubmissionIds.size > 0}
                         remainings={remainings}
-                        setRemainings={setRemainings}
                         bounty={bounty}
                         submissions={paginatedSubmissions}
                         usedPositions={usedPositions || []}
@@ -590,9 +657,14 @@ export default function BountySubmissions({ slug }: Props) {
               }}
               unsetDefaultPosition
               hideCloseIcon
-              className="fixed bottom-4 left-1/2 w-fit -translate-x-1/2 overflow-hidden px-1 py-1"
+              className="fixed bottom-4 left-1/2 w-fit max-w-none -translate-x-1/2 overflow-hidden px-1 py-1"
             >
               <div className="mx-auto w-fit rounded-lg">
+                {multiBonusPodiumsOverSelected && (
+                  <p className="pb-2 text-center text-sm text-red-500">
+                    You have {remainings?.bonus || 0} bonus spots available
+                  </p>
+                )}
                 <div className="flex items-center gap-4 text-lg">
                   <div className="flex items-center gap-2 px-2 pl-4 font-medium">
                     <p>{selectedSubmissionIds.size}</p>
@@ -612,36 +684,69 @@ export default function BountySubmissions({ slug }: Props) {
                   </Button>
 
                   <Button
-                    className="gap-2 bg-red-100 px-2 font-medium text-rose-600 hover:bg-red-50/90"
-                    disabled={selectedSubmissionIds.size === 0}
-                    onClick={rejectedOnOpen}
+                    className={cn(
+                      'gap-2 px-2 font-medium',
+                      isProject &&
+                        'bg-red-100 text-rose-600 hover:bg-red-50/90',
+                      !isProject &&
+                        'bg-green-100 text-emerald-600 hover:bg-green-50/90',
+                    )}
+                    disabled={
+                      selectedSubmissionIds.size === 0 ||
+                      multiBonusPodiumsOverSelected
+                    }
+                    onClick={multiActionConfirmOnOpen}
                   >
-                    <svg
-                      width="13"
-                      height="13"
-                      viewBox="0 0 13 13"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
-                      <path
-                        d="M6.11111 0.777832C9.49056 0.777832 12.2222 3.5095 12.2222 6.88894C12.2222 10.2684 9.49056 13.0001 6.11111 13.0001C2.73167 13.0001 0 10.2684 0 6.88894C0 3.5095 2.73167 0.777832 6.11111 0.777832ZM8.305 3.83339L6.11111 6.02728L3.91722 3.83339L3.05556 4.69505L5.24944 6.88894L3.05556 9.08283L3.91722 9.9445L6.11111 7.75061L8.305 9.9445L9.16667 9.08283L6.97278 6.88894L9.16667 4.69505L8.305 3.83339Z"
-                        fill="#E11D48"
-                      />
-                    </svg>
-                    Reject {selectedSubmissionIds.size} Application
-                    {selectedSubmissionIds.size > 1 ? 's' : ''}
+                    {isProject && (
+                      <svg
+                        width="13"
+                        height="13"
+                        viewBox="0 0 13 13"
+                        fill="none"
+                        xmlns="http://www.w3.org/2000/svg"
+                      >
+                        <path
+                          d="M6.11111 0.777832C9.49056 0.777832 12.2222 3.5095 12.2222 6.88894C12.2222 10.2684 9.49056 13.0001 6.11111 13.0001C2.73167 13.0001 0 10.2684 0 6.88894C0 3.5095 2.73167 0.777832 6.11111 0.777832ZM8.305 3.83339L6.11111 6.02728L3.91722 3.83339L3.05556 4.69505L5.24944 6.88894L3.05556 9.08283L3.91722 9.9445L6.11111 7.75061L8.305 9.9445L9.16667 9.08283L6.97278 6.88894L9.16667 4.69505L8.305 3.83339Z"
+                          fill="#E11D48"
+                        />
+                      </svg>
+                    )}
+                    {!isProject && (
+                      <span className="flex items-center justify-between rounded-full bg-green-600 p-[3px]">
+                        <Check className="!size-2.5 stroke-3 text-white" />
+                      </span>
+                    )}
+                    {isProject ? `Reject` : 'Assign'}{' '}
+                    {selectedSubmissionIds.size}{' '}
+                    {isProject ? 'Application' : 'Bonus'}
+                    {selectedSubmissionIds.size > 1
+                      ? isProject
+                        ? 's'
+                        : 'es'
+                      : ''}
                   </Button>
                 </div>
               </div>
             </DialogContent>
           </Dialog>
-          <RejectAllSubmissionModal
-            allSubmissionsLength={submissions?.length || 0}
-            submissionIds={Array.from(selectedSubmissionIds)}
-            rejectIsOpen={rejectedIsOpen}
-            rejectOnClose={rejectedOnClose}
-            onRejectSubmission={handleRejectSubmission}
-          />
+          {isProject && (
+            <RejectAllSubmissionModal
+              allSubmissionsLength={submissions?.length || 0}
+              submissionIds={Array.from(selectedSubmissionIds)}
+              rejectIsOpen={multiActionConfrimIsOpen}
+              rejectOnClose={multiActionConfirmOnClose}
+              onRejectSubmission={handleRejectSubmission}
+            />
+          )}
+          {!isProject && (
+            <MultiBonusModal
+              allSubmissionsLength={submissions?.length || 0}
+              submissionIds={Array.from(selectedSubmissionIds)}
+              bonusIsOpen={multiActionConfrimIsOpen}
+              bonusOnClose={multiActionConfirmOnClose}
+              onBonusSubmission={handleAssignBonus}
+            />
+          )}
         </>
       )}
     </SponsorLayout>
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 6df0d9af1..30790883f 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -1,5 +1,5 @@
 @import 'tailwindcss';
-@import "tw-animate-css";
+@import 'tw-animate-css';
 
 @plugin 'tailwind-scrollbar';
 
@@ -7,7 +7,7 @@
 
 @layer base {
   button:not(:disabled),
-  [role="button"]:not(:disabled) {
+  [role='button']:not(:disabled) {
     cursor: pointer;
   }
 }
@@ -362,7 +362,7 @@ body {
 
 #reset-des a,
 #reset-des a * {
-  color: #6366F1 !important;
+  color: #6366f1 !important;
 }
 
 /* Flattened nested selectors for #richtext */
@@ -492,7 +492,7 @@ body {
 
 #richtext a,
 #richtext a * {
-  color: #6366F1 !important;
+  color: #6366f1 !important;
 }
 
 .listing-description > *:first-child {
@@ -615,7 +615,13 @@ pre hr {
 .banner-wrapper {
   width: 100%;
   overflow: hidden;
-  mask-image: linear-gradient(to right, transparent 0, black 40px, black calc(100% - 40px), transparent 100%);
+  mask-image: linear-gradient(
+    to right,
+    transparent 0,
+    black 40px,
+    black calc(100% - 40px),
+    transparent 100%
+  );
 }
 
 .banner-wrapper .wrapper {
@@ -658,15 +664,15 @@ pre hr {
 }
 
 h1.tiptap-heading {
-  @apply mb-6 mt-8 text-4xl font-bold;
+  @apply mt-8 mb-6 text-4xl font-bold;
 }
 
 h2.tiptap-heading {
-  @apply mb-4 mt-6 text-3xl font-bold;
+  @apply mt-6 mb-4 text-3xl font-bold;
 }
 
 h3.tiptap-heading {
-  @apply mb-3 mt-4 text-xl font-bold;
+  @apply mt-4 mb-3 text-xl font-bold;
 }
 
 h1.tiptap-heading:first-child,
@@ -685,7 +691,7 @@ h3.tiptap-heading + h2.tiptap-heading {
 }
 
 .tiptap p.is-editor-empty:first-child::before {
-  @apply pointer-events-none float-left h-0 text-accent-foreground;
+  @apply text-accent-foreground pointer-events-none float-left h-0;
   content: attr(data-placeholder);
 }
 
@@ -701,6 +707,19 @@ h3.tiptap-heading + h2.tiptap-heading {
 }
 
 /* hide metamask */
-.wallet-adapter-button:has(img[alt="MetaMask icon"]) {
+.wallet-adapter-button:has(img[alt='MetaMask icon']) {
   display: none !important;
-}
\ No newline at end of file
+}
+
+.scrollbar-visible::-webkit-scrollbar {
+  width: 8px;
+  display: block;
+}
+.scrollbar-visible::-webkit-scrollbar-thumb {
+  background-color: rgba(0, 0, 0, 0.2);
+  border-radius: 4px;
+}
+.scrollbar-visible::-webkit-scrollbar-track {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (14)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts (1)</summary><blockquote>

`18-18`: **Increased batch size for submission rejections.**

The batch size for processing submissions has been increased from 10 to 50, which will reduce the number of API calls needed when rejecting multiple submissions.


This performance optimization is a good change. Make sure the backend API endpoint can handle this increased batch size (which appears to be the case based on the corresponding change in the reject.ts file).

</blockquote></details>
<details>
<summary>src/pages/api/listings/publish/[id].ts (1)</summary><blockquote>

`352-380`: **Improved type safety by using the correct Prisma input type.**

The type has been changed from `BountiesUncheckedCreateInput` to `BountiesUncheckedUpdateInput`, which better reflects that this operation is updating an existing record, not creating a new one.


I notice the `pocId` field has been removed from the data object. This is consistent with the type change, but please verify that this field is no longer needed for updates.

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx (1)</summary><blockquote>

`238-251`: **Conditionally render individual selection checkboxes based on multi-select state.**

Individual selection checkboxes are now conditionally rendered based on the `isMultiSelectDisabled` flag, maintaining UI consistency.


Consider using optional chaining for the `isToggled` function check:

```diff
-checked={isToggled && isToggled(submission.id)}
+checked={isToggled?.(submission.id)}
```

This would be a cleaner way to handle the potentially undefined callback.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 241-241: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

---

[error] 248-248: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/components/PublishResults.tsx (1)</summary><blockquote>

`110-116`: **Similar toggle logic - consider extracting**  
You’re repeating the toggle logic for resetting to non-winner status. If you need this pattern in multiple places, consider extracting it into a helper function to simplify maintenance.

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx (1)</summary><blockquote>

`1-28`: **Interface & state handling**  
1. Your `BonusModalProps` interface is comprehensive, but consider aligning param names—e.g., `onBonusSubmission` could be named to emphasize handling multiple IDs.  
2. In the `assignBonus` function, consider adding a user-facing alert (e.g., toast) on failure to improve UX, instead of relying solely on `console.error`.

</blockquote></details>
<details>
<summary>src/pages/api/sponsor-dashboard/submission/toggle-winner.ts (2)</summary><blockquote>

`22-94`: **Comprehensive request body validation**  
1. Good job ensuring submissions is an array, not empty, and within the 50-item limit.  
2. Checking for missing IDs, duplicates, and mismatch between `isWinner`/`winnerPosition` pairs ensures robust data integrity.  
3. Consider extracting these validations into a shared helper if other endpoints need the same logic.

---

`259-302`: **Repeated token value fetching**  
Your approach updates each submission individually, re-fetching token USD values for variable compensation. If you frequently batch large updates, consider caching or batching calls to reduce overhead.

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (2)</summary><blockquote>

`27-41`: **Consider handling partial batch failures.**
If any request in `Promise.all()` fails, the entire batch operation will fail. You may want to use `Promise.allSettled()` or a custom approach for more granular error handling and resilience.

---

`42-50`: **Refine error typing and handling.**
Currently, the `error` parameter is typed as `any`. Consider adding a more specific type or checking error properties (e.g., `statusCode`) for clearer debugging.

</blockquote></details>
<details>
<summary>src/pages/dashboard/listings/[slug]/submissions.tsx (5)</summary><blockquote>

`168-185`: **Handle assign bonus logic lacks user feedback.**
Consider adding a success toast to inform the user that bonuses have been assigned successfully, mirroring the error console logging for consistency.

---

`187-196`: **Nested checks for multi-select disabling.**
This logic is clear but somewhat nested. A small refactor to flatten the ‘if’ statements might enhance readability.

---

`198-208`: **Naming clarity.**
`multiBonusPodiumsOverSelected` might be more intuitive if renamed to something like `exceedsBonusSpots`.

---

`663-667`: **User message clarity.**
When `multiBonusPodiumsOverSelected` is true, consider rephrasing the text to emphasize that the user's selection exceeds available spots, if that is the intended scenario.

---

`700-726`: **Icon usage for project vs. non-project.**
This dynamic text and icons succession reads well, though extracting this logic into a small helper might keep the template simpler.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7fa6a8229b698246ba1815d8c5d4fffdce79a895 and c520b7fb231b00d2f9ca53978b87a9b4435190ba.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `src/features/sponsor-dashboard/components/PublishResults.tsx` (3 hunks)
* `src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx` (1 hunks)
* `src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx` (5 hunks)
* `src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx` (4 hunks)
* `src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx` (2 hunks)
* `src/features/sponsor-dashboard/mutations/useRejectSubmissions.ts` (1 hunks)
* `src/features/sponsor-dashboard/mutations/useToggleWinner.ts` (1 hunks)
* `src/pages/api/listings/draft.ts` (1 hunks)
* `src/pages/api/listings/publish/[id].ts` (1 hunks)
* `src/pages/api/sponsor-dashboard/submission/reject.ts` (3 hunks)
* `src/pages/api/sponsor-dashboard/submission/toggle-winner.ts` (1 hunks)
* `src/pages/dashboard/hackathon/[listing]/submissions.tsx` (1 hunks)
* `src/pages/dashboard/listings/[slug]/submissions.tsx` (11 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (6)</summary>

<details>
<summary>src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx (3)</summary><blockquote>

<details>
<summary>src/components/ui/dialog.tsx (4)</summary>

* `Dialog` (157-157)
* `DialogContent` (159-159)
* `DialogHeader` (162-162)
* `DialogTitle` (164-164)

</details>
<details>
<summary>src/components/ui/separator.tsx (1)</summary>

* `Separator` (27-27)

</details>
<details>
<summary>src/components/ui/button.tsx (1)</summary>

* `Button` (58-58)

</details>

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (2)</summary><blockquote>

<details>
<summary>src/features/listings/types/index.ts (2)</summary>

* `Listing` (7-54)
* `Rewards` (90-92)

</details>
<details>
<summary>src/features/sponsor-dashboard/atoms/index.ts (1)</summary>

* `selectedSubmissionAtom` (24-44)

</details>

</blockquote></details>
<details>
<summary>src/pages/api/sponsor-dashboard/submission/toggle-winner.ts (2)</summary><blockquote>

<details>
<summary>src/prisma.ts (1)</summary>

* `prisma` (31-31)

</details>
<details>
<summary>src/features/wallet/utils/fetchHistoricalTokenUSDValue.ts (1)</summary>

* `fetchHistoricalTokenUSDValue` (10-33)

</details>

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/components/PublishResults.tsx (1)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (1)</summary>

* `useToggleWinner` (20-97)

</details>

</blockquote></details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx (1)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (1)</summary>

* `useToggleWinner` (20-97)

</details>

</blockquote></details>
<details>
<summary>src/pages/dashboard/listings/[slug]/submissions.tsx (3)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (1)</summary>

* `useToggleWinner` (20-97)

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/Modals/RejectAllModal.tsx (1)</summary>

* `RejectAllSubmissionModal` (21-90)

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx (1)</summary>

* `MultiBonusModal` (21-84)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx</summary>

[error] 241-241: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (38)</summary><blockquote>

<details>
<summary>src/pages/api/sponsor-dashboard/submission/reject.ts (3)</summary>

`11-11`: **Increased maximum batch size for submission rejections.**

The maximum number of records allowed in a single request has been increased from 10 to 50, aligning with the client-side batch size change.

This change will improve efficiency by reducing the number of API calls needed for bulk operations.

---

`31-34`: **Updated error messages to reflect new batch size limit.**

Error messages have been properly updated to communicate the new 50-record limit.

---

`65-68`: **Terminology improvement in error messages.**

Changed "grant ID" to "listing ID" in error messages, making the terminology more consistent with the rest of the application.

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx (2)</summary>

`45-45`: **Added property to control multi-select functionality.**

The new `isMultiSelectDisabled` property enhances the component's flexibility by allowing multi-selection to be disabled in certain contexts.

---

`114-123`: **Conditionally render "select all" checkbox based on multi-select state.**

The checkbox for selecting all submissions is now conditionally rendered based on the `isMultiSelectDisabled` flag, improving UI consistency.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 120-120: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>
<details>
<summary>src/pages/api/listings/draft.ts (1)</summary>

`140-146`: **Improvement to preserve original POC when editing listings.**

The conditional assignment of `pocId` is now properly handled, ensuring that when updating an existing listing, the original point of contact is preserved rather than being overwritten with the current user's ID. This fix maintains data integrity when listings are edited by users other than the original creator.

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx (1)</summary>

`144-148`: **More precise conditional rendering for SelectLabel.**

The SelectLabel component now only appears when three specific conditions are met: not on a hackathon page, the submission status is "Pending", and winners haven't been announced yet. This improved condition prevents label selection when it would be inappropriate, enhancing the user experience by clearly limiting when submissions can be labeled.

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx (3)</summary>

`54-54`: **Simplified toggleWinner hook usage.**

The hook call has been simplified to only pass the bounty parameter, aligning with changes in the useToggleWinner hook implementation.

---

`65-71`: **Updated winner selection to support batch operations.**

The selectWinner function now passes an array to toggleWinner instead of a single object, supporting the new batch update functionality in the API. This change enables more efficient operations when processing multiple selections.

---

`166-173`: **Improved button semantics and accessibility.**

Converting from a div to a proper button element with appropriate disabled state handling improves accessibility. The explicit disabled attribute based on isMultiSelectOn state and the early return in the click handler ensure the button behaves correctly when multi-select mode is active.

</details>
<details>
<summary>src/pages/dashboard/hackathon/[listing]/submissions.tsx (1)</summary>

`230-231`: **Disabled multi-select functionality for hackathon submissions.**

The isMultiSelectDisabled prop is now added to the SubmissionList component, disabling multi-select functionality specifically for hackathon submission pages. This change creates a clearer distinction between the hackathon submission workflow and regular submission handling.

</details>
<details>
<summary>src/features/sponsor-dashboard/components/PublishResults.tsx (3)</summary>

`4-4`: **Standard React Hooks Import**  
Your import of `useState` and `useEffect` from `react` is valid and typical usage for local state and side-effect management. Looks good.

---

`87-87`: **Mutate function destructuring**  
Destructuring `mutateAsync` as `toggleWinner` is consistent with the batch updating approach in `useToggleWinner`. No issues here.

---

`95-101`: **Adopting batch updates**  
Passing an array (even with a single submission object) to `toggleWinner` aligns with the new batched API, and the surrounding `try`/`catch` handles errors properly.

</details>
<details>
<summary>src/features/sponsor-dashboard/components/Submissions/Modals/MultiBonusModal.tsx (1)</summary>

`29-84`: **Dialog structure & UI**  
Your modal design, confirming bonus assignment to multiple submissions, is clear and user-friendly. Employing straightforward button states (loading vs non-loading) is a good touch.

</details>
<details>
<summary>src/pages/api/sponsor-dashboard/submission/toggle-winner.ts (9)</summary>

`9-9`: **Import constant usage**  
Importing `BONUS_REWARD_POSITION` clarifies explicit usage of the bonus position constant.

---

`12-16`: **Structured request body type**  
Your `SubmissionUpdate` interface, with optional `isWinner` and `winnerPosition`, is clear and prevents unstructured incoming data.

---

`18-18`: **Submission update cap**  
Defining `MAX_SUBMISSIONS_UPDATE_LIMIT = 50` is a good guard to avoid overload in batch updates.

---

`99-144`: **Fetching linked listings and ensuring consistency**  
Performing a bulk fetch and validating that all submissions belong to the same listing is a strong measure. This step, along with verifying sponsor authorization, is well-handled.

---

`157-164`: **Preventing updates after announcement**  
Your short-circuit for already-announced winners prevents invalid updates. This is a good safeguard.

---

`166-189`: **Single-winner constraint for projects**  
You’ve added clear logic to ensure that only one submission can be a winner in project listings. This is an effective check.

---

`191-239`: **Existing winner checks**  
Collecting existing winners and validating new winner positions helps avoid conflicts. This supports consistent awarding logic.

---

`241-253`: **Bonus spot maximum**  
Enforcing `maxBonusSpots` ensures the bonus capacity is respected. This prevents overshooting the listing’s bonus limit.

---

`311-319`: **Error logging & response**  
Logging the error with context and returning a descriptive message is helpful for debugging. This final catch-all is effective in capturing unexpected failures.

</details>
<details>
<summary>src/features/sponsor-dashboard/mutations/useToggleWinner.ts (4)</summary>

`2-2`: **No issues with this new import.**

---

`20-25`: **Optional bounty parameter logic looks consistent.**
The function gracefully handles the `bounty` parameter being optional by referencing `bounty?.slug` in subsequent queries.

---

`60-67`: **Mapping updates to a dictionary is a solid approach.**
Building a `Map` from the `variables` array is efficient and concise, ensuring each submission is easily retrievable by ID.

---

`68-95`: **Good handling of the currently selected submission.**
This logic properly updates both the submission list and the `selectedSubmission`. This maintains consistent state for the user interface.

</details>
<details>
<summary>src/pages/dashboard/listings/[slug]/submissions.tsx (10)</summary>

`4-4`: **Utilizing additional icons is fine.**
No concerns about these new imports from `lucide-react`.

---

`31-31`: **Modal import is well-structured.**
Glad to see a separate component for multi-bonus logic.

---

`37-37`: **Importing the new mutation hook is consistent.**
No issues here.

---

`77-87`: **Clearing selected submissions on route change.**
This effect ensures the selections are reset when navigating away, preventing stale data from persisting.

---

`96-99`: **Multi-action confirmation state.**
Renaming these hooks and variables clarifies the interaction flow for multiple actions.

---

`161-162`: **Use of destructured mutateAsync.**
Renaming it to `toggleWinner` aligns with domain semantics.

---

`165-167`: **Closing modal after mutation.**
Confirming user action is completed by closing the modal is user-friendly.

---

`321-324`: **Filtering for pending, non-winner submissions.**
The check for `submission.status === 'Pending' && !submission.winnerPosition` is correct for toggling only eligible submissions.

---

`687-699`: **Differentiated styling.**
Conditionally applying color schemes for `project` vs. `non-project` is a nice UX touch.

---

`732-749`: **Conditional modals for project vs. general submissions.**
This approach clearly separates the ‘RejectAllSubmissionModal’ from the ‘MultiBonusModal’, improving code readability.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T06:23:24Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/app/api/dynamic-og/region/route.tsx (1)</summary><blockquote>

`147-147`: **Consider adding a default fallback image.**

You're using optional chaining with an empty string fallback, which is good defensive programming. However, an empty string for the `src` attribute will result in no image being displayed when no matching superteam is found. Consider providing a default image URL as a fallback.


```diff
-              src={superteam?.banner || ''}
+              src={superteam?.banner || '/default-region-banner.jpg'}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ce29d4dbb85604db100a340ffe8226aa3d9e16cb and a785f4be84d28cd1bb8a1313ca442d7c7ddc70a8.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/app/api/dynamic-og/region/route.tsx` (3 hunks)

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>src/app/api/dynamic-og/region/route.tsx (3)</summary>

`3-3`: **Added import for regional banner images.**

This import of `Superteams` is necessary for the implementation to dynamically fetch banner images based on region values.

---

`26-27`: **LGTM: Good approach to find the matching superteam.**

The implementation correctly uses `Array.find()` to locate the superteam that matches the region parameter.

---

`26-27`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify all regions have associated banner images.**

The current implementation assumes that all possible `region` values have corresponding entries in the `Superteams` array. Let's verify this to ensure no regions will result in missing images.





Also applies to: 147-147

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if all regions used in the codebase have matching entries in Superteams

echo "Searching for region parameters in API calls..."
rg -o "region=[\'\"]([^\'\"]+)" --no-filename | sort | uniq

echo "Checking the Superteams constant for displayValue entries..."
cat $(fd -e ts -e tsx Superteam.ts) | grep -o "displayValue: ['\"].*['\"]" || echo "Superteam constant file not found"
```

Length of output: 1045

---



</details>

**Verify all regions have associated banner images**

- The code in `src/app/api/dynamic-og/region/route.tsx` (lines 26–27) uses:
  ```ts
    const superteam = Superteams.find((s) => s.displayValue === region);
  ```
  This relies on the assumption that every `region` value used in the codebase has a corresponding entry in the `Superteams` constant.
- Verification of the `Superteams` constant (located at `src/constants/Superteam.ts`) confirms it includes these region entries:  
  India, Germany, UK, Turkey, Vietnam, UAE, Nigeria, Brazil, Malaysia, Balkan, Philippines, Japan, France, Mexico, Canada, Singapore, Poland, South Korea, Ukraine, Argentina, USA, Spain, Pakistan.
- However, the script intended to extract region parameters from API calls encountered a regex parse error. This means we couldn’t automatically gather all region usages from the code. Please refine the regex (for example, ensuring proper escaping for characters like `'` and `"`) so that all region parameters can be captured and cross-verified with the `Superteams` entries.
- **Action:** Manually confirm (or rerun a corrected extraction script) that every region used in API calls is covered by the entries in `Superteams`.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T06:47:07Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx (1)</summary><blockquote>

`92-95`: _⚠️ Potential issue_

**Fix ineffective useMemo hook**

The useMemo hook computes a value but doesn't store it anywhere, making it ineffective. The computed value is never used or assigned to any variable.


Either remove the ineffective hook or update it to properly set the state:

```diff
  const [estimatedTimeSingular, setEstimatedTimeSingular] = useState('~0mins');
- useMemo(() => {
-   return estimateTime(nonAnalysedApplications?.length || 1, true);
- }, [nonAnalysedApplications?.length]);
+ useEffect(() => {
+   setEstimatedTimeSingular(estimateTime(nonAnalysedApplications?.length || 1, true));
+ }, [nonAnalysedApplications?.length]);
```

Alternatively, if the value is only needed during the review process and the initial '~0mins' is sufficient, you could remove this hook entirely.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx (2)</summary><blockquote>

`167-167`: **Remove unnecessary dependency from onReviewClick**

The `applications` prop is included in the dependency array but isn't directly used within the `onReviewClick` function body. This can cause unnecessary function recreations when `applications` changes.


```diff
-  }, [applications, unreviewedApplications, nonAnalysedApplications, posthog]);
+  }, [unreviewedApplications, nonAnalysedApplications, posthog]);
```

---

`78-78`: **Clean up console.log statements**

There are multiple console.log statements throughout the code that should be removed or replaced with a proper logging mechanism before deploying to production. These statements can expose sensitive information and clutter the browser console.


Consider implementing a dedicated logging utility that respects the current environment (development vs production) and allows for appropriate log levels.

```diff
- console.log('unreviewedApplications', unreviewedApplications);
+ // Use a proper logging utility or remove if not needed

- console.log('nonAnalysedApplications - ', nonAnalysedApplications);
+ // Use a proper logging utility or remove if not needed

- console.log('start review for application - ', appl.id);
+ // Use a proper logging utility or remove if not needed

- console.log(
-   'Error occured while reviewing application with id ',
-   appl.id,
- );
+ // Use a proper logging utility with appropriate error handling

- console.log('Commiting Reviewed applications');
- console.log('commit data - ', data.data);
+ // Use a proper logging utility or remove if not needed

- console.log(
-   'error occured while commiting reviewed applications',
-   error,
- );
+ // Use a proper logging utility with appropriate error handling

- console.log('total applications', totalApplications);
+ // Use a proper logging utility or remove if not needed
```


Also applies to: 104-104, 118-118, 128-131, 140-142, 160-163, 478-478

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a785f4be84d28cd1bb8a1313ca442d7c7ddc70a8 and 9932c2be4a43df8d25d67224961bca8f8ff64ea3.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `src/components/ui/datetime-picker.tsx` (1 hunks)
* `src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* src/components/ui/datetime-picker.tsx

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T07:17:51Z
user: login: JayeshVP24
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T07:26:47Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T07:27:13Z

Review comments:
diff_hunk: @@ -1,136 +1,97 @@
 import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useAtom, useSetAtom } from 'jotai';
+import { useAtom } from 'jotai';
 import { toast } from 'sonner';
 
 import { type SubmissionWithUser } from '@/interface/submission';
 import { api } from '@/lib/api';
 
-import { BONUS_REWARD_POSITION } from '@/features/listing-builder/constants';
 import { type Listing, type Rewards } from '@/features/listings/types';
 
-import { isStateUpdatingAtom, selectedSubmissionAtom } from '../atoms';
+import { selectedSubmissionAtom } from '../atoms';
 
-export const useToggleWinner = (
-  bounty: Listing | undefined,
-  submissions: SubmissionWithUser[],
-  setRemainings: React.Dispatch<
-    React.SetStateAction<{ podiums: number; bonus: number } | null>
-  >,
-  usedPositions: number[],
-) => {
-  const queryClient = useQueryClient();
-  const [, setSelectedSubmission] = useAtom(selectedSubmissionAtom);
+interface SubmissionUpdatePayload {
+  id: string;
+  isWinner: boolean;
+  winnerPosition: number | null;
+}
+
+const BATCH_UPDATE_LIMIT = 50;
 
-  const setPositionUpdating = useSetAtom(isStateUpdatingAtom);
+export const useToggleWinner = (bounty: Listing | undefined) => {
+  const queryClient = useQueryClient();
+  const [selectedSubmission, setSelectedSubmission] = useAtom(
+    selectedSubmissionAtom,
+  );
 
   return useMutation({
-    mutationFn: async ({
-      id,
-      isWinner,
-      winnerPosition,
-    }: {
-      id: string;
-      isWinner: boolean;
-      winnerPosition: number | null;
-    }) => {
-      const response = await api.post(
-        `/api/sponsor-dashboard/submission/toggle-winner/`,
-        {
-          id,
-          isWinner,
-          winnerPosition,
-        },
+    mutationFn: async (allUpdates: SubmissionUpdatePayload[]) => {
+      const updateBatches: SubmissionUpdatePayload[][] = [];
+      for (let i = 0; i < allUpdates.length; i += BATCH_UPDATE_LIMIT) {
+        updateBatches.push(allUpdates.slice(i, i + BATCH_UPDATE_LIMIT));
+      }
+
+      const batchPromises = updateBatches.map((batch) =>
+        api.post(`/api/sponsor-dashboard/submission/toggle-winner/`, {
+          submissions: batch,
+        }),
       );
-      if (!response.data) throw new Error('Failed to toggle winner');
-      return response.data;
+
+      await Promise.all(batchPromises);
+      return { success: true };
     },
-    onSuccess: (_, variables) => {
-      setPositionUpdating(false);
-      queryClient.setQueryData<SubmissionWithUser[]>(
-        ['sponsor-submissions', bounty?.slug],
-        (old) =>
-          old?.map((submission) =>
-            submission.id === variables.id
-              ? {
-                  ...submission,
-                  isWinner: variables.isWinner,
-                  winnerPosition: variables.winnerPosition ?? undefined,
-                }
-              : submission,
-          ),
+    onError: (error: any) => {
+      queryClient.invalidateQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
+      toast.error(
+        'An error occurred while assigning bonus spots. Please try again.',
       );
+      console.error('Failed to toggle winners:', error);
+    },
+    onMutate: async (variables) => {
+      await queryClient.cancelQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
 
-      const submissionIndex = submissions.findIndex(
-        (s) => s.id === variables.id,
-      );
-      if (submissionIndex >= 0) {
-        const oldPosition: number =
-          Number(submissions[submissionIndex]?.winnerPosition) || 0;
-        const newPosition = variables.winnerPosition || 0;
+      const previousSubmissions = queryClient.getQueryData<
+        SubmissionWithUser[]
+      >(['sponsor-submissions', bounty?.slug]);
 
path: src/features/sponsor-dashboard/mutations/useToggleWinner.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enable rollback on error for optimistic updates.**
The code stores the `previousSubmissions` but does not revert the state in `onError`. Consider restoring `previousSubmissions` in `onError` or `onSettled` to maintain a standard optimistic update pattern.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-07T06:23:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -62,6 +62,10 @@ export default function AiReviewModal({ applications, grant }: Props) {
     refetchOnWindowFocus: false,
   });
 
+  useEffect(() => {
+    refetchUnreviewedApplications();
+  }, [applications]);
path: src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Include refetchUnreviewedApplications in the dependency array**

The useEffect hook correctly ensures that unreviewed applications are refreshed when the applications prop changes. However, the `refetchUnreviewedApplications` function is missing from the dependency array, which could lead to the effect using a stale version of this function if it ever changes.


```diff
  useEffect(() => {
    refetchUnreviewedApplications();
-  }, [applications]);
+  }, [applications, refetchUnreviewedApplications]);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  useEffect(() => {
    refetchUnreviewedApplications();
  }, [applications, refetchUnreviewedApplications]);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 7e2ccfe
created_at: 2025-04-07T07:17:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,136 +1,97 @@
 import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useAtom, useSetAtom } from 'jotai';
+import { useAtom } from 'jotai';
 import { toast } from 'sonner';
 
 import { type SubmissionWithUser } from '@/interface/submission';
 import { api } from '@/lib/api';
 
-import { BONUS_REWARD_POSITION } from '@/features/listing-builder/constants';
 import { type Listing, type Rewards } from '@/features/listings/types';
 
-import { isStateUpdatingAtom, selectedSubmissionAtom } from '../atoms';
+import { selectedSubmissionAtom } from '../atoms';
 
-export const useToggleWinner = (
-  bounty: Listing | undefined,
-  submissions: SubmissionWithUser[],
-  setRemainings: React.Dispatch<
-    React.SetStateAction<{ podiums: number; bonus: number } | null>
-  >,
-  usedPositions: number[],
-) => {
-  const queryClient = useQueryClient();
-  const [, setSelectedSubmission] = useAtom(selectedSubmissionAtom);
+interface SubmissionUpdatePayload {
+  id: string;
+  isWinner: boolean;
+  winnerPosition: number | null;
+}
+
+const BATCH_UPDATE_LIMIT = 50;
 
-  const setPositionUpdating = useSetAtom(isStateUpdatingAtom);
+export const useToggleWinner = (bounty: Listing | undefined) => {
+  const queryClient = useQueryClient();
+  const [selectedSubmission, setSelectedSubmission] = useAtom(
+    selectedSubmissionAtom,
+  );
 
   return useMutation({
-    mutationFn: async ({
-      id,
-      isWinner,
-      winnerPosition,
-    }: {
-      id: string;
-      isWinner: boolean;
-      winnerPosition: number | null;
-    }) => {
-      const response = await api.post(
-        `/api/sponsor-dashboard/submission/toggle-winner/`,
-        {
-          id,
-          isWinner,
-          winnerPosition,
-        },
+    mutationFn: async (allUpdates: SubmissionUpdatePayload[]) => {
+      const updateBatches: SubmissionUpdatePayload[][] = [];
+      for (let i = 0; i < allUpdates.length; i += BATCH_UPDATE_LIMIT) {
+        updateBatches.push(allUpdates.slice(i, i + BATCH_UPDATE_LIMIT));
+      }
+
+      const batchPromises = updateBatches.map((batch) =>
+        api.post(`/api/sponsor-dashboard/submission/toggle-winner/`, {
+          submissions: batch,
+        }),
       );
-      if (!response.data) throw new Error('Failed to toggle winner');
-      return response.data;
+
+      await Promise.all(batchPromises);
+      return { success: true };
     },
-    onSuccess: (_, variables) => {
-      setPositionUpdating(false);
-      queryClient.setQueryData<SubmissionWithUser[]>(
-        ['sponsor-submissions', bounty?.slug],
-        (old) =>
-          old?.map((submission) =>
-            submission.id === variables.id
-              ? {
-                  ...submission,
-                  isWinner: variables.isWinner,
-                  winnerPosition: variables.winnerPosition ?? undefined,
-                }
-              : submission,
-          ),
+    onError: (error: any) => {
+      queryClient.invalidateQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
+      toast.error(
+        'An error occurred while assigning bonus spots. Please try again.',
       );
+      console.error('Failed to toggle winners:', error);
+    },
+    onMutate: async (variables) => {
+      await queryClient.cancelQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
 
-      const submissionIndex = submissions.findIndex(
-        (s) => s.id === variables.id,
-      );
-      if (submissionIndex >= 0) {
-        const oldPosition: number =
-          Number(submissions[submissionIndex]?.winnerPosition) || 0;
-        const newPosition = variables.winnerPosition || 0;
+      const previousSubmissions = queryClient.getQueryData<
+        SubmissionWithUser[]
+      >(['sponsor-submissions', bounty?.slug]);
 
path: src/features/sponsor-dashboard/mutations/useToggleWinner.ts
user: login: JayeshVP24
body:     onError: (error: any) => {
      queryClient.invalidateQueries({
        queryKey: ['sponsor-submissions', bounty?.slug],
      });
      toast.error(
        'An error occurred while assigning bonus spots. Please try again.',
      );
      console.error('Failed to toggle winners:', error);
    },

this will make sure rollback is accurate by refetching the submissions.
created_at: 2025-04-07T07:26:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,136 +1,97 @@
 import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { useAtom, useSetAtom } from 'jotai';
+import { useAtom } from 'jotai';
 import { toast } from 'sonner';
 
 import { type SubmissionWithUser } from '@/interface/submission';
 import { api } from '@/lib/api';
 
-import { BONUS_REWARD_POSITION } from '@/features/listing-builder/constants';
 import { type Listing, type Rewards } from '@/features/listings/types';
 
-import { isStateUpdatingAtom, selectedSubmissionAtom } from '../atoms';
+import { selectedSubmissionAtom } from '../atoms';
 
-export const useToggleWinner = (
-  bounty: Listing | undefined,
-  submissions: SubmissionWithUser[],
-  setRemainings: React.Dispatch<
-    React.SetStateAction<{ podiums: number; bonus: number } | null>
-  >,
-  usedPositions: number[],
-) => {
-  const queryClient = useQueryClient();
-  const [, setSelectedSubmission] = useAtom(selectedSubmissionAtom);
+interface SubmissionUpdatePayload {
+  id: string;
+  isWinner: boolean;
+  winnerPosition: number | null;
+}
+
+const BATCH_UPDATE_LIMIT = 50;
 
-  const setPositionUpdating = useSetAtom(isStateUpdatingAtom);
+export const useToggleWinner = (bounty: Listing | undefined) => {
+  const queryClient = useQueryClient();
+  const [selectedSubmission, setSelectedSubmission] = useAtom(
+    selectedSubmissionAtom,
+  );
 
   return useMutation({
-    mutationFn: async ({
-      id,
-      isWinner,
-      winnerPosition,
-    }: {
-      id: string;
-      isWinner: boolean;
-      winnerPosition: number | null;
-    }) => {
-      const response = await api.post(
-        `/api/sponsor-dashboard/submission/toggle-winner/`,
-        {
-          id,
-          isWinner,
-          winnerPosition,
-        },
+    mutationFn: async (allUpdates: SubmissionUpdatePayload[]) => {
+      const updateBatches: SubmissionUpdatePayload[][] = [];
+      for (let i = 0; i < allUpdates.length; i += BATCH_UPDATE_LIMIT) {
+        updateBatches.push(allUpdates.slice(i, i + BATCH_UPDATE_LIMIT));
+      }
+
+      const batchPromises = updateBatches.map((batch) =>
+        api.post(`/api/sponsor-dashboard/submission/toggle-winner/`, {
+          submissions: batch,
+        }),
       );
-      if (!response.data) throw new Error('Failed to toggle winner');
-      return response.data;
+
+      await Promise.all(batchPromises);
+      return { success: true };
     },
-    onSuccess: (_, variables) => {
-      setPositionUpdating(false);
-      queryClient.setQueryData<SubmissionWithUser[]>(
-        ['sponsor-submissions', bounty?.slug],
-        (old) =>
-          old?.map((submission) =>
-            submission.id === variables.id
-              ? {
-                  ...submission,
-                  isWinner: variables.isWinner,
-                  winnerPosition: variables.winnerPosition ?? undefined,
-                }
-              : submission,
-          ),
+    onError: (error: any) => {
+      queryClient.invalidateQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
+      toast.error(
+        'An error occurred while assigning bonus spots. Please try again.',
       );
+      console.error('Failed to toggle winners:', error);
+    },
+    onMutate: async (variables) => {
+      await queryClient.cancelQueries({
+        queryKey: ['sponsor-submissions', bounty?.slug],
+      });
 
-      const submissionIndex = submissions.findIndex(
-        (s) => s.id === variables.id,
-      );
-      if (submissionIndex >= 0) {
-        const oldPosition: number =
-          Number(submissions[submissionIndex]?.winnerPosition) || 0;
-        const newPosition = variables.winnerPosition || 0;
+      const previousSubmissions = queryClient.getQueryData<
+        SubmissionWithUser[]
+      >(['sponsor-submissions', bounty?.slug]);
 
path: src/features/sponsor-dashboard/mutations/useToggleWinner.ts
user: login: coderabbitai[bot]
body: @JayeshVP24 You're absolutely right! Invalidating the queries and triggering a refetch is a valid approach to handle rollback. This ensures that your UI will display the most up-to-date data from the server after an error occurs.

While using the stored `previousSubmissions` would provide an immediate rollback without a network request, your approach has the advantage of ensuring data consistency with the server state, which is especially valuable if multiple users might be making changes concurrently.

Your implementation is clean and makes good use of React Query's built-in functionality.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-07T07:27:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-07T05:53:14Z
body: [vc]: #WY88dGubQ3qepocZLJtB3anBLMn0w7xw+kKCLnqR3kM=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJlYXJuIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N1cGVydGVhbS1lYXJuL2Vhcm4vNkZNelpndUROVjhaUUE0Tm1idjJ4RUJVb1pwaiIsInByZXZpZXdVcmwiOiJlYXJuLWdpdC1zdGFnaW5nLXN1cGVydGVhbS1lYXJuLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **earn** | ✅ Ready ([Inspect](https://vercel.com/superteam-earn/earn/6FMzZguDNV8ZQA4Nmbv2xEBUoZpj)) | [Visit Preview](https://earn-git-staging-superteam-earn.vercel.app) | Apr 7, 2025 7:45am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-07T05:53:20Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This update refines several UI components by modifying CSS classes and control flow conditions. Scrollable command list elements in comboboxes and multi-select components now show a visible scrollbar. An image within the EasterEgg component has its styling updated to maintain its aspect ratio. In the SubmissionDrawer, the Easter egg is triggered unconditionally, while the SubmissionPanel’s label now requires additional conditions. Additionally, the sponsor dashboard components include new logic to check a user's superteam status and render an associated tooltip. Global CSS has been updated for consistency and enhanced scrollbar styling.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `src/components/shared/RegionCombobox.tsx`, `src/components/shared/SuperteamCombobox.tsx` | Updated `CommandList` classes by adding `scrollbar-visible` and `overflow-auto` to enable visible scrollbars when content overflows. |
| `src/components/ui/multi-select.tsx` | Added the `scrollbar-visible` class to the `CommandList` to adjust scrollbar styling. |
| `src/features/listings/components/Submission/EasterEgg.tsx` | Replaced fixed-dimension classes (`h-100 w-100`) with `object-contain` for responsive image scaling. |
| `src/features/listings/components/Submission/SubmissionDrawer.tsx` | Removed sponsor filtering and constant array; now calls `showEasterEgg()` unconditionally when not in edit mode. |
| `src/features/sponsor-dashboard/components/GrantApplications/ApplicationDetails.tsx`, `src/features/sponsor-dashboard/components/GrantApplications/ApplicationList.tsx` | Introduced new logic and variables to determine superteam membership and render an associated tooltip with an icon. |
| `src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx` | Added extra conditions (non-hackathon page, pending submission status, and bounty not announced) for rendering the `SelectLabel`. |
| `src/styles/globals.css` | Improved syntax consistency, adjusted color formatting, refined margin ordering, and added styles for `.scrollbar-visible`. |
| `src/features/sponsor-dashboard/components/PublishResults.tsx`, `src/features/sponsor-dashboard/components/Submissions/SelectWinner.tsx` | Removed several props from the `Props` interface, streamlining component interfaces. |
| `src/features/sponsor-dashboard/components/Submissions/SubmissionList.tsx` | Added `isMultiSelectDisabled` prop to control checkbox rendering based on multi-selection state. |
| `src/features/sponsor-dashboard/mutations/useToggleWinner.ts` | Refactored hook to accept fewer parameters and handle batch updates for submission toggles. |
| `src/pages/api/listings/draft.ts`, `src/pages/api/listings/publish/[id].ts`, `src/pages/api/sponsor-dashboard/submission/reject.ts` | Updated logic for handling `pocId` and modified maximum record limits for submission operations. |
| `src/pages/dashboard/hackathon/[listing]/submissions.tsx`, `src/pages/dashboard/listings/[slug]/submissions.tsx` | Removed certain props from components and added new modal for bonus assignments, enhancing submission management. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User as User
    participant Drawer as SubmissionDrawer
    Note over Drawer: Easter Egg Flow
    User->>Drawer: Triggers submission (editMode false)
    Drawer->>Drawer: Unconditionally call showEasterEgg()
    Drawer->>User: Displays Easter Egg
```

```mermaid
sequenceDiagram
    participant User as User
    participant Panel as SubmissionPanel
    Note over Panel: SelectLabel Rendering Flow
    User->>Panel: Opens submission panel
    Panel->>Panel: Check if (non-Hackathon page AND submission status = 'Pending' AND bounty.isWinnersAnnounced = false)
    alt Conditions met
        Panel->>User: Render SelectLabel
    else
        Panel->>User: Skip SelectLabel rendering
    end
```

```mermaid
sequenceDiagram
    participant App as Application Component
    participant User as Application User
    participant Tooltip as Tooltip Component
    Note over App: Superteam Tooltip Flow
    App->>User: Evaluate user.superteamLevel
    alt User is a Superteam member
        App->>App: Retrieve superteam data
        App->>Tooltip: Render tooltip with icon
        Tooltip->>User: Display superteam info
    else
        App->>User: Render plain user name
    end
```

## Possibly related PRs
- **SuperteamDAO/earn#1033**: The changes in the main PR and the retrieved PR are related as both modify the `toggleWinner` function to accept an array of objects instead of a single object, indicating a shared focus on handling multiple submissions in a similar manner.

## Poem
> I'm a bunny in the codeyard, hopping with glee,  
> Scrollbars now gleam for all the world to see.  
> Easter eggs pop without a care,  
> Superteam tooltips float in the air.  
> Conditions refined with a clever twist,  
> My paws tap away amidst a digital mist.  
> Code and carrots, a techie bunny's bliss!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9932c2be4a43df8d25d67224961bca8f8ff64ea3 and 7e2ccfef4988419418dbd4d88321d41e774157c1.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/features/sponsor-dashboard/components/GrantApplications/Modals/AiReview.tsx` (2 hunks)

</details>

</details>
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=SuperteamDAO/earn&utm_content=1031):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR