diff --git a/.prettierrc.js b/.prettierrc.js
index 65e18f5ff..28033a2b6 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -7,4 +7,5 @@ module.exports = {
bracketSpacing: true,
arrowParens: "avoid",
htmlWhitespaceSensitivity: "ignore",
+ endOfLine: "auto",
};
diff --git a/docs/mvp-spec.md b/docs/mvp-spec.md
index fab47685e..e083d7788 100644
--- a/docs/mvp-spec.md
+++ b/docs/mvp-spec.md
@@ -14,9 +14,10 @@
Количество карточек для каждого уровня сложности можете назначать и свои или выбрать готовый пресет.
Предлагаем следующее пресеты:
- - Легкий уровень - 6 карточек (3 пары)
- - Средний уровень - 12 карточек (6 пар)
- - Сложный уровень - 18 карточек (9 пар)
+
+- Легкий уровень - 6 карточек (3 пары)
+- Средний уровень - 12 карточек (6 пар)
+- Сложный уровень - 18 карточек (9 пар)
Как только уровень сложности выбран, игроку показывается на игровой поле.
diff --git a/package-lock.json b/package-lock.json
index edaf5083f..dd3e2dd10 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5961,9 +5961,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001522",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz",
- "integrity": "sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==",
+ "version": "1.0.30001660",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
+ "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
"funding": [
{
"type": "opencollective",
@@ -22559,9 +22559,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001522",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz",
- "integrity": "sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg=="
+ "version": "1.0.30001660",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
+ "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
diff --git a/public/Vector.svg b/public/Vector.svg
new file mode 100644
index 000000000..dc94e72f4
--- /dev/null
+++ b/public/Vector.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/robots.txt b/public/robots.txt
index e9e57dc4d..5537f0739 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,3 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
-Disallow:
+Disallow:
\ No newline at end of file
diff --git a/src/api/getLeaderboard.js b/src/api/getLeaderboard.js
new file mode 100644
index 000000000..fd64ba158
--- /dev/null
+++ b/src/api/getLeaderboard.js
@@ -0,0 +1,14 @@
+const apiUrl = "https://wedev-api.sky.pro/api/leaderboard";
+
+export const getLeaderboard = async () => {
+ // Запрос к API получения списка победителей
+ const response = await fetch(apiUrl, {
+ method: "GET",
+ });
+
+ if (!response.ok) {
+ throw new Error(`Не удалось получить данные с сервера! status: ${response.status}`);
+ }
+
+ return await response.json();
+};
diff --git a/src/api/postLeaderboard.js b/src/api/postLeaderboard.js
new file mode 100644
index 000000000..a3eb0b40e
--- /dev/null
+++ b/src/api/postLeaderboard.js
@@ -0,0 +1,23 @@
+const apiUrl = "https://wedev-api.sky.pro/api/leaderboard";
+
+export const postLeaderboard = async ({ userName, userTime }) => {
+ // Запрос к API отправки победителя
+ const response = await fetch(apiUrl, {
+ method: "POST",
+ body: JSON.stringify({ name: goodByeHacker(userName), time: userTime }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Не удалось отправить данные на сервер! status: ${response.status}`);
+ }
+
+ return await response.json();
+};
+
+function goodByeHacker(text) {
+ return text
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll("QUOTE_BEGIN", "
")
+ .replaceAll("QUOTE_END", "
");
+}
diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx
index 7526a56c8..bdb9ef3e2 100644
--- a/src/components/Cards/Cards.jsx
+++ b/src/components/Cards/Cards.jsx
@@ -1,14 +1,18 @@
import { shuffle } from "lodash";
-import { useEffect, useState } from "react";
+import { useContext, useEffect, useState } from "react";
import { generateDeck } from "../../utils/cards";
import styles from "./Cards.module.css";
import { EndGameModal } from "../../components/EndGameModal/EndGameModal";
import { Button } from "../../components/Button/Button";
import { Card } from "../../components/Card/Card";
+import { EasyContext } from "../../contexte/contexte";
+import { useNavigate } from "react-router-dom";
// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
const STATUS_WON = "STATUS_WON";
+// Пауза игры при допускании ошибки выбора карточки
+const STATUS_PAUSED = "STATUS_PAUSED";
// Идет игра: карты закрыты, игрок может их открыть
const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS";
// Начало игры: игрок видит все карты в течении нескольких секунд
@@ -19,6 +23,7 @@ function getTimerValue(startDate, endDate) {
return {
minutes: 0,
seconds: 0,
+ diffInSecconds: 0,
};
}
@@ -27,11 +32,13 @@ function getTimerValue(startDate, endDate) {
}
const diffInSecconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000);
+
const minutes = Math.floor(diffInSecconds / 60);
const seconds = diffInSecconds % 60;
return {
minutes,
seconds,
+ diffInSecconds,
};
}
@@ -41,8 +48,18 @@ function getTimerValue(startDate, endDate) {
* previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
*/
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
+ // Когда игра окончена, переход на главную страницу
+ const navigate = useNavigate();
+ function goTo() {
+ navigate("/");
+ }
+
+ // Обработка количества попыток
+ const { tries, setTries, isEasyMode, checkedLevel, leadrs, setLeaders } = useContext(EasyContext);
+
// В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
const [cards, setCards] = useState([]);
+
// Текущий статус игры
const [status, setStatus] = useState(STATUS_PREVIEW);
@@ -55,18 +72,35 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const [timer, setTimer] = useState({
seconds: 0,
minutes: 0,
+ diffInSecconds: 0,
});
- function finishGame(status = STATUS_LOST) {
+ // Если количество попыток равно 0 устанавливается стату проиграл и игра заканчивается
+ useEffect(() => {
+ if (tries === 0) {
+ finishGame(STATUS_LOST);
+ }
+ }, [tries]);
+
+ function finishGame(status) {
setGameEndDate(new Date());
setStatus(status);
}
+
+ function pausedGame(status = STATUS_PAUSED) {
+ setStatus(status);
+ }
+
function startGame() {
const startDate = new Date();
setGameEndDate(null);
setGameStartDate(startDate);
setTimer(getTimerValue(startDate, null));
setStatus(STATUS_IN_PROGRESS);
+ // Добавлена проверка на включенный режим 3-х попыток
+ if (!isEasyMode) {
+ setTries(1);
+ }
}
function resetGame() {
setGameStartDate(null);
@@ -75,6 +109,24 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
setStatus(STATUS_PREVIEW);
}
+ function сontinueGame(status = STATUS_IN_PROGRESS) {
+ setStatus(status);
+ }
+
+ // Функция запускает разные сценарии для кнопки в модальном окне
+ function whatsNext() {
+ if (status === STATUS_PAUSED) {
+ сontinueGame(STATUS_IN_PROGRESS);
+ }
+ if (status === STATUS_LOST) {
+ goTo();
+ setTries(3);
+ }
+ if (status === STATUS_WON) {
+ resetGame();
+ }
+ }
+
/**
* Обработка основного действия в игре - открытие карты.
* После открытия карты игра может пепереходит в следующие состояния
@@ -123,18 +175,34 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
return false;
});
- const playerLost = openCardsWithoutPair.length >= 2;
+ const havMistake = openCardsWithoutPair.length >= 2;
- // "Игрок проиграл", т.к на поле есть две открытые карты без пары
- if (playerLost) {
- finishGame(STATUS_LOST);
- return;
+ // Если на поле есть две открытые карты без пары, то игра паузится и уменьшается количество попыток
+ function minusTries() {
+ setTries(prev => prev - 1);
+ }
+
+ // "Игрок допустил ошибку", т.к на поле есть две открытые карты без пары
+ if (havMistake) {
+ minusTries();
+ pausedGame(STATUS_PAUSED);
}
// ... игра продолжается
};
- const isGameEnded = status === STATUS_LOST || status === STATUS_WON;
+ // Проверка на попадание в топ 10 игроков
+ function isTopTen() {
+ const isTenPlayers = leadrs.length === 10;
+ if (status === STATUS_WON && checkedLevel === 3) {
+ if (leadrs.at(-1).time > timer.diffInSecconds || (isTenPlayers && leadrs[9].time > timer.diffInSecconds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const isGameEnded = status === STATUS_LOST || status === STATUS_WON || status === STATUS_PAUSED;
// Игровой цикл
useEffect(() => {
@@ -160,6 +228,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
return () => {
clearTimeout(timerId);
};
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [status, pairsCount, previewSeconds]);
// Обновляем значение таймера в интервале
@@ -167,6 +236,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const intervalId = setInterval(() => {
setTimer(getTimerValue(gameStartDate, gameEndDate));
}, 300);
+
return () => {
clearInterval(intervalId);
};
@@ -195,7 +265,12 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
>
)}
- {status === STATUS_IN_PROGRESS ? : null}
+
+ {isEasyMode && status === STATUS_IN_PROGRESS && (
+ Осталось {tries} попытки!
+ )}
+ {status === STATUS_IN_PROGRESS ? : null}
+
@@ -206,6 +281,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
open={status !== STATUS_IN_PROGRESS ? true : card.open}
suit={card.suit}
rank={card.rank}
+ status={STATUS_IN_PROGRESS}
/>
))}
@@ -213,10 +289,16 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
{isGameEnded ? (
) : null}
diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css
index 000c5006c..07b4904ef 100644
--- a/src/components/Cards/Cards.module.css
+++ b/src/components/Cards/Cards.module.css
@@ -70,3 +70,20 @@
margin-bottom: -12px;
}
+
+.attempt {
+ color: #fff;
+ font-variant-numeric: lining-nums proportional-nums;
+ font-family: StratosSkyeng;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 32px;
+}
+
+.buttonContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ row-gap: 12px;
+}
diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx
index 722394833..edb824971 100644
--- a/src/components/EndGameModal/EndGameModal.jsx
+++ b/src/components/EndGameModal/EndGameModal.jsx
@@ -1,27 +1,120 @@
import styles from "./EndGameModal.module.css";
-
import { Button } from "../Button/Button";
-
import deadImageUrl from "./images/dead.png";
import celebrationImageUrl from "./images/celebration.png";
+import { postLeaderboard } from "../../api/postLeaderboard";
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+
+export function EndGameModal({
+ isWon,
+ gameDurationSeconds,
+ gameDurationMinutes,
+ onClick,
+ tries,
+ checkedLevel,
+ isTopTen,
+ leaders,
+ setLeaders,
+ diffInSecconds,
+}) {
+ const [inputValue, setInputValue] = useState("");
+
+ const navigate = useNavigate();
+ function goTo() {
+ navigate("/Leaderboard");
+ }
+
+ function handleClick() {
+ postLeaderboard({ userName: inputValue, userTime: diffInSecconds }).then(leaderboard => {
+ setLeaders(leaderboard.leaders.sort((a, b) => a.time - b.time).slice(0, 10));
+ });
+ goTo();
+ }
-export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) {
- const title = isWon ? "Вы победили!" : "Вы проиграли!";
+ let imgSrc;
+ let imgAlt;
- const imgSrc = isWon ? celebrationImageUrl : deadImageUrl;
+ if (isWon === "STATUS_PAUSED") {
+ isWon = "Вы допустили ошибку";
+ imgSrc = deadImageUrl;
+ imgAlt = "dead emodji";
+ }
- const imgAlt = isWon ? "celebration emodji" : "dead emodji";
+ // Модалка лидерборда
+ if (isTopTen) {
+ isWon = "Вы попали на лидерборд!";
+ imgSrc = celebrationImageUrl;
+ imgAlt = "celebration emodji";
+ } else if (isWon === "STATUS_WON") {
+ isWon = "Вы победили!";
+ imgSrc = celebrationImageUrl;
+ imgAlt = "celebration emodji";
+ }
+
+ if (isWon === "STATUS_LOST") {
+ isWon = "Вы проиграли!";
+ imgSrc = deadImageUrl;
+ imgAlt = "dead emodji";
+ }
+
+ if (tries === 0) {
+ isWon = "Вы проиграли!";
+ imgSrc = deadImageUrl;
+ imgAlt = "dead emodji";
+ }
+
+ const title = isWon;
return (
{title}
-
Затраченное время:
-
- {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
-
+ {isWon === "Вы допустили ошибку" &&
Оставшеся количество попыток:
}
+ {isWon === "Вы победили!" &&
Затраченное время:
}
+ {isWon === "Вы попали на лидерборд!" && (
+
setInputValue(e.target.value)}
+ placeholder="Введите имя"
+ maxLength="20"
+ />
+ )}
+ {isWon === "Вы попали на лидерборд!" &&
Затраченное время:
}
+ {isWon === "Вы проиграли!" &&
Затраченное время:
}
+ {isWon === "Вы допустили ошибку" && (
+
+ )}
+ {isWon === "Вы победили!" && (
+
+ {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
+
+ )}
+
+ {/* Модалка лидербборда */}
+ {isWon === "Вы попали на лидерборд!" && (
+
+ {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
+
+ )}
+ {isWon === "Вы проиграли!" && (
+
+ {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
+
+ )}
+ {isWon === "Вы допустили ошибку" &&
}
+ {isWon === "Вы победили!" &&
}
+ {isWon === "Вы попали на лидерборд!" &&
}
+ {isWon === "Вы проиграли!" &&
}
-
+ {isWon === "Вы попали на лидерборд!" && (
+
+ )}
);
}
diff --git a/src/components/EndGameModal/EndGameModal.module.css b/src/components/EndGameModal/EndGameModal.module.css
index 9368cb8b5..b4b55c370 100644
--- a/src/components/EndGameModal/EndGameModal.module.css
+++ b/src/components/EndGameModal/EndGameModal.module.css
@@ -1,12 +1,13 @@
.modal {
width: 480px;
- height: 459px;
+ /* height: 459px; */
border-radius: 12px;
background: #c2f5ff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
+ padding-block: 36px;
}
.image {
@@ -25,6 +26,7 @@
line-height: 48px;
margin-bottom: 28px;
+ text-align: center;
}
.description {
@@ -49,3 +51,49 @@
margin-bottom: 40px;
}
+
+.nameInput {
+ width: 276px;
+ height: 45px;
+ border-radius: 10px;
+ border: none;
+ box-sizing: border-box;
+ padding: 10px 14px 10px 14px;
+ margin-bottom: 28px;
+ color: #999999;
+ font-size: 24px;
+}
+
+.nameInput:active,
+:hover,
+:focus {
+ outline: 0;
+ outline-offset: 0;
+}
+
+.nameInput::placeholder {
+ color: #999999;
+ font-family: StratosSkyeng;
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: center;
+}
+
+.gameButtonsLink {
+ background-color: transparent;
+ border: none;
+ font-family: Roboto;
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: center;
+ color: #004980;
+ margin-top: 18px;
+}
+
+.gameButtonsLink:hover {
+ text-decoration: underline;
+ cursor: pointer;
+}
diff --git a/src/contexte/contexte.jsx b/src/contexte/contexte.jsx
new file mode 100644
index 000000000..14360784d
--- /dev/null
+++ b/src/contexte/contexte.jsx
@@ -0,0 +1,34 @@
+import { createContext, useState, useEffect } from "react";
+import { getLeaderboard } from "../api/getLeaderboard";
+
+export const EasyContext = createContext(false);
+
+export const EasyProvider = ({ children }) => {
+ const [tries, setTries] = useState(3);
+ const [isEasyMode, setIsEasyMode] = useState(false);
+ const [leadrs, setLeaders] = useState([]);
+ const [checkedLevel, setCheckedLevel] = useState();
+
+ useEffect(() => {
+ if (leadrs.length === 0) {
+ getData();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ async function getData() {
+ const data = await getLeaderboard();
+ const leaders = data.leaders.sort((a, b) => a.time - b.time).slice(0, 10);
+ setLeaders(leaders);
+ }
+
+
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/index.js b/src/index.js
index f689c5f0b..d39f1dd9d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,10 +3,13 @@ import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";
+import { EasyProvider } from "./contexte/contexte";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
-
+
+
+
,
);
diff --git a/src/pages/Leaderboard/Leaderboard.jsx b/src/pages/Leaderboard/Leaderboard.jsx
new file mode 100644
index 000000000..3dac563c2
--- /dev/null
+++ b/src/pages/Leaderboard/Leaderboard.jsx
@@ -0,0 +1,35 @@
+import { Button } from "../../components/Button/Button";
+import styles from "../Leaderboard/Leaderboard.module.css";
+import { useNavigate } from "react-router-dom";
+import { LeaderboardPage } from "./LeaderboardPage";
+import { useContext } from "react";
+import { EasyContext } from "../../contexte/contexte";
+
+export function Leaderboard() {
+ const { leadrs } = useContext(EasyContext);
+
+ const navigate = useNavigate();
+ function goTo() {
+ navigate("/");
+ }
+
+ return (
+
+
+
Лидерборд
+
+
+
+
+
+
Позиция
+
Пользователь
+
Время
+
+ {leadrs?.length &&
+ leadrs.map((leader, index) => (
+
+ ))}
+
+ );
+}
diff --git a/src/pages/Leaderboard/Leaderboard.module.css b/src/pages/Leaderboard/Leaderboard.module.css
new file mode 100644
index 000000000..c43287db6
--- /dev/null
+++ b/src/pages/Leaderboard/Leaderboard.module.css
@@ -0,0 +1,51 @@
+.container {
+ max-width: 944px;
+ margin: 0 auto;
+ padding-top: 22px;
+ box-sizing: border-box;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 40px;
+}
+
+.titleLeaderBoard {
+ font-family: Roboto;
+ color: #ffffff;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: left;
+}
+
+.titleBoard {
+ width: 944px;
+ height: 64px;
+ background-color: #ffffff;
+ border-radius: 12px;
+ padding: 16px 20px 16px 20px;
+ box-sizing: border-box;
+ display: grid;
+ grid-template-columns: [start] 264px [line2] 564px [line3] 74px [end];
+}
+
+.title {
+ font-family: Roboto;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: left;
+ color: #999999;
+}
+
+.titlePage {
+ font-family: Roboto;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: left;
+ color: #000000;
+}
diff --git a/src/pages/Leaderboard/LeaderboardPage.jsx b/src/pages/Leaderboard/LeaderboardPage.jsx
new file mode 100644
index 000000000..ec2736f46
--- /dev/null
+++ b/src/pages/Leaderboard/LeaderboardPage.jsx
@@ -0,0 +1,19 @@
+import styles from "../Leaderboard/Leaderboard.module.css";
+
+export function LeaderboardPage({ id, name, time }) {
+ const minutes = Math.floor(time / 60);
+ const seconds = time % 60;
+
+ return (
+
+
+
#{id}
+
{name.slice(0, 20)}
+
+
+ {minutes.toString().padStart("2", "0")}.{seconds.toString().padStart("2", "0")}
+
+
+
+ );
+}
diff --git a/src/pages/SelectLevelPage/SelectLevelButton.jsx b/src/pages/SelectLevelPage/SelectLevelButton.jsx
new file mode 100644
index 000000000..b2c0f1c10
--- /dev/null
+++ b/src/pages/SelectLevelPage/SelectLevelButton.jsx
@@ -0,0 +1,17 @@
+import styles from "./SelectLevelPage.module.css";
+export function SelectLevelButton({ checkedLevel, levelNumber }) {
+ return (
+ <>
+ {checkedLevel !== levelNumber && (
+
+ {levelNumber}
+
+ )}
+ {checkedLevel === levelNumber && (
+
+ {levelNumber}
+
+ )}
+ >
+ );
+}
diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx
index 758942e51..cf1fbd143 100644
--- a/src/pages/SelectLevelPage/SelectLevelPage.jsx
+++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx
@@ -1,28 +1,63 @@
-import { Link } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
import styles from "./SelectLevelPage.module.css";
+import { useContext } from "react";
+import { EasyContext } from "../../contexte/contexte";
+import { Button } from "../../components/Button/Button";
+import { SelectLevelButton } from "./SelectLevelButton";
export function SelectLevelPage() {
+ const { isEasyMode, setIsEasyMode, checkedLevel, setCheckedLevel } = useContext(EasyContext);
+
+ const handleClick = id => {
+ setCheckedLevel(Number(id));
+ };
+
+ const navigate = useNavigate();
+ function goTo() {
+ if (!checkedLevel) {
+ alert("Необходимо выбрать уровень игры");
+ } else {
+ navigate(`/game/${checkedLevel * 3}`);
+ }
+ }
+
return (
-
Выбери сложность
+
+ Выбери
сложность
+
+
+
+
+
+
+
Перейти к лидерборду
+
+
);
diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css
index 390ac0def..81aa13230 100644
--- a/src/pages/SelectLevelPage/SelectLevelPage.module.css
+++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css
@@ -8,13 +8,14 @@
.modal {
width: 480px;
- height: 459px;
+ /* height: 459px; */
border-radius: 12px;
background: #c2f5ff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
+ padding-block: 36px;
}
.title {
@@ -46,6 +47,22 @@
border-radius: 12px;
background: #fff;
+ cursor: pointer;
+}
+
+.levelChek {
+ display: flex;
+ width: 97px;
+ height: 98px;
+ flex-direction: column;
+ justify-content: center;
+ flex-shrink: 0;
+
+ border-radius: 12px;
+ background: #fff;
+ border: 4px solid #0080c1;
+ box-sizing: border-box;
+ cursor: pointer;
}
.levelLink {
@@ -62,3 +79,77 @@
.levelLink:visited {
color: #0080c1;
}
+
+.isEasyMode {
+ display: flex;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 8px 16px 8px 16px;
+ border-radius: 12px;
+ background-color: #fff;
+}
+
+.isEasyModeTitle {
+ font-family: Roboto;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: center;
+ cursor: pointer;
+}
+
+.checkbox {
+ display: none;
+}
+
+.customCheckbox {
+ position: relative;
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ background-color: #fff;
+ margin-right: 8px;
+ vertical-align: sub;
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+.customCheckbox::before {
+ content: "";
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ background-image: url(/public/Vector.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%) scale(0);
+ margin-top: 2px;
+}
+
+.checkbox:checked + .customCheckbox::before {
+ transform: translate(-50%, -50%) scale(1);
+}
+
+.gameButtonsContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 18px;
+ margin-top: 38px;
+}
+
+.gameButtonsLink {
+ font-family: Roboto;
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 32px;
+ text-align: left;
+ color: #004980;
+}
+
+.radio {
+ display: none;
+}
diff --git a/src/router.js b/src/router.js
index da6e94b51..4826abf63 100644
--- a/src/router.js
+++ b/src/router.js
@@ -1,6 +1,7 @@
import { createBrowserRouter } from "react-router-dom";
import { GamePage } from "./pages/GamePage/GamePage";
import { SelectLevelPage } from "./pages/SelectLevelPage/SelectLevelPage";
+import { Leaderboard } from "./pages/Leaderboard/Leaderboard";
export const router = createBrowserRouter(
[
@@ -12,6 +13,10 @@ export const router = createBrowserRouter(
path: "/game/:pairsCount",
element: ,
},
+ {
+ path: "/leaderboard",
+ element: ,
+ },
],
/**
* basename нужен для корректной работы в gh pages