mirror of
https://github.com/StepanovPlaton/torrent_frontend.git
synced 2026-04-05 21:30:50 +04:00
Add masonry layout and small fixes
This commit is contained in:
16
package-lock.json
generated
16
package-lock.json
generated
@@ -13,12 +13,14 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-responsive-masonry": "^2.2.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-responsive-masonry": "^2.1.3",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
@@ -485,6 +487,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-responsive-masonry": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-responsive-masonry/-/react-responsive-masonry-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-aOFUtv3QwNMmy0BgpQpvivQ/+vivMTB6ARrzf9eTSXsLzXpVnfEtjpHpSknYDnr8KaQmlgeauAj8E7wo/qMOTg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
||||||
@@ -3744,6 +3755,11 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/react-responsive-masonry": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-IYbnfe2tWCZ3pvyTLyBWPj7uv5ZmNOULYMcAZi5a47ZLhSotOck1vkkISq6gP2qiyWdMvPfeMhjvYzUYGw9BOQ=="
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
|||||||
@@ -14,12 +14,14 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-responsive-masonry": "^2.2.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-responsive-masonry": "^2.1.3",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
|||||||
@@ -1,126 +1,130 @@
|
|||||||
import { GameService } from "@/entities/game";
|
import { GameService } from "@/entities/game";
|
||||||
import { GameCard } from "@/features/gameCard";
|
import { GameCard } from "@/features/gameCard";
|
||||||
|
import { getYouTubeID } from "@/shared/utils";
|
||||||
import { Section } from "@/widgets/section";
|
import { Section } from "@/widgets/section";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default async function Games({
|
export default async function Games({
|
||||||
params: { game_id },
|
params: { game_id },
|
||||||
}: {
|
}: {
|
||||||
params: { game_id: number };
|
params: { game_id: number };
|
||||||
}) {
|
}) {
|
||||||
const gameCards = await GameService.getGameCards();
|
const gameCards = await GameService.getGameCards();
|
||||||
const game = await GameService.getGame(game_id);
|
const game = await GameService.getGame(game_id);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{game && (
|
{game && (
|
||||||
<div className="p-4 flex flex-col lp:block">
|
<div className="p-4 flex flex-col lp:block">
|
||||||
{game.cover && (
|
{game.cover && (
|
||||||
<div className="lp:w-[60%] lp:px-4 lp:pl-0 pt-2 float-left">
|
<div className="lp:w-[60%] lp:px-4 lp:pl-0 py-2 float-left">
|
||||||
<Image
|
<Image
|
||||||
src={game.cover}
|
src={game.cover}
|
||||||
className="rounded-lg aspect-video object-cover"
|
className="rounded-lg w-full object-contain"
|
||||||
alt=""
|
alt=""
|
||||||
width={1280}
|
width={1280}
|
||||||
height={720}
|
height={720}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="pt-2 lp:max-w-[40%]">
|
<span className="lp:max-w-[40%]">
|
||||||
<h1 className="text-4xl">{game.title}</h1>
|
<h1 className="text-4xl">{game.title}</h1>
|
||||||
{game.description && (
|
{game.description && (
|
||||||
<p className="text-md text-justify text-fg4 pt-2">
|
<p className="text-md text-justify text-fg4 pt-2">
|
||||||
{game.description}
|
{game.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex justify-between pt-6">
|
<div className="w-full flex justify-between pt-4">
|
||||||
{[
|
{[
|
||||||
[
|
[
|
||||||
{ name: "Система", value: game.system },
|
{ name: "Система", value: game.system },
|
||||||
{ name: "Процессор", value: game.processor },
|
{ name: "Процессор", value: game.processor },
|
||||||
{ name: "Оперативная память", value: game.memory },
|
{ name: "Оперативная память", value: game.memory },
|
||||||
{ name: "Видеокарта", value: game.graphics },
|
{ name: "Видеокарта", value: game.graphics },
|
||||||
{ name: "Место на диске", value: game.storage },
|
{ name: "Место на диске", value: game.storage },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "Версия игры",
|
name: "Версия игры",
|
||||||
value: `${
|
value: `${
|
||||||
game.version
|
game.version
|
||||||
} (обновлена ${game.update_date.toLocaleDateString(
|
} (обновлена ${game.update_date.toLocaleDateString(
|
||||||
"ru-ru"
|
"ru-ru"
|
||||||
)})`,
|
)})`,
|
||||||
},
|
},
|
||||||
{ name: "Язык", value: game.language },
|
{ name: "Язык", value: game.language },
|
||||||
{ name: "Разработчик", value: game.developer },
|
{ name: "Разработчик", value: game.developer },
|
||||||
{
|
{
|
||||||
name: "Год выхода",
|
name: "Год выхода",
|
||||||
value: game.release_date.toLocaleDateString("en-us", {
|
value: game.release_date.toLocaleDateString("en-us", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{ name: "Объём загрузки", value: game.download_size },
|
{ name: "Объём загрузки", value: game.download_size },
|
||||||
],
|
],
|
||||||
].map((section, i) => (
|
].map((section, i) => (
|
||||||
<ul key={i} className="w-[48%] bg-bg1 rounded-lg py-1 px-4">
|
<ul key={i} className="w-[48%] bg-bg1 rounded-lg py-1 px-4">
|
||||||
{section.map((req) => (
|
{section.map((req) => (
|
||||||
<li
|
<li
|
||||||
key={req.name}
|
key={req.name}
|
||||||
className="font-bold text-sm lp:text-md py-1"
|
className="font-bold text-sm lp:text-md py-1"
|
||||||
>
|
>
|
||||||
{req.name + ": "}
|
{req.name + ": "}
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-normal",
|
"font-normal",
|
||||||
req.value === undefined && "text-fg4"
|
req.value === undefined && "text-fg4"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{req.value ?? "Не известно"}
|
{req.value ?? "Не известно"}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{game.trailer && (
|
{game.trailer && getYouTubeID(game.trailer) && (
|
||||||
<iframe
|
<iframe
|
||||||
src={game.trailer.replace("/watch?v=", "/embed/")}
|
src={"https://youtube.com/embed/" + getYouTubeID(game.trailer)}
|
||||||
className="w-full aspect-video rounded-lg my-4"
|
className="w-full aspect-video rounded-lg mt-4"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="relative w-full flex items-center justify-around">
|
<div className="relative w-full flex items-center justify-around pt-4">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
process.env.NEXT_PUBLIC_CONTENT_URL + "/" + game.torrent_file
|
process.env.NEXT_PUBLIC_CONTENT_URL + "/" + game.torrent_file
|
||||||
}
|
}
|
||||||
className="p-4 bg-ac0 text-fg1 text-xl rounded-lg"
|
className="p-4 bg-ac0 text-fg1 text-2xl rounded-lg"
|
||||||
>
|
>
|
||||||
Скачать {game.title}.torrent
|
Скачать {game.title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex justify-end">
|
<div className="w-full flex justify-end">
|
||||||
<Link className="text-right text-sm" href="/how_to_download">
|
<Link
|
||||||
Как скачать игру
|
className="text-right text-sm relative top-4 lp:-top-4"
|
||||||
<br /> с помощью .torrent файла?
|
href="/how_to_download"
|
||||||
</Link>
|
>
|
||||||
</div>
|
Как скачать игру
|
||||||
</div>
|
<br /> с помощью .torrent файла?
|
||||||
)}
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{gameCards && (
|
{gameCards && (
|
||||||
<Section
|
<Section
|
||||||
name="Другие популярные игры"
|
name="Популярные игры"
|
||||||
link="/games"
|
link="/games"
|
||||||
invite_text={'Перейти в раздел "Игры"'}
|
invite_text={'Перейти в раздел "Игры"'}
|
||||||
>
|
>
|
||||||
{gameCards.map((card) => (
|
{gameCards.map((card) => (
|
||||||
<GameCard key={card.id} card={card} />
|
<GameCard key={card.id} card={card} />
|
||||||
))}
|
))}
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
--color-ac2: #8ec07c;
|
--color-ac2: #8ec07c;
|
||||||
|
|
||||||
--app-width: 70%;
|
--app-width: 70%;
|
||||||
font-size: calc((100vw / 1920) * 24);
|
font-size: calc((100vw / 1920) * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
@@ -50,19 +50,16 @@ body {
|
|||||||
background-color: var(--color-bg0);
|
background-color: var(--color-bg0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
|
||||||
:root {
|
|
||||||
--app-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
:root {
|
:root {
|
||||||
font-size: calc((100vw / 1920) * 56);
|
font-size: calc((100vw / 1920) * 56);
|
||||||
|
--app-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
:root {
|
:root {
|
||||||
font-size: calc((100vw / 1920) * 64);
|
font-size: calc((100vw / 1920) * 64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,61 +1,61 @@
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: ".Torrent: Как скачать?",
|
title: ".Torrent: Как скачать?",
|
||||||
description:
|
description:
|
||||||
".Torrent: Как скачать? - краткое руководство по скачиваю данных с помощью .torrent файлов",
|
".Torrent: Как скачать? - краткое руководство по скачиваю данных с помощью .torrent файлов",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function HowToDownload() {
|
export default async function HowToDownload() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col lp:flex-row justify-between p-4">
|
<div className="w-full flex flex-col lp:flex-row justify-between p-4">
|
||||||
<div className="w-full p-4 lp:w-[50%] lp:pr-10">
|
<div className="w-full p-4 lp:w-[50%] lp:pr-10">
|
||||||
<h1 className="text-4xl lp:text-3xl">Как скачать?</h1>
|
<h1 className="text-4xl lp:text-3xl">Как скачать?</h1>
|
||||||
<div className="text-fg4 text-justify pt-2">
|
<div className="text-fg4 text-justify pt-2">
|
||||||
Чтобы скачать что-либо с помощью торрент-файла, выполните следующие
|
Чтобы скачать данные с помощью торрент-файла, выполните следующие
|
||||||
шаги:
|
шаги:
|
||||||
<ul className="*:text-fg4">
|
<ul className="*:text-fg4">
|
||||||
<li>
|
<li>
|
||||||
1. Найдите и загрузите торрент-файл или magnet-ссылку, содержащую
|
1. Загрузите торрент-файл, содержащий информацию о файлах, которые
|
||||||
информацию о файле, который вы хотите скачать.
|
вы хотите скачать с нашего сайта.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
2. Откройте программу-клиент для загрузки торрентов, например,
|
2. Откройте программу-клиент для загрузки торрентов, например,
|
||||||
uTorrent, BitTorrent или qBittorrent.
|
uTorrent, BitTorrent или qBittorrent.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
3. В программе-клиенте выберите опцию "Open Torrent File" или "Add
|
3. В программе-клиенте выберите опцию "Open Torrent File" или "Add
|
||||||
Torrent" и выберите торрент-файл, который вы скачали в первом
|
Torrent" и выберите торрент-файл, который вы скачали в первом
|
||||||
шаге.
|
шаге.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
4. После этого начнется загрузка файлов, указанных в
|
4. После этого начнется загрузка файлов, указанных в
|
||||||
торрент-файле. Вы также можете выбрать папку, куда сохранять
|
торрент-файле. Вы также можете выбрать папку, куда сохранять
|
||||||
скачанные файлы.
|
скачанные файлы.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
5. Дождитесь завершения загрузки файлов. После этого вы сможете
|
5. Дождитесь завершения загрузки файлов. После этого вы сможете
|
||||||
открыть и использовать скачанные файлы на своем компьютере.
|
открыть и использовать скачанные файлы на своем компьютере.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full p-4 lp:w-[50%] lp:pl-10">
|
<div className="w-full p-4 lp:w-[50%] lp:pl-10">
|
||||||
<h2 className="text-4xl lp:text-3xl">Что такое .torrent файл?</h2>
|
<h2 className="text-4xl lp:text-3xl">Что такое .torrent файл?</h2>
|
||||||
<p className="text-fg4 text-justify pt-2">
|
<p className="text-fg4 text-justify pt-2">
|
||||||
Торрент-файл (или .torrent-файл) - это небольшой файл, который
|
Торрент-файл (или .torrent-файл) - это небольшой файл, который
|
||||||
содержит метаданные о файле или наборе файлов, которые можно загрузить
|
содержит метаданные о файле или наборе файлов, которые можно загрузить
|
||||||
с помощью протокола BitTorrent. В торрент-файле обычно указан адрес
|
с помощью протокола BitTorrent. В торрент-файле обычно указан адрес
|
||||||
трекера (специального сервера, отслеживающего пиров) и хеш-суммы
|
трекера (специального сервера, отслеживающего пиров) и хеш-суммы
|
||||||
частей файлов, которые необходимы для скачивания.
|
частей файлов, которые необходимы для скачивания.
|
||||||
<br />
|
<br />
|
||||||
<br /> Пользователь, желающий загрузить файл через BitTorrent, сначала
|
<br /> Пользователь, желающий загрузить файл через BitTorrent, сначала
|
||||||
скачивает торрент-файл или magnet-ссылку, загружает ее в
|
скачивает торрент-файл или magnet-ссылку, загружает ее в
|
||||||
торрент-клиент (программу для скачивания торрентов), и затем начинает
|
торрент-клиент (программу для скачивания торрентов), и затем начинает
|
||||||
загрузку файлов, участвуя в обмене данными с другими пользователями
|
загрузку файлов, участвуя в обмене данными с другими пользователями
|
||||||
(пирами) через сеть BitTorrent.
|
(пирами) через сеть BitTorrent.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
@@ -6,28 +5,22 @@ import { Header } from "@/widgets/header";
|
|||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: ".Torrent",
|
|
||||||
description:
|
|
||||||
".Torrent - сервис обмена .torrent файлами видеоигр, фильмов и аудиокниг",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
// suppressHydrationWarning for theme support
|
// suppressHydrationWarning for theme support
|
||||||
<html lang="ru" suppressHydrationWarning>
|
<html lang="ru" suppressHydrationWarning>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<ThemeProvider enableSystem={false} defaultTheme="light">
|
<ThemeProvider enableSystem={false} defaultTheme="light">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="w-full h-full max-w-[var(--app-width)] m-auto">
|
<div className="w-full h-full max-w-[var(--app-width)] m-auto">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import { GameService } from "@/entities/game";
|
import { GameService } from "@/entities/game";
|
||||||
import { GameCard } from "@/features/gameCard";
|
import { GameCard } from "@/features/gameCard";
|
||||||
import { Section } from "@/widgets/section";
|
import { Section } from "@/widgets/section";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: ".Torrent",
|
||||||
|
description:
|
||||||
|
".Torrent - сервис обмена .torrent файлами видеоигр, фильмов и аудиокниг",
|
||||||
|
};
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
const gameCards = await GameService.getGameCards();
|
const gameCards = await GameService.getGameCards();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{gameCards && gameCards.length > 0 && (
|
{gameCards && gameCards.length > 0 && (
|
||||||
<Section
|
<Section
|
||||||
name="Игры"
|
name="Игры"
|
||||||
link="/games"
|
link="/games"
|
||||||
invite_text={'Перейти в раздел "Игры"'}
|
invite_text={'Перейти в раздел "Игры"'}
|
||||||
>
|
>
|
||||||
{gameCards.map((card) => (
|
{gameCards.map((card) => (
|
||||||
<GameCard key={card.id} card={card} />
|
<GameCard key={card.id} card={card} />
|
||||||
))}
|
))}
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,30 +3,30 @@ import Image from "next/image";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export const GameCard = ({ card }: { card: GameCardType }) => {
|
export const GameCard = ({ card }: { card: GameCardType }) => {
|
||||||
return (
|
return (
|
||||||
<Link className="group/gamecard cursor-pointer" href={"/games/" + card.id}>
|
<Link className="group/gamecard cursor-pointer" href={"/games/" + card.id}>
|
||||||
{!!card.cover_preview && (
|
{!!card.cover_preview && (
|
||||||
<Image
|
<Image
|
||||||
src={card.cover_preview}
|
src={card.cover_preview}
|
||||||
className="rounded-lg"
|
className="rounded-lg object-contain"
|
||||||
alt=""
|
alt=""
|
||||||
width={700}
|
width={1280}
|
||||||
height={400}
|
height={720}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center justify-between pr-2">
|
<div className="flex items-center justify-between pr-2">
|
||||||
<h2 className="text-2xl py-1 group-hover/gamecard:underline underline-offset-4">
|
<h2 className="text-3xl tb:text-xl py-1 group-hover/gamecard:underline underline-offset-4">
|
||||||
{card.title}
|
{card.title}
|
||||||
</h2>
|
</h2>
|
||||||
{card.version && (
|
{card.version && (
|
||||||
<span className="text-xs max-w-[30%] line-clamp-2 text-fg4">
|
<span className="text-xs max-w-[30%] text-right line-clamp-2 text-fg4">
|
||||||
{card.version}
|
{card.version}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg tb:text-sm pr-2 text-justify line-clamp-5 text-fg4">
|
<p className="text-lg tb:text-sm pr-2 text-justify line-clamp-5 text-fg4">
|
||||||
{card.description}
|
{card.description}
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
6
src/shared/utils/get_youtube_id.ts
Normal file
6
src/shared/utils/get_youtube_id.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const getYouTubeID = (url: string) => {
|
||||||
|
const regExp =
|
||||||
|
/^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
||||||
|
const match = url.match(regExp);
|
||||||
|
return match && match[7].length == 11 ? match[7] : false;
|
||||||
|
};
|
||||||
3
src/shared/utils/index.ts
Normal file
3
src/shared/utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { getYouTubeID } from "./get_youtube_id";
|
||||||
|
|
||||||
|
export { getYouTubeID };
|
||||||
@@ -1,62 +1,71 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { SchemeSwitch } from "@/features/colorSchemeSwitch";
|
import { SchemeSwitch } from "@/features/colorSchemeSwitch";
|
||||||
import { PersonIcon, SearchIcon } from "@/shared/assets/icons";
|
import { PersonIcon, SearchIcon } from "@/shared/assets/icons";
|
||||||
import { MobileMenu } from "./mobileMenu/mobileMenu";
|
import { MobileMenu } from "./mobileMenu/mobileMenu";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useSelectedLayoutSegment } from "next/navigation";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{ title: "Игры", href: "/games" },
|
{ title: "Игры", href: "games" },
|
||||||
{ title: "Фильмы", href: "/films" },
|
{ title: "Фильмы", href: "films" },
|
||||||
{ title: "Аудиокниги", href: "/audiobooks" },
|
{ title: "Аудиокниги", href: "audiobooks" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
return (
|
const currentPageName = useSelectedLayoutSegment();
|
||||||
<header className="w-full h-20 bg-bg1 sticky top-0">
|
|
||||||
<div
|
return (
|
||||||
className="w-full h-full max-w-[var(--app-width)] m-auto px-5
|
<header className="w-full h-20 bg-bg1 sticky top-0 shadow-xl">
|
||||||
|
<div
|
||||||
|
className="w-full h-full max-w-[var(--app-width)] m-auto px-5
|
||||||
flex items-center justify-between"
|
flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<h1 className="text-4xl font-bold flex items-center">
|
<h1 className="text-4xl font-bold flex items-center">
|
||||||
<div className="lp:hidden">
|
<div className="lp:hidden">
|
||||||
<MobileMenu sections={sections} />
|
<MobileMenu sections={sections} />
|
||||||
</div>
|
</div>
|
||||||
<Link href="/">.Torrent</Link>
|
<Link href="/">.Torrent</Link>
|
||||||
</h1>
|
</h1>
|
||||||
<div className="hidden text-2xl dsk:block">
|
<div className="hidden text-2xl dsk:block">
|
||||||
{sections.map((section) => (
|
{sections.map((section) => (
|
||||||
<Link
|
<Link
|
||||||
key={section.title}
|
key={section.title}
|
||||||
className="px-5 cursor-pointer hover:underline"
|
className={clsx(
|
||||||
href={section.href}
|
"px-5 cursor-pointer hover:underline underline-offset-2",
|
||||||
>
|
currentPageName === section.href && "underline"
|
||||||
{section.title}
|
)}
|
||||||
</Link>
|
href={section.href}
|
||||||
))}
|
>
|
||||||
</div>
|
{section.title}
|
||||||
<div className="flex flex-col items-end">
|
</Link>
|
||||||
<span className="flex items-center mb-1 ">
|
))}
|
||||||
<SchemeSwitch />
|
</div>
|
||||||
<span className="cursor-pointer flex items-center">
|
<div className="flex flex-col items-end">
|
||||||
<PersonIcon className="mr-1 h-4 w-4" />
|
<span className="flex items-center mb-1 ">
|
||||||
Войти
|
<SchemeSwitch />
|
||||||
</span>
|
<span className="cursor-pointer flex items-center">
|
||||||
</span>
|
<PersonIcon className="mr-1 h-4 w-4" />
|
||||||
<label className="flex flex-col items-start relative w-36">
|
Войти
|
||||||
<input
|
</span>
|
||||||
className="peer/search w-full rounded-lg bg-bg4 px-2"
|
</span>
|
||||||
placeholder=" "
|
<label className="flex flex-col items-start relative w-36">
|
||||||
/>
|
<input
|
||||||
<span
|
className="peer/search w-full rounded-lg bg-bg4 px-2"
|
||||||
className="peer-focus/search:opacity-0
|
placeholder=" "
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="peer-focus/search:opacity-0
|
||||||
peer-[:not(:placeholder-shown)]/search:opacity-0
|
peer-[:not(:placeholder-shown)]/search:opacity-0
|
||||||
transition-opacity h-0 flex items-center relative bottom-3"
|
transition-opacity h-0 flex items-center relative bottom-3"
|
||||||
>
|
>
|
||||||
<SearchIcon className="w-4 h-4 mx-2" />
|
<SearchIcon className="w-4 h-4 mx-2" />
|
||||||
Поиск
|
Поиск
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
|
||||||
|
|
||||||
export const Section = ({
|
export const Section = ({
|
||||||
name,
|
name,
|
||||||
@@ -17,7 +18,7 @@ export const Section = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="w-full h-fit p-5 mb-20 pt-8">
|
<section className="w-full h-fit p-2 mb-20 pt-8">
|
||||||
{name && (
|
{name && (
|
||||||
<h2
|
<h2
|
||||||
className="text-4xl pb-2 cursor-pointer w-fit"
|
className="text-4xl pb-2 cursor-pointer w-fit"
|
||||||
@@ -26,12 +27,15 @@ export const Section = ({
|
|||||||
{name}
|
{name}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
<div className="grid grid-cols-1 tb:grid-cols-2 lp:grid-cols-3 gap-y-10 gap-x-3">
|
<ResponsiveMasonry columnsCountBreakPoints={{ 0: 1, 640: 2, 1024: 3 }}>
|
||||||
{children}
|
<Masonry gutter="1rem">{children}</Masonry>
|
||||||
</div>
|
</ResponsiveMasonry>
|
||||||
{link && invite_text && (
|
{link && invite_text && (
|
||||||
<div className="w-full flex justify-end pt-5">
|
<div className="w-full flex justify-end pt-5">
|
||||||
<Link href={link} className="text-lg">
|
<Link
|
||||||
|
href={link}
|
||||||
|
className="text-lg hover:underline underline-offset-4"
|
||||||
|
>
|
||||||
{invite_text}
|
{invite_text}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user