Add games page

This commit is contained in:
2024-05-12 10:05:49 +04:00
parent a2ea5fc409
commit 275d99bd85
7 changed files with 101 additions and 39 deletions

View File

@@ -1,2 +1,3 @@
NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000/api NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000/api
NEXT_PUBLIC_COVER_FULL_URL=http://127.0.0.1:8000/content/images/cover/full_size NEXT_PUBLIC_COVER_FULL_URL=http://127.0.0.1:8000/content/images/cover/full_size
NEXT_PUBLIC_COVER_PREVIEW_URL=http://127.0.0.1:8000/content/images/cover/preview

25
src/app/games/page.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { GameService } from "@/entities/game";
import { GameCard } from "@/features/gameCard";
import { Section } from "@/widgets/section";
import { Metadata } from "next";
export const metadata: Metadata = {
title: ".Torrent: Игры",
description:
".Torrent: Игры - каталог .torrent файлов для обмена видеоиграми",
};
export default async function Home() {
const gameCards = await GameService.getGameCards();
return (
<div className="w-full h-full max-w-[var(--app-width)] m-auto overflow-y-auto">
{gameCards && (
<Section>
{gameCards.map((card) => (
<GameCard key={card.id} card={card} />
))}
</Section>
)}
</div>
);
}

View File

@@ -8,7 +8,8 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
title: ".Torrent", title: ".Torrent",
description: ".torrent file sharing service", description:
".Torrent - сервис обмена .torrent файлами видеоигр, фильмов и аудиокниг",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -17,7 +18,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
// suppressHydrationWarning only for html 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">

View File

@@ -5,9 +5,13 @@ import { Section } from "@/widgets/section";
export default async function Home() { export default async function Home() {
const gameCards = await GameService.getGameCards(); const gameCards = await GameService.getGameCards();
return ( return (
<div className="w-full max-w-[var(--app-width)] m-auto"> <div className="w-full h-full max-w-[var(--app-width)] m-auto overflow-y-auto">
{gameCards && ( {gameCards && (
<Section name="Игры"> <Section
name="Игры"
link="/games"
invite_text={'Перейти в раздел "Игры"'}
>
{gameCards.map((card) => ( {gameCards.map((card) => (
<GameCard key={card.id} card={card} /> <GameCard key={card.id} card={card} />
))} ))}

View File

@@ -1,20 +1,27 @@
import { z } from "zod"; import { z } from "zod";
export const gameCardSchema = z.object({ export const gameCardSchema = z
.object({
id: z.number(), id: z.number(),
title: z.string().min(3), title: z.string().min(3),
cover: z cover: z.string().optional(),
.string()
.optional()
.transform((u) => {
if (!!u) return process.env.NEXT_PUBLIC_COVER_FULL_URL + "/" + u;
}),
description: z.string().optional(), description: z.string().optional(),
release_date: z release_date: z
.string() .string()
.min(1) .min(1)
.transform((d) => new Date(d)), .transform((d) => new Date(d)),
}); })
.transform((card) => {
return {
...card,
cover: card.cover
? process.env.NEXT_PUBLIC_COVER_FULL_URL + "/" + card.cover
: undefined,
cover_preview: card.cover
? process.env.NEXT_PUBLIC_COVER_PREVIEW_URL + "/" + card.cover
: undefined,
};
});
export type GameCardType = z.infer<typeof gameCardSchema>; export type GameCardType = z.infer<typeof gameCardSchema>;
export const isGameCard = (a: any): a is GameCardType => { export const isGameCard = (a: any): a is GameCardType => {

View File

@@ -4,9 +4,9 @@ import Image from "next/image";
export const GameCard = ({ card }: { card: GameCardType }) => { export const GameCard = ({ card }: { card: GameCardType }) => {
return ( return (
<div className="group/gamecard cursor-pointer"> <div className="group/gamecard cursor-pointer">
{!!card.cover && ( {!!card.cover_preview && (
<Image <Image
src={card.cover} src={card.cover_preview}
className="rounded-lg" className="rounded-lg"
alt="" alt=""
width={700} width={700}

View File

@@ -1,17 +1,41 @@
"use client";
import Link from "next/link";
import { useRouter } from "next/navigation";
export const Section = ({ export const Section = ({
name, name,
invite_text,
link,
children, children,
}: { }: {
name: string; name?: string;
invite_text?: string;
link?: string;
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
const router = useRouter();
return ( return (
// open section onClick <section className="w-full h-fit p-5 mb-20 pt-8">
<section className="w-full p-5 pt-8"> {name && (
<h2 className="text-4xl pb-2 cursor-pointer w-fit">{name}</h2> <h2
className="text-4xl pb-2 cursor-pointer w-fit"
onClick={() => link && router.push(link)}
>
{name}
</h2>
)}
<div className="grid grid-cols-1 tb:grid-cols-2 lp:grid-cols-3 gap-y-10 gap-x-3"> <div className="grid grid-cols-1 tb:grid-cols-2 lp:grid-cols-3 gap-y-10 gap-x-3">
{children} {children}
</div> </div>
{link && invite_text && (
<div className="w-full flex justify-end pt-5">
<Link href={link} className="text-lg">
{invite_text}
</Link>
</div>
)}
</section> </section>
); );
}; };