From ea723b88b0d8813606f94cdedb8332f7ff332015 Mon Sep 17 00:00:00 2001 From: StepanovPlaton Date: Sun, 23 Jun 2024 19:43:12 +0400 Subject: [PATCH] Refactoring --- src/app/[section]/[item_id]/page.tsx | 28 +++-- src/app/[section]/add/page.tsx | 32 +++-- src/app/[section]/page.tsx | 18 ++- src/app/page.tsx | 34 +++--- src/entities/item/index.ts | 12 +- src/entities/item/item.ts | 72 +---------- src/entities/item/types.ts | 18 --- src/features/sections/index.ts | 2 + src/features/sections/sections.ts | 64 ++++++++++ .../userActivities/userActivities.tsx | 17 ++- src/shared/utils/http/http.ts | 1 - src/widgets/header/header.tsx | 6 +- src/widgets/header/mobileMenu/mobileMenu.tsx | 6 +- src/{features => widgets}/itemCard/index.ts | 0 .../itemCard/itemCard.tsx | 10 +- src/widgets/itemInfo/itemInfo.tsx | 14 +-- src/widgets/itemInfo/itemProperties.tsx | 112 +++++++++--------- src/widgets/section/section.tsx | 1 - 18 files changed, 224 insertions(+), 223 deletions(-) create mode 100644 src/features/sections/index.ts create mode 100644 src/features/sections/sections.ts rename src/{features => widgets}/itemCard/index.ts (100%) rename src/{features => widgets}/itemCard/itemCard.tsx (85%) diff --git a/src/app/[section]/[item_id]/page.tsx b/src/app/[section]/[item_id]/page.tsx index 5b0240c..ee8ad61 100644 --- a/src/app/[section]/[item_id]/page.tsx +++ b/src/app/[section]/[item_id]/page.tsx @@ -1,5 +1,6 @@ -import { GameService, GameType, isSection, ItemService } from "@/entities/item"; -import { ItemCard } from "@/features/itemCard"; +import { ItemService } from "@/entities/item"; +import { SectionService } from "@/features/sections"; +import { ItemCard } from "@/widgets/itemCard"; import { ItemInfo } from "@/widgets/itemInfo"; import { Section } from "@/widgets/section"; import { redirect } from "next/navigation"; @@ -9,13 +10,17 @@ export default async function Item({ }: { params: { section: string; item_id: number }; }) { - const game = isSection(section) - ? await ItemService.itemSections[section].service.Get(item_id) + const game = SectionService.isSection(section) + ? await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType + ].service.Get(item_id) : redirect("/"); const cards = - isSection(section) && - (await ItemService.itemSections[section].service.GetCards()); + SectionService.isSection(section) && + (await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType + ].service.GetCards()); return ( <> @@ -24,14 +29,15 @@ export default async function Item({ {cards && (
diff --git a/src/app/[section]/add/page.tsx b/src/app/[section]/add/page.tsx index 8c5422f..d0e72e8 100644 --- a/src/app/[section]/add/page.tsx +++ b/src/app/[section]/add/page.tsx @@ -1,21 +1,22 @@ -import { GameService, isSection, ItemService } from "@/entities/item"; -import { ItemCard } from "@/features/itemCard"; +import { ItemCard } from "@/widgets/itemCard"; import { ItemInfo } from "@/widgets/itemInfo"; import { Section } from "@/widgets/section"; import { redirect } from "next/navigation"; import { Metadata } from "next"; +import { SectionService } from "@/features/sections"; +import { ItemService } from "@/entities/item"; export async function generateMetadata({ params: { section }, }: { params: { section: string }; }): Promise { - if (!isSection(section)) { + if (!SectionService.isSection(section)) { redirect("/"); return {}; } return { - title: `.Torrent: ${ItemService.itemSections[section].addItemText}`, + title: `.Torrent: ${SectionService.sectionsConfiguration[section].addItemText}`, }; } @@ -24,13 +25,17 @@ export default async function AddItem({ }: { params: { section: string }; }) { - const emptyItem = isSection(section) - ? await ItemService.itemSections[section].service.GetEmpty() + const emptyItem = SectionService.isSection(section) + ? await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType + ].service.GetEmpty() : redirect("/"); const cards = - isSection(section) && - (await ItemService.itemSections[section].service.GetCards()); + SectionService.isSection(section) && + (await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType + ].service.GetCards()); return ( <> @@ -39,14 +44,15 @@ export default async function AddItem({ {cards && (
diff --git a/src/app/[section]/page.tsx b/src/app/[section]/page.tsx index 29782ae..91cb203 100644 --- a/src/app/[section]/page.tsx +++ b/src/app/[section]/page.tsx @@ -1,21 +1,25 @@ import { isSection, ItemService, MovieService } from "@/entities/item"; -import { ItemCard } from "@/features/itemCard"; +import { ItemCard } from "@/widgets/itemCard"; import { Section } from "@/widgets/section"; import { redirect } from "next/navigation"; import { Metadata } from "next"; +import { SectionService } from "@/features/sections"; export async function generateMetadata({ params: { section }, }: { params: { section: string }; }): Promise { - if (!isSection(section)) { + if (!SectionService.isSection(section)) { redirect("/"); return {}; } return { - title: `.Torrent: ${ItemService.itemSections[section].sectionName}`, - description: `.Torrent: ${ItemService.itemSections[section].sectionName} - ${ItemService.itemSections[section].sectionName}`, + title: `.Torrent: ${SectionService.sectionsConfiguration[section].sectionName}`, + description: + `.Torrent: ` + + `${SectionService.sectionsConfiguration[section].sectionName} - ` + + `${SectionService.sectionsConfiguration[section].sectionDescription}`, }; } @@ -24,8 +28,10 @@ export default async function SectionPage({ }: { params: { section: string }; }) { - const cards = isSection(section) - ? await ItemService.itemSections[section].service.GetCards() + const cards = SectionService.isSection(section) + ? await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType + ].service.GetCards() : redirect("/"); return ( diff --git a/src/app/page.tsx b/src/app/page.tsx index d72a017..11e462a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,6 @@ -import { - isSection, - ItemCardType, - ItemSections, - ItemSectionsType, - ItemService, -} from "@/entities/item"; -import { ItemCard } from "@/features/itemCard"; +import { ItemCardType, ItemService } from "@/entities/item"; +import { ItemCard } from "@/widgets/itemCard"; +import { SectionService, SectionType } from "@/features/sections"; import { Section } from "@/widgets/section"; import { Metadata } from "next"; @@ -16,24 +11,31 @@ export const metadata: Metadata = { }; export default async function Home() { - const cards: { [k in ItemSectionsType]?: ItemCardType[] | null } = {}; + const cards: { [k in SectionType]?: ItemCardType[] | null } = {}; await Promise.all( - ItemSections.map(async (section) => { - cards[section] = await ItemService.itemSections[ - section + SectionService.sections.map(async (section) => { + cards[section] = await ItemService.itemsConfiguration[ + SectionService.sectionsConfiguration[section].itemType ].service.GetCards(); }) ); return ( <> - {ItemSections.map((section) => ( + {SectionService.sections.map((section) => (
{cards[section] && cards[section].length > 0 && (
{cards[section].map((card) => ( diff --git a/src/entities/item/index.ts b/src/entities/item/index.ts index 603c1f3..7f0556f 100644 --- a/src/entities/item/index.ts +++ b/src/entities/item/index.ts @@ -85,20 +85,16 @@ import { ItemService } from "./item"; export { ItemService }; import { - isSection, + TypesOfItems, + type IItemService, type ItemType, type ItemCardType, type ItemCreateType, - type TypesOfItems, - type ItemSectionsType, - ItemSections, } from "./types"; export { - isSection, + TypesOfItems, + type IItemService, type ItemType, type ItemCardType, type ItemCreateType, - type TypesOfItems, - type ItemSectionsType, - ItemSections, }; diff --git a/src/entities/item/item.ts b/src/entities/item/item.ts index 895a5f7..323bbfd 100644 --- a/src/entities/item/item.ts +++ b/src/entities/item/item.ts @@ -7,10 +7,8 @@ import { audiobookCreateSchema } from "./audiobook/schemas/audiobook"; import { AudiobookService } from "./audiobook/audiobook"; import { IItemService, - ItemCardType, ItemCreateType, ItemPropertiesDescriptionType, - ItemSectionsType, ItemType, TypesOfItems, UnionItemType, @@ -18,9 +16,8 @@ import { import { EraseCacheByTags } from "@/shared/utils/http"; export abstract class ItemService { - private static get itemsConfiguration(): { + static get itemsConfiguration(): { [k in TypesOfItems]: { - sectionUrl: ItemSectionsType; formResolver: ZodSchema; propertiesDescription: ItemPropertiesDescriptionType; service: IItemService; @@ -28,19 +25,16 @@ export abstract class ItemService { } { return { [TypesOfItems.game]: { - sectionUrl: "games", formResolver: gameCreateSchema, propertiesDescription: GameService.propertiesDescription, service: GameService, }, [TypesOfItems.movie]: { - sectionUrl: "movies", formResolver: movieCreateSchema, propertiesDescription: MovieService.propertiesDescription, service: MovieService, }, [TypesOfItems.audiobook]: { - sectionUrl: "audiobooks", formResolver: audiobookCreateSchema, propertiesDescription: AudiobookService.propertiesDescription, service: AudiobookService, @@ -48,76 +42,12 @@ export abstract class ItemService { }; } - static get itemSections(): { - [k in ItemSectionsType]: { - sectionName: string; - itemType: TypesOfItems; - popularSubsectionName: string; - sectionInviteText: string; - addItemText: string; - sectionDescription: string; - service: IItemService; - }; - } { - return { - games: { - sectionName: "Игры", - itemType: TypesOfItems.game, - popularSubsectionName: "Популярные игры", - sectionInviteText: 'Перейти в раздел "Игры"', - addItemText: "Добавить игру", - sectionDescription: - "каталог .torrent файлов для обмена актуальными версиями популярных игр", - service: GameService, - }, - movies: { - sectionName: "Фильмы", - itemType: TypesOfItems.movie, - popularSubsectionName: "Популярные фильмы", - sectionInviteText: 'Перейти в раздел "Фильмы"', - addItemText: "Добавить фильм", - sectionDescription: - "каталог .torrent файлов для обмена популярными фильмами в лучшем качестве", - service: MovieService, - }, - audiobooks: { - sectionName: "Аудиокниги", - itemType: TypesOfItems.audiobook, - popularSubsectionName: "Популярные аудиокниги", - sectionInviteText: 'Перейти в раздел "Аудиокниги"', - addItemText: "Добавить аудиокнигу", - sectionDescription: - "каталог .torrent файлов для обмена популярными аудиокнигами любимых авторов", - service: AudiobookService, - }, - }; - } - public static isExistingItem( item: ItemCreateType | ItemType ): item is ItemType { return (item as ItemType).id !== undefined; } - public static GetFormResolver( - item: ItemCardType | ItemCreateType | ItemType - ) { - return this.itemsConfiguration[item.type].formResolver; - } - - public static GetSectionUrlByItemType( - item: ItemCardType | ItemCreateType | ItemType - ) { - return this.itemsConfiguration[item.type].sectionUrl; - } - - public static GetPropertiesDescriptionForItem< - T extends ItemType | ItemCreateType - >(item: T) { - return this.itemsConfiguration[item.type] - .propertiesDescription as ItemPropertiesDescriptionType; - } - public static async AddItem(itemInfo: ItemCreateType) { const item = await this.itemsConfiguration[itemInfo.type].service.Add( itemInfo diff --git a/src/entities/item/types.ts b/src/entities/item/types.ts index 8f11f68..dc6146b 100644 --- a/src/entities/item/types.ts +++ b/src/entities/item/types.ts @@ -16,30 +16,12 @@ export type ItemCreateType = | AudiobookCreateType; export type UnionItemType = GameType & MovieType & AudiobookType; -export type UnionItemCardType = GameCardType & - MovieCardType & - AudiobookCardType; -export type UnionItemCreateType = GameCreateType & - MovieCreateType & - AudiobookCreateType; export enum TypesOfItems { game, movie, audiobook, } - -export type ItemSectionsType = "games" | "movies" | "audiobooks"; -export const ItemSections = [ - "games", - "movies", - "audiobooks", -] as ItemSectionsType[]; - -export const isSection = (a: string): a is ItemSectionsType => { - return (ItemSections as string[]).includes(a); -}; - export type ItemPropertiesDescriptionType = { name: string; diff --git a/src/features/sections/index.ts b/src/features/sections/index.ts new file mode 100644 index 0000000..a878f51 --- /dev/null +++ b/src/features/sections/index.ts @@ -0,0 +1,2 @@ +import { SectionService, type SectionType } from "./sections"; +export { SectionService, type SectionType }; diff --git a/src/features/sections/sections.ts b/src/features/sections/sections.ts new file mode 100644 index 0000000..cfa8342 --- /dev/null +++ b/src/features/sections/sections.ts @@ -0,0 +1,64 @@ +import { TypesOfItems } from "@/entities/item"; + +export type SectionType = (typeof SectionService.sections)[number]; + +export abstract class SectionService { + static get itemTypeToSection(): { [k in TypesOfItems]: SectionType } { + return { + [TypesOfItems.game]: "games", + [TypesOfItems.movie]: "movies", + [TypesOfItems.audiobook]: "audiobooks", + }; + } + + static get sectionsConfiguration(): { + [k in SectionType]: { + sectionName: string; + sectionUrl: string; + itemType: TypesOfItems; + popularSubsectionName: string; + sectionInviteText: string; + addItemText: string; + sectionDescription: string; + }; + } { + return { + games: { + sectionName: "Игры", + sectionUrl: "games", + itemType: TypesOfItems.game, + popularSubsectionName: "Популярные игры", + sectionInviteText: 'Перейти в раздел "Игры"', + addItemText: "Добавить игру", + sectionDescription: + "каталог .torrent файлов для обмена актуальными версиями популярных игр", + }, + movies: { + sectionName: "Фильмы", + sectionUrl: "movies", + itemType: TypesOfItems.movie, + popularSubsectionName: "Популярные фильмы", + sectionInviteText: 'Перейти в раздел "Фильмы"', + addItemText: "Добавить фильм", + sectionDescription: + "каталог .torrent файлов для обмена популярными фильмами в лучшем качестве", + }, + audiobooks: { + sectionName: "Аудиокниги", + sectionUrl: "audiobooks", + itemType: TypesOfItems.audiobook, + popularSubsectionName: "Популярные аудиокниги", + sectionInviteText: 'Перейти в раздел "Аудиокниги"', + addItemText: "Добавить аудиокнигу", + sectionDescription: + "каталог .torrent файлов для обмена популярными аудиокнигами любимых авторов", + }, + }; + } + + static sections = ["games", "movies", "audiobooks"] as const; + + static isSection = (a: string): a is SectionType => { + return this.sections.includes(a as SectionType); + }; +} diff --git a/src/features/userActivities/userActivities.tsx b/src/features/userActivities/userActivities.tsx index 3e63022..aa3d8f9 100644 --- a/src/features/userActivities/userActivities.tsx +++ b/src/features/userActivities/userActivities.tsx @@ -7,7 +7,7 @@ import useSWR, { mutate } from "swr"; import clsx from "clsx"; import Cookies from "js-cookie"; import { useState } from "react"; -import { ItemService } from "@/entities/item"; +import { SectionService } from "../sections"; export const UserActivities = () => { const { data: me } = useSWR("user", () => UserService.IdentifyYourself()); @@ -39,14 +39,13 @@ export const UserActivities = () => { {[ { group: "Добавить:", - items: Object.entries(ItemService.itemSections).map( - ([sectionId, section]) => { - return { - name: section.addItemText, - link: `/${sectionId}/add`, - }; - } - ), + items: SectionService.sections.map((section) => { + return { + name: SectionService.sectionsConfiguration[section] + .addItemText, + link: `/${SectionService.sectionsConfiguration[section].sectionUrl}/add`, + }; + }), }, { name: "Выйти", diff --git a/src/shared/utils/http/http.ts b/src/shared/utils/http/http.ts index 5178c84..498ed3b 100644 --- a/src/shared/utils/http/http.ts +++ b/src/shared/utils/http/http.ts @@ -33,7 +33,6 @@ export abstract class HTTPService { schema: Z, options?: RequestOptions ) { - console.log(options?.body); return await fetch(process.env.NEXT_PUBLIC_BASE_URL + url, { method: method, headers: { diff --git a/src/widgets/header/header.tsx b/src/widgets/header/header.tsx index c3e85fa..d04b3d4 100644 --- a/src/widgets/header/header.tsx +++ b/src/widgets/header/header.tsx @@ -7,7 +7,7 @@ import Link from "next/link"; import { useSelectedLayoutSegment } from "next/navigation"; import clsx from "clsx"; import { UserActivities } from "@/features/userActivities"; -import { ItemSections, ItemService } from "@/entities/item"; +import { SectionService } from "@/features/sections"; export const Header = () => { const currentPageName = useSelectedLayoutSegment(); @@ -25,7 +25,7 @@ export const Header = () => { .Torrent
- {ItemSections.map((section) => ( + {SectionService.sections.map((section) => ( { )} href={"/" + section} > - {ItemService.itemSections[section].sectionName} + {SectionService.sectionsConfiguration[section].sectionName} ))}
diff --git a/src/widgets/header/mobileMenu/mobileMenu.tsx b/src/widgets/header/mobileMenu/mobileMenu.tsx index efa5aee..1a128a2 100644 --- a/src/widgets/header/mobileMenu/mobileMenu.tsx +++ b/src/widgets/header/mobileMenu/mobileMenu.tsx @@ -1,6 +1,6 @@ "use client"; -import { ItemSections, ItemService } from "@/entities/item"; +import { SectionService } from "@/features/sections"; import clsx from "clsx"; import Link from "next/link"; import { useState } from "react"; @@ -32,13 +32,13 @@ export const MobileMenu = () => { )} onClick={() => changeMenuOpen(false)} > - {ItemSections.map((section) => ( + {SectionService.sections.map((section) => ( - {ItemService.itemSections[section].sectionName} + {SectionService.sectionsConfiguration[section].sectionName} ))} diff --git a/src/features/itemCard/index.ts b/src/widgets/itemCard/index.ts similarity index 100% rename from src/features/itemCard/index.ts rename to src/widgets/itemCard/index.ts diff --git a/src/features/itemCard/itemCard.tsx b/src/widgets/itemCard/itemCard.tsx similarity index 85% rename from src/features/itemCard/itemCard.tsx rename to src/widgets/itemCard/itemCard.tsx index d5ceec5..e9981ae 100644 --- a/src/features/itemCard/itemCard.tsx +++ b/src/widgets/itemCard/itemCard.tsx @@ -5,6 +5,7 @@ import { ItemCardType, ItemService, } from "@/entities/item"; +import { SectionService } from "@/features/sections"; import { Img } from "@/shared/ui"; import Link from "next/link"; @@ -12,7 +13,14 @@ export const ItemCard = ({ card }: { card: ItemCardType }) => { return ( {!!card.cover && ( ({ register, handleSubmit, setValue, + setError, watch, reset, formState: { dirtyFields, errors }, } = useForm({ - // Unfortunately, react hook form does not accept generic type correctly - // useForm causes an error when calling register(key) -> - // key is not assignable to parameter of type 'Path' defaultValues: init_item, - resolver: zodResolver(ItemService.GetFormResolver(item)), + resolver: zodResolver( + ItemService.itemsConfiguration[item.type].formResolver + ), }); useEffect(() => { @@ -72,7 +72,6 @@ export const ItemInfo = ({ useEffect(() => { if (!Object.keys(dirtyFields).length) return; if (JSON.stringify(watchedData) === JSON.stringify(formData)) return; - console.log(dirtyFields); changeFormData(watchedData as T); if (savedTimeout) clearTimeout(savedTimeout); changeSavedTimeout( @@ -83,14 +82,15 @@ export const ItemInfo = ({ }, [watchedData]); const onSubmit = async (formData: ItemCreateType) => { - changeSavedTimeout(null); - console.log(formData); const updatedItem = ItemService.isExistingItem(item) ? await ItemService.ChangeItem(item.id, formData) : await ItemService.AddItem(formData); + changeSavedTimeout(null); if (updatedItem) { changeItem(updatedItem as T); reset({}, { keepValues: true }); + } else { + setError("root", { message: "Ошибка сервера" }); } }; diff --git a/src/widgets/itemInfo/itemProperties.tsx b/src/widgets/itemInfo/itemProperties.tsx index 8677c93..8af7821 100644 --- a/src/widgets/itemInfo/itemProperties.tsx +++ b/src/widgets/itemInfo/itemProperties.tsx @@ -1,5 +1,6 @@ import { ItemCreateType, ItemType } from "@/entities/item"; import { ItemService } from "@/entities/item/item"; +import { ItemPropertiesDescriptionType } from "@/entities/item/types"; import clsx from "clsx"; import { UseFormRegister, UseFormSetValue } from "react-hook-form"; @@ -23,62 +24,63 @@ export const ItemProperties = ({ !editable && "cursor-default" )} > - {(ItemService.GetPropertiesDescriptionForItem(item) ?? []).map( - (section, i) => ( -
    - {section.map((req) => ( -
  • - {req.name + ": "} - - - Не известно - - { - setValue( - req.key as keyof ItemType, - e.currentTarget.innerText, - { - shouldValidate: true, - shouldDirty: true, - } - ); - }} - > - {req.value - ? req.value(item) - : undefined ?? (item[req.key] as string)} - + {( + ItemService.itemsConfiguration[item.type] + .propertiesDescription as ItemPropertiesDescriptionType + ).map((section, i) => ( +
      + {section.map((req) => ( +
    • + {req.name + ": "} + + + Не известно -
    • - ))} -
    - ) - )} + { + setValue( + req.key as keyof ItemType, + e.currentTarget.innerText, + { + shouldValidate: true, + shouldDirty: true, + } + ); + }} + > + {req.value + ? req.value(item) + : undefined ?? (item[req.key] as string)} + +
    +
  • + ))} +
+ ))} ); }; diff --git a/src/widgets/section/section.tsx b/src/widgets/section/section.tsx index b23779f..75e3c05 100644 --- a/src/widgets/section/section.tsx +++ b/src/widgets/section/section.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import Masonry, { ResponsiveMasonry } from "react-responsive-masonry"; -import { boolean } from "zod"; export const Section = ({ name,