Refactoring

This commit is contained in:
2024-06-23 19:43:12 +04:00
parent 7f4d1bf87d
commit ea723b88b0
18 changed files with 224 additions and 223 deletions

View File

@@ -1,5 +1,6 @@
import { GameService, GameType, isSection, ItemService } from "@/entities/item"; import { ItemService } from "@/entities/item";
import { ItemCard } from "@/features/itemCard"; import { SectionService } from "@/features/sections";
import { ItemCard } from "@/widgets/itemCard";
import { ItemInfo } from "@/widgets/itemInfo"; import { ItemInfo } from "@/widgets/itemInfo";
import { Section } from "@/widgets/section"; import { Section } from "@/widgets/section";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
@@ -9,13 +10,17 @@ export default async function Item({
}: { }: {
params: { section: string; item_id: number }; params: { section: string; item_id: number };
}) { }) {
const game = isSection(section) const game = SectionService.isSection(section)
? await ItemService.itemSections[section].service.Get(item_id) ? await ItemService.itemsConfiguration[
SectionService.sectionsConfiguration[section].itemType
].service.Get(item_id)
: redirect("/"); : redirect("/");
const cards = const cards =
isSection(section) && SectionService.isSection(section) &&
(await ItemService.itemSections[section].service.GetCards()); (await ItemService.itemsConfiguration[
SectionService.sectionsConfiguration[section].itemType
].service.GetCards());
return ( return (
<> <>
@@ -24,14 +29,15 @@ export default async function Item({
{cards && ( {cards && (
<Section <Section
name={ name={
isSection(section) SectionService.isSection(section)
? ItemService.itemSections[section].popularSubsectionName ? SectionService.sectionsConfiguration[section]
.popularSubsectionName
: undefined : undefined
} }
link={isSection(section) ? `/${section}` : undefined} link={SectionService.isSection(section) ? `/${section}` : undefined}
invite_text={ invite_text={
isSection(section) SectionService.isSection(section)
? ItemService.itemSections[section].sectionInviteText ? SectionService.sectionsConfiguration[section].sectionInviteText
: undefined : undefined
} }
> >

View File

@@ -1,21 +1,22 @@
import { GameService, isSection, ItemService } from "@/entities/item"; import { ItemCard } from "@/widgets/itemCard";
import { ItemCard } from "@/features/itemCard";
import { ItemInfo } from "@/widgets/itemInfo"; import { ItemInfo } from "@/widgets/itemInfo";
import { Section } from "@/widgets/section"; import { Section } from "@/widgets/section";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Metadata } from "next"; import { Metadata } from "next";
import { SectionService } from "@/features/sections";
import { ItemService } from "@/entities/item";
export async function generateMetadata({ export async function generateMetadata({
params: { section }, params: { section },
}: { }: {
params: { section: string }; params: { section: string };
}): Promise<Metadata> { }): Promise<Metadata> {
if (!isSection(section)) { if (!SectionService.isSection(section)) {
redirect("/"); redirect("/");
return {}; return {};
} }
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 }; params: { section: string };
}) { }) {
const emptyItem = isSection(section) const emptyItem = SectionService.isSection(section)
? await ItemService.itemSections[section].service.GetEmpty() ? await ItemService.itemsConfiguration[
SectionService.sectionsConfiguration[section].itemType
].service.GetEmpty()
: redirect("/"); : redirect("/");
const cards = const cards =
isSection(section) && SectionService.isSection(section) &&
(await ItemService.itemSections[section].service.GetCards()); (await ItemService.itemsConfiguration[
SectionService.sectionsConfiguration[section].itemType
].service.GetCards());
return ( return (
<> <>
@@ -39,14 +44,15 @@ export default async function AddItem({
{cards && ( {cards && (
<Section <Section
name={ name={
isSection(section) SectionService.isSection(section)
? ItemService.itemSections[section].popularSubsectionName ? SectionService.sectionsConfiguration[section]
.popularSubsectionName
: undefined : undefined
} }
link={isSection(section) ? `/${section}` : undefined} link={SectionService.isSection(section) ? `/${section}` : undefined}
invite_text={ invite_text={
isSection(section) SectionService.isSection(section)
? ItemService.itemSections[section].sectionInviteText ? SectionService.sectionsConfiguration[section].sectionInviteText
: undefined : undefined
} }
> >

View File

@@ -1,21 +1,25 @@
import { isSection, ItemService, MovieService } from "@/entities/item"; import { isSection, ItemService, MovieService } from "@/entities/item";
import { ItemCard } from "@/features/itemCard"; import { ItemCard } from "@/widgets/itemCard";
import { Section } from "@/widgets/section"; import { Section } from "@/widgets/section";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Metadata } from "next"; import { Metadata } from "next";
import { SectionService } from "@/features/sections";
export async function generateMetadata({ export async function generateMetadata({
params: { section }, params: { section },
}: { }: {
params: { section: string }; params: { section: string };
}): Promise<Metadata> { }): Promise<Metadata> {
if (!isSection(section)) { if (!SectionService.isSection(section)) {
redirect("/"); redirect("/");
return {}; return {};
} }
return { return {
title: `.Torrent: ${ItemService.itemSections[section].sectionName}`, title: `.Torrent: ${SectionService.sectionsConfiguration[section].sectionName}`,
description: `.Torrent: ${ItemService.itemSections[section].sectionName} - ${ItemService.itemSections[section].sectionName}`, description:
`.Torrent: ` +
`${SectionService.sectionsConfiguration[section].sectionName} - ` +
`${SectionService.sectionsConfiguration[section].sectionDescription}`,
}; };
} }
@@ -24,8 +28,10 @@ export default async function SectionPage({
}: { }: {
params: { section: string }; params: { section: string };
}) { }) {
const cards = isSection(section) const cards = SectionService.isSection(section)
? await ItemService.itemSections[section].service.GetCards() ? await ItemService.itemsConfiguration[
SectionService.sectionsConfiguration[section].itemType
].service.GetCards()
: redirect("/"); : redirect("/");
return ( return (

View File

@@ -1,11 +1,6 @@
import { import { ItemCardType, ItemService } from "@/entities/item";
isSection, import { ItemCard } from "@/widgets/itemCard";
ItemCardType, import { SectionService, SectionType } from "@/features/sections";
ItemSections,
ItemSectionsType,
ItemService,
} from "@/entities/item";
import { ItemCard } from "@/features/itemCard";
import { Section } from "@/widgets/section"; import { Section } from "@/widgets/section";
import { Metadata } from "next"; import { Metadata } from "next";
@@ -16,24 +11,31 @@ export const metadata: Metadata = {
}; };
export default async function Home() { export default async function Home() {
const cards: { [k in ItemSectionsType]?: ItemCardType[] | null } = {}; const cards: { [k in SectionType]?: ItemCardType[] | null } = {};
await Promise.all( await Promise.all(
ItemSections.map(async (section) => { SectionService.sections.map(async (section) => {
cards[section] = await ItemService.itemSections[ cards[section] = await ItemService.itemsConfiguration[
section SectionService.sectionsConfiguration[section].itemType
].service.GetCards(); ].service.GetCards();
}) })
); );
return ( return (
<> <>
{ItemSections.map((section) => ( {SectionService.sections.map((section) => (
<section key={section}> <section key={section}>
{cards[section] && cards[section].length > 0 && ( {cards[section] && cards[section].length > 0 && (
<Section <Section
name={ItemService.itemSections[section].popularSubsectionName} name={
link={isSection(section) ? `/${section}` : undefined} SectionService.sectionsConfiguration[section]
invite_text={ItemService.itemSections[section].sectionInviteText} .popularSubsectionName
}
link={
SectionService.isSection(section) ? `/${section}` : undefined
}
invite_text={
SectionService.sectionsConfiguration[section].sectionInviteText
}
> >
{cards[section].map((card) => ( {cards[section].map((card) => (
<ItemCard key={card.id} card={card} /> <ItemCard key={card.id} card={card} />

View File

@@ -85,20 +85,16 @@ import { ItemService } from "./item";
export { ItemService }; export { ItemService };
import { import {
isSection, TypesOfItems,
type IItemService,
type ItemType, type ItemType,
type ItemCardType, type ItemCardType,
type ItemCreateType, type ItemCreateType,
type TypesOfItems,
type ItemSectionsType,
ItemSections,
} from "./types"; } from "./types";
export { export {
isSection, TypesOfItems,
type IItemService,
type ItemType, type ItemType,
type ItemCardType, type ItemCardType,
type ItemCreateType, type ItemCreateType,
type TypesOfItems,
type ItemSectionsType,
ItemSections,
}; };

View File

@@ -7,10 +7,8 @@ import { audiobookCreateSchema } from "./audiobook/schemas/audiobook";
import { AudiobookService } from "./audiobook/audiobook"; import { AudiobookService } from "./audiobook/audiobook";
import { import {
IItemService, IItemService,
ItemCardType,
ItemCreateType, ItemCreateType,
ItemPropertiesDescriptionType, ItemPropertiesDescriptionType,
ItemSectionsType,
ItemType, ItemType,
TypesOfItems, TypesOfItems,
UnionItemType, UnionItemType,
@@ -18,9 +16,8 @@ import {
import { EraseCacheByTags } from "@/shared/utils/http"; import { EraseCacheByTags } from "@/shared/utils/http";
export abstract class ItemService { export abstract class ItemService {
private static get itemsConfiguration(): { static get itemsConfiguration(): {
[k in TypesOfItems]: { [k in TypesOfItems]: {
sectionUrl: ItemSectionsType;
formResolver: ZodSchema; formResolver: ZodSchema;
propertiesDescription: ItemPropertiesDescriptionType<UnionItemType>; propertiesDescription: ItemPropertiesDescriptionType<UnionItemType>;
service: IItemService; service: IItemService;
@@ -28,19 +25,16 @@ export abstract class ItemService {
} { } {
return { return {
[TypesOfItems.game]: { [TypesOfItems.game]: {
sectionUrl: "games",
formResolver: gameCreateSchema, formResolver: gameCreateSchema,
propertiesDescription: GameService.propertiesDescription, propertiesDescription: GameService.propertiesDescription,
service: GameService, service: GameService,
}, },
[TypesOfItems.movie]: { [TypesOfItems.movie]: {
sectionUrl: "movies",
formResolver: movieCreateSchema, formResolver: movieCreateSchema,
propertiesDescription: MovieService.propertiesDescription, propertiesDescription: MovieService.propertiesDescription,
service: MovieService, service: MovieService,
}, },
[TypesOfItems.audiobook]: { [TypesOfItems.audiobook]: {
sectionUrl: "audiobooks",
formResolver: audiobookCreateSchema, formResolver: audiobookCreateSchema,
propertiesDescription: AudiobookService.propertiesDescription, propertiesDescription: AudiobookService.propertiesDescription,
service: AudiobookService, 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( public static isExistingItem(
item: ItemCreateType | ItemType item: ItemCreateType | ItemType
): item is ItemType { ): item is ItemType {
return (item as ItemType).id !== undefined; 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<T>;
}
public static async AddItem(itemInfo: ItemCreateType) { public static async AddItem(itemInfo: ItemCreateType) {
const item = await this.itemsConfiguration[itemInfo.type].service.Add( const item = await this.itemsConfiguration[itemInfo.type].service.Add(
itemInfo itemInfo

View File

@@ -16,30 +16,12 @@ export type ItemCreateType =
| AudiobookCreateType; | AudiobookCreateType;
export type UnionItemType = GameType & MovieType & AudiobookType; export type UnionItemType = GameType & MovieType & AudiobookType;
export type UnionItemCardType = GameCardType &
MovieCardType &
AudiobookCardType;
export type UnionItemCreateType = GameCreateType &
MovieCreateType &
AudiobookCreateType;
export enum TypesOfItems { export enum TypesOfItems {
game, game,
movie, movie,
audiobook, 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<T extends ItemType | ItemCreateType> = export type ItemPropertiesDescriptionType<T extends ItemType | ItemCreateType> =
{ {
name: string; name: string;

View File

@@ -0,0 +1,2 @@
import { SectionService, type SectionType } from "./sections";
export { SectionService, type SectionType };

View File

@@ -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);
};
}

View File

@@ -7,7 +7,7 @@ import useSWR, { mutate } from "swr";
import clsx from "clsx"; import clsx from "clsx";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { useState } from "react"; import { useState } from "react";
import { ItemService } from "@/entities/item"; import { SectionService } from "../sections";
export const UserActivities = () => { export const UserActivities = () => {
const { data: me } = useSWR("user", () => UserService.IdentifyYourself()); const { data: me } = useSWR("user", () => UserService.IdentifyYourself());
@@ -39,14 +39,13 @@ export const UserActivities = () => {
{[ {[
{ {
group: "Добавить:", group: "Добавить:",
items: Object.entries(ItemService.itemSections).map( items: SectionService.sections.map((section) => {
([sectionId, section]) => {
return { return {
name: section.addItemText, name: SectionService.sectionsConfiguration[section]
link: `/${sectionId}/add`, .addItemText,
link: `/${SectionService.sectionsConfiguration[section].sectionUrl}/add`,
}; };
} }),
),
}, },
{ {
name: "Выйти", name: "Выйти",

View File

@@ -33,7 +33,6 @@ export abstract class HTTPService {
schema: Z, schema: Z,
options?: RequestOptions options?: RequestOptions
) { ) {
console.log(options?.body);
return await fetch(process.env.NEXT_PUBLIC_BASE_URL + url, { return await fetch(process.env.NEXT_PUBLIC_BASE_URL + url, {
method: method, method: method,
headers: { headers: {

View File

@@ -7,7 +7,7 @@ import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation"; import { useSelectedLayoutSegment } from "next/navigation";
import clsx from "clsx"; import clsx from "clsx";
import { UserActivities } from "@/features/userActivities"; import { UserActivities } from "@/features/userActivities";
import { ItemSections, ItemService } from "@/entities/item"; import { SectionService } from "@/features/sections";
export const Header = () => { export const Header = () => {
const currentPageName = useSelectedLayoutSegment(); const currentPageName = useSelectedLayoutSegment();
@@ -25,7 +25,7 @@ export const Header = () => {
<Link href="/">.Torrent</Link> <Link href="/">.Torrent</Link>
</h1> </h1>
<div className="hidden text-2xl lp:block"> <div className="hidden text-2xl lp:block">
{ItemSections.map((section) => ( {SectionService.sections.map((section) => (
<Link <Link
key={section} key={section}
className={clsx( className={clsx(
@@ -34,7 +34,7 @@ export const Header = () => {
)} )}
href={"/" + section} href={"/" + section}
> >
{ItemService.itemSections[section].sectionName} {SectionService.sectionsConfiguration[section].sectionName}
</Link> </Link>
))} ))}
</div> </div>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { ItemSections, ItemService } from "@/entities/item"; import { SectionService } from "@/features/sections";
import clsx from "clsx"; import clsx from "clsx";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
@@ -32,13 +32,13 @@ export const MobileMenu = () => {
)} )}
onClick={() => changeMenuOpen(false)} onClick={() => changeMenuOpen(false)}
> >
{ItemSections.map((section) => ( {SectionService.sections.map((section) => (
<Link <Link
key={section} key={section}
className="text-xl py-2 cursor-pointer hover:underline" className="text-xl py-2 cursor-pointer hover:underline"
href={"/" + section} href={"/" + section}
> >
{ItemService.itemSections[section].sectionName} {SectionService.sectionsConfiguration[section].sectionName}
</Link> </Link>
))} ))}
</div> </div>

View File

@@ -5,6 +5,7 @@ import {
ItemCardType, ItemCardType,
ItemService, ItemService,
} from "@/entities/item"; } from "@/entities/item";
import { SectionService } from "@/features/sections";
import { Img } from "@/shared/ui"; import { Img } from "@/shared/ui";
import Link from "next/link"; import Link from "next/link";
@@ -12,7 +13,14 @@ export const ItemCard = ({ card }: { card: ItemCardType }) => {
return ( return (
<Link <Link
className="group/itemcard cursor-pointer" className="group/itemcard cursor-pointer"
href={"/" + ItemService.GetSectionUrlByItemType(card) + "/" + card.id} href={
"/" +
SectionService.sectionsConfiguration[
SectionService.itemTypeToSection[card.type]
].sectionUrl +
"/" +
card.id
}
> >
{!!card.cover && ( {!!card.cover && (
<Img <Img

View File

@@ -44,15 +44,15 @@ export const ItemInfo = <T extends ItemType | ItemCreateType>({
register, register,
handleSubmit, handleSubmit,
setValue, setValue,
setError,
watch, watch,
reset, reset,
formState: { dirtyFields, errors }, formState: { dirtyFields, errors },
} = useForm<ItemType | ItemCreateType>({ } = useForm<ItemType | ItemCreateType>({
// Unfortunately, react hook form does not accept generic type correctly
// useForm<T> causes an error when calling register(key) ->
// key is not assignable to parameter of type 'Path<T>'
defaultValues: init_item, defaultValues: init_item,
resolver: zodResolver(ItemService.GetFormResolver(item)), resolver: zodResolver(
ItemService.itemsConfiguration[item.type].formResolver
),
}); });
useEffect(() => { useEffect(() => {
@@ -72,7 +72,6 @@ export const ItemInfo = <T extends ItemType | ItemCreateType>({
useEffect(() => { useEffect(() => {
if (!Object.keys(dirtyFields).length) return; if (!Object.keys(dirtyFields).length) return;
if (JSON.stringify(watchedData) === JSON.stringify(formData)) return; if (JSON.stringify(watchedData) === JSON.stringify(formData)) return;
console.log(dirtyFields);
changeFormData(watchedData as T); changeFormData(watchedData as T);
if (savedTimeout) clearTimeout(savedTimeout); if (savedTimeout) clearTimeout(savedTimeout);
changeSavedTimeout( changeSavedTimeout(
@@ -83,14 +82,15 @@ export const ItemInfo = <T extends ItemType | ItemCreateType>({
}, [watchedData]); }, [watchedData]);
const onSubmit = async (formData: ItemCreateType) => { const onSubmit = async (formData: ItemCreateType) => {
changeSavedTimeout(null);
console.log(formData);
const updatedItem = ItemService.isExistingItem(item) const updatedItem = ItemService.isExistingItem(item)
? await ItemService.ChangeItem(item.id, formData) ? await ItemService.ChangeItem(item.id, formData)
: await ItemService.AddItem(formData); : await ItemService.AddItem(formData);
changeSavedTimeout(null);
if (updatedItem) { if (updatedItem) {
changeItem(updatedItem as T); changeItem(updatedItem as T);
reset({}, { keepValues: true }); reset({}, { keepValues: true });
} else {
setError("root", { message: "Ошибка сервера" });
} }
}; };

View File

@@ -1,5 +1,6 @@
import { ItemCreateType, ItemType } from "@/entities/item"; import { ItemCreateType, ItemType } from "@/entities/item";
import { ItemService } from "@/entities/item/item"; import { ItemService } from "@/entities/item/item";
import { ItemPropertiesDescriptionType } from "@/entities/item/types";
import clsx from "clsx"; import clsx from "clsx";
import { UseFormRegister, UseFormSetValue } from "react-hook-form"; import { UseFormRegister, UseFormSetValue } from "react-hook-form";
@@ -23,8 +24,10 @@ export const ItemProperties = <T extends ItemType | ItemCreateType>({
!editable && "cursor-default" !editable && "cursor-default"
)} )}
> >
{(ItemService.GetPropertiesDescriptionForItem(item) ?? []).map( {(
(section, i) => ( ItemService.itemsConfiguration[item.type]
.propertiesDescription as ItemPropertiesDescriptionType<T>
).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="text-sm lp:text-md py-1"> <li key={req.name} className="text-sm lp:text-md py-1">
@@ -77,8 +80,7 @@ export const ItemProperties = <T extends ItemType | ItemCreateType>({
</li> </li>
))} ))}
</ul> </ul>
) ))}
)}
</div> </div>
); );
}; };

View File

@@ -5,7 +5,6 @@ import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry"; import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import { boolean } from "zod";
export const Section = ({ export const Section = ({
name, name,