diff --git a/api/queries.ts b/api/queries.ts index 35f8d88..26980ca 100644 --- a/api/queries.ts +++ b/api/queries.ts @@ -191,6 +191,12 @@ export class Queries { }).then((res) => res.json().then((res) => res as IQuizQuestions)); } + public static async getQuizByUUID(quiz_id: string): Promise { + return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuizUUID(quiz_id)}`, { + next: { revalidate: 3600 }, + }).then((res) => res.json().then((res) => res as IQuizQuestions)); + } + public static async getQuizById(quiz_id: string) { return await fetch(`${baseUrl.QUIZ_SRC}${routes.getQuiz(quiz_id)}`, { next: { revalidate: 3600 }, @@ -234,6 +240,12 @@ export class Queries { }).then((res) => res.json().then((res) => res as IVote)); } + public static async getVoteByUUID(vote_id: string): Promise { + return await fetch(`${baseUrl.QUIZ_SRC}${routes.voteUUID(vote_id)}`, { + next: { revalidate: 3600 }, + }).then((res) => res.json().then((res) => res as IVote)); + } + // ============================================================================================ // Sms ======================================================================================== diff --git a/app/(main)/quiz/page.tsx b/app/(main)/quiz/page.tsx new file mode 100644 index 0000000..b59bf61 --- /dev/null +++ b/app/(main)/quiz/page.tsx @@ -0,0 +1,197 @@ +"use client"; + +import { Queries } from "@/api/queries"; +import Loader from "@/components/Loader"; +import QuizQuestionList from "@/components/quiz/QuizQuestionList"; +import QuizSearch from "@/components/quiz/QuizSearch"; +import QuizTable from "@/components/quiz/QuizTable"; +import QuizWinnerTable from "@/components/quiz/QuizWinnerTable"; +import GradientTitle from "@/components/vote/GradientTitle"; +import { IQuizQuestions } from "@/models/quizQuestions.model"; +import QuizProvider from "@/providers/QuizProvider"; +import { useQuizSearchActive, useSteps } from "@/store/store"; +import { Validator } from "@/utils/validator"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { useMediaQuery } from "usehooks-ts"; + +const page = () => { + const searchParams = useSearchParams(); + const router = useRouter(); + const [quizFinished, setQuizFinished] = useState(false); + const [data, setData] = useState(); + const { active } = useQuizSearchActive(); + const { step, setStep } = useSteps(); + const id = searchParams.get("d"); + + useEffect(() => { + if (id) { + Queries.getQuizByUUID(id).then((res) => { + setData(res); + if (res.data.questions) { + res.data.questions.map((question) => + question.status === "active" || question.status === "new" + ? setQuizFinished(false) + : setQuizFinished(true) + ); + } else if (res.data.steps && res.data.steps?.length > 0) { + setStep(res.data.steps[0].tapgyr); + for (let i = 0; i < res.data.steps.length; i++) { + res.data.steps[i].questions.map((question) => + question.status === "active" || question.status === "new" + ? setQuizFinished(false) + : setQuizFinished(true) + ); + } + } + }); + } else { + router.push("/quiz/active"); + } + }, []); + + const mobile = useMediaQuery("(max-width: 768px)"); + + if (data) { + if (!data.data) { + return ( +
+
+ +
+
+ ); + } + + return ( +
+ {typeof data !== "string" ? ( +
+ +
+
+
+

+ {data ? Validator.reveseDate(data?.data.date) : null} +

+

+ {data?.data.title} +

+

+ {data?.data.description} +

+
+ {data?.data.banner ? ( +
+ {mobile ? ( + {"banner"} + ) : ( + {"banner"} + )} +
+ ) : null} +
+ + {data?.data.rules && data.data.notes ? ( + + ) : null} +
+ +
+ {data.data.has_steps !== 0 && + data.data.steps && + data.data.steps?.length > 0 && ( +
+

+ Tapgyr +

+
+ {data.data.steps.map((item) => ( + + ))} + + Netije + +
+
+ )} + + {data?.data && !active ? ( + + ) : null} + + {data?.data.id && quizFinished && data.data.has_steps === 0 ? ( + + ) : null} + + {data?.data.id && data.data.has_steps === 0 && ( + + )} +
+
+
+ ) : ( +
+ Непредвиденная ошибка. Нет активной викторины. +
+ )} +
+ ); + } else { + return ( +
+
+ +
+
+ ); + } +}; + +export default page; diff --git a/app/(main)/vote/page.tsx b/app/(main)/vote/page.tsx new file mode 100644 index 0000000..a57ffb6 --- /dev/null +++ b/app/(main)/vote/page.tsx @@ -0,0 +1,18 @@ +import ParticipantsList from "@/components/vote/ParticipantsList"; +import VoteProvider from "@/providers/VoteProvider"; + +const page = () => { + return ( +
+
+ +
+ +
+
+
+
+ ); +}; + +export default page; \ No newline at end of file diff --git a/components/vote/ParticipantsList.tsx b/components/vote/ParticipantsList.tsx index 8f2aa8d..48568d6 100644 --- a/components/vote/ParticipantsList.tsx +++ b/components/vote/ParticipantsList.tsx @@ -1,23 +1,19 @@ -'use client'; -import React, { useContext, useEffect, useState } from 'react'; -import GradientTitle from './GradientTitle'; -import ParticipantCard from './ParticipantCard'; -import { v4 } from 'uuid'; -import { IAllVotes, VotingItem } from '@/models/allVotes.model'; -import { Queries } from '@/api/queries'; -import Loader from '../Loader'; -import VoteContext from '@/context/VoteContext'; -import PageBage from './PageBage'; -import Image from 'next/image'; -import { useMediaQuery } from 'usehooks-ts'; -import Countdown from './Countdown'; -import Link from 'next/link'; -import { useWindowSize } from 'react-use'; -import Confetti from '../common/Confetti'; - -interface IParams { - vote_id?: string; -} +"use client"; +import React, { useContext, useEffect, useState } from "react"; +import GradientTitle from "./GradientTitle"; +import ParticipantCard from "./ParticipantCard"; +import { v4 } from "uuid"; +import { IAllVotes, VotingItem } from "@/models/allVotes.model"; +import { Queries } from "@/api/queries"; +import Loader from "../Loader"; +import VoteContext from "@/context/VoteContext"; +import PageBage from "./PageBage"; +import Image from "next/image"; +import { useMediaQuery } from "usehooks-ts"; +import Countdown from "./Countdown"; +import Link from "next/link"; +import Confetti from "../common/Confetti"; +import { useRouter, useSearchParams } from "next/navigation"; interface ISocketMessage { voting_id: number; @@ -27,11 +23,13 @@ interface ISocketMessage { date: string; } -const ParticipantsList = ({ vote_id }: IParams) => { +const ParticipantsList = () => { + const searchParams = useSearchParams(); + const router = useRouter(); const [data, setData] = useState(); const [participantsData, setParticipantsData] = useState([]); const [voteStatus, setVoteStatus] = useState(); - const [eventStatus, setEventStatus] = useState('Not started'); + const [eventStatus, setEventStatus] = useState("Not started"); const [manualClose, setManualClose] = useState(false); // Track manual closure const [winnersCount, setWinnersCount] = useState(0); @@ -41,28 +39,22 @@ const ParticipantsList = ({ vote_id }: IParams) => { const [socket, setSocket] = useState(null); const [isConnected, setIsConnected] = useState(false); - const mobile = useMediaQuery('(max-width: 768px)'); - const { width, height } = useWindowSize(); + const mobile = useMediaQuery("(max-width: 768px)"); const { setVoteDescription } = useContext(VoteContext).voteDescriptionContext; useEffect(() => { - if (!vote_id) { - Queries.getAllVotes().then((res) => { - setData(res); - setParticipantsData([...res.data.voting_items]); - setVoteDescription(res.data.description); - setVoteStatus(res.data.status); - setSmsNumber(res.data.sms_number); - }); - } else { - Queries.getVote(vote_id).then((res) => { + const id = searchParams.get("d") + if (id) { + Queries.getVote(id).then((res) => { setData(res); setParticipantsData(res.data.voting_items); setVoteDescription(res.data.description); setVoteStatus(res.data.status); setSmsNumber(res.data.sms_number); }); + } else { + router.push('/vote/active') } if (participantsData) { @@ -79,19 +71,21 @@ const ParticipantsList = ({ vote_id }: IParams) => { try { // Only connect if manualClose is false if (!manualClose) { - socket = new WebSocket(`wss://sms.turkmentv.gov.tm/ws/voting?dst=${smsNumber}`); + socket = new WebSocket( + `wss://sms.turkmentv.gov.tm/ws/voting?dst=${smsNumber}` + ); setSocket(socket); socket.onopen = () => { - console.log('WebSocket is connected'); + console.log("WebSocket is connected"); setIsConnected(true); pingInterval = setInterval(() => { if (socket?.readyState === WebSocket.OPEN) { try { - socket.send(JSON.stringify({ type: 'ping' })); + socket.send(JSON.stringify({ type: "ping" })); } catch (error) { - console.error('Error sending ping:', error); + console.error("Error sending ping:", error); } } }, 25000); // Ping every 25 seconds @@ -102,23 +96,23 @@ const ParticipantsList = ({ vote_id }: IParams) => { const message = JSON.parse(event.data); handleWebSocketMessage(message); } catch (error) { - console.error('Error processing message:', error); + console.error("Error processing message:", error); } }; socket.onerror = (error) => { - console.error('WebSocket error:', error); + console.error("WebSocket error:", error); if (!manualClose && !reconnectTimeout) { reconnectTimeout = setTimeout(() => { - console.log('Attempting to reconnect WebSocket after error...'); + console.log("Attempting to reconnect WebSocket after error..."); connectWebSocket(); }, 5000); // Reconnect after 5 seconds } }; socket.onclose = () => { - console.log('WebSocket is closed'); + console.log("WebSocket is closed"); setIsConnected(false); if (pingInterval) { @@ -132,12 +126,12 @@ const ParticipantsList = ({ vote_id }: IParams) => { }; } } catch (error) { - console.error('WebSocket connection error:', error); + console.error("WebSocket connection error:", error); } }; // WebSocket connection only if eventStatus is 'Started' - if (smsNumber && eventStatus === 'Started' && !manualClose) { + if (smsNumber && eventStatus === "Started" && !manualClose) { connectWebSocket(); } @@ -161,7 +155,9 @@ const ParticipantsList = ({ vote_id }: IParams) => { // Update the corresponding voting item const updatedItems = prevVotingItems.map((item, index) => - item.id === message.voting_item_id ? { ...item, votes_count: item.votes_count + 1 } : item, + item.id === message.voting_item_id + ? { ...item, votes_count: item.votes_count + 1 } + : item ); // Sort the updated items array by votes_count in descending order @@ -175,10 +171,10 @@ const ParticipantsList = ({ vote_id }: IParams) => { // Update the corresponding voting item const updatedItems = prevVotingItems.map((item, index) => - index === 1 ? { ...item, votes_count: item.votes_count + 1 } : item, + index === 1 ? { ...item, votes_count: item.votes_count + 1 } : item ); - console.log('votes updated'); + console.log("votes updated"); console.log(updatedItems.sort((a, b) => b.votes_count - a.votes_count)); // Sort the updated items array by votes_count in descending order return updatedItems.sort((a, b) => b.votes_count - a.votes_count); @@ -188,7 +184,10 @@ const ParticipantsList = ({ vote_id }: IParams) => { const winnersCountHandle = (winners: VotingItem[]) => { let count = 0; winners.map((winner) => { - if (winner.votes_percents === 100 && winner.votes_count === winners[0].votes_count) { + if ( + winner.votes_percents === 100 && + winner.votes_count === winners[0].votes_count + ) { count++; setWinnersCount(count); } @@ -202,23 +201,29 @@ const ParticipantsList = ({ vote_id }: IParams) => { if (!data?.data) { return (
- +
); } return (
- {data.data.description ? : null} + {data.data.description ? ( + + ) : null} - {eventStatus === 'Finished' && } + {eventStatus === "Finished" && } {data.data.banner ? (
{mobile ? ( {data.data.title} { ) : null}
- {winnersCount > 1 ? : null} + {winnersCount > 1 ? ( + + ) : null} {participantsData && participantsData[0].votes_count > 0 ? (
{participantsData.map((participant, index) => - participant.votes_count === participantsData[0].votes_count ? ( + participant.votes_count === + participantsData[0].votes_count ? ( participant.url ? ( + key={v4()} + > { key={v4()} index={index} hasUrl={false} - voteStatus={voteStatus ? voteStatus : ''} + voteStatus={voteStatus ? voteStatus : ""} isFirst={index === 0 ? true : false} name={participant.title} progress={participant.votes_percents} @@ -295,12 +304,14 @@ const ParticipantsList = ({ vote_id }: IParams) => { winner={true} /> ) - ) : null, + ) : null )}
) : null} - {winnersCount > 1 ?
: null} + {winnersCount > 1 ? ( +
+ ) : null}
@@ -309,14 +320,15 @@ const ParticipantsList = ({ vote_id }: IParams) => { !hasVotes ? ( participant.url ? ( + key={v4()} + > { hasUrl={false} key={v4()} index={index} - voteStatus={voteStatus ? voteStatus : ''} + voteStatus={voteStatus ? voteStatus : ""} isFirst={index === 0 ? true : false} name={participant.title} progress={participant.votes_percents} @@ -346,17 +358,19 @@ const ParticipantsList = ({ vote_id }: IParams) => { /> ) ) : ( - participant.votes_count !== participantsData[0].votes_count && + participant.votes_count !== + participantsData[0].votes_count && (participant.url ? ( + key={v4()} + > { hasUrl={false} key={v4()} index={index} - voteStatus={voteStatus ? voteStatus : ''} + voteStatus={voteStatus ? voteStatus : ""} isFirst={index === 0 ? true : false} name={participant.title} progress={participant.votes_percents} @@ -385,7 +399,7 @@ const ParticipantsList = ({ vote_id }: IParams) => { winner={false} /> )) - ), + ) ) : null}
diff --git a/routes.ts b/routes.ts index 52bdf49..36f0095 100644 --- a/routes.ts +++ b/routes.ts @@ -11,6 +11,7 @@ export default { // Quiz ============================================================== getQuizQuestions: `/quiz/active`, getQuiz: (quiz_id: string) => `/quiz/${quiz_id}`, + getQuizUUID: (quiz_id: string) => `/quiz/uuid/${quiz_id}`, getQuizQuestionsWinners: (id: number) => `/quiz/${id}/winners`, getQuizNetijeWinners: (id: string) => `/quiz/${id}/netije`, getQuizQuestionHistory: (id: number) => `/question/${id}/history`, @@ -19,6 +20,7 @@ export default { // Votes ================================================================ allVotes: "/voting/show_on_site", vote: (vote_id: string) => `/voting/${vote_id}`, + voteUUID: (vote_id: string) => `/voting/uuid/${vote_id}`, // ====================================================================== // Lottery ================================================================