From 05cfd9c955fd40242bfe9f9dd4f8a5488a618dc3 Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Sat, 6 Jul 2024 20:09:02 +0400 Subject: [PATCH] Add genres and actors --- README.md | 2 +- src/app/@auth/(.)login/page.tsx | 2 +- src/app/@auth/(.)registration/page.tsx | 2 +- src/app/[section]/page.tsx | 2 +- src/entities/item/audiobook/audiobook.ts | 27 ++- .../item/audiobook/schemas/audiobook.ts | 15 +- .../item/audiobook/schemas/audiobookCard.ts | 4 - src/entities/item/audiobook/schemas/genre.ts | 32 ++++ src/entities/item/game/game.ts | 24 ++- src/entities/item/game/schemas/game.ts | 15 +- src/entities/item/game/schemas/gameCard.ts | 4 - src/entities/item/game/schemas/genre.ts | 28 +++ src/entities/item/index.ts | 91 ++------- src/entities/item/movie/movie.ts | 42 ++++- src/entities/item/movie/schemas/actors.ts | 28 +++ src/entities/item/movie/schemas/genre.ts | 28 +++ src/entities/item/movie/schemas/movie.ts | 17 +- src/entities/item/movie/schemas/movieCard.ts | 4 - src/entities/item/schemas/owner.ts | 7 + src/entities/item/types.ts | 22 ++- src/shared/assets/icons/cross.tsx | 30 +++ src/shared/assets/icons/index.ts | 3 +- src/shared/ui/image.tsx | 32 ++-- src/shared/utils/http/http.ts | 1 + src/shared/utils/types.ts | 7 + src/widgets/header/mobileMenu/mobileMenu.tsx | 5 +- src/widgets/itemInfo/itemCover.tsx | 36 ++-- src/widgets/itemInfo/itemDetails.tsx | 47 ++--- src/widgets/itemInfo/itemFragment.tsx | 29 ++- src/widgets/itemInfo/itemInfo.tsx | 174 ++++++++---------- src/widgets/itemInfo/itemListProperties.tsx | 126 +++++++++++++ src/widgets/itemInfo/itemProperties.tsx | 47 ++--- src/widgets/itemInfo/itemTorrent.tsx | 38 ++-- src/widgets/itemInfo/itemTrailer.tsx | 24 ++- 34 files changed, 671 insertions(+), 324 deletions(-) create mode 100644 src/entities/item/audiobook/schemas/genre.ts create mode 100644 src/entities/item/game/schemas/genre.ts create mode 100644 src/entities/item/movie/schemas/actors.ts create mode 100644 src/entities/item/movie/schemas/genre.ts create mode 100644 src/entities/item/schemas/owner.ts create mode 100644 src/shared/assets/icons/cross.tsx create mode 100644 src/shared/utils/types.ts create mode 100644 src/widgets/itemInfo/itemListProperties.tsx diff --git a/README.md b/README.md index 96d60ab..ff6ac00 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ - Next.js 14 (App Router) - Tailwind CSS - Zod +- React Hook Form - SWR - clsx -- React Hook Form - и другие - next-themes - js-cookie diff --git a/src/app/@auth/(.)login/page.tsx b/src/app/@auth/(.)login/page.tsx index 39d0a21..1baed90 100644 --- a/src/app/@auth/(.)login/page.tsx +++ b/src/app/@auth/(.)login/page.tsx @@ -46,7 +46,7 @@ export default function Login() { > diff --git a/src/app/@auth/(.)registration/page.tsx b/src/app/@auth/(.)registration/page.tsx index 96ec24b..b918bd7 100644 --- a/src/app/@auth/(.)registration/page.tsx +++ b/src/app/@auth/(.)registration/page.tsx @@ -50,7 +50,7 @@ export default function Registration() { > diff --git a/src/app/[section]/page.tsx b/src/app/[section]/page.tsx index 91cb203..825e290 100644 --- a/src/app/[section]/page.tsx +++ b/src/app/[section]/page.tsx @@ -1,4 +1,4 @@ -import { isSection, ItemService, MovieService } from "@/entities/item"; +import { ItemService } from "@/entities/item"; import { ItemCard } from "@/widgets/itemCard"; import { Section } from "@/widgets/section"; import { redirect } from "next/navigation"; diff --git a/src/entities/item/audiobook/audiobook.ts b/src/entities/item/audiobook/audiobook.ts index abe0a4f..887d1f0 100644 --- a/src/entities/item/audiobook/audiobook.ts +++ b/src/entities/item/audiobook/audiobook.ts @@ -14,6 +14,12 @@ import { TypesOfItems, } from "../types"; import { ItemService } from "../item"; +import { + AudiobookGenreCreateType, + audiobookGenreSchema, + audiobookGenresSchema, +} from "./schemas/genre"; +import { RequiredFrom } from "@/shared/utils/types"; @staticImplements() export abstract class AudiobookService { @@ -30,7 +36,7 @@ export abstract class AudiobookService { public static async GetCards() { return await HTTPService.get( - `/${this.urlPrefix}/cards`, + `/${this.urlPrefix}`, audiobookCardsSchema, this.cacheOptions(`/${this.urlPrefix}/cards`) ); @@ -53,7 +59,7 @@ export abstract class AudiobookService { }); } - public static GetEmpty(): AudiobookCreateType { + public static GetEmpty(): RequiredFrom { return { title: "", torrent_file: "", @@ -61,6 +67,23 @@ export abstract class AudiobookService { }; } + public static async GetGenres() { + return await HTTPService.get( + `/genres/${this.urlPrefix}`, + audiobookGenresSchema + ); + } + + public static async CreateGenre(info: AudiobookGenreCreateType) { + return await HTTPService.post( + `/genres/${this.urlPrefix}`, + audiobookGenreSchema, + { + body: info, + } + ); + } + static propertiesDescription: ItemPropertiesDescriptionType = [ [ { name: "Автор", key: "author" }, diff --git a/src/entities/item/audiobook/schemas/audiobook.ts b/src/entities/item/audiobook/schemas/audiobook.ts index 8f241e2..36e2f1e 100644 --- a/src/entities/item/audiobook/schemas/audiobook.ts +++ b/src/entities/item/audiobook/schemas/audiobook.ts @@ -1,5 +1,8 @@ import { z } from "zod"; import { audiobookCardBaseSchema } from "./audiobookCard"; +import { audiobookGenresSchema } from "./genre"; +import { ownerSchema } from "../../schemas/owner"; +import { TypesOfItems } from "../../types"; export const audiobookBaseSchema = audiobookCardBaseSchema.merge( z.object({ @@ -22,6 +25,8 @@ export const audiobookBaseSchema = audiobookCardBaseSchema.merge( }) : undefined ), + + genres: audiobookGenresSchema.optional().nullable(), }) ); @@ -31,15 +36,11 @@ export type AudiobookCreateType = z.infer; export const audiobookSchema = audiobookBaseSchema.merge( z.object({ id: z.number().positive(), - owner_id: z.number().positive(), + owner: ownerSchema, update_date: z .string() .min(1) .transform((d) => new Date(d)), - upload_date: z - .string() - .min(1) - .transform((d) => new Date(d)), }) ); export type AudiobookType = z.infer; @@ -56,3 +57,7 @@ export const audiobooksSchema = z.array(z.any()).transform((a) => { }); return audiobooks; }); + +export const isAudiobook = (a: any): a is AudiobookType => { + return (a as AudiobookType).type === TypesOfItems.audiobook; +}; diff --git a/src/entities/item/audiobook/schemas/audiobookCard.ts b/src/entities/item/audiobook/schemas/audiobookCard.ts index dfcc32b..2a1dc30 100644 --- a/src/entities/item/audiobook/schemas/audiobookCard.ts +++ b/src/entities/item/audiobook/schemas/audiobookCard.ts @@ -34,7 +34,3 @@ export const audiobookCardsSchema = z.array(z.any()).transform((a) => { }); return cards; }); - -export const isAudiobook = (a: any): a is AudiobookCardType => { - return (a as AudiobookCardType).type === TypesOfItems.audiobook; -}; diff --git a/src/entities/item/audiobook/schemas/genre.ts b/src/entities/item/audiobook/schemas/genre.ts new file mode 100644 index 0000000..d3069f7 --- /dev/null +++ b/src/entities/item/audiobook/schemas/genre.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +export const audiobookGenreBaseSchema = z.object({ + genre: z.string().min(3), +}); + +export const audiobookGenreCreateSchema = audiobookGenreBaseSchema.merge( + z.object({}) +); +export type AudiobookGenreCreateType = z.infer< + typeof audiobookGenreCreateSchema +>; + +export const audiobookGenreSchema = audiobookGenreBaseSchema.merge( + z.object({ + id: z.number().positive(), + }) +); +export type AudiobookGenreType = z.infer; + +export const isAudiobookGenreStrict = (a: any): a is AudiobookGenreType => { + return audiobookGenreSchema.safeParse(a).success; +}; + +export const audiobookGenresSchema = z.array(z.any()).transform((a) => { + const cards: AudiobookGenreType[] = []; + a.forEach((e) => { + if (isAudiobookGenreStrict(e)) cards.push(audiobookGenreSchema.parse(e)); + else console.error("AudiobookGenre parse error - ", e); + }); + return cards; +}); diff --git a/src/entities/item/game/game.ts b/src/entities/item/game/game.ts index e702939..63d22c3 100644 --- a/src/entities/item/game/game.ts +++ b/src/entities/item/game/game.ts @@ -10,6 +10,12 @@ import { TypesOfItems, } from "../types"; import { ItemService } from "../item"; +import { + GameGenreCreateType, + gameGenreSchema, + gameGenresSchema, +} from "./schemas/genre"; +import { RequiredFrom } from "@/shared/utils/types"; @staticImplements() export abstract class GameService { @@ -26,7 +32,7 @@ export abstract class GameService { public static async GetCards() { return await HTTPService.get( - `/${this.urlPrefix}/cards`, + `/${this.urlPrefix}`, gameCardsSchema, this.cacheOptions(`/${this.urlPrefix}/cards`) ); @@ -49,7 +55,7 @@ export abstract class GameService { }); } - public static GetEmpty(): GameCreateType { + public static GetEmpty(): RequiredFrom { return { title: "", torrent_file: "", @@ -57,6 +63,20 @@ export abstract class GameService { }; } + public static async GetGenres() { + return await HTTPService.get(`/genres/${this.urlPrefix}`, gameGenresSchema); + } + + public static async CreateGenre(info: GameGenreCreateType) { + return await HTTPService.post( + `/genres/${this.urlPrefix}`, + gameGenreSchema, + { + body: info, + } + ); + } + static propertiesDescription: ItemPropertiesDescriptionType = [ [ { name: "Система", key: "system" }, diff --git a/src/entities/item/game/schemas/game.ts b/src/entities/item/game/schemas/game.ts index 7983111..ccd58f7 100644 --- a/src/entities/item/game/schemas/game.ts +++ b/src/entities/item/game/schemas/game.ts @@ -1,5 +1,8 @@ import { z } from "zod"; import { gameCardBaseSchema } from "./gameCard"; +import { gameGenresSchema } from "./genre"; +import { ownerSchema } from "../../schemas/owner"; +import { TypesOfItems } from "../../types"; export const gameBaseSchema = gameCardBaseSchema.merge( z.object({ @@ -27,6 +30,8 @@ export const gameBaseSchema = gameCardBaseSchema.merge( }) : undefined ), + + genres: gameGenresSchema.optional().nullable(), }) ); @@ -36,15 +41,11 @@ export type GameCreateType = z.infer; export const gameSchema = gameBaseSchema.merge( z.object({ id: z.number().positive(), - owner_id: z.number().positive(), + owner: ownerSchema, update_date: z .string() .min(1) .transform((d) => new Date(d)), - upload_date: z - .string() - .min(1) - .transform((d) => new Date(d)), }) ); export type GameType = z.infer; @@ -61,3 +62,7 @@ export const gamesSchema = z.array(z.any()).transform((a) => { }); return games; }); + +export const isGame = (a: any): a is GameType => { + return (a as GameType).type === TypesOfItems.game; +}; diff --git a/src/entities/item/game/schemas/gameCard.ts b/src/entities/item/game/schemas/gameCard.ts index 8c99a65..f5396b4 100644 --- a/src/entities/item/game/schemas/gameCard.ts +++ b/src/entities/item/game/schemas/gameCard.ts @@ -34,7 +34,3 @@ export const gameCardsSchema = z.array(z.any()).transform((a) => { }); return cards; }); - -export const isGame = (a: any): a is GameCardType => { - return (a as GameCardType).type === TypesOfItems.game; -}; diff --git a/src/entities/item/game/schemas/genre.ts b/src/entities/item/game/schemas/genre.ts new file mode 100644 index 0000000..bec89c7 --- /dev/null +++ b/src/entities/item/game/schemas/genre.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; + +export const gameGenreBaseSchema = z.object({ + genre: z.string().min(3), +}); + +export const gameGenreCreateSchema = gameGenreBaseSchema.merge(z.object({})); +export type GameGenreCreateType = z.infer; + +export const gameGenreSchema = gameGenreBaseSchema.merge( + z.object({ + id: z.number().positive(), + }) +); +export type GameGenreType = z.infer; + +export const isGameGenreStrict = (a: any): a is GameGenreType => { + return gameGenreSchema.safeParse(a).success; +}; + +export const gameGenresSchema = z.array(z.any()).transform((a) => { + const cards: GameGenreType[] = []; + a.forEach((e) => { + if (isGameGenreStrict(e)) cards.push(gameGenreSchema.parse(e)); + else console.error("GameGenre parse error - ", e); + }); + return cards; +}); diff --git a/src/entities/item/index.ts b/src/entities/item/index.ts index 7f0556f..2f28b1a 100644 --- a/src/entities/item/index.ts +++ b/src/entities/item/index.ts @@ -1,100 +1,37 @@ -import { - gameSchema, - gamesSchema, - gameCreateSchema, - type GameType, - type GameCreateType, -} from "./game/schemas/game"; -export { - gameSchema, - gamesSchema, - gameCreateSchema, - type GameType, - type GameCreateType, -}; - -import { - gameCardSchema, - gameCardsSchema, - type GameCardType, - isGame, -} from "./game/schemas/gameCard"; -export { gameCardSchema, gameCardsSchema, type GameCardType, isGame }; - -import { GameService } from "./game/game"; -export { GameService }; - -import { - movieSchema, - moviesSchema, - movieCreateSchema, - type MovieType, - type MovieCreateType, -} from "./movie/schemas/movie"; -export { - movieSchema, - moviesSchema, - movieCreateSchema, - type MovieType, - type MovieCreateType, -}; - -import { - movieCardSchema, - movieCardsSchema, - type MovieCardType, - isMovie, -} from "./movie/schemas/movieCard"; -export { movieCardSchema, movieCardsSchema, type MovieCardType, isMovie }; +import { isGame } from "./game/schemas/game"; +export { isGame }; import { MovieService } from "./movie/movie"; export { MovieService }; -import { - audiobookSchema, - audiobooksSchema, - audiobookCreateSchema, - type AudiobookType, - type AudiobookCreateType, -} from "./audiobook/schemas/audiobook"; -export { - audiobookSchema, - audiobooksSchema, - audiobookCreateSchema, - type AudiobookType, - type AudiobookCreateType, -}; +import { isMovie } from "./movie/schemas/movie"; +export { isMovie }; -import { - audiobookCardSchema, - audiobookCardsSchema, - type AudiobookCardType, - isAudiobook, -} from "./audiobook/schemas/audiobookCard"; -export { - audiobookCardSchema, - audiobookCardsSchema, - type AudiobookCardType, - isAudiobook, -}; - -import { AudiobookService } from "./audiobook/audiobook"; -export { AudiobookService }; +import { isAudiobook } from "./audiobook/schemas/audiobook"; +export { isAudiobook }; import { ItemService } from "./item"; export { ItemService }; import { TypesOfItems, + isGenre, type IItemService, type ItemType, type ItemCardType, type ItemCreateType, + type GenreType, + type CreateGenreType, + type ItemListPropertyType, } from "./types"; export { TypesOfItems, + isGenre, type IItemService, type ItemType, type ItemCardType, type ItemCreateType, + type GenreType, + type CreateGenreType, + type ItemListPropertyType, }; diff --git a/src/entities/item/movie/movie.ts b/src/entities/item/movie/movie.ts index dadfeb7..fd0e919 100644 --- a/src/entities/item/movie/movie.ts +++ b/src/entities/item/movie/movie.ts @@ -10,6 +10,17 @@ import { TypesOfItems, } from "../types"; import { ItemService } from "../item"; +import { + MovieGenreCreateType, + movieGenreSchema, + movieGenresSchema, +} from "./schemas/genre"; +import { + MovieActorCreateType, + movieActorSchema, + movieActorsSchema, +} from "./schemas/actors"; +import { RequiredFrom } from "@/shared/utils/types"; @staticImplements() export abstract class MovieService { @@ -26,7 +37,7 @@ export abstract class MovieService { public static async GetCards() { return await HTTPService.get( - `/${this.urlPrefix}/cards`, + `/${this.urlPrefix}`, movieCardsSchema, this.cacheOptions(`/${this.urlPrefix}/cards`) ); @@ -49,7 +60,7 @@ export abstract class MovieService { }); } - public static GetEmpty(): MovieCreateType { + public static GetEmpty(): RequiredFrom { return { title: "", torrent_file: "", @@ -57,6 +68,33 @@ export abstract class MovieService { }; } + public static async GetGenres() { + return await HTTPService.get( + `/genres/${this.urlPrefix}`, + movieGenresSchema + ); + } + + public static async CreateGenre(info: MovieGenreCreateType) { + return await HTTPService.post( + `/genres/${this.urlPrefix}`, + movieGenreSchema, + { + body: info, + } + ); + } + + public static async GetActors() { + return await HTTPService.get(`/actors`, movieActorsSchema); + } + + public static async CreateActor(info: MovieActorCreateType) { + return await HTTPService.post(`/actors`, movieActorSchema, { + body: info, + }); + } + public static propertiesDescription: ItemPropertiesDescriptionType = [ [ diff --git a/src/entities/item/movie/schemas/actors.ts b/src/entities/item/movie/schemas/actors.ts new file mode 100644 index 0000000..d158b89 --- /dev/null +++ b/src/entities/item/movie/schemas/actors.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; + +export const movieActorBaseSchema = z.object({ + actor: z.string().min(3), +}); + +export const movieActorCreateSchema = movieActorBaseSchema.merge(z.object({})); +export type MovieActorCreateType = z.infer; + +export const movieActorSchema = movieActorBaseSchema.merge( + z.object({ + id: z.number().positive(), + }) +); +export type MovieActorType = z.infer; + +export const isMovieActorStrict = (a: any): a is MovieActorType => { + return movieActorSchema.safeParse(a).success; +}; + +export const movieActorsSchema = z.array(z.any()).transform((a) => { + const cards: MovieActorType[] = []; + a.forEach((e) => { + if (isMovieActorStrict(e)) cards.push(movieActorSchema.parse(e)); + else console.error("MovieActor parse error - ", e); + }); + return cards; +}); diff --git a/src/entities/item/movie/schemas/genre.ts b/src/entities/item/movie/schemas/genre.ts new file mode 100644 index 0000000..d07f2e6 --- /dev/null +++ b/src/entities/item/movie/schemas/genre.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; + +export const movieGenreBaseSchema = z.object({ + genre: z.string().min(3), +}); + +export const movieGenreCreateSchema = movieGenreBaseSchema.merge(z.object({})); +export type MovieGenreCreateType = z.infer; + +export const movieGenreSchema = movieGenreBaseSchema.merge( + z.object({ + id: z.number().positive(), + }) +); +export type MovieGenreType = z.infer; + +export const isMovieGenreStrict = (a: any): a is MovieGenreType => { + return movieGenreSchema.safeParse(a).success; +}; + +export const movieGenresSchema = z.array(z.any()).transform((a) => { + const cards: MovieGenreType[] = []; + a.forEach((e) => { + if (isMovieGenreStrict(e)) cards.push(movieGenreSchema.parse(e)); + else console.error("MovieGenre parse error - ", e); + }); + return cards; +}); diff --git a/src/entities/item/movie/schemas/movie.ts b/src/entities/item/movie/schemas/movie.ts index 5899ce2..d8aa3e8 100644 --- a/src/entities/item/movie/schemas/movie.ts +++ b/src/entities/item/movie/schemas/movie.ts @@ -1,5 +1,9 @@ import { z } from "zod"; import { movieCardBaseSchema } from "./movieCard"; +import { movieGenresSchema } from "./genre"; +import { movieActorsSchema } from "./actors"; +import { ownerSchema } from "../../schemas/owner"; +import { TypesOfItems } from "../../types"; export const movieBaseSchema = movieCardBaseSchema.merge( z.object({ @@ -24,6 +28,9 @@ export const movieBaseSchema = movieCardBaseSchema.merge( }) : undefined ), + + actors: movieActorsSchema.optional().nullable(), + genres: movieGenresSchema.optional().nullable(), }) ); @@ -33,15 +40,11 @@ export type MovieCreateType = z.infer; export const movieSchema = movieBaseSchema.merge( z.object({ id: z.number().positive(), - owner_id: z.number().positive(), + owner: ownerSchema, update_date: z .string() .min(1) .transform((d) => new Date(d)), - upload_date: z - .string() - .min(1) - .transform((d) => new Date(d)), }) ); export type MovieType = z.infer; @@ -58,3 +61,7 @@ export const moviesSchema = z.array(z.any()).transform((a) => { }); return games; }); + +export const isMovie = (a: any): a is MovieType => { + return (a as MovieType).type === TypesOfItems.movie; +}; diff --git a/src/entities/item/movie/schemas/movieCard.ts b/src/entities/item/movie/schemas/movieCard.ts index e919113..1eb421a 100644 --- a/src/entities/item/movie/schemas/movieCard.ts +++ b/src/entities/item/movie/schemas/movieCard.ts @@ -34,7 +34,3 @@ export const movieCardsSchema = z.array(z.any()).transform((a) => { }); return cards; }); - -export const isMovie = (a: any): a is MovieCardType => { - return (a as MovieCardType).type === TypesOfItems.movie; -}; diff --git a/src/entities/item/schemas/owner.ts b/src/entities/item/schemas/owner.ts new file mode 100644 index 0000000..7e683a1 --- /dev/null +++ b/src/entities/item/schemas/owner.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const ownerSchema = z.object({ + id: z.number().positive(), + name: z.string().min(3), + email: z.string().min(3), +}); diff --git a/src/entities/item/types.ts b/src/entities/item/types.ts index dc6146b..578a608 100644 --- a/src/entities/item/types.ts +++ b/src/entities/item/types.ts @@ -7,6 +7,13 @@ import { AudiobookType, } from "./audiobook/schemas/audiobook"; import { AudiobookCardType } from "./audiobook/schemas/audiobookCard"; +import { GameGenreCreateType, GameGenreType } from "./game/schemas/genre"; +import { MovieGenreCreateType, MovieGenreType } from "./movie/schemas/genre"; +import { + AudiobookGenreCreateType, + AudiobookGenreType, +} from "./audiobook/schemas/genre"; +import { MovieActorType } from "./movie/schemas/actors"; export type ItemType = GameType | MovieType | AudiobookType; export type ItemCardType = GameCardType | MovieCardType | AudiobookCardType; @@ -14,9 +21,20 @@ export type ItemCreateType = | GameCreateType | MovieCreateType | AudiobookCreateType; - export type UnionItemType = GameType & MovieType & AudiobookType; +export type GenreType = GameGenreType | MovieGenreType | AudiobookGenreType; +export type CreateGenreType = + | GameGenreCreateType + | MovieGenreCreateType + | AudiobookGenreCreateType; + +export type ItemListPropertyType = GenreType | MovieActorType; + +export const isGenre = (g: any): g is GenreType => { + return (g as GenreType).genre !== undefined; +}; + export enum TypesOfItems { game, movie, @@ -39,6 +57,8 @@ export interface IItemService { Change(id: number, info: ItemCreateType): Promise; GetEmpty(): ItemCreateType; propertiesDescription: ItemPropertiesDescriptionType; + CreateGenre(info: CreateGenreType): Promise; + GetGenres(): Promise; } export const staticImplements = () => diff --git a/src/shared/assets/icons/cross.tsx b/src/shared/assets/icons/cross.tsx new file mode 100644 index 0000000..f6b4884 --- /dev/null +++ b/src/shared/assets/icons/cross.tsx @@ -0,0 +1,30 @@ +export const CrossIcon = ({ className }: { className?: string }) => { + return ( + + + + + {" "} + + + ); +}; diff --git a/src/shared/assets/icons/index.ts b/src/shared/assets/icons/index.ts index e6b7ea9..1b81a44 100644 --- a/src/shared/assets/icons/index.ts +++ b/src/shared/assets/icons/index.ts @@ -2,5 +2,6 @@ import { SearchIcon } from "./searchIcon"; import { PersonIcon } from "./personIcon"; import { SunIcon } from "./sunIcon"; import { SpinnerIcon } from "./spinnerIcon"; +import { CrossIcon } from "./cross"; -export { SearchIcon, PersonIcon, SunIcon, SpinnerIcon }; +export { SearchIcon, PersonIcon, SunIcon, SpinnerIcon, CrossIcon }; diff --git a/src/shared/ui/image.tsx b/src/shared/ui/image.tsx index da16a36..32f78d8 100644 --- a/src/shared/ui/image.tsx +++ b/src/shared/ui/image.tsx @@ -8,7 +8,7 @@ export const Img = ({ alt, className, }: { - src: string; + src: string | null | undefined; preview?: boolean; width?: number; height?: number; @@ -16,18 +16,22 @@ export const Img = ({ className: string; }) => { return ( - {alt + <> + {src && ( + {alt + )} + ); }; diff --git a/src/shared/utils/http/http.ts b/src/shared/utils/http/http.ts index 498ed3b..6d032c6 100644 --- a/src/shared/utils/http/http.ts +++ b/src/shared/utils/http/http.ts @@ -17,6 +17,7 @@ type RequestOptions = GetRequestOptions & { export abstract class HTTPService { private static deepUndefinedToNull(o?: object): object | undefined { + if (Array.isArray(o)) return o; if (o) return Object.fromEntries( Object.entries(o).map(([k, v]) => { diff --git a/src/shared/utils/types.ts b/src/shared/utils/types.ts new file mode 100644 index 0000000..a58be9e --- /dev/null +++ b/src/shared/utils/types.ts @@ -0,0 +1,7 @@ +type RequiredKeys = { + [K in keyof T]-?: {} extends Pick ? never : K; +}[keyof T]; + +export type RequiredFrom = { + [K in RequiredKeys]-?: T[K]; +}; diff --git a/src/widgets/header/mobileMenu/mobileMenu.tsx b/src/widgets/header/mobileMenu/mobileMenu.tsx index 1a128a2..72c9f51 100644 --- a/src/widgets/header/mobileMenu/mobileMenu.tsx +++ b/src/widgets/header/mobileMenu/mobileMenu.tsx @@ -13,7 +13,10 @@ export const MobileMenu = () => { + )} + + ))} + {editable && ( + <> + +
+ changeSearchField(e.target.value)} + onKeyDown={async (e) => { + if (e.key === "Enter") { + const tag = await createTag(searchField); + if (tag) replace([...(watched_property ?? []), tag]); + changeAdditionState(false); + } + }} + > +
+ {searchTags?.map((tag) => ( +
{ + append(tag); + console.log("updated"); + changeAdditionState(false); + }} + > + {getTagValue(tag)} +
+ ))} +
+
+ + )} + + + ); +}; diff --git a/src/widgets/itemInfo/itemProperties.tsx b/src/widgets/itemInfo/itemProperties.tsx index 8af7821..1c529d6 100644 --- a/src/widgets/itemInfo/itemProperties.tsx +++ b/src/widgets/itemInfo/itemProperties.tsx @@ -1,22 +1,27 @@ import { ItemCreateType, ItemType } from "@/entities/item"; import { ItemService } from "@/entities/item/item"; import { ItemPropertiesDescriptionType } from "@/entities/item/types"; +import { RequiredFrom } from "@/shared/utils/types"; import clsx from "clsx"; -import { UseFormRegister, UseFormSetValue } from "react-hook-form"; +import { + useFormContext, + UseFormRegister, + UseFormSetValue, +} from "react-hook-form"; -export const ItemProperties = ({ +export const ItemProperties = < + T extends ItemType | RequiredFrom +>({ item, - watchedFormData: watchedData, editable, - setFormValue: setValue, - registerFormField: register, }: { item: T; // Init values - watchedFormData: T; // Updated values editable: boolean; - setFormValue: UseFormSetValue; - registerFormField: UseFormRegister; }) => { + const { register, setValue, watch } = useFormContext(); + + const watchedData = watch(); + return (
({ > {( ItemService.itemsConfiguration[item.type] - .propertiesDescription as ItemPropertiesDescriptionType + .propertiesDescription as ItemPropertiesDescriptionType ).map((section, i) => (
    {section.map((req) => ( @@ -54,27 +59,25 @@ export const ItemProperties = ({ (watchedData[req.key] as string) === "") && "opacity-100 absolute left-0 top-0 inline-block min-w-10 z-10" )} - {...register(req.key as keyof ItemType, { + {...register(req.key, { value: req.value - ? req.value(item) - : undefined ?? (item[req.key] as string), + ? req.value(item as ItemCreateType) + : undefined ?? + ((item as ItemCreateType)[req.key] as string), })} contentEditable={editable && (req.editable ?? true)} suppressContentEditableWarning={true} onInput={(e) => { - setValue( - req.key as keyof ItemType, - e.currentTarget.innerText, - { - shouldValidate: true, - shouldDirty: true, - } - ); + setValue(req.key, e.currentTarget.innerText, { + shouldValidate: true, + shouldDirty: true, + }); }} > {req.value - ? req.value(item) - : undefined ?? (item[req.key] as string)} + ? req.value(item as ItemCreateType) + : undefined ?? + ((item as ItemCreateType)[req.key] as string)} diff --git a/src/widgets/itemInfo/itemTorrent.tsx b/src/widgets/itemInfo/itemTorrent.tsx index 1822abd..033d935 100644 --- a/src/widgets/itemInfo/itemTorrent.tsx +++ b/src/widgets/itemInfo/itemTorrent.tsx @@ -1,24 +1,32 @@ import { FilesService } from "@/entities/files"; import { ItemCreateType, ItemType } from "@/entities/item"; import Link from "next/link"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { useDropzone } from "react-dropzone"; -import { UseFormSetValue } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import clsx from "clsx"; export const ItemTorrent = ({ - title, torrent_file, editable, - error, - setFormValue: setValue, }: { - title: string; torrent_file: string; editable: boolean; - error: string | undefined; - setFormValue: UseFormSetValue; }) => { + const { + register, + setValue, + watch, + formState: { errors }, + } = useFormContext(); + + const watched_torrent_file = watch("torrent_file"); + const watched_title = watch("title"); + + useEffect(() => { + register("torrent_file", { value: torrent_file }); + }, [torrent_file, register]); + const onTorrentDrop = useCallback( (acceptedFiles: File[]) => { const file = acceptedFiles[0]; @@ -58,21 +66,23 @@ export const ItemTorrent = ({ >
    - Скачать {title} + Скачать {watched_title} {editable && ( <> - {error && ( - {error} - )} + + {errors.torrent_file?.message} + {isTorrentDragActive ? ( Изменить .torrent файл... diff --git a/src/widgets/itemInfo/itemTrailer.tsx b/src/widgets/itemInfo/itemTrailer.tsx index 5051297..da1cd3a 100644 --- a/src/widgets/itemInfo/itemTrailer.tsx +++ b/src/widgets/itemInfo/itemTrailer.tsx @@ -1,32 +1,30 @@ -import { ItemCreateType, ItemType } from "@/entities/item"; +import { ItemCreateType } from "@/entities/item"; import { getYouTubeID } from "@/shared/utils/getYoutubeId"; -import { UseFormRegister, UseFormSetValue } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; export const ItemTrailer = ({ - trailer, default_trailer, editable, - setFormValue: setValue, - registerFormField: register, }: { - trailer: string | undefined | null; default_trailer: string | undefined | null; editable: boolean; - setFormValue: UseFormSetValue; - registerFormField: UseFormRegister; }) => { + const { register, setValue, watch } = useFormContext(); + + const watched_trailer = watch("trailer"); + return ( <> - {(trailer || editable) && ( + {(watched_trailer || editable) && (
    - {trailer && getYouTubeID(trailer) && ( + {watched_trailer && getYouTubeID(watched_trailer) && (