diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..e558ab2
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,2 @@
+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
\ No newline at end of file
diff --git a/next.config.mjs b/next.config.mjs
index 4678774..ea8e890 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,24 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {};
+const nextConfig = {
+ async rewrites() {
+ return process.env.NODE_ENV == "development"
+ ? [
+ {
+ source: "/api/:path*",
+ destination: "http://127.0.0.1:8000/:path*",
+ },
+ ]
+ : [];
+ },
+ images: {
+ remotePatterns: [
+ {
+ protocol: "http",
+ hostname: "127.0.0.1",
+ port: "8000",
+ },
+ ],
+ },
+};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 0e39b7e..3b671f5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,8 @@
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
@@ -4821,6 +4822,14 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index a8f70ff..9f972d6 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3ee6344..83f413a 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,6 +1,18 @@
+import { GameService } from "@/entities/game";
import { GameCard } from "@/features/gameCard";
import { Section } from "@/widgets/section";
-export default function Home() {
- return
;
+export default async function Home() {
+ const gameCards = await GameService.getGameCards();
+ return (
+
+ {gameCards && (
+
+ {gameCards.map((card) => (
+
+ ))}
+
+ )}
+
+ );
}
diff --git a/src/entities/game/game.ts b/src/entities/game/game.ts
new file mode 100644
index 0000000..c16032a
--- /dev/null
+++ b/src/entities/game/game.ts
@@ -0,0 +1,11 @@
+import { HTTPService } from "@/shared/http/httpService";
+import { gameCardsSchema, GameCardType } from "./schemas/gameCard";
+
+export abstract class GameService {
+ public static async getGameCards() {
+ return await HTTPService.get(
+ "/games/cards",
+ gameCardsSchema
+ );
+ }
+}
diff --git a/src/entities/game/index.ts b/src/entities/game/index.ts
new file mode 100644
index 0000000..6fa905e
--- /dev/null
+++ b/src/entities/game/index.ts
@@ -0,0 +1,5 @@
+import { GameCardType } from "./schemas/gameCard";
+import { GameType } from "./schemas/game";
+import { GameService } from "./game";
+
+export { GameService, type GameType, type GameCardType };
diff --git a/src/entities/game/schemas/game.ts b/src/entities/game/schemas/game.ts
new file mode 100644
index 0000000..5131223
--- /dev/null
+++ b/src/entities/game/schemas/game.ts
@@ -0,0 +1,35 @@
+import { z } from "zod";
+import { gameCardSchema } from "./gameCard";
+
+export const gameSchema = z.union([
+ gameCardSchema,
+ z.object({
+ torrent_file: z.string().min(1),
+ language: z.string().optional(),
+ version: z.string().optional(),
+ download_size: z.string().optional(),
+ system: z.string().optional(),
+ processor: z.string().optional(),
+ memory: z.string().optional(),
+ graphics: z.string().optional(),
+ storage: z.string().optional(),
+ upload_date: z
+ .string()
+ .min(1)
+ .transform((d) => new Date(d)),
+ }),
+]);
+export type GameType = z.infer;
+
+export const isGame = (a: any): a is GameType => {
+ return gameSchema.safeParse(a).success;
+};
+
+export const gamesSchema = z.array(z.any()).transform((a) => {
+ const games: GameType[] = [];
+ a.forEach((e) => {
+ if (isGame(e)) games.push(gameSchema.parse(e));
+ else console.error("Game parse error - ", e);
+ });
+ return games;
+});
diff --git a/src/entities/game/schemas/gameCard.ts b/src/entities/game/schemas/gameCard.ts
new file mode 100644
index 0000000..ac7b50f
--- /dev/null
+++ b/src/entities/game/schemas/gameCard.ts
@@ -0,0 +1,31 @@
+import { z } from "zod";
+
+export const gameCardSchema = z.object({
+ id: z.number(),
+ title: z.string().min(3),
+ cover: z
+ .string()
+ .optional()
+ .transform((u) => {
+ if (!!u) return process.env.NEXT_PUBLIC_COVER_FULL_URL + "/" + u;
+ }),
+ description: z.string().optional(),
+ release_date: z
+ .string()
+ .min(1)
+ .transform((d) => new Date(d)),
+});
+export type GameCardType = z.infer;
+
+export const isGameCard = (a: any): a is GameCardType => {
+ return gameCardSchema.safeParse(a).success;
+};
+
+export const gameCardsSchema = z.array(z.any()).transform((a) => {
+ const cards: GameCardType[] = [];
+ a.forEach((e) => {
+ if (isGameCard(e)) cards.push(gameCardSchema.parse(e));
+ else console.error("GameCard parse error - ", e);
+ });
+ return cards;
+});
diff --git a/src/features/gameCard/gameCard.tsx b/src/features/gameCard/gameCard.tsx
index cf61e5f..024316d 100644
--- a/src/features/gameCard/gameCard.tsx
+++ b/src/features/gameCard/gameCard.tsx
@@ -1,3 +1,31 @@
-export const GameCard = () => {
- return <>>;
+import { GameCardType } from "@/entities/game";
+import Image from "next/image";
+
+export const GameCard = ({ card }: { card: GameCardType }) => {
+ return (
+
+ {!!card.cover && (
+
+ )}
+
+
+ {card.title}
+
+
+ {card.release_date.toLocaleDateString("ru-ru", {
+ year: "numeric",
+ })}
+
+
+
+ {card.description}
+
+
+ );
};
diff --git a/src/shared/http/httpService.ts b/src/shared/http/httpService.ts
new file mode 100644
index 0000000..e0b8888
--- /dev/null
+++ b/src/shared/http/httpService.ts
@@ -0,0 +1,34 @@
+import { z } from "zod";
+
+export abstract class HTTPService {
+ public static async get(
+ url: string,
+ schema: z.ZodTypeAny
+ ): Promise {
+ return await fetch(process.env.NEXT_PUBLIC_BASE_URL + url, {
+ method: "GET",
+ headers: {
+ accept: "application/json",
+ },
+ cache: "no-cache",
+ })
+ .then((r) => {
+ if (r && r.ok) return r;
+ else throw Error("Response ok = false");
+ })
+ .then((r) => r.json())
+ .then((d) => {
+ const parseResult = schema.safeParse(d);
+ if (parseResult.success) {
+ return parseResult.data as Z;
+ } else {
+ console.error(parseResult.error);
+ return null;
+ }
+ })
+ .catch((e) => {
+ console.error(e);
+ return null;
+ });
+ }
+}
diff --git a/src/widgets/header/header.tsx b/src/widgets/header/header.tsx
index ead011c..e307e6c 100644
--- a/src/widgets/header/header.tsx
+++ b/src/widgets/header/header.tsx
@@ -48,8 +48,8 @@ export const Header = () => {
/>
Поиск
diff --git a/src/widgets/section/section.tsx b/src/widgets/section/section.tsx
index 90d07b8..3cc4da7 100644
--- a/src/widgets/section/section.tsx
+++ b/src/widgets/section/section.tsx
@@ -7,9 +7,11 @@ export const Section = ({
}) => {
return (
// open section onClick
-
- {name}
- {children}
+
+ {name}
+
+ {children}
+
);
};