mirror of
https://github.com/StepanovPlaton/torrent_frontend.git
synced 2026-04-03 12:20:48 +04:00
Work on game form
This commit is contained in:
41
package-lock.json
generated
41
package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.51.4",
|
"react-hook-form": "^7.51.4",
|
||||||
"react-responsive-masonry": "^2.2.0",
|
"react-responsive-masonry": "^2.2.0",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
@@ -923,6 +924,14 @@
|
|||||||
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
|
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@@ -2026,6 +2035,17 @@
|
|||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-selector": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@@ -3263,7 +3283,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -3725,7 +3744,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -3784,6 +3802,22 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dropzone": {
|
||||||
|
"version": "14.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
|
||||||
|
"integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
|
||||||
|
"dependencies": {
|
||||||
|
"attr-accept": "^2.2.2",
|
||||||
|
"file-selector": "^0.6.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8 || 18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.51.4",
|
"version": "7.51.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz",
|
||||||
@@ -3802,8 +3836,7 @@
|
|||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-responsive-masonry": {
|
"node_modules/react-responsive-masonry": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.51.4",
|
"react-hook-form": "^7.51.4",
|
||||||
"react-responsive-masonry": "^2.2.0",
|
"react-responsive-masonry": "^2.2.0",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
|
|||||||
16
src/entities/files/files.ts
Normal file
16
src/entities/files/files.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { HTTPService } from "@/shared/utils/http";
|
||||||
|
import { coverNameSchema } from "./schemas/cover";
|
||||||
|
|
||||||
|
export abstract class FilesService {
|
||||||
|
public static async UploadCover(cover: File) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("cover", cover);
|
||||||
|
return await HTTPService.post(
|
||||||
|
`/files/cover`,
|
||||||
|
coverNameSchema,
|
||||||
|
formData,
|
||||||
|
{},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/entities/files/index.ts
Normal file
3
src/entities/files/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { FilesService } from "./files";
|
||||||
|
|
||||||
|
export { FilesService };
|
||||||
4
src/entities/files/schemas/cover.ts
Normal file
4
src/entities/files/schemas/cover.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const coverNameSchema = z.string().min(5);
|
||||||
|
export type CoverNameType = z.infer<typeof coverNameSchema>;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { gameCardBaseSchema } from "./gameCard";
|
import { gameCardBaseSchema } from "./gameCard";
|
||||||
|
|
||||||
export const gameBaseSchema = gameCardBaseSchema.and(
|
export const gameBaseSchema = gameCardBaseSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
torrent_file: z.string().min(1),
|
torrent_file: z.string().min(1),
|
||||||
trailer: z.string().optional(),
|
trailer: z.string().optional(),
|
||||||
@@ -23,10 +23,10 @@ export const gameBaseSchema = gameCardBaseSchema.and(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const gameCreateSchema = gameBaseSchema.and(z.object({}));
|
export const gameCreateSchema = gameBaseSchema.merge(z.object({}));
|
||||||
export type GameCreateType = z.infer<typeof gameCreateSchema>;
|
export type GameCreateType = z.infer<typeof gameCreateSchema>;
|
||||||
|
|
||||||
export const gameSchema = gameBaseSchema.and(
|
export const gameSchema = gameBaseSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number().positive(),
|
id: z.number().positive(),
|
||||||
owner_id: z.number().positive(),
|
owner_id: z.number().positive(),
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const gameCardBaseSchema = z
|
export const gameCardBaseSchema = z.object({
|
||||||
.object({
|
|
||||||
title: z.string().min(3),
|
title: z.string().min(3),
|
||||||
cover: z.string().optional(),
|
cover: z.string().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
version: z.string().optional(),
|
version: z.string().optional(),
|
||||||
})
|
});
|
||||||
.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 const gameCardSchema = gameCardBaseSchema.and(
|
export const gameCardSchema = gameCardBaseSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number().positive(),
|
id: z.number().positive(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const userSchema = z.object({
|
export const userSchema = z.object({
|
||||||
id: z.number().positive(),
|
id: z.number().positive(),
|
||||||
name: z.string().min(3),
|
username: z.string().min(3),
|
||||||
email: z.string().min(3),
|
email: z.string().min(3),
|
||||||
});
|
});
|
||||||
export type User = z.infer<typeof userSchema>;
|
export type User = z.infer<typeof userSchema>;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export abstract class UserService {
|
|||||||
new URLSearchParams(Object.entries(loginForm)),
|
new URLSearchParams(Object.entries(loginForm)),
|
||||||
{
|
{
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
}
|
},
|
||||||
|
false
|
||||||
);
|
);
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
const tokenData = this.DecodeToken(accessToken);
|
const tokenData = this.DecodeToken(accessToken);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { GameCardType } from "@/entities/game";
|
import { GameCardType } from "@/entities/game";
|
||||||
import Image from "next/image";
|
import { Img } from "@/shared/ui";
|
||||||
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 && (
|
||||||
<Image
|
<Img
|
||||||
src={card.cover_preview}
|
src={card.cover}
|
||||||
|
preview={true}
|
||||||
className="rounded-lg object-contain"
|
className="rounded-lg object-contain"
|
||||||
alt=""
|
|
||||||
width={1280}
|
width={1280}
|
||||||
height={720}
|
height={720}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Link from "next/link";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { TokenData } from "@/entities/user/schemas/auth";
|
||||||
|
|
||||||
export const UserActivities = () => {
|
export const UserActivities = () => {
|
||||||
const { data: me } = useSWR("user", () => UserService.IdentifyYourself());
|
const { data: me } = useSWR("user", () => UserService.IdentifyYourself());
|
||||||
@@ -22,7 +23,7 @@ export const UserActivities = () => {
|
|||||||
onClick={() => changeMenuOpen(!open)}
|
onClick={() => changeMenuOpen(!open)}
|
||||||
onBlur={() => changeMenuOpen(false)}
|
onBlur={() => changeMenuOpen(false)}
|
||||||
>
|
>
|
||||||
{me.name}
|
{me.username}
|
||||||
</button>
|
</button>
|
||||||
<li
|
<li
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SearchIcon } from "./searchIcon";
|
import { SearchIcon } from "./searchIcon";
|
||||||
import { PersonIcon } from "./personIcon";
|
import { PersonIcon } from "./personIcon";
|
||||||
import { SunIcon } from "./sunIcon";
|
import { SunIcon } from "./sunIcon";
|
||||||
|
import { SpinnerIcon } from "./spinnerIcon";
|
||||||
|
|
||||||
export { SearchIcon, PersonIcon, SunIcon };
|
export { SearchIcon, PersonIcon, SunIcon, SpinnerIcon };
|
||||||
|
|||||||
28
src/shared/assets/icons/spinnerIcon.tsx
Normal file
28
src/shared/assets/icons/spinnerIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export const SpinnerIcon = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={"w-4 h-4 text-fg4 animate-spin " + className}
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M32 3C35.8083 3 39.5794 3.75011 43.0978 5.20749C46.6163 6.66488 49.8132 8.80101 52.5061 11.4939C55.199 14.1868 57.3351 17.3837 58.7925 20.9022C60.2499 24.4206 61 28.1917 61 32C61 35.8083 60.2499 39.5794 58.7925 43.0978C57.3351 46.6163 55.199 49.8132 52.5061 52.5061C49.8132 55.199 46.6163 57.3351 43.0978 58.7925C39.5794 60.2499 35.8083 61 32 61C28.1917 61 24.4206 60.2499 20.9022 58.7925C17.3837 57.3351 14.1868 55.199 11.4939 52.5061C8.801 49.8132 6.66487 46.6163 5.20749 43.0978C3.7501 39.5794 3 35.8083 3 32C3 28.1917 3.75011 24.4206 5.2075 20.9022C6.66489 17.3837 8.80101 14.1868 11.4939 11.4939C14.1868 8.80099 17.3838 6.66487 20.9022 5.20749C24.4206 3.7501 28.1917 3 32 3L32 3Z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M32 3C36.5778 3 41.0906 4.08374 45.1692 6.16256C49.2477 8.24138 52.7762 11.2562 55.466 14.9605C58.1558 18.6647 59.9304 22.9531 60.6448 27.4748C61.3591 31.9965 60.9928 36.6232 59.5759 40.9762"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="text-bg1"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
33
src/shared/ui/image.tsx
Normal file
33
src/shared/ui/image.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export const Img = ({
|
||||||
|
src,
|
||||||
|
preview = false,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
alt,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
src: string;
|
||||||
|
preview?: boolean;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
alt?: string;
|
||||||
|
className: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
className={className}
|
||||||
|
src={
|
||||||
|
(preview
|
||||||
|
? process.env.NEXT_PUBLIC_COVER_PREVIEW_URL
|
||||||
|
: process.env.NEXT_PUBLIC_COVER_FULL_URL) +
|
||||||
|
"/" +
|
||||||
|
src
|
||||||
|
}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
alt={alt ?? ""}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
import { Modal } from "./modal";
|
import { Modal } from "./modal";
|
||||||
|
import { Img } from "./image";
|
||||||
|
|
||||||
export { Modal };
|
export { Modal, Img };
|
||||||
|
|||||||
@@ -16,10 +16,14 @@ export abstract class HTTPService {
|
|||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
accept: "application/json",
|
accept: "application/json",
|
||||||
|
...((stringify ?? true) != true
|
||||||
|
? {}
|
||||||
|
: { "Content-Type": "application/json" }),
|
||||||
Authorization: "Bearer " + UserService.GetToken(),
|
Authorization: "Bearer " + UserService.GetToken(),
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
body: stringify ? JSON.stringify(body) : (body as BodyInit),
|
body:
|
||||||
|
(stringify ?? true) != true ? (body as BodyInit) : JSON.stringify(body),
|
||||||
cache: "no-cache",
|
cache: "no-cache",
|
||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
|
|||||||
@@ -7,14 +7,19 @@ import {
|
|||||||
GameType,
|
GameType,
|
||||||
} from "@/entities/game";
|
} from "@/entities/game";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getYouTubeID } from "@/shared/utils";
|
import { getYouTubeID } from "@/shared/utils";
|
||||||
import { UserService } from "@/entities/user";
|
import { UserService } from "@/entities/user";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { SubmitHandler, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Img } from "@/shared/ui";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { FilesService } from "@/entities/files";
|
||||||
|
import { SpinnerIcon } from "@/shared/assets/icons";
|
||||||
|
|
||||||
|
const propertyUnknownText = "Не известно";
|
||||||
|
|
||||||
export const GameInfo = ({ game }: { game: GameType }) => {
|
export const GameInfo = ({ game }: { game: GameType }) => {
|
||||||
const { data: me } = useSWR("user", () => UserService.IdentifyYourself());
|
const { data: me } = useSWR("user", () => UserService.IdentifyYourself());
|
||||||
@@ -26,7 +31,8 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { errors },
|
watch,
|
||||||
|
formState: { dirtyFields, errors },
|
||||||
} = useForm<GameCreateType>({
|
} = useForm<GameCreateType>({
|
||||||
resolver: zodResolver(gameCreateSchema),
|
resolver: zodResolver(gameCreateSchema),
|
||||||
});
|
});
|
||||||
@@ -36,35 +42,89 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
register("cover", { value: game.cover });
|
register("cover", { value: game.cover });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [savedTimeout, changeSavedTimeout] = useState<NodeJS.Timeout | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const watchedData = watch();
|
||||||
|
const [formData, changeFormData] = useState<GameCreateType | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(errors);
|
if (!Object.keys(dirtyFields).length) return;
|
||||||
}, [errors]);
|
if (JSON.stringify(watchedData) === JSON.stringify(formData)) return;
|
||||||
|
console.log(dirtyFields);
|
||||||
|
changeFormData(watchedData);
|
||||||
|
if (savedTimeout) clearTimeout(savedTimeout);
|
||||||
|
changeSavedTimeout(
|
||||||
|
setTimeout(() => {
|
||||||
|
if (formRef.current) formRef.current.requestSubmit();
|
||||||
|
}, 5000)
|
||||||
|
);
|
||||||
|
}, [watchedData]);
|
||||||
|
|
||||||
const onSubmit = (formData: GameCreateType) => {
|
const onSubmit = async (formData: GameCreateType) => {
|
||||||
const updatedGame = GameService.changeGame(game.id, formData);
|
changeSavedTimeout(null);
|
||||||
|
const updatedGame = await GameService.changeGame(game.id, formData);
|
||||||
console.log(updatedGame);
|
console.log(updatedGame);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [cover, setCover] = useState<string | undefined>(game.cover);
|
||||||
|
|
||||||
|
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||||
|
const file = acceptedFiles[0];
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = async () => {
|
||||||
|
const coverName = await FilesService.UploadCover(file);
|
||||||
|
if (coverName) {
|
||||||
|
setCover(coverName);
|
||||||
|
setValue("cover", coverName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileReader.readAsDataURL(file);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
|
||||||
|
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
className="p-4 flex flex-col lp:block"
|
className="p-4 flex flex-col lp:block"
|
||||||
|
ref={formRef}
|
||||||
>
|
>
|
||||||
{game.cover && (
|
{cover && (
|
||||||
<div className="lp:w-[60%] lp:px-4 lp:pl-0 py-2 float-left">
|
<div
|
||||||
<Image
|
className="lp:w-[60%] lp:px-4 lp:pl-0 pt-2 float-left"
|
||||||
src={game.cover}
|
{...(editable ? getRootProps() : {})}
|
||||||
className="rounded-lg w-full object-contain"
|
>
|
||||||
alt=""
|
<Img
|
||||||
|
src={cover}
|
||||||
|
preview={false}
|
||||||
|
className="transition-all rounded-lg w-full object-contain"
|
||||||
width={1280}
|
width={1280}
|
||||||
height={720}
|
height={720}
|
||||||
/>
|
/>
|
||||||
|
{editable && (
|
||||||
|
<>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<span className="flex items-center ju w-full p-1">
|
||||||
|
{isDragActive ? (
|
||||||
|
<p>Изменить обложку...</p>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm w-full flex justify-around">
|
||||||
|
Для редактирования нажмите или перетащите новую обложку
|
||||||
|
поверх старой
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="lp:max-w-[40%]">
|
<span className="lp:max-w-[40%]">
|
||||||
|
<span className="flex items-end justify-between">
|
||||||
<h1
|
<h1
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"text-4xl outline-none",
|
"text-4xl outline-none max-w-[80%]",
|
||||||
!editable && "cursor-default"
|
!editable && "cursor-default"
|
||||||
)}
|
)}
|
||||||
suppressContentEditableWarning={true}
|
suppressContentEditableWarning={true}
|
||||||
@@ -73,12 +133,24 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
setValue("title", e.currentTarget.innerText, {
|
setValue("title", e.currentTarget.innerText, {
|
||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
|
shouldDirty: true,
|
||||||
});
|
});
|
||||||
console.log();
|
console.log();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{game.title}
|
{game.title}
|
||||||
</h1>
|
</h1>
|
||||||
|
<span className="text-sm text-fg4 flex items-center">
|
||||||
|
{savedTimeout && (
|
||||||
|
<>
|
||||||
|
<SpinnerIcon className="mr-2" />
|
||||||
|
Редактируется
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!savedTimeout && "Сохранено"}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
{game.description && (
|
{game.description && (
|
||||||
<div
|
<div
|
||||||
contentEditable={editable}
|
contentEditable={editable}
|
||||||
@@ -92,6 +164,7 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
setValue("description", e.currentTarget.innerText, {
|
setValue("description", e.currentTarget.innerText, {
|
||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
|
shouldDirty: true,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -105,7 +178,8 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
!editable && "cursor-default"
|
!editable && "cursor-default"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{[
|
{(
|
||||||
|
[
|
||||||
[
|
[
|
||||||
{ name: "Система", key: "system" },
|
{ name: "Система", key: "system" },
|
||||||
{ name: "Процессор", key: "processor" },
|
{ name: "Процессор", key: "processor" },
|
||||||
@@ -131,7 +205,8 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
},
|
},
|
||||||
{ name: "Объём загрузки", key: "download_size" },
|
{ name: "Объём загрузки", key: "download_size" },
|
||||||
],
|
],
|
||||||
].map((section, i) => (
|
] as { name: string; key: keyof GameCreateType; value?: string }[][]
|
||||||
|
).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 key={req.name} className="font-bold text-sm lp:text-md py-1">
|
<li key={req.name} className="font-bold text-sm lp:text-md py-1">
|
||||||
@@ -140,14 +215,28 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
readOnly={!editable}
|
readOnly={!editable}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"font-normal outline-none bg-bg1",
|
"font-normal outline-none bg-bg1",
|
||||||
req.value === undefined && "text-fg4"
|
req.value === undefined &&
|
||||||
|
(game[req.key] === undefined ||
|
||||||
|
game[req.key] === propertyUnknownText) &&
|
||||||
|
"text-fg4",
|
||||||
|
!editable && "cursor-default"
|
||||||
)}
|
)}
|
||||||
{...register(req.key as keyof GameCreateType)}
|
{...register(req.key, {
|
||||||
|
value:
|
||||||
|
req.value ??
|
||||||
|
(game[req.key] as string) ??
|
||||||
|
propertyUnknownText,
|
||||||
|
})}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
req.value ??
|
req.value ??
|
||||||
(game[req.key as keyof GameType] as string) ??
|
(game[req.key] as string) ??
|
||||||
"Не известно"
|
propertyUnknownText
|
||||||
}
|
}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (e.target.value === "") {
|
||||||
|
e.target.value = propertyUnknownText;
|
||||||
|
}
|
||||||
|
}}
|
||||||
></input>
|
></input>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -178,7 +267,7 @@ export const GameInfo = ({ game }: { game: GameType }) => {
|
|||||||
<br /> с помощью .torrent файла?
|
<br /> с помощью .torrent файла?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<input type="submit" className=" m-2 p-2 bg-ac0" />
|
<input type="submit" className="hidden" />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user