Compare commits
34 Commits
7b5db124da
...
effee2c557
| Author | SHA1 | Date |
|---|---|---|
|
|
effee2c557 | |
|
|
0f7e32d067 | |
|
|
bb70710c65 | |
|
|
875620b64e | |
|
|
b059d2afd6 | |
|
|
dd5590d4c8 | |
|
|
35704d99f4 | |
|
|
b6fcb92c8d | |
|
|
f37f4bd024 | |
|
|
52b75b5c19 | |
|
|
5fe9491f86 | |
|
|
ce90ebeb50 | |
|
|
3cbbdf21c4 | |
|
|
35fa202f61 | |
|
|
3b06c9baa9 | |
|
|
94a4df4ff2 | |
|
|
21187be32a | |
|
|
06ba1a923f | |
|
|
30dd824526 | |
|
|
ce4d7348d3 | |
|
|
fbda9a42f3 | |
|
|
abe1590dcb | |
|
|
37dd838a94 | |
|
|
cff0dd5371 | |
|
|
f6fddab1bf | |
|
|
a4eae0e528 | |
|
|
15ac1ca1c7 | |
|
|
40546c8a3d | |
|
|
87771711e2 | |
|
|
3a69358445 | |
|
|
6107dea9a5 | |
|
|
714932d3f3 | |
|
|
f035fd03cc | |
|
|
55cf8f593f |
|
|
@ -27,6 +27,7 @@ export async function authenticateLottery(phone: string, code: string) {
|
|||
|
||||
const result = await res.json();
|
||||
|
||||
console.log("Data fetched successfully " + res.status);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
|
@ -34,6 +35,6 @@ export async function authenticateLottery(phone: string, code: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const revalidateTagName = (tag: string) => {
|
||||
export const revalidateTagName = async (tag: string) => {
|
||||
revalidateTag(tag);
|
||||
};
|
||||
|
|
|
|||
157
api/queries.ts
157
api/queries.ts
|
|
@ -22,6 +22,7 @@ import { VideoModel } from "@/models/video.model";
|
|||
import { VideosModel } from "@/models/videos.model";
|
||||
import { IVote } from "@/models/vote.model";
|
||||
import routes from "@/routes";
|
||||
import { revalidateTag } from "next/cache";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export class Queries {
|
||||
|
|
@ -190,6 +191,18 @@ export class Queries {
|
|||
}).then((res) => res.json().then((res) => res as IQuizQuestions));
|
||||
}
|
||||
|
||||
public static async getQuizByUUID(quiz_id: string): Promise<IQuizQuestions> {
|
||||
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 },
|
||||
}).then((res) => res.json().then((res) => res));
|
||||
}
|
||||
|
||||
public static async getQuizHistory(
|
||||
id: number
|
||||
): Promise<IQuizQuestionsHistory> {
|
||||
|
|
@ -227,6 +240,12 @@ export class Queries {
|
|||
}).then((res) => res.json().then((res) => res as IVote));
|
||||
}
|
||||
|
||||
public static async getVoteByUUID(vote_id: string): Promise<IVote> {
|
||||
return await fetch(`${baseUrl.QUIZ_SRC}${routes.voteUUID(vote_id)}`, {
|
||||
next: { revalidate: 3600 },
|
||||
}).then((res) => res.json().then((res) => res as IVote));
|
||||
}
|
||||
|
||||
// ============================================================================================
|
||||
|
||||
// Sms ========================================================================================
|
||||
|
|
@ -270,7 +289,12 @@ export const getTossData = async ({
|
|||
id: string;
|
||||
}) => {
|
||||
try {
|
||||
const res = await fetch(`${baseUrl.QUIZ_SRC}${routes.tossId(type, id)}`);
|
||||
const res = await fetch(`${baseUrl.QUIZ_SRC}${routes.tossId(type, id)}`, {
|
||||
next: {
|
||||
revalidate: 300,
|
||||
tags: ["lotteryData"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return undefined;
|
||||
|
|
@ -282,3 +306,134 @@ export const getTossData = async ({
|
|||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const getQuizNetijeData = async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizNetijeWinners(id)}`,
|
||||
{
|
||||
next: {
|
||||
revalidate: 300,
|
||||
tags: ["netije"],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await res.json();
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const getNextQuizNetijeData = async (
|
||||
id: string,
|
||||
limit: number,
|
||||
offset: number
|
||||
) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizNetijeWinners(
|
||||
id
|
||||
)}?limit=${limit}&offset=${offset}`,
|
||||
{
|
||||
next: {
|
||||
revalidate: 300,
|
||||
tags: ["netije"],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await res.json();
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const getQuizWinnersById = async (id: number, step?: number) => {
|
||||
try {
|
||||
const res = step
|
||||
? await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
|
||||
id
|
||||
)}?tapgyr=${step}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
}
|
||||
)
|
||||
: await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(id)}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
return result as IQuizQuestionsWinners;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const getNextQuizWinnners = async (
|
||||
id: number,
|
||||
limit: number,
|
||||
offset: number,
|
||||
step?: number
|
||||
) => {
|
||||
try {
|
||||
const res = step
|
||||
? await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
|
||||
id
|
||||
)}?tapgyr=${step}&limit=${limit}&offset=${offset}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
}
|
||||
)
|
||||
: await fetch(
|
||||
`${baseUrl.QUIZ_SRC}${routes.getQuizQuestionsWinners(
|
||||
id
|
||||
)}?limit=${limit}&offset=${offset}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
console.log(result);
|
||||
|
||||
return result as IQuizQuestionsWinners;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
export const getPlaylistById = async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${baseUrl.MATERIALS_SRC}${routes.videos(
|
||||
`?per_page=8&page=1&category_id=${id}`
|
||||
)}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { getPlaylistById } from "@/api/queries";
|
||||
import PlaylistVideos from "@/components/playlist";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async ({ params }: IParams) => {
|
||||
const { id } = await params;
|
||||
|
||||
const videos = await getPlaylistById(id);
|
||||
|
||||
if (videos?.data?.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="video-item mt-6">
|
||||
<div className="container">
|
||||
<PlaylistVideos id={id} data={videos} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '@/components/Loader';
|
||||
import QuizQuestion from '@/components/quiz/QuizQuestion';
|
||||
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, Question } from '@/models/quizQuestions.model';
|
||||
import QuizProvider from '@/providers/QuizProvider';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
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 { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
|
|
@ -24,38 +25,90 @@ interface IParams {
|
|||
const page = ({ params }: IParams) => {
|
||||
const [quizFinished, setQuizFinished] = useState<boolean>(false);
|
||||
const [data, setData] = useState<IQuizQuestions>();
|
||||
|
||||
// const { data, error, isFetching } = useQuery(
|
||||
// ['quiz_questions'],
|
||||
// () => Queries.getQuizQuestions(),
|
||||
// {
|
||||
// keepPreviousData: true,
|
||||
// },
|
||||
// );
|
||||
const { active } = useQuizSearchActive();
|
||||
const { step, setStep } = useSteps();
|
||||
|
||||
useEffect(() => {
|
||||
const local_info = sessionStorage.getItem("TURKMENTV_QUIZ_INFO");
|
||||
|
||||
if (!params.quiz_id) {
|
||||
Queries.getQuizQuestions().then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1].status === 'closed'
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
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) {
|
||||
if (local_info) {
|
||||
if (
|
||||
JSON.parse(local_info)?.tab &&
|
||||
JSON.parse(local_info)?.uuid === res.data.uuid
|
||||
) {
|
||||
setStep(JSON.parse(local_info)?.tab);
|
||||
} else {
|
||||
setStep(res.data.steps[res.data.steps.length - 1].tapgyr);
|
||||
}
|
||||
} else {
|
||||
setStep(res.data.steps[res.data.steps.length - 1].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 {
|
||||
Queries.getQuiz(params.quiz_id).then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1]?.status === 'closed'
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
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) {
|
||||
if (local_info) {
|
||||
if (
|
||||
JSON.parse(local_info)?.tab &&
|
||||
JSON.parse(local_info)?.uuid === res.data.uuid
|
||||
) {
|
||||
setStep(JSON.parse(local_info)?.tab);
|
||||
} else {
|
||||
setStep(res.data.steps[res.data.steps.length - 1].tapgyr);
|
||||
}
|
||||
} else {
|
||||
setStep(res.data.steps[res.data.steps.length - 1].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)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const mobile = useMediaQuery('(max-width: 768px)');
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
sessionStorage.setItem(
|
||||
"TURKMENTV_QUIZ_INFO",
|
||||
JSON.stringify({
|
||||
uuid: data.data.uuid,
|
||||
tab: step,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [data, step]);
|
||||
|
||||
const mobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
if (data) {
|
||||
if (!data.data) {
|
||||
|
|
@ -70,7 +123,7 @@ const page = ({ params }: IParams) => {
|
|||
|
||||
return (
|
||||
<main className="pt-[60px] pb-[200px]">
|
||||
{typeof data !== 'string' ? (
|
||||
{typeof data !== "string" ? (
|
||||
<div className="container flex flex-col md:gap-[200px] gap-[80px]">
|
||||
<QuizProvider>
|
||||
<div className="flex flex-col gap-[100px]">
|
||||
|
|
@ -95,7 +148,7 @@ const page = ({ params }: IParams) => {
|
|||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={'banner'}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
|
|
@ -104,7 +157,7 @@ const page = ({ params }: IParams) => {
|
|||
) : (
|
||||
<Image
|
||||
src={data?.data.banner}
|
||||
alt={'banner'}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
|
|
@ -116,14 +169,48 @@ const page = ({ params }: IParams) => {
|
|||
</div>
|
||||
|
||||
{data?.data.rules && data.data.notes ? (
|
||||
<QuizTable rules={data?.data.rules} notes={data?.data.notes} />
|
||||
<QuizTable
|
||||
rules={data?.data.rules}
|
||||
notes={data?.data.notes}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.id && quizFinished ? <QuizSearch quizId={data?.data.id} /> : null}
|
||||
|
||||
<div className="flex flex-col md:gap-[160px] gap-[80px]">
|
||||
{data?.data ? (
|
||||
{data.data.has_steps !== 0 &&
|
||||
data.data.steps &&
|
||||
data.data.steps?.length > 0 && (
|
||||
<div className="flex flex-col gap-4 items-center w-full">
|
||||
<h1 className="text-textBlack md:text-[60px] leading-[100%] font-semibold">
|
||||
Gepleşik
|
||||
</h1>
|
||||
<div className="flex w-full md:w-1/2 gap-[10px]">
|
||||
{data.data.steps.map((item) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(item.tapgyr);
|
||||
}}
|
||||
key={item.tapgyr}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 ${
|
||||
step === item.tapgyr
|
||||
? "bg-lightPrimary text-white"
|
||||
: "bg-lightPrimaryContainer text-textLight"
|
||||
}`}
|
||||
>
|
||||
{item.tapgyr}
|
||||
</button>
|
||||
))}
|
||||
<Link
|
||||
href={`/quiz/${params.quiz_id}/results`}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 bg-lightPrimaryContainer text-center text-textLight`}
|
||||
>
|
||||
Netije
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data?.data && !active ? (
|
||||
<QuizQuestionList
|
||||
paramsId={params.quiz_id}
|
||||
initialQuestionsData={data}
|
||||
|
|
@ -132,11 +219,14 @@ const page = ({ params }: IParams) => {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{data?.data.id && (
|
||||
{data?.data.id && quizFinished && data.data.has_steps === 0 ? (
|
||||
<QuizSearch quizId={data?.data.id} />
|
||||
) : null}
|
||||
|
||||
{data?.data.id && data.data.has_steps === 0 && (
|
||||
<QuizWinnerTable
|
||||
smsNumber={data.data.sms_number}
|
||||
quizId={data?.data.id}
|
||||
quizFinished={quizFinished}
|
||||
questionsData={data.data.questions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
"use client";
|
||||
import { Queries } from "@/api/queries";
|
||||
import QuizHeader from "@/components/quiz/QuizHeader";
|
||||
import QuizResultsSearch from "@/components/quiz/QuizResultsSearch";
|
||||
import QuizResultsTabs from "@/components/quiz/QuizResultsTabs";
|
||||
import QuizTapgyrResults from "@/components/quiz/QuizTapgyrResults";
|
||||
import QuizTapgyrWinners from "@/components/quiz/QuizTapgyrWinners";
|
||||
import { Data } from "@/models/quizQuestions.model";
|
||||
import { useQuizResults } from "@/store/store";
|
||||
import { notFound } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
interface IParams {
|
||||
params: {
|
||||
quiz_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const Page = ({ params }: IParams) => {
|
||||
const [data, setData] = useState<Data>();
|
||||
const [tab, setTab] = useState<number | string>(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const { resultData, error } = useQuizResults();
|
||||
|
||||
useEffect(() => {
|
||||
const local_info = sessionStorage.getItem("TURKMENTV_QUIZ_RESULTS");
|
||||
if (!resultData.length && !error) {
|
||||
setLoading(true);
|
||||
Queries.getQuizById(params.quiz_id)
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
if (res.data.steps?.length) {
|
||||
if (local_info) {
|
||||
if (
|
||||
JSON.parse(local_info)?.tab &&
|
||||
JSON.parse(local_info)?.uuid === res.data.uuid
|
||||
) {
|
||||
setTab(JSON.parse(local_info)?.tab);
|
||||
} else {
|
||||
setTab(res.data?.steps[res.data?.steps.length - 1].tapgyr - 1);
|
||||
}
|
||||
} else {
|
||||
setTab(res.data?.steps[res.data?.steps.length - 1].tapgyr - 1);
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
notFound();
|
||||
});
|
||||
}
|
||||
}, [resultData, error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
sessionStorage.setItem(
|
||||
"TURKMENTV_QUIZ_RESULTS",
|
||||
JSON.stringify({
|
||||
uuid: data?.uuid,
|
||||
tab: tab,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [data, tab]);
|
||||
|
||||
return (
|
||||
<section className="container py-[40px]">
|
||||
<div className="flex flex-col w-full py-[40px] gap-[80px]">
|
||||
<QuizHeader data={data} />
|
||||
<QuizResultsSearch id={params.quiz_id} />
|
||||
<QuizResultsTabs
|
||||
steps={data?.steps ? data?.steps : []}
|
||||
tab={tab}
|
||||
setStep={setTab}
|
||||
loading={loading}
|
||||
/>
|
||||
{tab === "results" && (
|
||||
<QuizTapgyrResults
|
||||
id={params.quiz_id}
|
||||
steps={
|
||||
data?.steps ? data?.steps?.map((item) => String(item.tapgyr)) : []
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{data?.has_steps &&
|
||||
data.steps &&
|
||||
data.steps?.length > 0 &&
|
||||
tab !== "results" && (
|
||||
<QuizTapgyrWinners
|
||||
key={data.steps[Number(tab)].tapgyr}
|
||||
id={params.quiz_id}
|
||||
tapgyr={data.steps[Number(tab)].tapgyr}
|
||||
questions={data.steps[Number(tab)].questions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
'use client';
|
||||
import { Queries } from '@/api/queries';
|
||||
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 { IQuizQuestions, Question } from '@/models/quizQuestions.model';
|
||||
import QuizProvider from '@/providers/QuizProvider';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
"use client";
|
||||
import { Queries } from "@/api/queries";
|
||||
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 { IQuizQuestions, Question } from "@/models/quizQuestions.model";
|
||||
import QuizProvider from "@/providers/QuizProvider";
|
||||
import { Validator } from "@/utils/validator";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
const page = () => {
|
||||
const [quizFinished, setQuizFinished] = useState<boolean>(false);
|
||||
|
|
@ -18,19 +18,19 @@ const page = () => {
|
|||
useEffect(() => {
|
||||
Queries.getQuizQuestions().then((res) => {
|
||||
setData(res);
|
||||
res
|
||||
? res.data.questions[res.data.questions.length - 1]?.status === 'closed'
|
||||
res && res.data.questions
|
||||
? res.data.questions[res.data.questions.length - 1]?.status === "closed"
|
||||
? setQuizFinished(true)
|
||||
: setQuizFinished(false)
|
||||
: null;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const mobile = useMediaQuery('(max-width: 768px)');
|
||||
const mobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
return (
|
||||
<main className="pt-[60px] pb-[200px]">
|
||||
{typeof data !== 'string' ? (
|
||||
{typeof data !== "string" ? (
|
||||
<div className="container flex flex-col md:gap-[200px] gap-[80px]">
|
||||
<QuizProvider>
|
||||
<div className="flex flex-col gap-[100px]">
|
||||
|
|
@ -55,7 +55,7 @@ const page = () => {
|
|||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={'banner'}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
|
|
@ -64,7 +64,7 @@ const page = () => {
|
|||
) : (
|
||||
<Image
|
||||
src={data?.data.banner}
|
||||
alt={'banner'}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
|
|
@ -80,7 +80,9 @@ const page = () => {
|
|||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.id && quizFinished ? <QuizSearch quizId={data?.data.id} /> : null}
|
||||
{data?.data.id && quizFinished ? (
|
||||
<QuizSearch quizId={data?.data.id} />
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col md:gap-[160px] gap-[80px]">
|
||||
{data?.data ? (
|
||||
|
|
@ -96,9 +98,8 @@ const page = () => {
|
|||
) : null} */}
|
||||
{data?.data.id && (
|
||||
<QuizWinnerTable
|
||||
smsNumber={data.data.sms_number}
|
||||
quizId={data?.data.id}
|
||||
quizFinished={quizFinished}
|
||||
questionsData={data.data.questions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import QuizMainPage from "@/components/quiz/QuizMainPage";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
function page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<QuizMainPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default page;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import ParticipantsList from '@/components/vote/ParticipantsList';
|
||||
import VoteProvider from '@/providers/VoteProvider';
|
||||
import ParticipantsList from "@/components/vote/ParticipantsList";
|
||||
import VoteProvider from "@/providers/VoteProvider";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
|
|
@ -7,7 +8,9 @@ const page = () => {
|
|||
<div className="container">
|
||||
<VoteProvider>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<ParticipantsList />
|
||||
<Suspense>
|
||||
<ParticipantsList all />
|
||||
</Suspense>
|
||||
</div>
|
||||
</VoteProvider>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
"use client";
|
||||
import ParticipantsList from "@/components/vote/ParticipantsList";
|
||||
import VoteProvider from "@/providers/VoteProvider";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<main className="pt-[60px] pb-[120px]">
|
||||
<div className="container">
|
||||
<VoteProvider>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<Suspense>
|
||||
<ParticipantsList />
|
||||
</Suspense>
|
||||
</div>
|
||||
</VoteProvider>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
|
@ -1,42 +1,42 @@
|
|||
import localFont from 'next/font/local';
|
||||
import Script from 'next/script';
|
||||
import localFont from "next/font/local";
|
||||
import Script from "next/script";
|
||||
|
||||
import { Roboto } from 'next/font/google';
|
||||
import { Merriweather } from 'next/font/google';
|
||||
import { Merriweather_Sans } from 'next/font/google';
|
||||
import { Alexandria } from 'next/font/google';
|
||||
import { Roboto } from "next/font/google";
|
||||
import { Merriweather } from "next/font/google";
|
||||
import { Merriweather_Sans } from "next/font/google";
|
||||
import { Alexandria } from "next/font/google";
|
||||
|
||||
import 'swiper/swiper-bundle.css';
|
||||
import './globals.css';
|
||||
import QueryProvider from '@/providers/QueryProvider';
|
||||
import { HtmlContext } from 'next/dist/shared/lib/html-context.shared-runtime';
|
||||
import "swiper/swiper-bundle.css";
|
||||
import "./globals.css";
|
||||
import QueryProvider from "@/providers/QueryProvider";
|
||||
import { HtmlContext } from "next/dist/shared/lib/html-context.shared-runtime";
|
||||
// FONTS
|
||||
const aeroport = localFont({
|
||||
src: '../fonts/Aeroport.otf',
|
||||
variable: '--font-aeroport',
|
||||
src: "../fonts/Aeroport.otf",
|
||||
variable: "--font-aeroport",
|
||||
});
|
||||
const roboto = Roboto({
|
||||
subsets: ['latin'],
|
||||
weight: ['300', '400', '700'],
|
||||
variable: '--font-roboto',
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "700"],
|
||||
variable: "--font-roboto",
|
||||
});
|
||||
const mw = Merriweather({
|
||||
subsets: ['cyrillic', 'cyrillic-ext', 'latin', 'latin-ext'],
|
||||
weight: '700',
|
||||
variable: '--font-mw',
|
||||
subsets: ["cyrillic", "cyrillic-ext", "latin", "latin-ext"],
|
||||
weight: "700",
|
||||
variable: "--font-mw",
|
||||
});
|
||||
const mw_sans = Merriweather_Sans({
|
||||
subsets: ['cyrillic-ext', 'latin', 'latin-ext'],
|
||||
weight: ['300', '400', '700'],
|
||||
variable: '--font-mwsans',
|
||||
subsets: ["cyrillic-ext", "latin", "latin-ext"],
|
||||
weight: ["300", "400", "700"],
|
||||
variable: "--font-mwsans",
|
||||
});
|
||||
const alexandria = Alexandria({
|
||||
subsets: ['latin', 'latin-ext'],
|
||||
variable: '--font-alexandria',
|
||||
subsets: ["latin", "latin-ext"],
|
||||
variable: "--font-alexandria",
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
title: 'Turkmen TV',
|
||||
title: "Turkmen TV",
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
|
|
@ -47,12 +47,14 @@ export default function RootLayout({ children }: IProps) {
|
|||
return (
|
||||
<html
|
||||
lang="tm"
|
||||
className={`${aeroport.variable} ${mw.variable} ${roboto.variable} ${mw_sans.variable} ${alexandria.variable}`}>
|
||||
className={`${aeroport.variable} ${mw.variable} ${roboto.variable} ${mw_sans.variable} ${alexandria.variable}`}
|
||||
>
|
||||
<head>
|
||||
<link rel="icon" href="/logo.png" sizes="any" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;"></meta>
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;"
|
||||
></meta>
|
||||
</head>
|
||||
<body className="relative overflow-x-hidden">
|
||||
<QueryProvider>{children}</QueryProvider>
|
||||
|
|
@ -61,7 +63,8 @@ export default function RootLayout({ children }: IProps) {
|
|||
<Script
|
||||
id="ganalytics-import"
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-F2267QXY9T"></Script>
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-F2267QXY9T"
|
||||
></Script>
|
||||
<Script id="ganalytics-body">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import close from '@/public/close-white.svg';
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import GlobalContext from "@/context/GlobalContext";
|
||||
import close from "@/public/close-white.svg";
|
||||
const MobileMenu = () => {
|
||||
const path = usePathname() ?? '/';
|
||||
const path = usePathname() ?? "/";
|
||||
const { burgerOpen, setBurgerOpen } = useContext(GlobalContext).burgerContext;
|
||||
|
||||
const onClickCloseBurgerHandler = () => {
|
||||
|
|
@ -18,17 +18,20 @@ const MobileMenu = () => {
|
|||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setDropDownOpened(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Attach the event listener to the document
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
document.addEventListener("click", handleOutsideClick);
|
||||
|
||||
// Cleanup the event listener when the component unmounts
|
||||
return () => {
|
||||
document.removeEventListener('click', handleOutsideClick);
|
||||
document.removeEventListener("click", handleOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -39,10 +42,10 @@ const MobileMenu = () => {
|
|||
<div className="container">
|
||||
<div className="w-full h-screen flex flex-col gap-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href={'/'} className="logo">
|
||||
<Link href={"/"} className="logo">
|
||||
<Image
|
||||
priority
|
||||
src={'/logo.png'}
|
||||
src={"/logo.png"}
|
||||
alt="logo"
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
|
|
@ -53,7 +56,8 @@ const MobileMenu = () => {
|
|||
</Link>
|
||||
<div
|
||||
className="relative w-[24px] h-[24px] cursor-pointer "
|
||||
onClick={() => setBurgerOpen(false)}>
|
||||
onClick={() => setBurgerOpen(false)}
|
||||
>
|
||||
<Image src={close} fill alt="menu" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -69,41 +73,47 @@ const MobileMenu = () => {
|
|||
</li> */}
|
||||
<li>
|
||||
<Link
|
||||
href={'/treasury'}
|
||||
href={"/treasury"}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={
|
||||
path.includes('treasury') || path.includes('video/')
|
||||
? { color: '#FFAB48' }
|
||||
path.includes("treasury") || path.includes("video/")
|
||||
? { color: "#FFAB48" }
|
||||
: {}
|
||||
}>
|
||||
}
|
||||
>
|
||||
Hazyna
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/live'}
|
||||
href={"/live"}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('live') ? { color: '#FFAB48' } : {}}>
|
||||
style={path.includes("live") ? { color: "#FFAB48" } : {}}
|
||||
>
|
||||
Göni Ýaýlym
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/advert'}
|
||||
href={"/advert"}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="block text-3xl text-white transition-all font-roboto font-bold dark:text-white"
|
||||
style={path.includes('advert') ? { color: '#FFAB48' } : {}}>
|
||||
style={path.includes("advert") ? { color: "#FFAB48" } : {}}
|
||||
>
|
||||
Mahabat
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={'/contact_us'}
|
||||
href={"/contact_us"}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
style={path.includes('contact_us') ? { color: '#FFAB48' } : {}}
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all">
|
||||
style={
|
||||
path.includes("contact_us") ? { color: "#FFAB48" } : {}
|
||||
}
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all"
|
||||
>
|
||||
Habarlaşmak üçin
|
||||
</Link>
|
||||
</li>
|
||||
|
|
@ -111,7 +121,8 @@ const MobileMenu = () => {
|
|||
<div
|
||||
ref={dropdownRef}
|
||||
className="flex flex-col cursor-pointer relative"
|
||||
onClick={() => setDropDownOpened(!dropDownOpened)}>
|
||||
onClick={() => setDropDownOpened(!dropDownOpened)}
|
||||
>
|
||||
<div className="flex items-center gap-[8px]">
|
||||
<span className="block text-3xl text-white transition-all font-roboto font-bold">
|
||||
Interaktiw
|
||||
|
|
@ -123,8 +134,9 @@ const MobileMenu = () => {
|
|||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className={`${
|
||||
dropDownOpened ? '' : 'rotate-180'
|
||||
} transition-all ease-in duration-150`}>
|
||||
dropDownOpened ? "" : "rotate-180"
|
||||
} transition-all ease-in duration-150`}
|
||||
>
|
||||
<path
|
||||
d="M7.41 15.41L12 10.83L16.59 15.41L18 14L12 8L6 14L7.41 15.41Z"
|
||||
fill="#fff"
|
||||
|
|
@ -135,56 +147,68 @@ const MobileMenu = () => {
|
|||
<div
|
||||
className={` flex flex-col gap-4 rounded-[8px] transition-all duration-150 ${
|
||||
dropDownOpened
|
||||
? 'h-fit opacity-100 pointer-events-auto py-[16px] px-[24px]'
|
||||
: ' h-0 opacity-0 pointer-events-none'
|
||||
}`}>
|
||||
? "h-fit opacity-100 pointer-events-auto py-[16px] px-[24px]"
|
||||
: " h-0 opacity-0 pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
<Link
|
||||
href={'/quiz'}
|
||||
href={"/quiz"}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold "
|
||||
style={path.includes('quiz') ? { color: '#FFAB48' } : {}}
|
||||
style={
|
||||
path.includes("quiz") ? { color: "#FFAB48" } : {}
|
||||
}
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Bäsleşik
|
||||
</Link>
|
||||
<Link
|
||||
href={'/vote'}
|
||||
href={"/vote"}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold "
|
||||
style={path.includes('vote') ? { color: '#FFAB48' } : {}}
|
||||
style={
|
||||
path.includes("vote") ? { color: "#FFAB48" } : {}
|
||||
}
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Ses bermek
|
||||
</Link>
|
||||
<Link
|
||||
href={'https://shop.turkmentv.gov.tm/'}
|
||||
href={"https://shop.turkmentv.gov.tm/"}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold"
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
TV market
|
||||
</Link>
|
||||
<Link
|
||||
href={'/prizes/auth'}
|
||||
href={"/prizes/auth"}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold"
|
||||
style={path.includes('prizes') ? { color: '#FFAB48' } : {}}
|
||||
style={
|
||||
path.includes("prizes") ? { color: "#FFAB48" } : {}
|
||||
}
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Sowgatlar
|
||||
</Link>
|
||||
<Link
|
||||
href={'/lottery/auth'}
|
||||
href={"/b"}
|
||||
className="block text-2xl text-white transition-all font-roboto font-bold"
|
||||
style={path.includes('lottery') ? { color: '#FFAB48' } : {}}
|
||||
style={path.includes("b") ? { color: "#FFAB48" } : {}}
|
||||
onClick={() => {
|
||||
setDropDownOpened(false);
|
||||
onClickCloseBurgerHandler();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Bije
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -193,9 +217,10 @@ const MobileMenu = () => {
|
|||
<li>
|
||||
<Link
|
||||
target="_blank"
|
||||
href={'https://turkmentv.gov.tm/mahabat/admin/user/login'}
|
||||
href={"https://turkmentv.gov.tm/mahabat/admin/user/login"}
|
||||
onClick={() => onClickCloseBurgerHandler()}
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all p-2 bg-red-500 rounded-lg ">
|
||||
className="font-roboto font-bold text-white text-3xl dark:text-white transition-all p-2 bg-red-500 rounded-lg "
|
||||
>
|
||||
Ýaýlym tertibi
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { AiOutlineUser } from 'react-icons/ai';
|
||||
import ThemeSwitch from './home/ThemeSwitch';
|
||||
import { useContext, useEffect, useRef, useState } from 'react';
|
||||
import GlobalContext from '@/context/GlobalContext';
|
||||
import burger from '@/public/menu-outline.svg';
|
||||
|
|
@ -140,9 +138,9 @@ const Nav = () => {
|
|||
Sowgatlar
|
||||
</Link>
|
||||
<Link
|
||||
href={'/lottery/auth'}
|
||||
href={'/b'}
|
||||
className="block min-w-fit text-lg text-white transition-all font-roboto font-bold"
|
||||
style={path.includes('lottery') ? { color: '#FFAB48' } : {}}
|
||||
style={path.includes('b') ? { color: '#FFAB48' } : {}}
|
||||
onClick={() => setDropDownOpened(false)}>
|
||||
Bije
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -28,11 +28,13 @@ const VideoPlayer = ({ maxHeight, maxWidth, video_id }: IProps) => {
|
|||
queryKey: ["video", `video:${video_id}`],
|
||||
queryFn: async () => {
|
||||
const response = await Queries.getVideo(video_id);
|
||||
if (response.data.is_downloadable === 0) {
|
||||
setCanDownload(false);
|
||||
} else {
|
||||
setCanDownload(true);
|
||||
} // Set canDownload from API
|
||||
|
||||
// console.log(response);
|
||||
// if (response.data.is_downloadable == 1) {
|
||||
// setCanDownload(true);
|
||||
// } else {
|
||||
// setCanDownload(false);
|
||||
// } // Set canDownload from API
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
|
@ -67,13 +69,18 @@ const VideoPlayer = ({ maxHeight, maxWidth, video_id }: IProps) => {
|
|||
<div className="lg:w-[700px] md:w-[550px] w-full h-[200px] sm:h-[250px] md:h-[350px] lg:h-[420px]">
|
||||
<video
|
||||
controls
|
||||
controlsList={canDownload ? "" : "nodownload"} // Conditionally enable/disable download
|
||||
src={data!.data.video_stream_url}
|
||||
controlsList={
|
||||
data?.data.is_downloadable === 0 ? "nodownload" : ""
|
||||
} // Conditionally enable/disable download
|
||||
poster={data?.data.banner_url}
|
||||
playsInline
|
||||
itemType="video/mp4"
|
||||
onPlay={() => onPlayHandler()}
|
||||
></video>
|
||||
>
|
||||
<source
|
||||
src={data!.data.video_stream_url}
|
||||
type="video/mp4"
|
||||
onPlay={() => onPlayHandler()}
|
||||
/>
|
||||
</video>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 h-fit">
|
||||
|
|
@ -89,7 +96,9 @@ const VideoPlayer = ({ maxHeight, maxWidth, video_id }: IProps) => {
|
|||
</div>
|
||||
<audio
|
||||
controls
|
||||
controlsList={canDownload ? "" : "nodownload"} // Conditionally enable/disable download
|
||||
controlsList={
|
||||
data?.data.is_downloadable === 0 ? "nodownload" : ""
|
||||
} // Conditionally enable/disable download
|
||||
className="w-full rounded bg-white"
|
||||
onPlay={() => onPlayHandler()}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const Confetti = () => {
|
|||
const mobile = useMediaQuery("(max-width: 426px)");
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setRecycle(false), 30000);
|
||||
setTimeout(() => setRecycle(false), 10000);
|
||||
}, [recycle]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Image from 'next/image';
|
||||
import InfoDateAllert from '../common/InfoDateAllert';
|
||||
import Image from "next/image";
|
||||
import InfoDateAllert from "../common/InfoDateAllert";
|
||||
|
||||
interface LotteryHeaderProps {
|
||||
title: string;
|
||||
|
|
@ -9,7 +9,13 @@ interface LotteryHeaderProps {
|
|||
startDate: string;
|
||||
}
|
||||
|
||||
const LotteryHeader = ({ title, description, image, smsCode, startDate }: LotteryHeaderProps) => {
|
||||
const LotteryHeader = ({
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
smsCode,
|
||||
startDate,
|
||||
}: LotteryHeaderProps) => {
|
||||
return (
|
||||
<section className="container">
|
||||
<div className="flex flex-col md:gap-[32px] gap-[24px]">
|
||||
|
|
@ -17,7 +23,9 @@ const LotteryHeader = ({ title, description, image, smsCode, startDate }: Lotter
|
|||
<h1 className="sm:font-display-1-regular text-[32px] leading-[40px] text-center">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-center text-textLarge leading-textLarge">{description}</p>
|
||||
<p className="text-center text-textLarge leading-textLarge">
|
||||
{description}
|
||||
</p>
|
||||
<InfoDateAllert date={startDate} text="Senesi:" />
|
||||
</div>
|
||||
{image && (
|
||||
|
|
@ -27,7 +35,7 @@ const LotteryHeader = ({ title, description, image, smsCode, startDate }: Lotter
|
|||
width={1416}
|
||||
height={177}
|
||||
alt="banner"
|
||||
className="rounded-[12px] object-cover h-[177px]"
|
||||
className="rounded-[12px] h-[177px] object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,37 @@
|
|||
"use client";
|
||||
import LotteryHeader from "@/components/lottery/LotteryHeader";
|
||||
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
|
||||
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
|
||||
import Link from "next/link";
|
||||
import { authenticateLottery } from "@/api";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getLotteryStatus } from "@/lib/actions";
|
||||
import LotteryWinners from "./LotteryWinners";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
async function getData() {
|
||||
const cookieStore = await cookies();
|
||||
const phone = cookieStore.get("phoneNumber");
|
||||
const key = cookieStore.get("key");
|
||||
if (phone?.value && key?.value) {
|
||||
const res = await authenticateLottery(phone.value, key.value);
|
||||
return res;
|
||||
} else {
|
||||
redirect("/lottery/auth");
|
||||
}
|
||||
}
|
||||
const LotteryMain = () => {
|
||||
const [lotteryData, setLotteryData] = useState<any>();
|
||||
const router = useRouter();
|
||||
const mobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
const LotteryMain = async () => {
|
||||
const lotteryData = await getData();
|
||||
useEffect(() => {
|
||||
async function getData() {
|
||||
const phone = localStorage.getItem("phoneNumber");
|
||||
const key = localStorage.getItem("key");
|
||||
if (phone && key) {
|
||||
const res = await authenticateLottery(phone, key);
|
||||
setLotteryData(res);
|
||||
} else {
|
||||
localStorage.clear();
|
||||
router.push("/b/auth");
|
||||
}
|
||||
}
|
||||
|
||||
const status = await getLotteryStatus(
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const status = getLotteryStatus(
|
||||
lotteryData?.data.start_time,
|
||||
lotteryData?.data.end_time
|
||||
);
|
||||
|
|
@ -33,41 +41,45 @@ const LotteryMain = async () => {
|
|||
<h1 className="text-[50px]">{lotteryData.errorMessage}</h1>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
|
||||
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
|
||||
<LotteryHeader
|
||||
title={lotteryData.data.title}
|
||||
description={lotteryData.data.description}
|
||||
image={lotteryData.data.image}
|
||||
smsCode={lotteryData.data.sms_code}
|
||||
startDate={lotteryData.data.start_time}
|
||||
/>
|
||||
lotteryData?.data && (
|
||||
<div className="flex flex-col font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
|
||||
<div className="flex flex-col sm:gap-[64px] gap-[40px] pb-[80px]">
|
||||
<LotteryHeader
|
||||
title={lotteryData?.data.title}
|
||||
description={lotteryData?.data.description}
|
||||
image={
|
||||
mobile ? lotteryData?.data.image_mobile : lotteryData?.data.image
|
||||
}
|
||||
smsCode={lotteryData?.data.sms_code}
|
||||
startDate={lotteryData?.data.start_time}
|
||||
/>
|
||||
|
||||
{status === "Upcoming" && (
|
||||
<div className="container">
|
||||
<LotteryCountDown
|
||||
lotteryStatus={status}
|
||||
endDate={lotteryData.data.end_time}
|
||||
startDate={lotteryData.data.start_time}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<LotteryRulesSection data={lotteryData} />
|
||||
<div className="flex flex-col gap-10">
|
||||
<LotteryWinners data={lotteryData} lotteryStatus={status} />
|
||||
<div className="w-full">
|
||||
<div className="container">
|
||||
<Link
|
||||
href="/lottery/auth"
|
||||
className="sm:text-textLarge sm:leading-textLarge text-[16px] rounded-full leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center border-2 border-lightPrimary hover:bg-lightPrimary font-medium text-lightPrimary hover:text-lightOnPrimary disabled:opacity-50 transition-all duration-300"
|
||||
>
|
||||
Çykmak
|
||||
</Link>
|
||||
{status === "Upcoming" && (
|
||||
<div className="container">
|
||||
<LotteryCountDown
|
||||
lotteryStatus={status}
|
||||
endDate={lotteryData.data.end_time}
|
||||
startDate={lotteryData.data.start_time}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<LotteryRulesSection data={lotteryData} />
|
||||
<div className="flex flex-col gap-10 mt-[40px]">
|
||||
<LotteryWinners data={lotteryData} lotteryStatus={status} />
|
||||
<div className="w-full">
|
||||
<div className="container">
|
||||
<Link
|
||||
href="/b/auth"
|
||||
className="sm:text-textLarge sm:leading-textLarge text-[16px] rounded-full leading-[24px] sm:py-[12px] py-[8px] w-full flex justify-center items-center border-2 border-lightPrimary hover:bg-lightPrimary font-medium text-lightPrimary hover:text-lightOnPrimary disabled:opacity-50 transition-all duration-300"
|
||||
>
|
||||
Çykmak
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { useLotteryAuth } from "@/store/useLotteryAuth";
|
||||
import { LotteryWinnerDataSimplified } from "@/typings/lottery/lottery.types";
|
||||
import LotteryWinnersList from "./winners/LotteryWinnersList";
|
||||
import LotterySlotCounter from "./slotCounter/LotterySlotCounter";
|
||||
|
|
@ -83,10 +82,12 @@ const LotteryWinnersSection = ({ data }: { data: any }) => {
|
|||
setIsProcessing(true); // Lock processing
|
||||
const message = messageQueue[0]; // Get the first message in the queue
|
||||
|
||||
try {
|
||||
await handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error("Error processing message:", error);
|
||||
if (message?.winner_no) {
|
||||
try {
|
||||
await handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error("Error processing message:", error);
|
||||
}
|
||||
}
|
||||
|
||||
setMessageQueue((prevQueue) => prevQueue.slice(1)); // Remove the processed message from the queue
|
||||
|
|
@ -131,13 +132,13 @@ const LotteryWinnersSection = ({ data }: { data: any }) => {
|
|||
|
||||
<div className="container">
|
||||
<div
|
||||
className="flex flex-col items-center rounded-[32px] gap-[40px]"
|
||||
className="flex flex-col items-center rounded-[32px] gap-[60px] pt-[20px]"
|
||||
style={{
|
||||
background: "linear-gradient(180deg, #F0ECF4 0%, #E1E0FF 43.5%)",
|
||||
}}
|
||||
>
|
||||
<AnimatePresence>
|
||||
<div className="flex items-center justify-center w-full sm:min-h-[240px] pt-6">
|
||||
<div className="flex items-center justify-center w-full pt-6">
|
||||
{winnerSelectingStatus === "not-selected" ? (
|
||||
<AnimatedText
|
||||
key={topText}
|
||||
|
|
@ -187,7 +188,7 @@ const LotteryWinnersSection = ({ data }: { data: any }) => {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center sm:pb-[62px] pb-[32px] px-4">
|
||||
<div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center pb-[32px] px-4">
|
||||
{winners.length > 0 && <LotteryWinnersList winners={winners} />}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,10 +36,9 @@ const LotteryAuthForm = () => {
|
|||
if (response.errorMessage) {
|
||||
setError(response.errorMessage);
|
||||
} else {
|
||||
console.log(response);
|
||||
document.cookie = `phoneNumber=${phoneNumber};path=/`;
|
||||
document.cookie = `key=${key};path=/`;
|
||||
router.replace("/lottery");
|
||||
localStorage.setItem("phoneNumber", phoneNumber);
|
||||
localStorage.setItem("key", key);
|
||||
router.replace("/b");
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Telefon belgisi ýa-da açar nädogry");
|
||||
|
|
|
|||
|
|
@ -57,10 +57,10 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
|
|||
<div className="bg-lightPrimaryContainer sm:p-6 py-3 flex flex-col w-full md:gap-2 rounded-[12px] sm:gap-3 gap-0 text-lightOnPrimaryContainer">
|
||||
<h3 className="text-center md:font-heading-1-regular sm:text-[32px] sm:leading-[40px] text-[20px] leading-[28px] text-lightOnSurface">
|
||||
{status === "Ongoing"
|
||||
? "Bije dowam edýär"
|
||||
? "Çeklis dowam edýär"
|
||||
: status === "Finished"
|
||||
? "Bije tamamlandy"
|
||||
: "Bije"}
|
||||
? "Çeklisx tamamlandy"
|
||||
: null}
|
||||
</h3>
|
||||
{/* LotteryCountDown */}
|
||||
{status === "Upcoming" && (
|
||||
|
|
@ -69,9 +69,6 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
|
|||
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
|
||||
{timeLeft.hours}
|
||||
</h3>
|
||||
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
|
||||
sagat
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
{/* Dots */}
|
||||
|
|
@ -84,9 +81,6 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
|
|||
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
|
||||
{timeLeft.minutes}
|
||||
</h3>
|
||||
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
|
||||
minut
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
{/* Dots */}
|
||||
|
|
@ -99,22 +93,9 @@ const LotteryCountDown: React.FC<LotteryCountDownProps> = ({
|
|||
<h3 className="md:text-[80px] sm:text-[56px] text-[28px] md:leading-[88px] sm:leading-[64px] leading-[36px] -tracking-[1%]">
|
||||
{timeLeft.seconds}
|
||||
</h3>
|
||||
<h4 className="font-medium md:text-[20px] sm:text-[18px] text-[14px] sm:leading-[28px] leading-[20px] -tracking-[1%] text-lightOnSurfaceVariant">
|
||||
sekunt
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-center text-lightOnSurfaceVariant md:font-heading-1-regular md:text-[20px] sm:text-[18px] sm:leading-[28px] text-[14px] leading-[20px]">
|
||||
<span>
|
||||
{status === "Upcoming"
|
||||
? "- den başlar"
|
||||
: status === "Ongoing"
|
||||
? "girmek üçin aşakda kodyňyzy giriziň"
|
||||
: "netijeleri görmek üçin aşakda kodyňyzy giriziň"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { useWebsocketLottery } from "@/hooks/useWebSocketLottery";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface IProps {
|
||||
|
|
@ -19,11 +20,6 @@ const LotteryRulesSection = ({ show = true, data }: IProps) => {
|
|||
data ? data?.data?.bije_count : 0
|
||||
);
|
||||
|
||||
if (!data.errorMessage) {
|
||||
document.cookie = "phoneNumber=;path=/";
|
||||
document.cookie = "key=;path=/";
|
||||
}
|
||||
|
||||
// Subscribe to WebSocket messages
|
||||
useEffect(() => {
|
||||
const unsubscribe = subscribeToMessages((event) => {
|
||||
|
|
@ -62,46 +58,62 @@ const LotteryRulesSection = ({ show = true, data }: IProps) => {
|
|||
<section>
|
||||
<div className="container">
|
||||
<div className="flex flex-col md:gap-8 gap-6">
|
||||
<h2 className="md:font-heading-1-regular sm:text-[32px] text-[26px] sm:leading-[40px] leading-[34px]">
|
||||
Düzgünleri:
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div className="flex flex-col bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
|
||||
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
|
||||
{data?.data.rules?.map((item: any, i: number) => (
|
||||
<li className="font-small-regular" key={i}>
|
||||
{item.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div
|
||||
className={clsx(
|
||||
"grid gap-6",
|
||||
show ? "md:grid-cols-3" : "md:grid-cols-2"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<h2 className="md:font-heading-1-regular text-[22px] sm:leading-[40px] leading-[34px]">
|
||||
Düzgünleri:
|
||||
</h2>
|
||||
<div className="flex flex-1 flex-col bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
|
||||
<ul className="list-disc flex flex-col md:gap-4 gap-2 pl-[16px]">
|
||||
{data?.data.rules?.map((item: any, i: number) => (
|
||||
<li className="font-small-regular" key={i}>
|
||||
{item.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-lightSurfaceContainer flex flex-col gap-4 px-4 py-[12px] rounded-[12px]">
|
||||
<h1 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
|
||||
Umumy gatnaşyjylaryň sany:
|
||||
<div className="flex flex-col">
|
||||
<h1 className="md:font-heading-1-regular text-[22px] sm:leading-[40px] leading-[34px]">
|
||||
Gatnaşyjylaryň sany:
|
||||
</h1>
|
||||
<p className="text-[24px]">{totalParticipants}</p>
|
||||
<div className="bg-lightSurfaceContainer flex flex-1 items-center justify-center gap-4 px-4 py-[12px] rounded-[12px]">
|
||||
<p
|
||||
className={clsx(
|
||||
data?.data.rules?.length > 4
|
||||
? `text-[28px] sm:text-[56px] md:text-[120px]`
|
||||
: "text-[24px] md:text-[32px]", "font-bold"
|
||||
)}
|
||||
>
|
||||
{totalParticipants}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{show && (
|
||||
<div className="flex flex-col md:gap-4 sm:gap-2 gap-4 bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
|
||||
<h3 className="md:font-heading-5-regular sm:text-[20px] text-[18px] sm:leading-[24px] leading-[28px]">
|
||||
Siziň bijeli sanynyz:
|
||||
</h3>
|
||||
<ul className="flex flex-col items-center md:gap-4 gap-2">
|
||||
{data?.user_lottery_numbers.map((item: any, i: number) => (
|
||||
<li
|
||||
className="text-[24px] text-[#46464F] md:text-[48px] lg:text-[80px] list-none"
|
||||
key={i}
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{show && (
|
||||
<div className="flex flex-col">
|
||||
<h3 className="md:font-heading-1-regular text-[22px] sm:leading-[40px] leading-[34px]">
|
||||
Siziň bijeli sanyňyz:
|
||||
</h3>
|
||||
<ul className="flex flex-col flex-1 md:gap-4 gap-2 bg-lightSurfaceContainer sm:py-4 md:px-8 sm:px-6 py-3 px-4 rounded-[12px] w-full">
|
||||
{data?.user_lottery_numbers.map((item: any, i: number) => (
|
||||
<li
|
||||
className="text-[24px] text-[#46464F] list-none"
|
||||
key={i}
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface IProps {
|
||||
phone: string;
|
||||
|
|
@ -14,14 +14,14 @@ const LotteryWinner = ({ phone, ticket, isNew, winnerNumber }: IProps) => {
|
|||
<motion.div
|
||||
layout
|
||||
initial={isNew ? { opacity: 0, translateY: 20 } : false}
|
||||
animate={{ opacity: 1, translateY: 0 }}
|
||||
exit={{ opacity: 0, translateY: -20 }}
|
||||
animate={isNew ? { opacity: 1, translateY: 0 } : false}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="flex flex-col gap-2 md:pb-4 pb-3 border-b w-full border-[#CECCFF]">
|
||||
className="flex flex-col gap-2 md:pb-4 pb-3 border-b w-full border-[#CECCFF]"
|
||||
>
|
||||
<h4 className="md:font-heading-6-regular text-[20px] leading-[28px]">
|
||||
{/* The winner of the {winnerNumber} stage: */}
|
||||
{winnerNumber}
|
||||
{' - nji ýeňiji'}
|
||||
{" - nji ýeňiji"}
|
||||
</h4>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="md:font-base-medium font-base-regular">{phone}</p>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,285 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import aydym from "@/public/aydym-com.webp";
|
||||
import horjun from "@/public/horjun.png";
|
||||
import belet from "@/public/belet.jpg";
|
||||
import { v4 } from "uuid";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Queries } from "@/api/queries";
|
||||
import Loader from "../Loader";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
const PlaylistVideos = ({ id, data }: { id: string; data: any }) => {
|
||||
const searchParams = useSearchParams();
|
||||
const videoId = searchParams.get("video");
|
||||
const nextVideo = (Number(videoId) + 1) % data?.data?.length;
|
||||
|
||||
return (
|
||||
<div className="video-item-inner">
|
||||
<div className="video-item-wrapper flex flex-col md:flex-row md:items-start items-center gap-10 relative pb-14 w-full">
|
||||
<InfoBlock
|
||||
video_id={data?.data[Number(videoId)].id}
|
||||
nextId={nextVideo}
|
||||
/>
|
||||
|
||||
<div className="video-item-inner flex flex-col gap-4 grow-0">
|
||||
{data?.data.map(
|
||||
(item: any, i: number) =>
|
||||
i !== Number(videoId) && (
|
||||
<Link href={`/playlist/${id}?video=${i}`} key={v4()}>
|
||||
<VideoItem
|
||||
id={item.id}
|
||||
img={item.banner_url}
|
||||
title={item.title}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaylistVideos;
|
||||
|
||||
const InfoBlock = ({
|
||||
video_id,
|
||||
nextId,
|
||||
}: {
|
||||
video_id: number;
|
||||
nextId: number;
|
||||
}) => {
|
||||
const { data, isFetching, error } = useQuery({
|
||||
queryKey: ["video", video_id],
|
||||
queryFn: () => Queries.getVideo(video_id),
|
||||
});
|
||||
const router = useRouter();
|
||||
const pathName = usePathname();
|
||||
|
||||
if (isFetching)
|
||||
return (
|
||||
<div className="w-full h-[500px] sm:h-[667px] md:h-[600px] l flex items-center justify-center">
|
||||
<Loader height={700} />
|
||||
</div>
|
||||
);
|
||||
if (error) return <h1>{JSON.stringify(error)}</h1>;
|
||||
|
||||
return (
|
||||
<div className="flex gap-6 flex-1 w-full justify-between h-full">
|
||||
<div className="flex flex-col gap-6 w-full h-full">
|
||||
<div className="flex justify-between gap-[32px] w-full h-full">
|
||||
<div className="w-full flex flex-col gap-6 h-full">
|
||||
<div className="flex flex-col gap-[40px] h-full w-full">
|
||||
<div className="flex gap-[40px] flex-col h-full w-full">
|
||||
<div className="w-full">
|
||||
<VideoPlayer content={data?.data} nextId={nextId} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{data?.data?.desc ? (
|
||||
<p className="font-roboto text-lg w-full text-black">
|
||||
{data?.data.desc}
|
||||
</p>
|
||||
) : null}
|
||||
<Button
|
||||
onClick={() => router.push(`${pathName}?video=${nextId}`)}
|
||||
className="w-fit bg-blue-500 text-white flex gap-[5px] items-center"
|
||||
>
|
||||
<span>Indiki</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
className="lucide lucide-play"
|
||||
>
|
||||
<polygon points="6 3 20 12 6 21 6 3" />
|
||||
</svg>
|
||||
</Button>
|
||||
|
||||
{data?.data?.aydym_com_url ||
|
||||
data?.data?.horjun_content_url ||
|
||||
data?.data?.belet_url ? (
|
||||
<div className="flex flex-col gap-6">
|
||||
<h2 className="text-2xl font-semibold">
|
||||
Beýleki platformalarda seret:
|
||||
</h2>
|
||||
<div className="flex gap-11 items-center">
|
||||
{data?.data.aydym_com_url ? (
|
||||
<Link
|
||||
href={data?.data.aydym_com_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image
|
||||
src={aydym}
|
||||
alt="Aydym.com"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<h3>Aydym.com</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
{data?.data.horjun_content_url ? (
|
||||
<Link
|
||||
href={data?.data.horjun_content_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image
|
||||
src={horjun}
|
||||
alt="HorjunTv"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<h3>HorjunTv</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
{data?.data.belet_url ? (
|
||||
<Link
|
||||
href={data?.data.belet_url}
|
||||
target="_blank"
|
||||
className="flex flex-col items-center justify-center gap-2"
|
||||
>
|
||||
<div className="relative h-16 w-16 flex items-center justify-center overflow-hidden border-[#00AEFF] border-[1.5px] p-2 rounded-full">
|
||||
<Image
|
||||
src={belet}
|
||||
alt="BeletTv"
|
||||
className="rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<h3>BeletFilm</h3>
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VideoPlayer = ({ content, nextId }: { content: any; nextId: number }) => {
|
||||
const [hasWindow, setHasWindow] = useState<boolean>(false);
|
||||
const [data, setData] = useState<any>(content);
|
||||
const router = useRouter();
|
||||
const pathName = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
setHasWindow(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setData(content);
|
||||
}, [content]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
{hasWindow ? (
|
||||
data?.content_url.endsWith(".mp4") ? (
|
||||
<div className="lg:w-full md:w-[550px] w-full h-[200px] sm:h-[250px] md:h-[350px] lg:h-[620px]">
|
||||
<video
|
||||
controls
|
||||
controlsList={data?.is_downloadable === 0 ? "nodownload" : ""} // Conditionally enable/disable download
|
||||
poster={data?.banner_url}
|
||||
playsInline
|
||||
autoPlay={nextId !== 1}
|
||||
onEnded={() => router.push(`${pathName}?video=${nextId}`)}
|
||||
>
|
||||
<source src={data?.video_stream_url} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 h-fit">
|
||||
<div className="relative w-full h-[200px] sm:h-[400px] md:h-[420px]">
|
||||
{data?.banner_url ? (
|
||||
<Image
|
||||
src={data.banner_url}
|
||||
fill
|
||||
alt={"image"}
|
||||
className="object-cover"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<audio
|
||||
controls
|
||||
controlsList={data?.is_downloadable === 0 ? "nodownload" : ""} // Conditionally enable/disable download
|
||||
className="w-full rounded bg-white"
|
||||
onEnded={() => router.push(`${pathName}?video=${nextId}`)}
|
||||
autoPlay={nextId !== 1}
|
||||
>
|
||||
<source src={data?.content_url} />
|
||||
</audio>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VideoItem = ({
|
||||
img,
|
||||
title,
|
||||
id,
|
||||
}: {
|
||||
img: any;
|
||||
title: string;
|
||||
id: number;
|
||||
}) => {
|
||||
return (
|
||||
<div className="video-list-item flex flex-col gap-2 lg:w-[250px] md:w-[200px] sm:w-[200px] w-[300px] ">
|
||||
{img ? (
|
||||
<>
|
||||
<div className="relative sm:h-[150px] h-[200px] w-full overflow-hidden rounded-five">
|
||||
<Image
|
||||
src={img}
|
||||
alt={title}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="top-0 left-0 w-full h-full object-cover z-0 absolute pointer-events-none"
|
||||
width={280}
|
||||
height={160}
|
||||
/>
|
||||
<Image
|
||||
src={"/play.svg"}
|
||||
alt={"play icon"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
className="top-[50%] left-[50%] w-[52px] h-[52px] object-cover z-10 -translate-x-[50%] -translate-y-[50%] absolute"
|
||||
width={52}
|
||||
height={52}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* {premium ? <Premium /> : null} */}
|
||||
<div className="flex gap-3 justify-start items-center">
|
||||
<div className="flex flex-col">
|
||||
<h3 className="clamped font-mw_sans font-bold text-lg text-black transition-all dark:text-white overflow-hidden w-full">
|
||||
{title}
|
||||
</h3>
|
||||
{/* <span className="font-roboto text-lg font-normal text-black transition-all dark:text-white">{`${views} Views`}</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Queries } from '@/api/queries';
|
||||
import Loader from '../Loader';
|
||||
import { IQuizQuestionsHistory } from '@/models/quizQuestionHistory.model';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import { v4 } from 'uuid';
|
||||
import QuizContext from '@/context/QuizContext';
|
||||
"use client";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Queries } from "@/api/queries";
|
||||
import Loader from "../Loader";
|
||||
import { IQuizQuestionsHistory } from "@/models/quizQuestionHistory.model";
|
||||
import { Validator } from "@/utils/validator";
|
||||
import { v4 } from "uuid";
|
||||
import QuizContext from "@/context/QuizContext";
|
||||
|
||||
type TProps = {
|
||||
finished: string;
|
||||
|
|
@ -15,13 +15,19 @@ type TProps = {
|
|||
|
||||
const QuizAccordion = ({ finished, questionId }: TProps) => {
|
||||
const [data, setData] = useState<IQuizQuestionsHistory>();
|
||||
const [opened, setOpened] = useState<boolean>(finished === 'closed' ? false : true);
|
||||
const [opened, setOpened] = useState<boolean>(
|
||||
finished === "closed" ? false : true
|
||||
);
|
||||
|
||||
const { quizSearchData } = useContext(QuizContext).quizSearchContext;
|
||||
const { searchActive } = useContext(QuizContext).quizSearchActiveContext;
|
||||
|
||||
useEffect(() => {
|
||||
if (quizSearchData && Object.values(quizSearchData.data).length != 0 && searchActive) {
|
||||
if (
|
||||
quizSearchData &&
|
||||
Object.values(quizSearchData.data).length != 0 &&
|
||||
searchActive
|
||||
) {
|
||||
setOpened(true);
|
||||
} else {
|
||||
setOpened(false);
|
||||
|
|
@ -56,10 +62,14 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
<>
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}>
|
||||
<div className="flex bg-fillTableHead md:p-0 gap-3 md:gap-0 border-b border-fillTableStrokeTableHead p-[8px] w-full">
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div
|
||||
onClick={() => setOpened(!opened)}
|
||||
className="flex bg-fillTableHead md:p-0 gap-3 md:gap-0 border-b border-fillTableStrokeTableHead p-[8px] w-full cursor-pointer"
|
||||
>
|
||||
<div className="block text-[12px] sm:text-base text-textBlack leading-[125%] font-semibold max-w-[15px] sm:max-w-[100px] w-full sm:px-6 sm:py-5">
|
||||
<span>№</span>
|
||||
</div>
|
||||
|
|
@ -83,9 +93,12 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
{data.data.map((user, id) => (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow last:border-none ${
|
||||
id % 2 === 0 ? ' bg-fillTableRow' : 'bg-fillTableRow2'
|
||||
id % 2 === 0
|
||||
? " bg-fillTableRow"
|
||||
: "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<div className="block text-base text-textBlack leading-[125%] max-w-[100px] w-[100%] px-6 py-5">
|
||||
<span>{id + 1}</span>
|
||||
</div>
|
||||
|
|
@ -111,9 +124,12 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
{data.data.map((user, id) => (
|
||||
<div
|
||||
className={`flex items-center justify-center flex-col border-b border-fillTableStrokeTableRow last:border-none p-[8px] gap-1 ${
|
||||
id % 2 === 0 ? ' bg-fillTableRow' : 'bg-fillTableRow2'
|
||||
id % 2 === 0
|
||||
? " bg-fillTableRow"
|
||||
: "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex gap-[12px] w-full items-center">
|
||||
<div className="block text-[12px] text-textBlack leading-[125%] max-w-[15px] w-full">
|
||||
<span>{id + 1}</span>
|
||||
|
|
@ -147,9 +163,12 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
<div key={v4()}>
|
||||
<div
|
||||
className={`sm:flex border-b border-fillTableStrokeTableRow last:border-none hidden ${
|
||||
answerId % 2 === 0 ? ' bg-fillTableRow' : 'bg-fillTableRow2'
|
||||
answerId % 2 === 0
|
||||
? " bg-fillTableRow"
|
||||
: "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<div className="block text-base leading-[125%] max-w-[100px] w-[100%] px-6 py-5">
|
||||
{answer.serial_number_for_correct != null ? (
|
||||
<span className="text-fillGreen">
|
||||
|
|
@ -177,12 +196,16 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
|
||||
<div
|
||||
className={`sm:hidden flex items-center justify-center flex-col border-b border-fillTableStrokeTableRow last:border-none p-[8px] gap-1 ${
|
||||
answerId % 2 === 0 ? ' bg-fillTableRow' : 'bg-fillTableRow2'
|
||||
answerId % 2 === 0
|
||||
? " bg-fillTableRow"
|
||||
: "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex gap-[12px] w-full items-center">
|
||||
<div className="block text-[12px] leading-[125%] max-w-[15px] w-full">
|
||||
{answer.serial_number_for_correct != null ? (
|
||||
{answer.serial_number_for_correct !=
|
||||
null ? (
|
||||
<span className="text-fillGreen">
|
||||
{answer.serial_number_for_correct}
|
||||
</span>
|
||||
|
|
@ -204,12 +227,14 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="text-[12px] text-textDarkt leading-[125%] max-w-[289px] w-[100%] text-center">
|
||||
<span>Wagty: {Validator.parseDate(answer.dt)}</span>
|
||||
<span>
|
||||
Wagty: {Validator.parseDate(answer.dt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: null,
|
||||
: null
|
||||
)
|
||||
: null}
|
||||
</motion.div>
|
||||
|
|
@ -217,12 +242,15 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
)}
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
{finished === 'closed' ? (
|
||||
{finished === "closed" ? (
|
||||
<button
|
||||
onClick={() => setOpened(!opened)}
|
||||
className={`w-full ${
|
||||
opened ? 'bg-fillRed text-white' : 'bg-fillButtonLowContrastDefault text-textDarkt'
|
||||
} flex items-center justify-center text-xs md:text-base uppercase leading-[150%] p-[8px] md:py-5 gap-[5px] md:gap-[10px] transition-all delay-[0.2s] font-medium `}>
|
||||
opened
|
||||
? "bg-fillRed text-white"
|
||||
: "bg-fillButtonLowContrastDefault text-textDarkt"
|
||||
} flex items-center justify-center text-xs md:text-base uppercase leading-[150%] p-[8px] md:py-5 gap-[5px] md:gap-[10px] transition-all delay-[0.2s] font-medium `}
|
||||
>
|
||||
Netijeler
|
||||
<div>
|
||||
<svg
|
||||
|
|
@ -231,10 +259,11 @@ const QuizAccordion = ({ finished, questionId }: TProps) => {
|
|||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
className="w-[20px] h-[20px] md:w-[24px] md:h-[24px]">
|
||||
className="w-[20px] h-[20px] md:w-[24px] md:h-[24px]"
|
||||
>
|
||||
<path
|
||||
d="M7.41 15.41L12 10.83L16.59 15.41L18 14L12 8L6 14L7.41 15.41Z"
|
||||
fill={`${opened ? '#fff' : '#636370'}`}
|
||||
fill={`${opened ? "#fff" : "#636370"}`}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
"use client";
|
||||
import { Data } from "@/models/quizQuestions.model";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
const QuizHeader = ({ data }: { data: Data | undefined }) => {
|
||||
return (
|
||||
<header className="flex flex-col gap-[40px]">
|
||||
<article>
|
||||
<span className="text-[#46464F]">{data?.date}</span>
|
||||
<h1 className="text-[44px] font-[500] text-[#1B1B21] leading-10">
|
||||
<span className="md:hidden">Bäsleşik </span>
|
||||
<span className="hidden md:inline">"Bäsleşigiň" netijeleri</span>
|
||||
</h1>
|
||||
<p className="text-[#46464F] mt-[8px]">{data?.description}</p>
|
||||
</article>
|
||||
{(data?.banner || data?.banner_mobile) && (
|
||||
<>
|
||||
{data.banner_mobile && (
|
||||
<div className="w-full relative bg-[#E1E0FF] h-[100px] rounded-[8px] overflow-hidden md:hidden object-cover">
|
||||
<Image
|
||||
src={data.banner_mobile ? data.banner_mobile : data.banner}
|
||||
alt="banner"
|
||||
unselectable="off"
|
||||
className="object-cover"
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full h-[100px] hidden md:block bg-[#E1E0FF] rounded-[8px] relative overflow-hidden">
|
||||
<Image
|
||||
src={data.banner}
|
||||
alt="banner"
|
||||
unselectable="off"
|
||||
className="object-cover"
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuizHeader;
|
||||
|
|
@ -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 QuizMainPage = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const [quizFinished, setQuizFinished] = useState<boolean>(false);
|
||||
const [data, setData] = useState<IQuizQuestions>();
|
||||
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 (
|
||||
<main className="h-full py-[200px]">
|
||||
<div className="container">
|
||||
<GradientTitle title={data?.message} size="big" />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-[60px] pb-[200px]">
|
||||
{typeof data !== "string" ? (
|
||||
<div className="container flex flex-col md:gap-[200px] gap-[80px]">
|
||||
<QuizProvider>
|
||||
<div className="flex flex-col gap-[100px]">
|
||||
<div className="flex flex-col gap-[45px]">
|
||||
<div className="flex flex-col gap-[10px] md:gap-[5px]">
|
||||
<h3 className="text-base md:text-[14px] text-textLight font-semibold md:font-normal">
|
||||
{data ? Validator.reveseDate(data?.data.date) : null}
|
||||
</h3>
|
||||
<h1 className="text-textBlack text-[32px] md:text-[60px] leading-[100%] font-semibold">
|
||||
{data?.data.title}
|
||||
</h1>
|
||||
<h3 className="text-base font-medium leading-[125%] md:text-[14px] text-textDarkt mt-[5px] max-w-[600px]">
|
||||
{data?.data.description}
|
||||
</h3>
|
||||
</div>
|
||||
{data?.data.banner ? (
|
||||
<div className="relative w-full md:min-h-[150px] md:h-auto h-[100px]">
|
||||
{mobile ? (
|
||||
<Image
|
||||
src={
|
||||
data.data.banner_mobile !== null
|
||||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={data?.data.banner}
|
||||
alt={"banner"}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
fill
|
||||
className="rounded-[8px]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{data?.data.rules && data.data.notes ? (
|
||||
<QuizTable
|
||||
rules={data?.data.rules}
|
||||
notes={data?.data.notes}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:gap-[160px] gap-[80px]">
|
||||
{data.data.has_steps !== 0 &&
|
||||
data.data.steps &&
|
||||
data.data.steps?.length > 0 && (
|
||||
<div className="flex flex-col gap-4 items-center w-full">
|
||||
<h1 className="text-textBlack md:text-[60px] leading-[100%] font-semibold">
|
||||
Tapgyr
|
||||
</h1>
|
||||
<div className="flex w-full md:w-1/2 gap-[10px]">
|
||||
{data.data.steps.map((item) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(item.tapgyr);
|
||||
}}
|
||||
key={item.tapgyr}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 ${
|
||||
step === item.tapgyr
|
||||
? "bg-lightPrimary text-white"
|
||||
: "bg-lightPrimaryContainer text-textLight"
|
||||
}`}
|
||||
>
|
||||
{item.tapgyr}
|
||||
</button>
|
||||
))}
|
||||
<Link
|
||||
href={`/quiz/${data.data.id}/results`}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 bg-lightPrimaryContainer text-center text-textLight`}
|
||||
>
|
||||
Netije
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data?.data && !active ? (
|
||||
<QuizQuestionList
|
||||
paramsId={String(data.data.id)}
|
||||
initialQuestionsData={data}
|
||||
setQuizFinished={setQuizFinished}
|
||||
quizFinished={quizFinished}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{data?.data.id && quizFinished && data.data.has_steps === 0 ? (
|
||||
<QuizSearch quizId={data?.data.id} />
|
||||
) : null}
|
||||
|
||||
{data?.data.id && data.data.has_steps === 0 && (
|
||||
<QuizWinnerTable
|
||||
quizId={data?.data.id}
|
||||
questionsData={data.data.questions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</QuizProvider>
|
||||
</div>
|
||||
) : (
|
||||
<div className="container text-[40px] flex items-center justify-center font-bold text-textLight min-h-[30vh]">
|
||||
Непредвиденная ошибка. Нет активной викторины.
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<main className="h-full py-[200px]">
|
||||
<div className="container">
|
||||
<Loader />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default QuizMainPage;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useContext } from 'react';
|
||||
import QuizAccordion from './QuizAccordion';
|
||||
import { Validator } from '@/utils/validator';
|
||||
import QuizContext from '@/context/QuizContext';
|
||||
import { v4 } from 'uuid';
|
||||
import React, { useContext } from "react";
|
||||
import QuizAccordion from "./QuizAccordion";
|
||||
import { Validator } from "@/utils/validator";
|
||||
import QuizContext from "@/context/QuizContext";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
type TProps = {
|
||||
finished: string;
|
||||
|
|
@ -15,26 +15,26 @@ type TProps = {
|
|||
};
|
||||
|
||||
const numbers = [
|
||||
'Birinji sowal',
|
||||
'Ikinji sowal',
|
||||
'Üçünji sowal',
|
||||
'Dördünji sowal',
|
||||
'Bäşinji sowal',
|
||||
'Altynjy sowal',
|
||||
'Ýedinji sowal',
|
||||
'Sekizinji sowal',
|
||||
'Dokuzynjy sowal',
|
||||
'Onunjy sowal',
|
||||
'On Birinji sowal',
|
||||
'On Ikinji sowal',
|
||||
'On Üçünji sowal',
|
||||
'On Dördünji sowal',
|
||||
'On Bäşinji sowal',
|
||||
'On Altynji sowal',
|
||||
'On Ýeddi sowal',
|
||||
'On Sekiznji sowal',
|
||||
'On Dokuzunji sowal',
|
||||
'Ýigrinci sowal',
|
||||
"Birinji sowal",
|
||||
"Ikinji sowal",
|
||||
"Üçünji sowal",
|
||||
"Dördünji sowal",
|
||||
"Bäşinji sowal",
|
||||
"Altynjy sowal",
|
||||
"Ýedinji sowal",
|
||||
"Sekizinji sowal",
|
||||
"Dokuzynjy sowal",
|
||||
"Onunjy sowal",
|
||||
"On Birinji sowal",
|
||||
"On Ikinji sowal",
|
||||
"On Üçünji sowal",
|
||||
"On Dördünji sowal",
|
||||
"On Bäşinji sowal",
|
||||
"On Altynji sowal",
|
||||
"On Ýeddi sowal",
|
||||
"On Sekiznji sowal",
|
||||
"On Dokuzunji sowal",
|
||||
"Ýigrinci sowal",
|
||||
];
|
||||
|
||||
const QuizQuestion = ({
|
||||
|
|
@ -51,20 +51,21 @@ const QuizQuestion = ({
|
|||
Object.values(quizSearchData.data).map((userQuestion, id) =>
|
||||
userQuestion.question_id === questionId ? (
|
||||
<div className="flex flex-col gap-[20px]" key={v4()}>
|
||||
<div className="flex flex-col gap-[30px] max-w-[700px]">
|
||||
<div className="flex flex-col gap-[30px]">
|
||||
<div className="flex flex-col gap-[20px] md:gap-[10px]">
|
||||
<h2 className="text-[24px] md:text-[32px] leading-[100%] font-semibold text-textBlack text-center md:text-left">
|
||||
{numbers.map((number, id) => (id === questionNumber ? number : ''))}:
|
||||
<h2 className="text-[24px] md:text-[32px] leading-[120%] font-semibold text-textBlack">
|
||||
{question}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start md:gap-5 gap-[10px]">
|
||||
<div className="flex flex-wrap md:gap-5 gap-[10px]">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div
|
||||
className={`w-[18px] h-[18px] md:w-4 md:h-4 ${
|
||||
finished === 'closed' ? 'bg-fillRed' : 'bg-fillGreen'
|
||||
} rounded-full`}></div>
|
||||
finished === "closed" ? "bg-fillRed" : "bg-fillGreen"
|
||||
} rounded-full`}
|
||||
></div>
|
||||
<span className="text-textLight text-sm md:text-base">
|
||||
{finished === 'closed' ? 'ýapyk' : 'açyk'}
|
||||
{finished === "closed" ? "ýapyk" : "açyk"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
|
|
@ -73,14 +74,16 @@ const QuizQuestion = ({
|
|||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M7.99967 13.3334C10.933 13.3334 13.333 10.9334 13.333 8.00004C13.333 5.06671 10.933 2.66671 7.99967 2.66671C5.06634 2.66671 2.66634 5.06671 2.66634 8.00004C2.66634 10.9334 5.06634 13.3334 7.99967 13.3334ZM7.99967 1.33337C11.6663 1.33337 14.6663 4.33337 14.6663 8.00004C14.6663 11.6667 11.6663 14.6667 7.99967 14.6667C4.33301 14.6667 1.33301 11.6667 1.33301 8.00004C1.33301 4.33337 4.33301 1.33337 7.99967 1.33337ZM10.1997 10.8L9.33301 11.3334L7.33301 7.86671V4.66671H8.33301V7.60004L10.1997 10.8Z"
|
||||
fill="#878799"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-textLight text-sm md:text-base">
|
||||
{Validator.parseDate(startsAt)}-den {Validator.parseDate(endsAt)}-e çenli
|
||||
{Validator.parseDate(startsAt)}-den{" "}
|
||||
{Validator.parseDate(endsAt)}-e çenli
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-[5px]">
|
||||
|
|
@ -89,7 +92,8 @@ const QuizQuestion = ({
|
|||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M9 5.25C8.5875 5.25 8.25 5.5875 8.25 6V8.25H6C5.5875 8.25 5.25 8.5875 5.25 9C5.25 9.4125 5.5875 9.75 6 9.75H8.25V12C8.25 12.4125 8.5875 12.75 9 12.75C9.4125 12.75 9.75 12.4125 9.75 12V9.75H12C12.4125 9.75 12.75 9.4125 12.75 9C12.75 8.5875 12.4125 8.25 12 8.25H9.75V6C9.75 5.5875 9.4125 5.25 9 5.25ZM9 1.5C4.8675 1.5 1.5 4.8675 1.5 9C1.5 13.1325 4.8675 16.5 9 16.5C13.1325 16.5 16.5 13.1325 16.5 9C16.5 4.8675 13.1325 1.5 9 1.5ZM9 15C5.6925 15 3 12.3075 3 9C3 5.6925 5.6925 3 9 3C12.3075 3 15 5.6925 15 9C15 12.3075 12.3075 15 9 15Z"
|
||||
fill="#878799"
|
||||
|
|
@ -101,33 +105,31 @@ const QuizQuestion = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-base md:text-xl text-textDarkt italic md:text-start text-center">
|
||||
«{question}»
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{finished === 'closed' ? (
|
||||
{finished === "closed" ? (
|
||||
<QuizAccordion finished={finished} questionId={questionId} />
|
||||
) : null}
|
||||
</div>
|
||||
) : null,
|
||||
) : null
|
||||
)
|
||||
) : (
|
||||
<div className="flex flex-col gap-[20px]">
|
||||
<div className="flex flex-col gap-[30px] max-w-[700px]">
|
||||
<div className="flex flex-col gap-[30px]">
|
||||
<div className="flex flex-col gap-[20px] md:gap-[10px]">
|
||||
<h2 className="text-[24px] md:text-[32px] leading-[100%] font-semibold text-textBlack text-center md:text-left">
|
||||
{numbers.map((number, id) => (id === questionNumber ? number : ''))}:
|
||||
<h2 className="text-[18px] md:text-[32px] leading-[120%] font-semibold text-textBlack">
|
||||
{question}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-start md:gap-5 gap-[10px]">
|
||||
<div className="flex flex-wrap md:gap-5 gap-[10px]">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div
|
||||
className={`w-[18px] h-[18px] md:w-4 md:h-4 ${
|
||||
finished === 'closed' ? 'bg-fillRed' : 'bg-fillGreen'
|
||||
} rounded-full`}></div>
|
||||
finished === "closed" ? "bg-fillRed" : "bg-fillGreen"
|
||||
} rounded-full`}
|
||||
></div>
|
||||
<span className="text-textLight text-sm md:text-base">
|
||||
{finished === 'closed' ? 'ýapyk' : 'açyk'}
|
||||
{finished === "closed" ? "ýapyk" : "açyk"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
|
|
@ -136,14 +138,16 @@ const QuizQuestion = ({
|
|||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M7.99967 13.3334C10.933 13.3334 13.333 10.9334 13.333 8.00004C13.333 5.06671 10.933 2.66671 7.99967 2.66671C5.06634 2.66671 2.66634 5.06671 2.66634 8.00004C2.66634 10.9334 5.06634 13.3334 7.99967 13.3334ZM7.99967 1.33337C11.6663 1.33337 14.6663 4.33337 14.6663 8.00004C14.6663 11.6667 11.6663 14.6667 7.99967 14.6667C4.33301 14.6667 1.33301 11.6667 1.33301 8.00004C1.33301 4.33337 4.33301 1.33337 7.99967 1.33337ZM10.1997 10.8L9.33301 11.3334L7.33301 7.86671V4.66671H8.33301V7.60004L10.1997 10.8Z"
|
||||
fill="#878799"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-textLight text-sm md:text-base">
|
||||
{Validator.parseDate(startsAt)}-den {Validator.parseDate(endsAt)}-e çenli
|
||||
{Validator.parseDate(startsAt)}-den{" "}
|
||||
{Validator.parseDate(endsAt)}-e çenli
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-[5px]">
|
||||
|
|
@ -152,7 +156,8 @@ const QuizQuestion = ({
|
|||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M9 5.25C8.5875 5.25 8.25 5.5875 8.25 6V8.25H6C5.5875 8.25 5.25 8.5875 5.25 9C5.25 9.4125 5.5875 9.75 6 9.75H8.25V12C8.25 12.4125 8.5875 12.75 9 12.75C9.4125 12.75 9.75 12.4125 9.75 12V9.75H12C12.4125 9.75 12.75 9.4125 12.75 9C12.75 8.5875 12.4125 8.25 12 8.25H9.75V6C9.75 5.5875 9.4125 5.25 9 5.25ZM9 1.5C4.8675 1.5 1.5 4.8675 1.5 9C1.5 13.1325 4.8675 16.5 9 16.5C13.1325 16.5 16.5 13.1325 16.5 9C16.5 4.8675 13.1325 1.5 9 1.5ZM9 15C5.6925 15 3 12.3075 3 9C3 5.6925 5.6925 3 9 3C12.3075 3 15 5.6925 15 9C15 12.3075 12.3075 15 9 15Z"
|
||||
fill="#878799"
|
||||
|
|
@ -164,12 +169,11 @@ const QuizQuestion = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-base md:text-xl text-textDarkt italic md:text-start text-center">
|
||||
«{question}»
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{finished === 'closed' ? <QuizAccordion finished={finished} questionId={questionId} /> : null}
|
||||
{finished === "closed" ? (
|
||||
<QuizAccordion finished={finished} questionId={questionId} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
'use client';
|
||||
import QuizQuestion from './QuizQuestion';
|
||||
import { Queries } from '@/api/queries';
|
||||
import { v4 } from 'uuid';
|
||||
import { Dispatch, useContext, useEffect, useState } from 'react';
|
||||
import { IQuizQuestions, Question } from '@/models/quizQuestions.model';
|
||||
import QuizContext from '@/context/QuizContext';
|
||||
"use client";
|
||||
import QuizQuestion from "./QuizQuestion";
|
||||
import { Queries } from "@/api/queries";
|
||||
import { v4 } from "uuid";
|
||||
import { Dispatch, useContext, useEffect, useState } from "react";
|
||||
import { IQuizQuestions, Question } from "@/models/quizQuestions.model";
|
||||
import QuizContext from "@/context/QuizContext";
|
||||
import { useSteps } from "@/store/store";
|
||||
|
||||
interface IProps {
|
||||
setQuizFinished: Dispatch<boolean>;
|
||||
|
|
@ -19,32 +20,38 @@ const QuizQuestionList = ({
|
|||
initialQuestionsData,
|
||||
paramsId,
|
||||
}: IProps) => {
|
||||
const [data, setData] = useState<IQuizQuestions>(initialQuestionsData);
|
||||
const { quizSearchData } = useContext(QuizContext).quizSearchContext;
|
||||
const { setQuestionsData } = useContext(QuizContext).quizQuestionsContext;
|
||||
const { step } = useSteps();
|
||||
const [questionData, setQuestionsData] = useState<Question[] | undefined>(
|
||||
initialQuestionsData.data.questions
|
||||
? initialQuestionsData.data.questions
|
||||
: initialQuestionsData.data.steps &&
|
||||
initialQuestionsData.data.steps.length > 0
|
||||
? initialQuestionsData.data.steps[0].questions
|
||||
: []
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Queries.getQuizQuestions().then((res) => setData(res));
|
||||
|
||||
setQuestionsData(initialQuestionsData.data.questions);
|
||||
|
||||
// data?.data.questions.map((question) =>
|
||||
// question.status === 'active' || question.status === 'new'
|
||||
// ? setQuizFinished(false)
|
||||
// : setQuizFinished(true),
|
||||
// );
|
||||
|
||||
if (paramsId && !quizFinished) {
|
||||
const interval = setInterval(() => {
|
||||
Queries.getQuiz(paramsId).then((res) => {
|
||||
setData(res);
|
||||
setQuestionsData(res.data.questions);
|
||||
|
||||
res.data.questions.map((question) =>
|
||||
question.status === 'active' || question.status === 'new'
|
||||
? setQuizFinished(false)
|
||||
: setQuizFinished(true),
|
||||
);
|
||||
if (res.data.questions) {
|
||||
setQuestionsData(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) {
|
||||
setQuestionsData(res.data.steps[0].questions);
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 60000);
|
||||
return () => clearInterval(interval);
|
||||
|
|
@ -53,66 +60,47 @@ const QuizQuestionList = ({
|
|||
if (!paramsId && !quizFinished) {
|
||||
const interval = setInterval(() => {
|
||||
Queries.getQuizQuestions().then((res) => {
|
||||
setData(res);
|
||||
setQuestionsData(res.data.questions);
|
||||
|
||||
res.data.questions.map((question) =>
|
||||
question.status === 'active' || question.status === 'new'
|
||||
? setQuizFinished(false)
|
||||
: setQuizFinished(true),
|
||||
);
|
||||
if (res.data.questions) {
|
||||
setQuestionsData(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) {
|
||||
setQuestionsData(res.data.steps[0].questions);
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// const isActive = data?.data.questions.some(
|
||||
// (question) => question.status === 'active' || question.status === 'new',
|
||||
// );
|
||||
|
||||
// data.data.questions.map((question) =>
|
||||
// question.status === 'active' || question.status === 'new'
|
||||
// ? setQuizFinished(false)
|
||||
// : setQuizFinished(true),
|
||||
// );
|
||||
}, 60000);
|
||||
return () => clearInterval(interval);
|
||||
|
||||
// Queries.getQuizQuestions().then((res) => {
|
||||
// setData(res);
|
||||
// setQuestionsData(res.data.questions);
|
||||
// setSmsNumber(res.data.sms_number);
|
||||
|
||||
// res.data.questions.map((question) =>
|
||||
// question.status === 'active' || question.status === 'new'
|
||||
// ? setQuizFinished(false)
|
||||
// : setQuizFinished(true),
|
||||
// );
|
||||
// });
|
||||
}
|
||||
}, [quizFinished]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialQuestionsData.data.steps) {
|
||||
const tapgyrQuestions = initialQuestionsData.data.steps.find(
|
||||
(item) => item.tapgyr === step
|
||||
);
|
||||
setQuestionsData(tapgyrQuestions?.questions);
|
||||
}
|
||||
}, [step]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[40px] md:gap-[160px]">
|
||||
{data && !quizSearchData ? (
|
||||
data.data.questions.map((question, paramsId) =>
|
||||
question.status !== 'new' ? (
|
||||
<QuizQuestion
|
||||
score={question.score}
|
||||
questionId={question.id}
|
||||
questionNumber={paramsId}
|
||||
finished={question.status}
|
||||
question={question.question}
|
||||
key={v4()}
|
||||
startsAt={question.starts_at}
|
||||
endsAt={question.ends_at}
|
||||
/>
|
||||
) : null,
|
||||
)
|
||||
) : quizSearchData && Object.values(quizSearchData.data).length === 0 ? (
|
||||
{quizSearchData && Object.values(quizSearchData.data).length === 0 ? (
|
||||
<h2 className="text-textDarkt text-center text-[28px] ms:text-[32px] flex items-center justify-center bg-fillBGBlockbg px-[20px] py-[40px] md:px-[40px] md:py-[80px] rounded-[24px]">
|
||||
Вы не участвовали ни в одном вопросе
|
||||
</h2>
|
||||
) : data ? (
|
||||
data.data.questions.map((question, id) =>
|
||||
question.status !== 'new' ? (
|
||||
) : questionData ? (
|
||||
questionData.map((question, id) =>
|
||||
question.status !== "new" ? (
|
||||
<QuizQuestion
|
||||
score={question.score}
|
||||
questionId={question.id}
|
||||
|
|
@ -123,7 +111,7 @@ const QuizQuestionList = ({
|
|||
startsAt={question.starts_at}
|
||||
endsAt={question.ends_at}
|
||||
/>
|
||||
) : null,
|
||||
) : null
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
"use client";
|
||||
import { useQuizResults, useResultsLoading } from "@/store/store";
|
||||
import { X } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const QuizResultsSearch = ({ id }: { id: string }) => {
|
||||
const [phone, setPhone] = useState<string>("");
|
||||
const { setError, setResultData, error, resultData } = useQuizResults();
|
||||
const { setLoading } = useResultsLoading();
|
||||
|
||||
const handleSearchSubmit = async (event: any) => {
|
||||
if (event.key === "Enter" && phone.length === 8) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await fetch(
|
||||
`https://sms.turkmentv.gov.tm/api/quiz/${id}/search_netije`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ phone }),
|
||||
}
|
||||
);
|
||||
|
||||
// Handle the response as needed
|
||||
const data = await response.json();
|
||||
setLoading(false);
|
||||
if (!data.error) {
|
||||
setResultData([data.data]);
|
||||
} else {
|
||||
setResultData([]);
|
||||
setError("Telefon belgisi tapylmady");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full items-center gap-4">
|
||||
<h1 className="text-[28px] leading-[120%] font-semibold text-textBlack text-center md:text-left">
|
||||
Öz jogaplaryňyzy görüň
|
||||
</h1>
|
||||
<div className="flex items-center gap-[14px] relative md:w-1/2 w-full">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="absolute left-4"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.93853 0.608964C5.90914 0.206926 6.94943 0 8 0C9.05058 0 10.0909 0.206926 11.0615 0.608964C12.0321 1.011 12.914 1.60028 13.6569 2.34315C14.3997 3.08601 14.989 3.96793 15.391 4.93853C15.7931 5.90914 16 6.94943 16 8C16 9.05057 15.7931 10.0909 15.391 11.0615C15.1172 11.7226 14.7565 12.3425 14.3196 12.9054L19.7071 18.2929C20.0976 18.6834 20.0976 19.3166 19.7071 19.7071C19.3166 20.0976 18.6834 20.0976 18.2929 19.7071L12.9054 14.3196C12.3425 14.7565 11.7226 15.1172 11.0615 15.391C10.0909 15.7931 9.05057 16 8 16C6.94943 16 5.90914 15.7931 4.93853 15.391C3.96793 14.989 3.08601 14.3997 2.34315 13.6569C1.60028 12.914 1.011 12.0321 0.608964 11.0615C0.206926 10.0909 0 9.05058 0 8C0 6.94942 0.206926 5.90914 0.608964 4.93853C1.011 3.96793 1.60028 3.08601 2.34315 2.34315C3.08601 1.60028 3.96793 1.011 4.93853 0.608964ZM8 2C7.21207 2 6.43185 2.15519 5.7039 2.45672C4.97595 2.75825 4.31451 3.20021 3.75736 3.75736C3.20021 4.31451 2.75825 4.97595 2.45672 5.7039C2.15519 6.43185 2 7.21207 2 8C2 8.78793 2.15519 9.56815 2.45672 10.2961C2.75825 11.0241 3.20021 11.6855 3.75736 12.2426C4.31451 12.7998 4.97595 13.2417 5.7039 13.5433C6.43185 13.8448 7.21207 14 8 14C8.78793 14 9.56815 13.8448 10.2961 13.5433C11.0241 13.2417 11.6855 12.7998 12.2426 12.2426C12.7998 11.6855 13.2417 11.0241 13.5433 10.2961C13.8448 9.56815 14 8.78793 14 8C14 7.21207 13.8448 6.43185 13.5433 5.7039C13.2417 4.97595 12.7998 4.31451 12.2426 3.75736C11.6855 3.20021 11.0241 2.75825 10.2961 2.45672C9.56815 2.15519 8.78793 2 8 2Z"
|
||||
fill="#46464F"
|
||||
/>
|
||||
</svg>
|
||||
<input
|
||||
type="tel"
|
||||
className="text-[#46464F] focus:outline-[#1B1B21] flex-1 bg-[#E4E1E9] rounded-[12px] py-4 pr-4 pl-12 w-full"
|
||||
placeholder="63879809"
|
||||
required
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
onKeyDown={(e) => handleSearchSubmit(e)}
|
||||
maxLength={8}
|
||||
minLength={8}
|
||||
/>
|
||||
{phone && (
|
||||
<button
|
||||
className="absolute right-4"
|
||||
onClick={() => {
|
||||
setPhone("");
|
||||
if (resultData.length || error) {
|
||||
setResultData([]);
|
||||
setError("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuizResultsSearch;
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { Question } from "@/models/quizQuestions.model";
|
||||
import React from "react";
|
||||
|
||||
interface IQuizTabProps {
|
||||
steps: {
|
||||
tapgyr: number;
|
||||
questions: Question[];
|
||||
}[];
|
||||
setStep: React.Dispatch<React.SetStateAction<number | string>>;
|
||||
tab: number | string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
function QuizResultsTabs({ steps, setStep, tab, loading }: IQuizTabProps) {
|
||||
return (
|
||||
!loading && (
|
||||
<div className="flex gap-[10px] w-full md:w-1/2 self-center">
|
||||
{steps.map((item) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(item.tapgyr - 1);
|
||||
}}
|
||||
key={item.tapgyr}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 ${
|
||||
tab === item.tapgyr - 1
|
||||
? "bg-lightPrimary text-white"
|
||||
: "bg-lightPrimaryContainer text-textLight"
|
||||
}`}
|
||||
>
|
||||
{item.tapgyr}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep("results");
|
||||
}}
|
||||
className={`flex-1 py-[5px] rounded-lg transition-all duration-300 ${
|
||||
tab === "results"
|
||||
? "bg-lightPrimary text-white"
|
||||
: "bg-lightPrimaryContainer text-textLight"
|
||||
}`}
|
||||
>
|
||||
Netije
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default QuizResultsTabs;
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
'use client';
|
||||
import QuizContext from '@/context/QuizContext';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ChangeEvent, FormEvent, useContext, useState } from 'react';
|
||||
"use client";
|
||||
import QuizContext from "@/context/QuizContext";
|
||||
import { useQuizSearchActive } from "@/store/store";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ChangeEvent, FormEvent, useContext, useState } from "react";
|
||||
|
||||
const QuizSearch = ({ quizId }: { quizId: number }) => {
|
||||
const [phone, setPhone] = useState<string>('');
|
||||
const [phone, setPhone] = useState<string>("");
|
||||
const { setActive } = useQuizSearchActive();
|
||||
|
||||
const { setSearchActive } = useContext(QuizContext).quizSearchActiveContext;
|
||||
|
||||
|
|
@ -12,8 +14,9 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
|
||||
const handleCleanSearch = () => {
|
||||
setQuizSearchData(undefined);
|
||||
setPhone('');
|
||||
setPhone("");
|
||||
setSearchActive(false);
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -21,7 +24,7 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
};
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (event.key === "Enter") {
|
||||
// Prevent the default form submission behavior
|
||||
event.preventDefault();
|
||||
// Call the function to handle the form submission
|
||||
|
|
@ -29,23 +32,27 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleSearchSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleSearchSubmit = async (
|
||||
event: React.FormEvent<HTMLFormElement>
|
||||
) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://sms.turkmentv.gov.tm/api/quiz/${quizId}/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ phone }),
|
||||
});
|
||||
const response = await fetch(
|
||||
`https://sms.turkmentv.gov.tm/api/quiz/${quizId}/search`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ phone }),
|
||||
}
|
||||
);
|
||||
|
||||
// Handle the response as needed
|
||||
|
||||
const data = await response.json();
|
||||
setQuizSearchData(data);
|
||||
setSearchActive(true);
|
||||
setActive(true);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
|
|
@ -57,9 +64,10 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
<div className="max-w-[500px] w-full flex flex-col gap-[10px] items-center ">
|
||||
<form
|
||||
onSubmit={handleSearchSubmit}
|
||||
className="bg-fillFormRest flex border w-full rounded-xl shadow-quizButton">
|
||||
className="bg-fillFormRest flex border w-full rounded-xl shadow-quizButton"
|
||||
>
|
||||
<div className="relative rounded-lg rounded-e-none w-full">
|
||||
{' '}
|
||||
{" "}
|
||||
<input
|
||||
type="tel"
|
||||
className="block w-full h-full rounded-lg rounded-e-none py-3 px-2 md:px-4 text-sm md:text-base leading-[150%] text-textCaptioninform bg-transparent"
|
||||
|
|
@ -79,13 +87,15 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
onClick={handleCleanSearch}>
|
||||
onClick={handleCleanSearch}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M11.9998 13.4L7.0998 18.3C6.91647 18.4834 6.68314 18.575 6.3998 18.575C6.11647 18.575 5.88314 18.4834 5.6998 18.3C5.51647 18.1167 5.4248 17.8834 5.4248 17.6C5.4248 17.3167 5.51647 17.0834 5.6998 16.9L10.5998 12L5.6998 7.10005C5.51647 6.91672 5.4248 6.68338 5.4248 6.40005C5.4248 6.11672 5.51647 5.88338 5.6998 5.70005C5.88314 5.51672 6.11647 5.42505 6.3998 5.42505C6.68314 5.42505 6.91647 5.51672 7.0998 5.70005L11.9998 10.6L16.8998 5.70005C17.0831 5.51672 17.3165 5.42505 17.5998 5.42505C17.8831 5.42505 18.1165 5.51672 18.2998 5.70005C18.4831 5.88338 18.5748 6.11672 18.5748 6.40005C18.5748 6.68338 18.4831 6.91672 18.2998 7.10005L13.3998 12L18.2998 16.9C18.4831 17.0834 18.5748 17.3167 18.5748 17.6C18.5748 17.8834 18.4831 18.1167 18.2998 18.3C18.1165 18.4834 17.8831 18.575 17.5998 18.575C17.3165 18.575 17.0831 18.4834 16.8998 18.3L11.9998 13.4Z"
|
||||
fill="#BCBCD6"
|
||||
|
|
@ -97,13 +107,15 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
</div>
|
||||
<button
|
||||
className="bg-fillButtonAccentDefault px-4 py-3 rounded-lg rounded-l-none"
|
||||
type="submit">
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none">
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M9.5 3C11.2239 3 12.8772 3.68482 14.0962 4.90381C15.3152 6.12279 16 7.77609 16 9.5C16 11.11 15.41 12.59 14.44 13.73L14.71 14H15.5L20.5 19L19 20.5L14 15.5V14.71L13.73 14.44C12.5505 15.4468 11.0507 15.9999 9.5 16C7.77609 16 6.12279 15.3152 4.90381 14.0962C3.68482 12.8772 3 11.2239 3 9.5C3 7.77609 3.68482 6.12279 4.90381 4.90381C6.12279 3.68482 7.77609 3 9.5 3ZM9.5 5C7 5 5 7 5 9.5C5 12 7 14 9.5 14C12 14 14 12 14 9.5C14 7 12 5 9.5 5Z"
|
||||
fill="#fff"
|
||||
|
|
@ -112,7 +124,7 @@ const QuizSearch = ({ quizId }: { quizId: number }) => {
|
|||
</button>
|
||||
</form>
|
||||
<h4 className="text-sm md:text-base text-textLight">
|
||||
Her soragyň aşagynda siziň ugradan jogaplaryňyz görkeziler{' '}
|
||||
Her soragyň aşagynda siziň ugradan jogaplaryňyz görkeziler{" "}
|
||||
</h4>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
"use client";
|
||||
import { getNextQuizNetijeData, getQuizNetijeData } from "@/api/queries";
|
||||
import { Datum, ISearchNetije } from "@/models/quizQuestionsWinners.model";
|
||||
import { notFound } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Loader from "../Loader";
|
||||
import { useQuizResults, useResultsLoading } from "@/store/store";
|
||||
|
||||
const padding = "py-4";
|
||||
|
||||
const QuizTapgyrResults = ({ id, steps }: { id: string; steps: string[] }) => {
|
||||
const [data, setData] = useState<Datum[] | ISearchNetije[]>([]);
|
||||
const { error, resultData } = useQuizResults();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const searchLoading = useResultsLoading((state) => state.loading);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [nextPageQueries, setQueries] = useState<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
}>({
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
async function getData(next?: boolean) {
|
||||
if (next) {
|
||||
const res = await getNextQuizNetijeData(
|
||||
id,
|
||||
nextPageQueries.limit,
|
||||
nextPageQueries.offset
|
||||
);
|
||||
if (res) {
|
||||
setQueries({
|
||||
limit: res.per_page,
|
||||
offset: nextPageQueries.offset + res.per_page,
|
||||
});
|
||||
setData([...data, ...res.data]);
|
||||
}
|
||||
} else {
|
||||
setLoading(true);
|
||||
const res = await getQuizNetijeData(id);
|
||||
if (res) {
|
||||
setTotal(res.total);
|
||||
setQueries({
|
||||
limit: res.per_page,
|
||||
offset: res.per_page,
|
||||
});
|
||||
setData(res.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!resultData.length && !error) {
|
||||
getData();
|
||||
} else if (resultData.length) {
|
||||
setData(resultData);
|
||||
setTotal(0);
|
||||
} else if (error) {
|
||||
setTotal(0);
|
||||
setData([]);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
return (
|
||||
<article className="flex flex-col gap-[24px]">
|
||||
<header className="flex justify-center">
|
||||
<h1 className="text-[28px] text-[#1B1B21] md:text-[36px] text-center md:text-start">
|
||||
Netijeler
|
||||
</h1>
|
||||
</header>
|
||||
{data.length > 0 && !loading && !searchLoading ? (
|
||||
<div className="flex flex-col bg-[#F6F2FA] rounded-[12px] overflow-hidden max-w-[700px] self-center w-full">
|
||||
{/* Table Head */}
|
||||
<div className="flex justify-between bg-[#EAE7EF] p-[20px]">
|
||||
{((data[0] as Datum).client?.id ||
|
||||
(data[0] as ISearchNetije).place) && (
|
||||
<span className={`${padding} max-w-[20px] w-full text-center`}>
|
||||
Ýeri
|
||||
</span>
|
||||
)}
|
||||
{((data[0] as Datum)?.client?.phone || data[0].phone) && (
|
||||
<span className={`${padding} max-w-[150px] w-full text-center`}>
|
||||
Telefon beligisi
|
||||
</span>
|
||||
)}
|
||||
{((data[0] as Datum).correct_answers_time ||
|
||||
(data[0] as ISearchNetije).total_nobat) && (
|
||||
<span
|
||||
className={`${padding} min-w-[50px] text-center md:min-w-[115px] max-w-[150px] w-full flex justify-center`}
|
||||
>
|
||||
Nobatlaryň jemi
|
||||
</span>
|
||||
)}
|
||||
{((data[0] as Datum).total_score_of_client ||
|
||||
(data[0] as ISearchNetije).total_score) && (
|
||||
<span
|
||||
className={`${padding} min-w-[50px] text-center md:min-w-[115px] max-w-[150px] w-full flex justify-center`}
|
||||
>
|
||||
Utuklaryň jemi
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Table body */}
|
||||
<div className="text-[#46464F]">
|
||||
{data.map((winner, id) => (
|
||||
<React.Fragment key={id}>
|
||||
<div
|
||||
key={id}
|
||||
className={`${
|
||||
id !== data.length - 1 && "border-b border-[#C7C5D0]"
|
||||
} flex justify-between items-center px-[20px]`}
|
||||
>
|
||||
<span
|
||||
className={`${padding} max-w-[20px] w-full text-center`}
|
||||
>
|
||||
{id > 0 &&
|
||||
(winner as Datum).correct_answers_time ===
|
||||
(data[id - 1] as Datum).correct_answers_time
|
||||
? id
|
||||
: (winner as ISearchNetije).place
|
||||
? (winner as ISearchNetije).place
|
||||
: id + 1}
|
||||
</span>
|
||||
<span>
|
||||
+
|
||||
{(winner as Datum).client?.phone
|
||||
? (winner as Datum).client?.phone
|
||||
: (winner as ISearchNetije).phone
|
||||
? (winner as ISearchNetije).phone
|
||||
: "-"}
|
||||
</span>
|
||||
<div
|
||||
className={`min-w-[50px] md:min-w-[115px] max-w-[150px] w-full flex flex-col md:flex-row justify-center ${padding}`}
|
||||
>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
|
||||
{(winner as Datum).correct_answers_time
|
||||
? (winner as Datum).correct_answers_time
|
||||
: (winner as ISearchNetije).total_nobat
|
||||
? (winner as ISearchNetije).total_nobat
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`min-w-[50px] md:min-w-[115px] max-w-[150px] w-full flex flex-col md:flex-row justify-center ${padding}`}
|
||||
>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%]">
|
||||
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
|
||||
{(winner as Datum).total_score_of_client
|
||||
? (winner as Datum).total_score_of_client
|
||||
: (winner as ISearchNetije).total_score
|
||||
? (winner as ISearchNetije).total_score
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : !error ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className="text-center w-full">{error}</div>
|
||||
)}
|
||||
{data.length < total && !error && (
|
||||
<button
|
||||
onClick={() => getData(true)}
|
||||
className="py-[5px] self-center px-[10px] md:w-fit rounded-md bg-blue-500 text-white border border-blue-500 lg:hover:bg-white lg:hover:text-blue-500 transition-all duration-300"
|
||||
>
|
||||
Dowamy
|
||||
</button>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuizTapgyrResults;
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
"use client";
|
||||
import { getNextQuizWinnners, getQuizWinnersById } from "@/api/queries";
|
||||
import { Question } from "@/models/quizQuestions.model";
|
||||
import {
|
||||
Datum,
|
||||
ISearchNetije,
|
||||
} from "@/models/quizQuestionsWinners.model";
|
||||
import { notFound } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Loader from "../Loader";
|
||||
import { useQuizResults, useResultsLoading } from "@/store/store";
|
||||
|
||||
const padding = "py-4";
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
tapgyr: number;
|
||||
questions: Question[];
|
||||
}
|
||||
|
||||
const QuizTapgyrWinners = ({ id, tapgyr, questions }: IProps) => {
|
||||
const [data, setData] = useState<Datum[] | ISearchNetije[]>([]);
|
||||
const { error, resultData } = useQuizResults();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const searchLoading = useResultsLoading((state) => state.loading);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [nextPageQueries, setQueries] = useState<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
}>({
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
async function getData(next?: boolean) {
|
||||
if (next) {
|
||||
const res = await getNextQuizWinnners(
|
||||
Number(id),
|
||||
nextPageQueries.limit,
|
||||
nextPageQueries.offset,
|
||||
tapgyr
|
||||
);
|
||||
if (res) {
|
||||
setQueries({
|
||||
limit: res.meta.per_page,
|
||||
offset: nextPageQueries.offset + res.meta.per_page,
|
||||
});
|
||||
setData([...(data as Datum[]), ...res.data]);
|
||||
}
|
||||
} else {
|
||||
setLoading(true);
|
||||
const res = await getQuizWinnersById(Number(id), tapgyr);
|
||||
if (res) {
|
||||
setTotal(res.meta.total);
|
||||
setQueries({
|
||||
limit: res.meta.per_page,
|
||||
offset: res.meta.per_page,
|
||||
});
|
||||
setData(res.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!resultData.length && !error) {
|
||||
getData();
|
||||
} else if (resultData.length) {
|
||||
setData(resultData);
|
||||
setTotal(0);
|
||||
} else if (error) {
|
||||
setTotal(0);
|
||||
setData([]);
|
||||
}
|
||||
}, [resultData]);
|
||||
|
||||
return (
|
||||
<article className="flex flex-col gap-[24px]">
|
||||
<header className="flex justify-center">
|
||||
<h1 className="text-[28px] text-[#1B1B21] md:text-[36px]">
|
||||
Gepleşik {tapgyr}
|
||||
</h1>
|
||||
</header>
|
||||
{data.length > 0 && !loading && !searchLoading ? (
|
||||
<table className="bg-[#F6F2FA] rounded-[12px] overflow-hidden">
|
||||
<thead className="bg-[#EAE7EF] p-[20px]">
|
||||
<tr>
|
||||
{((data[0] as Datum).client_id ||
|
||||
(data[0] as ISearchNetije).place) && (
|
||||
<th scope="col" className={`${padding} text-center`}>
|
||||
Ýeri
|
||||
</th>
|
||||
)}
|
||||
{data[0].phone && (
|
||||
<th scope="col" className={`${padding} text-center`}>
|
||||
Telefon beligisi
|
||||
</th>
|
||||
)}
|
||||
{((data[0] as Datum).answers?.length > 0 ||
|
||||
(data[0] as ISearchNetije).tapgyr_breakdown?.length) && (
|
||||
<th
|
||||
scope="col"
|
||||
className={`${padding} text-center hidden md:inline-block md:w-full`}
|
||||
>
|
||||
Jogap beriş nobatlary
|
||||
</th>
|
||||
)}
|
||||
{((data[0] as Datum).correct_answers_time ||
|
||||
(data[0] as ISearchNetije).total_nobat) && (
|
||||
<th scope="col" className={`${padding} text-center`}>
|
||||
Nobatlaryň jemi
|
||||
</th>
|
||||
)}
|
||||
{((data[0] as Datum).total_score_of_client ||
|
||||
(data[0] as ISearchNetije).total_score) && (
|
||||
<th scope="col" className={`${padding} text-center`}>
|
||||
Utuklaryň jemi
|
||||
</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className=" text-[#46464F]">
|
||||
{data.map((winner, id) => (
|
||||
<React.Fragment key={id}>
|
||||
<tr
|
||||
className={`${
|
||||
id !== data.length - 1 && "md:border-b md:border-[#C7C5D0]"
|
||||
}`}
|
||||
>
|
||||
{/* Yeri */}
|
||||
<th scope="row" className={`${padding} text-center`}>
|
||||
{id > 0 &&
|
||||
(winner as Datum).correct_answers_time ===
|
||||
(data[id - 1] as Datum).correct_answers_time
|
||||
? id
|
||||
: (winner as ISearchNetije).tapgyr_breakdown
|
||||
? (winner as ISearchNetije).tapgyr_breakdown.find(
|
||||
(item) => item.tapgyr === tapgyr
|
||||
)?.tapgyr_place
|
||||
: id + 1}
|
||||
</th>
|
||||
{/* Phone number */}
|
||||
<td className={`${padding} text-center`}>
|
||||
+{winner.phone ? winner.phone : "-"}
|
||||
</td>
|
||||
{/* Jogap berish nobaty */}
|
||||
<td className={`${padding} hidden md:block`}>
|
||||
<div className="w-full flex gap-[5px] justify-center">
|
||||
{questions.map((question) => {
|
||||
const matchingAnswer =
|
||||
(winner as Datum).answers?.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
(winner as Datum).answers?.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
) ||
|
||||
((winner as ISearchNetije).tapgyr_breakdown &&
|
||||
(winner as ISearchNetije).tapgyr_breakdown
|
||||
.find((step) => step.tapgyr === tapgyr)
|
||||
?.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
)) ||
|
||||
((winner as ISearchNetije).tapgyr_breakdown &&
|
||||
(winner as ISearchNetije).tapgyr_breakdown
|
||||
.find((step) => step.tapgyr === tapgyr)
|
||||
?.answers.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
));
|
||||
|
||||
return (
|
||||
<span
|
||||
key={question.id}
|
||||
className={`text-sm font-semibold leading-[125%] ${
|
||||
matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct !== 0
|
||||
? "text-fillGreen"
|
||||
: matchingAnswer &&
|
||||
matchingAnswer?.serial_number_for_correct ===
|
||||
0
|
||||
? "text-fillRed"
|
||||
: "text-textLight"
|
||||
}`}
|
||||
>
|
||||
{matchingAnswer && matchingAnswer.score !== 0 ? (
|
||||
matchingAnswer.serial_number_for_correct
|
||||
) : matchingAnswer &&
|
||||
matchingAnswer?.score === 0 ? (
|
||||
"X"
|
||||
) : (
|
||||
<span className="text-[20px]">-</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</td>
|
||||
{/* Answer time */}
|
||||
<td className={`${padding}`}>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] w-[100%]">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
|
||||
{(winner as Datum).correct_answers_time
|
||||
? (winner as Datum).correct_answers_time
|
||||
: (winner as ISearchNetije).tapgyr_breakdown.find(
|
||||
(item) => item.tapgyr === tapgyr
|
||||
)?.tapgyr_total_nobat
|
||||
? (winner as ISearchNetije).tapgyr_breakdown.find(
|
||||
(item) => item.tapgyr === tapgyr
|
||||
)?.tapgyr_total_nobat
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
{/* Total score */}
|
||||
<td className={`${padding}`}>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] w-[100%]">
|
||||
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
|
||||
{(winner as Datum).total_score_of_client
|
||||
? (winner as Datum).total_score_of_client
|
||||
: (winner as ISearchNetije).tapgyr_breakdown.find(
|
||||
(item) => item.tapgyr === tapgyr
|
||||
)?.tapgyr_total_score
|
||||
? (winner as ISearchNetije).tapgyr_breakdown.find(
|
||||
(item) => item.tapgyr === tapgyr
|
||||
)?.tapgyr_total_score
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/* Mobile jogab berish nobatlary */}
|
||||
<tr
|
||||
className={`${
|
||||
id !== data.length - 1 && "border-b border-[#C7C5D0]"
|
||||
} md:hidden`}
|
||||
>
|
||||
<th scope="row" colSpan={2}>
|
||||
Jogap beriş nobatlary
|
||||
</th>
|
||||
<td colSpan={2}>
|
||||
<div className="w-full flex gap-[5px] justify-center">
|
||||
{questions.map((question) => {
|
||||
const matchingAnswer =
|
||||
(winner as Datum).answers?.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
(winner as Datum).answers?.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
) ||
|
||||
((winner as ISearchNetije).tapgyr_breakdown &&
|
||||
(winner as ISearchNetije).tapgyr_breakdown
|
||||
.find((step) => step.tapgyr === tapgyr)
|
||||
?.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
)) ||
|
||||
((winner as ISearchNetije).tapgyr_breakdown &&
|
||||
(winner as ISearchNetije).tapgyr_breakdown
|
||||
.find((step) => step.tapgyr === tapgyr)
|
||||
?.answers.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
));
|
||||
|
||||
return (
|
||||
<span
|
||||
key={question.id}
|
||||
className={`text-sm font-semibold leading-[125%] ${
|
||||
matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct !== 0
|
||||
? "text-fillGreen"
|
||||
: matchingAnswer &&
|
||||
matchingAnswer?.serial_number_for_correct ===
|
||||
0
|
||||
? "text-fillRed"
|
||||
: "text-textLight"
|
||||
}`}
|
||||
>
|
||||
{matchingAnswer && matchingAnswer.score !== 0
|
||||
? matchingAnswer.serial_number_for_correct
|
||||
: matchingAnswer && matchingAnswer?.score === 0
|
||||
? "X"
|
||||
: "0"}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : !error ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className="w-full text-center">{error}</div>
|
||||
)}
|
||||
{data.length < total && !error && (
|
||||
<button
|
||||
onClick={() => getData(true)}
|
||||
className="py-[5px] self-center px-[10px] md:w-fit rounded-md bg-blue-500 text-white border border-blue-500 lg:hover:bg-white lg:hover:text-blue-500 transition-all duration-300"
|
||||
>
|
||||
Dowamy
|
||||
</button>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuizTapgyrWinners;
|
||||
|
|
@ -1,193 +1,76 @@
|
|||
"use client";
|
||||
import { Queries } from "@/api/queries";
|
||||
import {
|
||||
getNextQuizWinnners,
|
||||
getQuizWinnersById,
|
||||
Queries,
|
||||
} from "@/api/queries";
|
||||
|
||||
import { v4 } from "uuid";
|
||||
import { useState, useEffect, useContext } from "react";
|
||||
import { IQuizQuestionsWinners } from "@/models/quizQuestionsWinners.model";
|
||||
import {
|
||||
Datum,
|
||||
IQuizQuestionsWinners,
|
||||
} from "@/models/quizQuestionsWinners.model";
|
||||
import QuizContext from "@/context/QuizContext";
|
||||
|
||||
interface Message {
|
||||
answer: string;
|
||||
score: number;
|
||||
date: string;
|
||||
serial_number: number;
|
||||
serial_number_for_correct: number;
|
||||
starred_src: string;
|
||||
quiz_id: number;
|
||||
question_id: number;
|
||||
}
|
||||
|
||||
interface Winner {
|
||||
total_score_of_client: string;
|
||||
correct_answers_time: string;
|
||||
client_id: number;
|
||||
client: {
|
||||
id: number;
|
||||
phone: string;
|
||||
answers: {
|
||||
id: number;
|
||||
question_id: number;
|
||||
score: number;
|
||||
serial_number_for_correct: number;
|
||||
client_id: number;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
import { useQuizSearchActive } from "@/store/store";
|
||||
import { Question } from "@/models/quizQuestions.model";
|
||||
interface IProps {
|
||||
quizId: number;
|
||||
quizFinished: boolean;
|
||||
smsNumber: string;
|
||||
questionsData: Question[] | undefined
|
||||
}
|
||||
|
||||
const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
||||
// const [questionsData, setQuestionsData] = useState<IQuizQuestions>();
|
||||
const [winnersData, setWinnersData] = useState<IQuizQuestionsWinners>();
|
||||
const { questionsData } = useContext(QuizContext).quizQuestionsContext;
|
||||
const QuizWinnerTable = ({ quizId, questionsData }: IProps) => {
|
||||
const [winnersData, setWinnersData] = useState<Datum[] | []>([]);
|
||||
const [winnersTotal, setWinnersTotal] = useState<number>(0);
|
||||
const [nextPageQueries, setQueries] = useState<{
|
||||
limit: number;
|
||||
offset: number;
|
||||
}>({
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
});
|
||||
const { quizSearchData } = useContext(QuizContext).quizSearchContext;
|
||||
const { active } = useQuizSearchActive();
|
||||
|
||||
const [socket, setSocket] = useState<WebSocket | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
async function getData(next?: boolean) {
|
||||
if (next) {
|
||||
const res = await getNextQuizWinnners(
|
||||
quizId,
|
||||
nextPageQueries.limit,
|
||||
nextPageQueries.offset
|
||||
);
|
||||
if (res) {
|
||||
setWinnersData([...winnersData, ...res.data]);
|
||||
setQueries({
|
||||
limit: res?.meta.per_page,
|
||||
offset: nextPageQueries.offset + res?.meta.per_page,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const res = await getQuizWinnersById(quizId);
|
||||
if (res) {
|
||||
setWinnersTotal(res.meta.total);
|
||||
setWinnersData(res.data);
|
||||
setQueries({
|
||||
limit: res?.meta.per_page,
|
||||
offset: res?.meta.per_page,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
Queries.getQuizWinners(quizId).then((res) => {
|
||||
setWinnersData(res);
|
||||
});
|
||||
}, [quizId]);
|
||||
|
||||
// let socket: WebSocket | null = null;
|
||||
// let reconnectTimeout: NodeJS.Timeout | null = null;
|
||||
// let pingInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
// const connectWebSocket = () => {
|
||||
// try {
|
||||
// socket = new WebSocket(`wss://sms.turkmentv.gov.tm/ws/quiz?dst=${smsNumber}`);
|
||||
// setSocket(socket);
|
||||
|
||||
// socket.onopen = () => {
|
||||
// console.log('WebSocket is connected');
|
||||
// setIsConnected(true);
|
||||
|
||||
// pingInterval = setInterval(() => {
|
||||
// if (socket?.readyState === WebSocket.OPEN) {
|
||||
// try {
|
||||
// socket.send(JSON.stringify({ type: 'ping' }));
|
||||
// } catch (error) {
|
||||
// console.error('Error sending ping:', error);
|
||||
// }
|
||||
// }
|
||||
// }, 25000); // Ping every 25 seconds
|
||||
// };
|
||||
|
||||
// socket.onmessage = (event) => {
|
||||
// try {
|
||||
// console.log('Message received from WebSocket:', event.data);
|
||||
// const message = JSON.parse(event.data);
|
||||
// handleOnMessage(message);
|
||||
// } catch (error) {
|
||||
// console.error('Error processing message:', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
// socket.onerror = (error) => {
|
||||
// console.error('WebSocket error:', error);
|
||||
// };
|
||||
|
||||
// socket.onclose = () => {
|
||||
// console.log('WebSocket is closed');
|
||||
// setIsConnected(false);
|
||||
|
||||
// if (pingInterval) {
|
||||
// clearInterval(pingInterval);
|
||||
// }
|
||||
|
||||
// if (!reconnectTimeout) {
|
||||
// reconnectTimeout = setTimeout(() => {
|
||||
// console.log('Attempting to reconnect WebSocket...');
|
||||
// connectWebSocket();
|
||||
// }, 5000); // Reconnect after 5 seconds
|
||||
// }
|
||||
// };
|
||||
// } catch (error) {
|
||||
// console.error('WebSocket connection error:', error);
|
||||
// }
|
||||
// };
|
||||
|
||||
// if (smsNumber && winnersData) {
|
||||
// connectWebSocket();
|
||||
// }
|
||||
|
||||
// return () => {
|
||||
// if (socket) {
|
||||
// socket.close();
|
||||
// }
|
||||
// if (reconnectTimeout) {
|
||||
// clearTimeout(reconnectTimeout);
|
||||
// }
|
||||
// if (pingInterval) {
|
||||
// clearInterval(pingInterval);
|
||||
// }
|
||||
// };
|
||||
// }, [smsNumber]);
|
||||
|
||||
// Function to handle incoming WebSocket message and update winnersData
|
||||
const handleOnMessage = (message: Message) => {
|
||||
if (!winnersData) {
|
||||
console.error("winnersData is undefined");
|
||||
return;
|
||||
if (!active) {
|
||||
// Queries.getQuizWinners(quizId).then((res) => {
|
||||
// setWinnersData(res);
|
||||
// });
|
||||
getData();
|
||||
} else if (active) {
|
||||
setWinnersData([]);
|
||||
}
|
||||
}, [quizId, active]);
|
||||
|
||||
console.log("updating winnersData");
|
||||
|
||||
// Update the winnersData by matching phone with starred_src from the message
|
||||
setWinnersData((prevWinnersData) => {
|
||||
if (!prevWinnersData) {
|
||||
return prevWinnersData;
|
||||
}
|
||||
|
||||
return {
|
||||
...prevWinnersData,
|
||||
data: prevWinnersData.data.map((winner) => {
|
||||
if (winner.client.phone === message.starred_src) {
|
||||
const updatedAnswers = [
|
||||
...winner.client.answers,
|
||||
{
|
||||
id: message.question_id,
|
||||
question_id: message.question_id,
|
||||
score: message.score,
|
||||
serial_number_for_correct: message.serial_number_for_correct,
|
||||
client_id: winner.client.id,
|
||||
},
|
||||
];
|
||||
|
||||
// Calculate the new correct_answers_time by summing serial_number_for_correct
|
||||
const updatedCorrectAnswersTime = updatedAnswers
|
||||
.reduce(
|
||||
(sum, answer) => sum + answer.serial_number_for_correct,
|
||||
0
|
||||
)
|
||||
.toString();
|
||||
|
||||
return {
|
||||
...winner,
|
||||
client: {
|
||||
...winner.client,
|
||||
answers: updatedAnswers,
|
||||
},
|
||||
total_score_of_client: (
|
||||
parseInt(winner.total_score_of_client) + message.score
|
||||
).toString(),
|
||||
correct_answers_time: updatedCorrectAnswersTime, // Update correct_answers_time
|
||||
};
|
||||
}
|
||||
return winner;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
console.log("winnersData is updated");
|
||||
};
|
||||
|
||||
return winnersData?.data.length !== 0 ? (
|
||||
return quizSearchData?.data || (winnersData && winnersData?.length) !== 0 ? (
|
||||
<div className="flex flex-col justify-center items-center gap-[60px]">
|
||||
<div className="flex flex-col gap-5 justify-center items-center w-full">
|
||||
<h2 className="text-textBlack text-[28px] text-center md:text-left md:text-[32px] font-semibold">
|
||||
|
|
@ -198,30 +81,30 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
<div className="table-desktop hidden sm:flex flex-col bg-fillTableHead rounded-[25px] shadow-quizButton overflow-hidden max-w-[1000px] w-full">
|
||||
{/* Table Head */}
|
||||
<div className="flex border-b border-fillTableStrokeTableHead">
|
||||
{winnersData?.data[0].client_id ? (
|
||||
{winnersData[0].client_id || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[54px] w-[100%] pl-6 pr-3 py-5">
|
||||
<span>№</span>
|
||||
<span>Ýeri</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData?.data[0].client.phone ? (
|
||||
{winnersData[0].phone || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[176px] w-[100%] px-3 py-5">
|
||||
<span>Gatnaşyjynyň tel. Beligisi</span>
|
||||
<span>Telefon beligisi</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData?.data[0].client.answers.length !== 0 ? (
|
||||
{winnersData[0].answers.length || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold w-[100%] px-3 py-5">
|
||||
<span>Soraglara jogap berilişiň nobaty</span>
|
||||
<span>Jogap beriş nobatlary</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData?.data[0].total_score_of_client ? (
|
||||
{winnersData[0].total_score_of_client || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[180px] w-[100%] px-3 py-5">
|
||||
<span>Nobatlaryň jemi</span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData?.data[0].total_score_of_client ? (
|
||||
{winnersData[0].total_score_of_client || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[180px] w-[100%] px-3 py-5">
|
||||
<span>Utuklaryň jemi</span>
|
||||
</div>
|
||||
|
|
@ -230,34 +113,111 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
|
||||
{/* Table Body */}
|
||||
<div className="">
|
||||
{winnersData?.data.map((winner, id) => (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow ${
|
||||
id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[54px] w-[100%] pl-6 pr-3 py-5">
|
||||
<span>{id + 1}</span>
|
||||
</div>
|
||||
{winnersData.data[0].client.phone ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[176px] w-[100%] px-3 py-5">
|
||||
<span>+{winner.client.phone}</span>
|
||||
{winnersData
|
||||
? winnersData.map((winner, id) => (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow ${
|
||||
id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[54px] w-[100%] pl-6 pr-3 py-5">
|
||||
<span>
|
||||
{id > 0 &&
|
||||
winner.correct_answers_time ===
|
||||
winnersData[id - 1].correct_answers_time
|
||||
? id
|
||||
: id + 1}
|
||||
</span>
|
||||
</div>
|
||||
{winnersData[0].phone ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[176px] w-[100%] px-3 py-5">
|
||||
<span>+{winner.phone}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData[0].answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center gap-6 text-base text-textGray leading-[125%] w-[100%] px-3 py-5">
|
||||
{questionsData
|
||||
? questionsData.map((question) => {
|
||||
const matchingAnswer =
|
||||
winner.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
winner.answers.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
key={v4()}
|
||||
className={`text-sm font-semibold leading-[125%] ${
|
||||
matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct !==
|
||||
0
|
||||
? "text-fillGreen"
|
||||
: matchingAnswer &&
|
||||
matchingAnswer?.serial_number_for_correct ===
|
||||
0
|
||||
? "text-fillRed"
|
||||
: "text-textLight"
|
||||
}`}
|
||||
>
|
||||
{matchingAnswer && matchingAnswer.score !== 0
|
||||
? matchingAnswer.serial_number_for_correct
|
||||
: matchingAnswer &&
|
||||
matchingAnswer?.score === 0
|
||||
? "X"
|
||||
: "-"}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
|
||||
{winner.correct_answers_time}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
|
||||
{winner.total_score_of_client}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData.data[0].client.answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center gap-6 text-base text-textGray leading-[125%] w-[100%] px-3 py-5">
|
||||
{questionsData
|
||||
? questionsData.map((question) => {
|
||||
const matchingAnswer =
|
||||
winner.client.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
winner.client.answers.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
);
|
||||
))
|
||||
: quizSearchData && (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow bg-fillTableRow2`}
|
||||
>
|
||||
{/* Place of the client */}
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[54px] w-[100%] pl-6 pr-3 py-5">
|
||||
<span>{quizSearchData.result.place}</span>
|
||||
</div>
|
||||
{/* Client phone number */}
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] font-semibold max-w-[176px] w-[100%] px-3 py-5">
|
||||
<span>
|
||||
+
|
||||
{Object.keys(quizSearchData.data).map(
|
||||
(questionId, i) =>
|
||||
i === 0 &&
|
||||
quizSearchData.data[questionId].answers[0].client
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{/* Serial number answer to questions */}
|
||||
<div className="flex justify-center items-center gap-6 text-base text-textGray leading-[125%] w-[100%] px-3 py-5">
|
||||
{Object.keys(quizSearchData.data)
|
||||
.map((quistionId) => quizSearchData.data[quistionId])
|
||||
.map((question) => {
|
||||
const matchingAnswer = question.answers[0];
|
||||
|
||||
return (
|
||||
<span
|
||||
|
|
@ -277,30 +237,24 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
? matchingAnswer.serial_number_for_correct
|
||||
: matchingAnswer && matchingAnswer?.score === 0
|
||||
? "X"
|
||||
: "0"}
|
||||
: "-"}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
})}
|
||||
</div>
|
||||
|
||||
{winnersData.data[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
|
||||
{winner.correct_answers_time}
|
||||
</span>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] ">
|
||||
{quizSearchData.result.total_serial}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
|
||||
{quizSearchData.result.total_score}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData.data[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-base text-textBlack leading-[125%] max-w-[180px] w-[100%] px-3 py-3">
|
||||
<span className="bg-fillOrange rounded-full w-[36px] h-[36px] flex justify-center items-center text-base leading-[125%] text-white">
|
||||
{winner.total_score_of_client}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -308,85 +262,164 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
<div className="sm:hidden flex flex-col bg-fillTableHead rounded-[13px] shadow-quizButton overflow-hidden max-w-[1000px] w-full">
|
||||
{/* Table Head */}
|
||||
<div className="flex border-b border-fillTableStrokeTableHead p-2 gap-[8px]">
|
||||
{winnersData?.data[0].client_id ? (
|
||||
{winnersData[0].client_id || quizSearchData?.data ? (
|
||||
<div className="text-center flex items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[14px] w-[100%]">
|
||||
<span>№</span>
|
||||
<span>Ýeri</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData?.data[0].client.phone ? (
|
||||
{winnersData[0].phone || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[107px] w-[100%]">
|
||||
<span>Gatnaşyjynyň tel. Beligisi</span>
|
||||
<span>Telefon beligisi</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData?.data[0].total_score_of_client ? (
|
||||
{winnersData[0].total_score_of_client || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[75px] w-[100%]">
|
||||
<span>Soraglara jogap berilişiň nobaty </span>
|
||||
<span>Nobatlaryň jemi </span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData?.data[0].total_score_of_client ? (
|
||||
{winnersData[0].total_score_of_client || quizSearchData?.data ? (
|
||||
<div className="text-center flex justify-center items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[99px] w-[100%]">
|
||||
<span>Nobatlaryň jemi</span>
|
||||
<span>Utuklaryň jemi</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<div className="">
|
||||
{winnersData?.data.map((winner, id) => (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow items-center p-[8px] gap-[8px] ${
|
||||
id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex items-center text-base text-textBlack leading-[125%] max-w-[14px] w-[100%] ">
|
||||
<span>{id + 1}</span>
|
||||
</div>
|
||||
{winnersData
|
||||
? winnersData.map((winner, id) => (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow items-center p-[8px] gap-[8px] ${
|
||||
id % 2 === 0 ? "bg-fillTableRow" : "bg-fillTableRow2"
|
||||
}`}
|
||||
key={v4()}
|
||||
>
|
||||
<div className="flex items-center text-base text-textBlack leading-[125%] max-w-[14px] w-[100%] ">
|
||||
<span>{id + 1}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
<div className="flex gap-[8px] items-center">
|
||||
{winnersData.data[0].client.phone ? (
|
||||
<div className="flex items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[107px] w-full">
|
||||
<span>+{winner.client.phone}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
<div className="flex gap-[8px] items-center">
|
||||
{winnersData[0].phone ? (
|
||||
<div className="flex items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[107px] w-full">
|
||||
<span>+{winner.phone}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersData.data[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[75px] w-full">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] ">
|
||||
{winner.correct_answers_time}
|
||||
</span>
|
||||
{winnersData[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[75px] w-full">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] ">
|
||||
{winner.correct_answers_time}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[99px] w-full">
|
||||
<span className="bg-fillOrange rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] text-white">
|
||||
{winner.total_score_of_client}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData.data[0].total_score_of_client ? (
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[99px] w-full">
|
||||
<span className="bg-fillOrange rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] text-white">
|
||||
{winner.total_score_of_client}
|
||||
</span>
|
||||
<div className="flex gap-[8px] items-center">
|
||||
{winnersData[0].answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center text-xs text-textLight leading-[125%] font-semibold w-fit">
|
||||
<span>Jogap beriş nobatlary:</span>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData[0].answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center gap-[4px] text-xs text-textGray leading-[125%] w-fit">
|
||||
{questionsData
|
||||
? questionsData.map((question) => {
|
||||
const matchingAnswer =
|
||||
winner.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
winner.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id
|
||||
);
|
||||
return (
|
||||
<span
|
||||
key={v4()}
|
||||
className={`text-sm font-semibold leading-[125%] ${
|
||||
matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct !==
|
||||
0
|
||||
? "text-fillGreen"
|
||||
: matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct ===
|
||||
0
|
||||
? "text-fillRed"
|
||||
: "text-textLight"
|
||||
}`}
|
||||
>
|
||||
{matchingAnswer &&
|
||||
matchingAnswer.score !== 0
|
||||
? matchingAnswer.serial_number_for_correct
|
||||
: matchingAnswer &&
|
||||
matchingAnswer?.score === 0
|
||||
? "X"
|
||||
: "-"}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-[8px] items-center">
|
||||
{winnersData?.data[0].client.answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center text-xs text-textLight leading-[125%] font-semibold w-fit">
|
||||
<span>Soraglara näçinji jogap berdi :</span>
|
||||
))
|
||||
: quizSearchData && (
|
||||
<div
|
||||
className={`flex border-b border-fillTableStrokeTableRow items-center p-[8px] gap-[8px] bg-fillTableRow2`}
|
||||
>
|
||||
<div className="flex items-center text-base text-textBlack leading-[125%] max-w-[14px] w-[100%] ">
|
||||
<span>{quizSearchData.result.place}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-[8px] w-full">
|
||||
<div className="flex gap-[8px] items-center">
|
||||
<div className="flex items-center text-xs text-textBlack leading-[125%] font-semibold max-w-[107px] w-full">
|
||||
<span>
|
||||
+
|
||||
{Object.keys(quizSearchData.data).map(
|
||||
(questionId, i) =>
|
||||
i === 0 &&
|
||||
quizSearchData.data[questionId].answers[0]
|
||||
.client
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[75px] w-full">
|
||||
<span className="border border-[#2C7CDA] text-[#2C7CDA] rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] ">
|
||||
{quizSearchData.result.total_serial}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-center items-center text-xs text-textBlack leading-[125%] max-w-[99px] w-full">
|
||||
<span className="bg-fillOrange rounded-full w-[24px] h-[24px] flex justify-center items-center text-xs leading-[125%] text-white">
|
||||
{quizSearchData.result.total_score}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{winnersData.data[0].client.answers.length !== 0 ? (
|
||||
<div className="flex justify-center items-center gap-[4px] text-xs text-textGray leading-[125%] w-fit">
|
||||
{questionsData
|
||||
? questionsData.map((question) => {
|
||||
const matchingAnswer =
|
||||
winner.client.answers.find(
|
||||
(answer) =>
|
||||
answer.question_id === question.id &&
|
||||
answer.score > 0
|
||||
) ||
|
||||
winner.client.answers.find(
|
||||
(answer) => answer.question_id === question.id
|
||||
);
|
||||
<div className="flex gap-[8px] items-center">
|
||||
<div className="flex justify-center items-center text-xs text-textLight leading-[125%] font-semibold w-fit">
|
||||
<span>Jogap beriş nobatlary:</span>
|
||||
</div>
|
||||
<div className="flex justify-center items-center gap-[4px] text-xs text-textGray leading-[125%] w-fit">
|
||||
{Object.keys(quizSearchData.data)
|
||||
.map(
|
||||
(quistionId) => quizSearchData.data[quistionId]
|
||||
)
|
||||
.map((question) => {
|
||||
const matchingAnswer = question.answers[0];
|
||||
|
||||
return (
|
||||
<span
|
||||
key={v4()}
|
||||
|
|
@ -396,7 +429,7 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
0
|
||||
? "text-fillGreen"
|
||||
: matchingAnswer &&
|
||||
matchingAnswer.serial_number_for_correct ===
|
||||
matchingAnswer?.serial_number_for_correct ===
|
||||
0
|
||||
? "text-fillRed"
|
||||
: "text-textLight"
|
||||
|
|
@ -407,21 +440,28 @@ const QuizWinnerTable = ({ quizId, quizFinished, smsNumber }: IProps) => {
|
|||
: matchingAnswer &&
|
||||
matchingAnswer?.score === 0
|
||||
? "X"
|
||||
: "0"}
|
||||
: "-"}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{winnersData.length < winnersTotal && (
|
||||
<button
|
||||
onClick={() => getData(true)}
|
||||
className="py-[5px] px-[10px] rounded-md bg-blue-500 text-white border border-blue-500 lg:hover:bg-white lg:hover:text-blue-500 transition-all duration-300"
|
||||
>
|
||||
Dowamy
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Rules block */}
|
||||
<div className="flex flex-col gap-[20px] p-5 border border-strokeLightGray1 rounded-[25px] max-w-[1000px] w-full items-center justify-center">
|
||||
<h3 className="text-[26px] text-textBlack font-semibold leading-[124%]">
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
"use client";
|
||||
import LotteryHeader from "@/components/lottery/LotteryHeader";
|
||||
import LotteryRulesSection from "@/components/lottery/rules/LotteryRulesSection";
|
||||
import LotteryCountDown from "@/components/lottery/countDown/LotteryCountDown";
|
||||
import { getTossData } from "@/api/queries";
|
||||
import { getLotteryStatus } from "@/lib/actions";
|
||||
import LotteryWinners from "../lottery/LotteryWinners";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
const TossPage = async ({
|
||||
type,
|
||||
id,
|
||||
}: {
|
||||
type: "bije" | "cekilis";
|
||||
id: string;
|
||||
}) => {
|
||||
const tossData = await getTossData({ type, id });
|
||||
const TossPage = ({ type, id }: { type: "bije" | "cekilis"; id: string }) => {
|
||||
const [tossData, setTossData] = useState<any>();
|
||||
const mobile = useMediaQuery("(max-width: 768px)");
|
||||
|
||||
const status = await getLotteryStatus(
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
setTossData(await getTossData({ type, id }));
|
||||
};
|
||||
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const status = getLotteryStatus(
|
||||
tossData?.data?.start_time,
|
||||
tossData?.data?.end_time
|
||||
);
|
||||
|
|
@ -22,13 +28,15 @@ const TossPage = async ({
|
|||
return (
|
||||
<>
|
||||
{tossData?.data ? (
|
||||
<div className="flex flex-col md:gap-[128px] gap-[80px] font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
|
||||
<div className="flex flex-col font-roboto md:pt-[64px] sm:pt-[48px] pt-[40px] ms:pb-[128px] pb-[80px] text-lightOnSurface">
|
||||
{tossData && (
|
||||
<div className="flex flex-col sm:gap-[64px] gap-[40px]">
|
||||
<div className="flex flex-col sm:gap-[64px] gap-[40px] pb-[40px]">
|
||||
<LotteryHeader
|
||||
title={tossData.data.title}
|
||||
description={tossData.data.description}
|
||||
image={tossData.data.image}
|
||||
image={
|
||||
mobile ? tossData?.data.image_mobile : tossData?.data.image
|
||||
}
|
||||
smsCode={tossData.data.sms_code}
|
||||
startDate={tossData.data.start_time}
|
||||
/>
|
||||
|
|
@ -47,7 +55,7 @@ const TossPage = async ({
|
|||
|
||||
<LotteryRulesSection show={false} data={tossData} />
|
||||
|
||||
<div className="flex flex-col gap-10">
|
||||
<div className="flex flex-col gap-10 mt-[40px]">
|
||||
<LotteryWinners data={tossData} lotteryStatus={status} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import Image from 'next/image';
|
||||
import placeholder from '@/public/person placeholder.svg';
|
||||
import clsx from 'clsx';
|
||||
import { motion, usePresence, Variant, Transition } from 'framer-motion';
|
||||
import RollingCounter from 'react-slot-counter';
|
||||
import Image from "next/image";
|
||||
import placeholder from "@/public/person placeholder.svg";
|
||||
import clsx from "clsx";
|
||||
import { motion, usePresence, Variant, Transition } from "framer-motion";
|
||||
import RollingCounter from "react-slot-counter";
|
||||
|
||||
interface IProps {
|
||||
number: number;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
photo: string | null;
|
||||
votes: number;
|
||||
progress: number;
|
||||
|
|
@ -24,6 +25,7 @@ interface IProps {
|
|||
const ParticipantCard = ({
|
||||
number,
|
||||
name,
|
||||
description,
|
||||
photo,
|
||||
votes,
|
||||
progress,
|
||||
|
|
@ -37,7 +39,7 @@ const ParticipantCard = ({
|
|||
const substractedProgress = progress > 99 ? progress - 2 : progress;
|
||||
|
||||
const transition: Transition = {
|
||||
type: 'spring',
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 50,
|
||||
mass: 1,
|
||||
|
|
@ -48,11 +50,11 @@ const ParticipantCard = ({
|
|||
|
||||
const animations = {
|
||||
layout: true,
|
||||
initial: 'out',
|
||||
initial: "out",
|
||||
style: {
|
||||
position: (isPresent ? 'static' : 'absolute') as 'static' | 'absolute', // Corrected cast
|
||||
position: (isPresent ? "static" : "absolute") as "static" | "absolute", // Corrected cast
|
||||
},
|
||||
animate: isPresent ? 'in' : 'out',
|
||||
animate: isPresent ? "in" : "out",
|
||||
variants: {
|
||||
in: { scaleY: 1, opacity: 1 },
|
||||
out: { scaleY: 0, opacity: 0, zIndex: -1 },
|
||||
|
|
@ -66,7 +68,8 @@ const ParticipantCard = ({
|
|||
////////////////////////////////////////////// Winner card
|
||||
<motion.div
|
||||
// {...animations}
|
||||
className="flex flex-col overflow-hidden bg-fillNavyBlue max-w-[940px] w-full group">
|
||||
className="flex flex-col overflow-hidden bg-fillNavyBlue max-w-[940px] w-full group"
|
||||
>
|
||||
<div className="flex items-center gap-[5px] sm:gap-[20px] p-[5px] pt-[10px] w-full">
|
||||
<h3 className="text-[26px] sm:text-[80px] leading-[100%] font-bold text-fillNavyBlue text-stroke">
|
||||
{number}
|
||||
|
|
@ -78,8 +81,9 @@ const ParticipantCard = ({
|
|||
fill
|
||||
src={photo}
|
||||
alt={name}
|
||||
className={clsx(' object-cover', {
|
||||
'group-hover:scale-110 transition-all duration-300 ease-out': hasUrl,
|
||||
className={clsx(" object-cover", {
|
||||
"group-hover:scale-110 transition-all duration-300 ease-out":
|
||||
hasUrl,
|
||||
})}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
|
|
@ -91,9 +95,10 @@ const ParticipantCard = ({
|
|||
<Image
|
||||
fill
|
||||
src={placeholder}
|
||||
alt={'placeholder'}
|
||||
className={clsx(' object-cover', {
|
||||
'group-hover:scale-110 transition-all duration-300 ease-out': hasUrl,
|
||||
alt={"placeholder"}
|
||||
className={clsx(" object-cover", {
|
||||
"group-hover:scale-110 transition-all duration-300 ease-out":
|
||||
hasUrl,
|
||||
})}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
|
|
@ -106,17 +111,31 @@ const ParticipantCard = ({
|
|||
{name ? (
|
||||
<h2
|
||||
className={clsx(
|
||||
'text-[18px] transition-all sm:text-[24px] leading-[100%] font-bold text-white',
|
||||
"text-[18px] transition-all sm:text-[24px] leading-[100%] font-bold text-white",
|
||||
{
|
||||
// 'group-hover:text-[28px] transition-all duration-300 ease-out': hasUrl,
|
||||
},
|
||||
)}>
|
||||
}
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</h2>
|
||||
) : null}
|
||||
|
||||
{description ? (
|
||||
<p
|
||||
className={clsx(
|
||||
"text-[14px] transition-all font-[400] text-[#B7B7D1]",
|
||||
{
|
||||
// 'group-hover:text-[28px] transition-all duration-300 ease-out': hasUrl,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{/* If we have voteCode and voting not closed, then show badge with code. Else dont show */}
|
||||
{voteCode && voteStatus !== 'closed' ? (
|
||||
{voteCode && voteStatus !== "closed" ? (
|
||||
// Desktop version
|
||||
<p className="hidden sm:block py-[10px] px-[8px] bg-[#1E1E7B] text-fillLightGray text-[14px] leading-[125%] max-w-[232px] rounded-[10px]">
|
||||
Ses bermek üçin
|
||||
|
|
@ -146,13 +165,13 @@ const ParticipantCard = ({
|
|||
</div>
|
||||
|
||||
{/* If we have voteCode and voting not closed, then show badge with code. Else dont show */}
|
||||
{voteCode && voteStatus !== 'closed' ? (
|
||||
{voteCode && voteStatus !== "closed" ? (
|
||||
// Mobile version
|
||||
<p className="block sm:hidden py-[10px] px-[4px] font-medium bg-[#1E1E7B] rounded-md text-fillLightGray text-[10px] leading-[125%] ">
|
||||
Ses bermek üçin{' '}
|
||||
Ses bermek üçin{" "}
|
||||
<span className="inline-block w-fit px-1 py-[4px] mx-[4px] font-bold text-white text-[16px] border rounded-md">
|
||||
{voteCode}
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
ugrat
|
||||
</p>
|
||||
) : null}
|
||||
|
|
@ -164,15 +183,18 @@ const ParticipantCard = ({
|
|||
// {...animations}
|
||||
>
|
||||
<div className="flex items-center gap-[5px] sm:gap-[20px] max-w-[900px] w-full px-[5px] sm:p-0">
|
||||
<h3 className="w-[24px] text-[16px] sm:text-[20px] leading-[100%] font-bold">{number}</h3>
|
||||
<h3 className="w-[24px] text-[16px] sm:text-[20px] leading-[100%] font-bold">
|
||||
{number}
|
||||
</h3>
|
||||
{photo && name ? (
|
||||
<div className="relative min-w-[50px] rounded-[10px] overflow-hidden sm:min-w-[80px] h-[50px] sm:h-[80px]">
|
||||
<div className="relative min-w-[50px] rounded-[10px] overflow-hidden sm:min-w-[104px] h-[50px] sm:h-[104px]">
|
||||
<Image
|
||||
fill
|
||||
src={photo}
|
||||
alt={name}
|
||||
className={clsx(' object-cover', {
|
||||
'group-hover:scale-110 transition-all duration-300 ease-out': hasUrl,
|
||||
className={clsx(" object-cover", {
|
||||
"group-hover:scale-110 transition-all duration-300 ease-out":
|
||||
hasUrl,
|
||||
})}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
|
|
@ -183,9 +205,10 @@ const ParticipantCard = ({
|
|||
<Image
|
||||
fill
|
||||
src={placeholder}
|
||||
alt={'placeholder'}
|
||||
className={clsx(' object-cover', {
|
||||
'group-hover:scale-110 transition-all duration-300 ease-out': hasUrl,
|
||||
alt={"placeholder"}
|
||||
className={clsx(" object-cover", {
|
||||
"group-hover:scale-110 transition-all duration-300 ease-out":
|
||||
hasUrl,
|
||||
})}
|
||||
unoptimized
|
||||
unselectable="off"
|
||||
|
|
@ -198,13 +221,28 @@ const ParticipantCard = ({
|
|||
{name ? (
|
||||
<h2
|
||||
className={clsx(
|
||||
'text-textBlack text-[16px] sm:text-[18px] leading-[100%] font-bold',
|
||||
)}>
|
||||
"text-textBlack text-[16px] sm:text-[18px] leading-[100%] font-bold"
|
||||
)}
|
||||
>
|
||||
{name}
|
||||
</h2>
|
||||
) : null}
|
||||
|
||||
{description ? (
|
||||
<p
|
||||
className={clsx(
|
||||
"text-[14px] transition-all font-[400] text-[#4D4D4D]",
|
||||
{
|
||||
// 'group-hover:text-[28px] transition-all duration-300 ease-out': hasUrl,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{/* If we have voteCode and voting not closed, then show badge with code. Else dont show */}
|
||||
{voteCode && voteStatus !== 'closed' ? (
|
||||
{voteCode && voteStatus !== "closed" ? (
|
||||
// Desktop version
|
||||
<p className="hidden sm:block py-[5px] px-[8px] bg-[#EAEAFF] text-[#9393DA] text-[14px] leading-[125%] rounded-[10px] w-fit">
|
||||
Ses bermek üçin
|
||||
|
|
@ -234,13 +272,13 @@ const ParticipantCard = ({
|
|||
</div>
|
||||
|
||||
{/* If we have voteCode and voting not closed, then show badge with code. Else dont show */}
|
||||
{voteCode && voteStatus !== 'closed' ? (
|
||||
{voteCode && voteStatus !== "closed" ? (
|
||||
// Mobile version
|
||||
<p className="block sm:hidden text-[#9393DA] text-[10px] leading-[125%] w-full">
|
||||
Ses bermek üçin{' '}
|
||||
Ses bermek üçin{" "}
|
||||
<span className="inline-block w-fit px-1 py-[4px] leading-[100%] mx-[4px] font-bold text-fillNavyBlue text-[16px] border border-fillNavyBlue rounded-md">
|
||||
{voteCode}
|
||||
</span>{' '}
|
||||
</span>{" "}
|
||||
ugrat
|
||||
</p>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
'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';
|
||||
"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 IParams {
|
||||
vote_id?: string;
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
interface ISocketMessage {
|
||||
|
|
@ -27,11 +28,13 @@ interface ISocketMessage {
|
|||
date: string;
|
||||
}
|
||||
|
||||
const ParticipantsList = ({ vote_id }: IParams) => {
|
||||
const ParticipantsList = ({ vote_id, all }: IParams) => {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const [data, setData] = useState<IAllVotes>();
|
||||
const [participantsData, setParticipantsData] = useState<VotingItem[]>([]);
|
||||
const [voteStatus, setVoteStatus] = useState<string>();
|
||||
const [eventStatus, setEventStatus] = useState<string>('Not started');
|
||||
const [eventStatus, setEventStatus] = useState<string>("Not started");
|
||||
const [manualClose, setManualClose] = useState(false); // Track manual closure
|
||||
|
||||
const [winnersCount, setWinnersCount] = useState<number>(0);
|
||||
|
|
@ -41,13 +44,29 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
const [socket, setSocket] = useState<WebSocket | null>(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) {
|
||||
const id = searchParams.get("d");
|
||||
if (id) {
|
||||
Queries.getVoteByUUID(id).then((res) => {
|
||||
setData(res);
|
||||
setParticipantsData(res.data.voting_items);
|
||||
setVoteDescription(res.data.description);
|
||||
setVoteStatus(res.data.status);
|
||||
setSmsNumber(res.data.sms_number);
|
||||
});
|
||||
} else if (vote_id) {
|
||||
Queries.getVote(vote_id).then((res) => {
|
||||
setData(res);
|
||||
setParticipantsData(res.data.voting_items);
|
||||
setVoteDescription(res.data.description);
|
||||
setVoteStatus(res.data.status);
|
||||
setSmsNumber(res.data.sms_number);
|
||||
});
|
||||
} else if (all) {
|
||||
Queries.getAllVotes().then((res) => {
|
||||
setData(res);
|
||||
setParticipantsData([...res.data.voting_items]);
|
||||
|
|
@ -56,13 +75,7 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
setSmsNumber(res.data.sms_number);
|
||||
});
|
||||
} else {
|
||||
Queries.getVote(vote_id).then((res) => {
|
||||
setData(res);
|
||||
setParticipantsData(res.data.voting_items);
|
||||
setVoteDescription(res.data.description);
|
||||
setVoteStatus(res.data.status);
|
||||
setSmsNumber(res.data.sms_number);
|
||||
});
|
||||
router.push("/vote/active");
|
||||
}
|
||||
|
||||
if (participantsData) {
|
||||
|
|
@ -79,19 +92,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 +117,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 +147,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 +176,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 +192,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 +205,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 +222,29 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
if (!data?.data) {
|
||||
return (
|
||||
<div className="py-12">
|
||||
<GradientTitle title={'No voting to show on the site'} size="big" />
|
||||
<GradientTitle title={"No voting to show on the site"} size="big" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-[20px] sm:gap-[40px] w-full items-center">
|
||||
{data.data.description ? <PageBage title={data.data.description} /> : null}
|
||||
{data.data.description ? (
|
||||
<PageBage title={data.data.description} />
|
||||
) : null}
|
||||
|
||||
{eventStatus === 'Finished' && <Confetti showConfetti={true} />}
|
||||
{eventStatus === "Finished" && <Confetti />}
|
||||
|
||||
{data.data.banner ? (
|
||||
<div className="relative w-full md:min-h-[150px] md:h-auto h-[100px] ">
|
||||
{mobile ? (
|
||||
<Image
|
||||
fill
|
||||
src={data.data.banner_mobile !== null ? data.data.banner_mobile : data.data.banner}
|
||||
src={
|
||||
data.data.banner_mobile !== null
|
||||
? data.data.banner_mobile
|
||||
: data.data.banner
|
||||
}
|
||||
alt={data.data.title}
|
||||
unselectable="off"
|
||||
unoptimized
|
||||
|
|
@ -251,24 +277,31 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
) : null}
|
||||
|
||||
<div className="flex w-full flex-col items-center gap-[10px] sm:gap-[20px]">
|
||||
{winnersCount > 1 ? <GradientTitle title="победители" size="small" /> : null}
|
||||
{winnersCount > 1 ? (
|
||||
<GradientTitle title="победители" size="small" />
|
||||
) : null}
|
||||
|
||||
{participantsData && participantsData[0].votes_count > 0 ? (
|
||||
{participantsData &&
|
||||
participantsData.length > 0 &&
|
||||
participantsData[0].votes_count > 0 ? (
|
||||
<div className="flex flex-col items-center overflow-hidden bg-fillNavyBlue rounded-[10px] sm:rounded-[30px] max-w-[940px] w-full px-[5px] py-[20px] sm:p-[20px] sm:gap-[20px] gap-[10px]">
|
||||
{participantsData.map((participant, index) =>
|
||||
participant.votes_count === participantsData[0].votes_count ? (
|
||||
participant.votes_count ===
|
||||
participantsData[0].votes_count ? (
|
||||
participant.url ? (
|
||||
<Link
|
||||
href={participant.url ? participant.url : ''}
|
||||
href={participant.url ? participant.url : ""}
|
||||
target="_blank"
|
||||
className="w-full"
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<ParticipantCard
|
||||
index={index}
|
||||
hasUrl={true}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -283,9 +316,10 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
key={v4()}
|
||||
index={index}
|
||||
hasUrl={false}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -295,12 +329,14 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
winner={true}
|
||||
/>
|
||||
)
|
||||
) : null,
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{winnersCount > 1 ? <div className="w-full h-[1px] bg-[#3636A3]"></div> : null}
|
||||
{winnersCount > 1 ? (
|
||||
<div className="w-full h-[1px] bg-[#3636A3]"></div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center max-w-[940px] w-full gap-5 justify-center mx-auto">
|
||||
|
|
@ -309,16 +345,18 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
!hasVotes ? (
|
||||
participant.url ? (
|
||||
<Link
|
||||
href={participant.url ? participant.url : ''}
|
||||
href={participant.url ? participant.url : ""}
|
||||
target="_blank"
|
||||
className="w-full mx-auto"
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<ParticipantCard
|
||||
index={index}
|
||||
hasUrl={true}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -333,9 +371,10 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
hasUrl={false}
|
||||
key={v4()}
|
||||
index={index}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -346,19 +385,22 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
/>
|
||||
)
|
||||
) : (
|
||||
participant.votes_count !== participantsData[0].votes_count &&
|
||||
participant.votes_count !==
|
||||
participantsData[0].votes_count &&
|
||||
(participant.url ? (
|
||||
<Link
|
||||
href={participant.url ? participant.url : ''}
|
||||
href={participant.url ? participant.url : ""}
|
||||
target="_blank"
|
||||
className="w-full mx-auto"
|
||||
key={v4()}>
|
||||
key={v4()}
|
||||
>
|
||||
<ParticipantCard
|
||||
index={index}
|
||||
hasUrl={true}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -373,9 +415,10 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
hasUrl={false}
|
||||
key={v4()}
|
||||
index={index}
|
||||
voteStatus={voteStatus ? voteStatus : ''}
|
||||
voteStatus={voteStatus ? voteStatus : ""}
|
||||
isFirst={index === 0 ? true : false}
|
||||
name={participant.title}
|
||||
description={participant.description}
|
||||
progress={participant.votes_percents}
|
||||
votes={participant.votes_count}
|
||||
voteCode={participant.vote_code}
|
||||
|
|
@ -385,7 +428,7 @@ const ParticipantsList = ({ vote_id }: IParams) => {
|
|||
winner={false}
|
||||
/>
|
||||
))
|
||||
),
|
||||
)
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
"use server";
|
||||
|
||||
export async function getLotteryStatus(startTime: string, endTime: string) {
|
||||
export function getLotteryStatus(startTime: string, endTime: string) {
|
||||
const now = new Date();
|
||||
const start = new Date(startTime);
|
||||
const end = new Date(endTime);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface VotingItem {
|
|||
votes_count: number;
|
||||
vote_code: string;
|
||||
title: null | string;
|
||||
description: null | string;
|
||||
photo: null | string;
|
||||
votes_percents: number;
|
||||
url?: string;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface IQuizQuestions {
|
|||
|
||||
export interface Data {
|
||||
id: number;
|
||||
uuid?: number;
|
||||
title: string;
|
||||
date: string;
|
||||
banner: string;
|
||||
|
|
@ -13,7 +14,12 @@ export interface Data {
|
|||
description: string;
|
||||
rules: Note[];
|
||||
notes: Note[];
|
||||
questions: Question[];
|
||||
questions?: Question[];
|
||||
has_steps: 0 | 1;
|
||||
steps?: {
|
||||
tapgyr: number;
|
||||
questions: Question[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,54 @@
|
|||
export interface IQuizQuestionsWinners {
|
||||
data: Datum[];
|
||||
meta: IMeta;
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
total_score_of_client: string;
|
||||
correct_answers_time: string;
|
||||
client_id: number;
|
||||
client: Client;
|
||||
}
|
||||
|
||||
export interface Client {
|
||||
id: number;
|
||||
phone: string;
|
||||
answers: Answer[];
|
||||
client?: {
|
||||
id: number;
|
||||
phone: string;
|
||||
};
|
||||
tapgyr_breakdown?: {
|
||||
tapgyr: number;
|
||||
tapgyr_correct_time: number;
|
||||
tapgyr_score: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Answer {
|
||||
id: number;
|
||||
question_id: number;
|
||||
score: number;
|
||||
serial_number_for_correct: number;
|
||||
client_id: number;
|
||||
tapgyr?: number;
|
||||
quiz_id?: number;
|
||||
}
|
||||
|
||||
interface IMeta {
|
||||
current_page: number;
|
||||
from: number;
|
||||
last_page: number;
|
||||
path: string;
|
||||
per_page: number;
|
||||
to: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ISearchNetije {
|
||||
total_nobat: number;
|
||||
total_score: number;
|
||||
place: number;
|
||||
phone: string;
|
||||
tapgyr_breakdown: {
|
||||
tapgyr: number;
|
||||
tapgyr_total_score: number;
|
||||
tapgyr_total_nobat: number;
|
||||
tapgyr_place: number;
|
||||
answers: Answer[];
|
||||
}[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
export interface IQuizSearchData {
|
||||
data: { [key: string]: Datum };
|
||||
result: {
|
||||
total_score: number;
|
||||
total_serial: number;
|
||||
place: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Datum {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface VotingItem {
|
|||
votes_count: number;
|
||||
vote_code: string;
|
||||
title: null | string;
|
||||
description: null | string;
|
||||
photo: null | string;
|
||||
votes_percents: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: false,
|
||||
|
||||
images: {
|
||||
|
|
|
|||
16
package.json
16
package.json
|
|
@ -22,8 +22,8 @@
|
|||
"@tanstack/react-query": "^4.32.0",
|
||||
"@tanstack/react-query-devtools": "^4.32.0",
|
||||
"@types/node": "18.15.13",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "^1.5.1",
|
||||
|
|
@ -33,13 +33,13 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"framer-motion": "^10.12.16",
|
||||
"lucide-react": "^0.408.0",
|
||||
"next": "^14.1.0",
|
||||
"next": "15.1.6",
|
||||
"next-seo": "^6.0.0",
|
||||
"postcss": "8.4.23",
|
||||
"react": "^18.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-fast-marquee": "^1.3.5",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^4.8.0",
|
||||
|
|
@ -60,6 +60,10 @@
|
|||
"devDependencies": {
|
||||
"@types/date-fns": "^2.6.0",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-config-next": "^14.1.0"
|
||||
"eslint-config-next": "15.1.6"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/react": "19.0.8",
|
||||
"@types/react-dom": "19.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ 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`,
|
||||
// ===================================================================
|
||||
|
||||
// Votes ================================================================
|
||||
allVotes: "/voting/show_on_site",
|
||||
vote: (vote_id: string) => `/voting/${vote_id}`,
|
||||
voteUUID: (vote_id: string) => `/voting/uuid/${vote_id}`,
|
||||
// ======================================================================
|
||||
|
||||
// Lottery ================================================================
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ISearchNetije } from "@/models/quizQuestionsWinners.model";
|
||||
import { create } from "zustand";
|
||||
|
||||
interface ILotteryStatus {
|
||||
|
|
@ -5,8 +6,52 @@ interface ILotteryStatus {
|
|||
setStatus: (value: "Upcoming" | "Finished" | "Ongoing") => void;
|
||||
}
|
||||
|
||||
interface IQuizSearch {
|
||||
active: boolean;
|
||||
setActive: (value: boolean) => void;
|
||||
}
|
||||
|
||||
interface IStep {
|
||||
step: number | null;
|
||||
setStep: (value: number | null) => void;
|
||||
}
|
||||
|
||||
interface ILoading {
|
||||
loading: boolean;
|
||||
setLoading: (value: boolean) => void;
|
||||
}
|
||||
|
||||
interface IQuizResults {
|
||||
resultData: ISearchNetije[];
|
||||
setResultData: (value: ISearchNetije[]) => void;
|
||||
error: string;
|
||||
setError: (value: string) => void;
|
||||
}
|
||||
|
||||
export const useLotteryStatus = create<ILotteryStatus>((set) => ({
|
||||
status: "Upcoming",
|
||||
setStatus: (value: "Upcoming" | "Finished" | "Ongoing") =>
|
||||
set({ status: value }),
|
||||
}));
|
||||
|
||||
export const useQuizSearchActive = create<IQuizSearch>((set) => ({
|
||||
active: false,
|
||||
setActive: (value: boolean) => set({ active: value }),
|
||||
}));
|
||||
|
||||
export const useSteps = create<IStep>((set) => ({
|
||||
step: null,
|
||||
setStep: (value: number | null) => set({ step: value }),
|
||||
}));
|
||||
|
||||
export const useQuizResults = create<IQuizResults>((set) => ({
|
||||
resultData: [],
|
||||
setResultData: (value: ISearchNetije[]) => set({ resultData: value }),
|
||||
error: "",
|
||||
setError: (value: string) => set({ error: value }),
|
||||
}));
|
||||
|
||||
export const useResultsLoading = create<ILoading>((set) => ({
|
||||
loading: false,
|
||||
setLoading: (value: boolean) => set({ loading: value }),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { IQuizSearchData } from '@/models/quizSearchData.model';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import { IQuizSearchData } from "@/models/quizSearchData.model";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
export interface IQuizSearch {
|
||||
quizSearchData: IQuizSearchData | undefined;
|
||||
setQuizSearchData: Dispatch<SetStateAction<IQuizSearchData | undefined>>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue