@@ -4,19 +4,23 @@ import useValidateUrlToken from "@/app/hooks/security/useValidateUrlToken";
44import useAuth from "@/app/hooks/context_imports/useAuth" ;
55
66import { SurvivalQuizInfo , TimeAttackQuizInfo , Message , Participant , ResponseStatus ,
7- PostTimeAttackEntry , PostSurvivalEntry } from "@/app/interfaces" ;
7+ PostTimeAttackEntry , PostSurvivalEntry , TimeAttackEntry , SurvivalEntry } from "@/app/interfaces" ;
88import AnimatedScoreCounter from "@/app/components/AnimatedScoreCounter" ;
99import Modal from "@/app/components/modals/Modal" ;
1010import { toggleModal , isModalOpen , applyTextMarkup , renderModalResponseAlert ,
11- playSoundEffect , executeEventSequence } from "@/app/utilities/miscFunctions" ;
11+ playSoundEffect , executeEventSequence , isTimeAttackEntry } from "@/app/utilities/miscFunctions" ;
1212
1313import useGetQuizInfo from "@/app/hooks/api_access/quizzes/useGetQuizInfo" ;
1414import useGetRandomQuizMessage from "@/app/hooks/api_access/messages/useGetRandomQuizMessage" ;
1515import usePostLeaderboardEntry from "@/app/hooks/api_access/leaderboards/usePostLeaderboardEntry" ;
1616import useAdjustContentHeight from "@/app/hooks/useAdjustContentHeight" ;
17+ import useGetPastPlayerLeaderboardEntry from "@/app/hooks/api_access/leaderboards/useGetPastPlayerLeaderboardEntry" ;
18+ import usePatchLeaderboardEntry from "@/app/hooks/api_access/leaderboards/usePatchLeaderboardEntry" ;
1719
1820import AnimateHeight from "react-animate-height" ;
1921import { Height } from "react-animate-height" ;
22+ import { v4 as uuidv4 } from "uuid" ;
23+
2024import Link from "next/link" ;
2125import { useRouter } from "next/navigation" ;
2226import { useEffect , useState , useReducer , useRef } from "react" ;
@@ -56,6 +60,8 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
5660 const getQuizInfo = useGetQuizInfo ( ) ;
5761 const getRandomQuizMessage = useGetRandomQuizMessage ( ) ;
5862 const postLeaderboardEntry = usePostLeaderboardEntry ( ) ;
63+ const getPastPlayerLeaderboardEntry = useGetPastPlayerLeaderboardEntry ( ) ;
64+ const patchLeaderboardEntry = usePatchLeaderboardEntry ( ) ;
5965
6066 // Security
6167 const { auth } = useAuth ( ) ;
@@ -69,6 +75,8 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
6975 const [ nextMessage , setNextMessage ] = useState < Message | null > ( null ) ;
7076 const [ seenMessageIds , setSeenMessageIds ] = useState < Array < number > > ( [ ] ) ; // Prevent repeats
7177 const [ playerName , setPlayerName ] = useState < string > ( "" ) ; // The name of the player (for submitting to the leaderboard)
78+ const [ playerUUID , setPlayerUUID ] = useState < string | null > ( null ) ; // The UUID of the player (for updating previous leaderboard submissions)
79+ const [ previousLeaderboardEntry , setPreviousLeaderboardEntry ] = useState < TimeAttackEntry | SurvivalEntry | null > ( null ) ; // The player's previous leaderboard entry [if any]
7280 const [ scoreSubmitted , setScoreSubmitted ] = useState < boolean > ( false ) ; // Whether the score has been submitted to the leaderboard
7381
7482 // ----------- State (Game) -----------
@@ -82,6 +90,7 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
8290 // ----------- State (UI) -------------
8391 const [ staticDataLoading , setStaticDataLoading ] = useState < boolean > ( true ) ;
8492 const [ submitting , setSubmitting ] = useState < boolean > ( false ) ; // Whether the score is being submitted to the leaderboard
93+ const [ noResubmit , setNoResubmit ] = useState < boolean > ( false ) ; // Whether the user has decided not to update their previous leaderboard entry
8594 // The response status of the leaderboard submission
8695 const [ responseStatus , setResponseStatus ] = useState < ResponseStatus > ( { message : "" , success : false , doAnimate : false } ) ;
8796
@@ -139,6 +148,19 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
139148 console . error ( "Error retrieving data, redirecting to root" ) ;
140149 router . push ( "/" ) ;
141150 }
151+
152+ // See if the player has a UUID in local storage, if not, create one and store it
153+ let uuid : string | null = localStorage . getItem ( "quizPlayerUUID" ) ;
154+ if ( uuid ) {
155+ // If there is a UUID, retrieve the player's previous leaderboard entry (if any)
156+ const entry : TimeAttackEntry | SurvivalEntry | null = await getPastPlayerLeaderboardEntry ( quizId , uuid , shareableToken || undefined ) ;
157+ if ( entry ) setPreviousLeaderboardEntry ( entry ) ;
158+ } else {
159+ uuid = uuidv4 ( ) ; // Generate a new one
160+ localStorage . setItem ( "quizPlayerUUID" , uuid ) ;
161+ }
162+ setPlayerUUID ( uuid ) ;
163+
142164 setStaticDataLoading ( false ) ;
143165
144166 // Begin the intro splash timing sequence
@@ -172,24 +194,43 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
172194 }
173195 }
174196
175- const submitLeaderboardEntry = async ( ) => {
176- if ( ! quizInfo || ! totalTimeTaken || playerName === "" ) return ;
197+ const submitLeaderboardEntry = async ( isUpdate : boolean = false ) => {
198+ if ( ! quizInfo || ! totalTimeTaken || ( ! isUpdate && playerName === "" ) || ! playerUUID ) {
199+ console . error ( "Error submitting leaderboard entry: Missing required data" ) ;
200+ return ;
201+ }
202+ if ( isUpdate && ! previousLeaderboardEntry ) {
203+ console . error ( "Error submitting leaderboard entry: No previous entry found" ) ;
204+ return ;
205+ }
177206 setSubmitting ( true ) ;
207+
208+ // Build the request object
178209 let postRequest : PostTimeAttackEntry | PostSurvivalEntry ;
179210 if ( isTimeAttack ( quizInfo ) ) {
180211 postRequest = {
181- playerName : playerName ,
212+ playerName : ( isUpdate && previousLeaderboardEntry ) ? previousLeaderboardEntry . playerName : playerName ,
182213 score : score ,
183214 timeTaken : totalTimeTaken ,
215+ playerUUID : playerUUID
184216 } ;
185217 } else {
186218 postRequest = {
187- playerName : playerName ,
219+ playerName : ( isUpdate && previousLeaderboardEntry ) ? previousLeaderboardEntry . playerName : playerName ,
188220 streak : score ,
189221 skipsUsed : skipsUsed ,
222+ playerUUID : playerUUID
190223 } ;
191224 }
192- const error : string | null = await postLeaderboardEntry ( quizId , postRequest , shareableToken || undefined ) ;
225+
226+ // Make the API request
227+ let error : string | null ;
228+ if ( isUpdate && previousLeaderboardEntry ) {
229+ error = await patchLeaderboardEntry ( quizId , previousLeaderboardEntry . id , postRequest , shareableToken || undefined ) ;
230+ } else {
231+ error = await postLeaderboardEntry ( quizId , postRequest , shareableToken || undefined ) ;
232+ }
233+
193234 if ( ! error ) {
194235 setResponseStatus ( { message : "Entry submitted!" , success : true , doAnimate : true } ) ;
195236 setScoreSubmitted ( true ) ;
@@ -198,14 +239,14 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
198239 setResponseStatus ( { message : error , success : false , doAnimate : true } ) ;
199240 }
200241
201- // Display the response message for 3 seconds, then close the modal
242+ // Display the response message for 1.5 seconds, then close the modal
202243 setTimeout ( ( ) => {
203244 if ( isModalOpen ( "leaderboard-submit-modal" ) ) {
204245 toggleModal ( "leaderboard-submit-modal" ) ;
205246 }
206247 setSubmitting ( false ) ;
207248 setResponseStatus ( { message : "" , success : false , doAnimate : false } ) ;
208- } , 3000 ) ;
249+ } , 1500 ) ;
209250 }
210251
211252 // Gets a random selection of 4 or 3 (depending on type) participants to choose from, including the correct participant.
@@ -639,16 +680,42 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
639680 { renderModalResponseAlert ( responseStatus , true ) }
640681 </ > ) ;
641682 } else if ( submitting ) {
642- modalContent = (
643- < div className = "mb-8 mt-1 sm:my-12" >
644- < div className = "mx-auto mb-3 text-2xl text-center" >
683+ modalContent = ( < >
684+ < div className = "mt-[-12px]" />
685+ < div className = "mb-6 mt-4 sm:mb-12 sm:mt-10" >
686+ < div className = "mx-auto mb-2 text-xl text-center" >
645687 Submitting...
646688 </ div >
647689 < div className = "flex justify-center" >
648690 < div className = "spinner-circle w-12 h-12 sm:w-14 sm:h-14" />
649691 </ div >
650692 </ div >
651- ) ;
693+ </ > ) ;
694+ } else if ( previousLeaderboardEntry && ! noResubmit ) {
695+ modalContent = ( < >
696+ < div className = "w-full mt-3 text-center text-2xl font-semibold" >
697+ Update previous entry?
698+ </ div >
699+ < div className = "w-full text-center text-lg mt-3 text-zinc-300 font-light" >
700+ You had a { isTimeAttack ( quizInfo ) ? "score" : "streak" } of:
701+ < span className = "ml-2 text-white font-medium" >
702+ { isTimeAttackEntry ( previousLeaderboardEntry ) ? previousLeaderboardEntry . score : previousLeaderboardEntry . streak }
703+ </ span >
704+ </ div >
705+ < div className = "grid grid-cols-2 gap-2 mt-6 mb-5 mx-7" >
706+ < button className = "grow btn btn-lg bg-black border border-zinc-900 text-zinc-400 font-light"
707+ onClick = { ( ) => setNoResubmit ( true ) } >
708+ No Thanks
709+ </ button >
710+ < button className = { `grow btn btn-lg bg-gradient-to-r font-semibold
711+ ${ isTimeAttack ( quizInfo )
712+ ? " from-blue-500 from-0% via-blue-400 to-blue-500 to-100% text-indigo-100"
713+ : " from-purple-500 from-0% via-pink-500 to-purple-500 to-100% text-purple-100" } `}
714+ onClick = { ( ) => submitLeaderboardEntry ( true ) } >
715+ Update
716+ </ button >
717+ </ div >
718+ </ > ) ;
652719 } else {
653720 modalContent = ( < >
654721 < div className = { `w-full mt-2 text-center text-3xl font-semibold text-transparent bg-clip-text bg-gradient-to-r
@@ -730,7 +797,10 @@ export default function Quiz({ params }: { params: { query: string[] } }) {
730797 ${ isTimeAttack ( quizInfo )
731798 ? " from-blue-500 from-0% via-blue-400 to-blue-500 to-100% text-indigo-100"
732799 : " from-purple-500 from-0% via-pink-500 to-purple-500 to-100% text-purple-100" } `}
733- onClick = { ( ) => { toggleModal ( "leaderboard-submit-modal" ) } } >
800+ onClick = { ( ) => {
801+ setNoResubmit ( false ) ;
802+ toggleModal ( "leaderboard-submit-modal" ) ;
803+ } } >
734804 Submit
735805 </ button >
736806 }
0 commit comments