Update readme

This commit is contained in:
2024-07-09 13:37:52 +04:00
parent 1577eabcde
commit 343a5be15b
41 changed files with 110 additions and 152 deletions

View File

@@ -2,5 +2,4 @@ IMAGES_PROTOCOL=https
IMAGES_DOMAIN=cdn-tp1.mozu.com IMAGES_DOMAIN=cdn-tp1.mozu.com
IMAGES_PORT=80 IMAGES_PORT=80
NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000
NEXT_PUBLIC_API_URL=https://jellybellywikiapi.onrender.com/api NEXT_PUBLIC_API_URL=https://jellybellywikiapi.onrender.com/api

View File

@@ -1,36 +1,57 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). # JellyBelly Wiki
> JellyBelly Wiki - сайт о бобах JellyBelly
## Getting Started
First, run the development server:
```bash ## Стек
npm run dev - TypeScript
# or - React 18
yarn dev - Next.js 14 (App Router)
# or - Tailwind CSS
pnpm dev - Zod
# or - next-themes
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. ## О проекте
- Написан за 10 часов (с использованием произвольных библиотек и snippet-ов прошлых проектов), в качестве тестового задания перед собеседованием
- Основан на открытом API https://jelly-belly-wiki.netlify.app
- Server Side Rendering (SSR) - в ответ на запрос пользователя сервер отправляет сгенерированный HTML заполненный контентом, а затем загружается весь необходимый для работы JavaScript. Для пользователя страница загружается быстрее
- Search Engine Optimization (SEO) - благодаря SSR поисковые боты могут индексировать страницы (в отличии от приложений написанных с использованием SPA подхода)
- Адаптивная верстка
- Поддержка мобильных устройств (в том числе планшетов)
- Верстка на основе rem
- Автоматическое масштабирование для малых и больших разрешений (сайт выглядит одинаково при любых разрешениях 16:9)
- Написан в соответствии с [Feature-Sliced Design (FSD)](https://feature-sliced.design/ru/)
- Легко масштабируемый
- Сущности, для которых необходимо реализовать общий функционал (например ленивую загрузку карточек) объедены абстракцией [item](./src//entities//item/) - это позволяет переиспользовать компонент [Grid](./src/widgets/grid/), а так же писать компоненты обёртки, например [ItemCard](./src/features/itemCard/) или [ItemInfo](./src/features/itemInfo/)
- Каждой сущности в рамках [item](./src//entities//item/), поставлена в соответствие секция [section](./src/features/sections/) - это позволяет написать переискользуемую страницу [\[section\]](./src/app/%5Bsection%5D/) и динамический [header](./src/widgets/header/)
- Вместе эти уровни абстракции позволяют, при необходимости, легко добавить новые сущности
- Валидация данных с помощью схем Zod
- Все данные, полученные с API валидируются с помощью библиотеки Zod
- При получении списка объектов, каждый валидируется отдельно, в случае возникновения ошибок, бракованный объект удаляется из списка, а остальные данные поступают в рендер - это обеспечивает отказоустойчивость, при единичных ошибках в данных
- Lazy loading - новые карточки подгружаются по мере необходимости при просмотре (скроллинге) страницы
- Используется цветовая схема [Solarized](https://en.wikipedia.org/wiki/Solarized). Есть возможность переключения между тёмной и светлой схемой
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. ## Скриншоты
## Learn More #### Desktop (HD+)
|![](./screenshots/HD%2B_root_dark.png)|![](./screenshots/HD%2B_root_light.png)|
|-|-|
|![](./screenshots/HD%2B_beans_light.png)|![](./screenshots/HD%2B_recipes_dark.png)|
|![](./screenshots/HD%2B_bean_light.png)|![](./screenshots/HD%2B_recipe_dark.png)|
To learn more about Next.js, take a look at the following resources: #### Tablet (iPad)
|![](./screenshots/iPad_beans_light.png)|![](./screenshots/iPad_recipes_dark.png)|
|-|-|
|![](./screenshots/iPad_bean_light.png)|![](./screenshots/iPad_recipe_dark.png)|
|![](./screenshots/iPad_history_light.png)|![](./screenshots/iPad_facts_dark.png)|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. #### Mobile (iPhoneX)
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. |![](./screenshots/iPhoneX_beans_light.png)|![](./screenshots/iPhoneX_bean_light.png)|![](./screenshots/iPhoneX_combinations_light.png)|
|-|-|-|
|![](./screenshots/iPhoneX_recipes_dark.png)|![](./screenshots/iPhoneX_recipe_dark.png)|![](./screenshots/iPhoneX_facts_dark.png)|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! ## Запуск
#### Локально
## Deploy on Vercel npm install
npm run dev
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

28
package-lock.json generated
View File

@@ -8,19 +8,16 @@
"name": "jelly_belly_wiki", "name": "jelly_belly_wiki",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"next": "14.2.4", "next": "14.2.4",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-responsive-masonry": "^2.2.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@types/react-responsive-masonry": "^2.1.3",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.4", "eslint-config-next": "14.2.4",
"postcss": "^8", "postcss": "^8",
@@ -477,15 +474,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-responsive-masonry": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/react-responsive-masonry/-/react-responsive-masonry-2.1.3.tgz",
"integrity": "sha512-aOFUtv3QwNMmy0BgpQpvivQ/+vivMTB6ARrzf9eTSXsLzXpVnfEtjpHpSknYDnr8KaQmlgeauAj8E7wo/qMOTg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
@@ -1094,14 +1082,6 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3830,14 +3810,6 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true "dev": true
}, },
"node_modules/react-responsive-masonry": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.2.1.tgz",
"integrity": "sha512-QY1vH8vWd8YpW2g40zsFp4CjttK2NWw2btzHbxks8vDRe+0JZfsrtK7Ob3siCtg+9mttwsofmAB6dp9ujSYwKw==",
"dependencies": {
"caniuse-lite": "^1.0.30001638"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@@ -9,19 +9,16 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"next": "14.2.4", "next": "14.2.4",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-responsive-masonry": "^2.2.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@types/react-responsive-masonry": "^2.1.3",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.4", "eslint-config-next": "14.2.4",
"postcss": "^8", "postcss": "^8",

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -10,7 +10,7 @@ export async function generateMetadata({
}: { }: {
params: { section: string; item_id: number }; params: { section: string; item_id: number };
}): Promise<Metadata> { }): Promise<Metadata> {
if (!SectionService.isSection(section)) redirect("/"); // if (!SectionService.isSection(section)) redirect("/");
return { return {
title: `JellyBelly: ${SectionService.sectionsConfiguration[section].sectionName}`, title: `JellyBelly: ${SectionService.sectionsConfiguration[section].sectionName}`,
}; };

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,4 +1,3 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
@@ -6,11 +5,6 @@ import { Header } from "@/widgets/header";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Jelly Belly Wiki",
description: "Information about everything Jelly belly",
};
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{

View File

@@ -4,10 +4,10 @@ import { SectionService, SectionType } from "@/features/sections";
import { Metadata } from "next"; import { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
export const revalidate = 0;
export const metadata: Metadata = { export const metadata: Metadata = {
title: ".Torrent", title: "JellyBelly Wiki",
description: description: "Information about JellyBelly beans",
".Torrent - сервис обмена .torrent файлами видеоигр, фильмов и аудиокниг",
}; };
export default async function Home() { export default async function Home() {

View File

@@ -39,3 +39,4 @@ export const BeanCard = React.forwardRef<HTMLDivElement, { item: BeanType }>(
); );
} }
); );
BeanCard.displayName = "BeanCard";

View File

@@ -1,7 +1,4 @@
import { CombinationType, TypesOfItems } from "@/entities/item"; import { CombinationType } from "@/entities/item";
import Link from "next/link";
import { SectionService } from "@/features/sections";
import Image from "next/image";
import React from "react"; import React from "react";
export const CombinationCard = React.forwardRef< export const CombinationCard = React.forwardRef<
@@ -23,3 +20,4 @@ export const CombinationCard = React.forwardRef<
</div> </div>
); );
}); });
CombinationCard.displayName = "CombinationCard";

View File

@@ -1,7 +1,4 @@
import { FactType, TypesOfItems } from "@/entities/item"; import { FactType } from "@/entities/item";
import Link from "next/link";
import { SectionService } from "@/features/sections";
import Image from "next/image";
import React from "react"; import React from "react";
export const FactCard = React.forwardRef<HTMLDivElement, { item: FactType }>( export const FactCard = React.forwardRef<HTMLDivElement, { item: FactType }>(
@@ -25,3 +22,4 @@ export const FactCard = React.forwardRef<HTMLDivElement, { item: FactType }>(
); );
} }
); );
FactCard.displayName = "FactCard";

View File

@@ -37,3 +37,4 @@ export const ItemCard = React.forwardRef<HTMLDivElement, { item: ItemType }>(
return ItemTypeToCard(item, ref); return ItemTypeToCard(item, ref);
} }
); );
ItemCard.displayName = "ItemCard";

View File

@@ -1,7 +1,3 @@
import { TypesOfItems } from "@/entities/item";
import Link from "next/link";
import { SectionService } from "@/features/sections";
import Image from "next/image";
import React from "react"; import React from "react";
import { MileStoneType } from "@/entities/item/mileStones"; import { MileStoneType } from "@/entities/item/mileStones";
@@ -24,3 +20,4 @@ export const MileStoneCard = React.forwardRef<
</div> </div>
); );
}); });
MileStoneCard.displayName = "MileStoneCard";

View File

@@ -1,4 +1,4 @@
import { BeanType, RecipeType, TypesOfItems } from "@/entities/item"; import { RecipeType, TypesOfItems } from "@/entities/item";
import Link from "next/link"; import Link from "next/link";
import { SectionService } from "@/features/sections"; import { SectionService } from "@/features/sections";
import Image from "next/image"; import Image from "next/image";
@@ -40,3 +40,4 @@ export const RecipeCard = React.forwardRef<
</div> </div>
); );
}); });
RecipeCard.displayName = "RecipeCard";

View File

@@ -28,7 +28,9 @@ export const BeanInfo = ({ item: bean }: { item: BeanType }) => {
<ul> <ul>
{bean.ingredients.length > 0 && {bean.ingredients.length > 0 &&
bean.ingredients.map((ingredient) => ( bean.ingredients.map((ingredient) => (
<li className="text-sm tb:text-xs">- {ingredient}</li> <li className="text-sm tb:text-xs" key={ingredient}>
- {ingredient}
</li>
))} ))}
</ul> </ul>
{bean.ingredients.length == 0 && ( {bean.ingredients.length == 0 && (
@@ -40,7 +42,9 @@ export const BeanInfo = ({ item: bean }: { item: BeanType }) => {
In theese groups: In theese groups:
<ul> <ul>
{bean.groupName.map((group) => ( {bean.groupName.map((group) => (
<li className="text-sm">- {group}</li> <li className="text-sm" key={group}>
- {group}
</li>
))} ))}
</ul> </ul>
</div> </div>
@@ -52,7 +56,7 @@ export const BeanInfo = ({ item: bean }: { item: BeanType }) => {
BeanPropertyDescription BeanPropertyDescription
) as (keyof typeof BeanPropertyDescription)[] ) as (keyof typeof BeanPropertyDescription)[]
).map((property) => ( ).map((property) => (
<li className="text-sm flex items-center pt-1"> <li className="text-sm flex items-center pt-1" key={property}>
- {BeanPropertyDescription[property]}:{" "} - {BeanPropertyDescription[property]}:{" "}
{bean[property] ? ( {bean[property] ? (
<CheckIcon className="h-5 pl-2" /> <CheckIcon className="h-5 pl-2" />

View File

@@ -38,39 +38,35 @@ export const RecipeInfo = ({ item: recipe }: { item: RecipeType }) => {
RecipeСookingDescription RecipeСookingDescription
) as (keyof typeof RecipeСookingDescription)[] ) as (keyof typeof RecipeСookingDescription)[]
).map((property) => ( ).map((property) => (
<> <div key={property} className="pt-1">
<span className="text-lg"> {(recipe[property] as string[]).length > 0 &&
{(recipe[property] as string[]).length > 0 && RecipeСookingDescription[property] + ":"}
RecipeСookingDescription[property] + ":"}
</span>
<ul> <ul>
{(recipe[property] as string[]).length > 0 && {(recipe[property] as string[]).length > 0 &&
(recipe[property] as string[]).map((ingredient) => ( (recipe[property] as string[]).map((ingredient) => (
<li className="text-sm tb:text-xs">- {ingredient}</li> <li className="text-sm tb:text-xs" key={ingredient}>
- {ingredient}
</li>
))} ))}
</ul> </ul>
</> </div>
))} ))}
</div> </div>
<div className=" w-full tb:w-[35%]"> <div className=" w-full tb:w-[35%]">
<div className="py-2"> <div className="py-2">
<span className="text-lg">How long does it take:</span> How long does it take:
<ul> <ul>
{( {(
Object.keys( Object.keys(
RecipePropertyDescription RecipePropertyDescription
) as (keyof typeof RecipePropertyDescription)[] ) as (keyof typeof RecipePropertyDescription)[]
).map((property) => ( ).map((property) => (
<> <li className="text-sm flex items-center" key={property}>
{ {`- ${RecipePropertyDescription[property]}: ${recipe[property]}`}
<li className="text-sm flex items-center pt-1"> {recipe[property] == "" && (
{`- ${RecipePropertyDescription[property]}: ${recipe[property]}`} <span className="text-fg4 pl-1">Classified</span>
{recipe[property] == "" && ( )}
<span className="text-fg4 pl-1">Classified</span> </li>
)}
</li>
}
</>
))} ))}
</ul> </ul>
</div> </div>
@@ -80,18 +76,18 @@ export const RecipeInfo = ({ item: recipe }: { item: RecipeType }) => {
RecipeIngredientsDescription RecipeIngredientsDescription
) as (keyof typeof RecipeIngredientsDescription)[] ) as (keyof typeof RecipeIngredientsDescription)[]
).map((property) => ( ).map((property) => (
<> <span key={property}>
<span className="text-lg"> {(recipe[property] as string[]).length > 0 &&
{(recipe[property] as string[]).length > 0 && RecipeIngredientsDescription[property] + ":"}
RecipeIngredientsDescription[property] + ":"}
</span>
<ul> <ul>
{(recipe[property] as string[]).length > 0 && {(recipe[property] as string[]).length > 0 &&
(recipe[property] as string[]).map((ingredient) => ( (recipe[property] as string[]).map((ingredient) => (
<li className="text-sm tb:text-xs">- {ingredient}</li> <li className="text-sm tb:text-xs" key={ingredient}>
- {ingredient}
</li>
))} ))}
</ul> </ul>
</> </span>
))} ))}
</div> </div>
</div> </div>

View File

@@ -9,8 +9,8 @@ export const CheckIcon = ({ className }: { className?: string }) => {
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g> <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g <g
id="SVGRepo_tracerCarrier" id="SVGRepo_tracerCarrier"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
></g> ></g>
<g id="SVGRepo_iconCarrier"> <g id="SVGRepo_iconCarrier">
<path <path

View File

@@ -6,16 +6,16 @@ export const CrossIcon = ({ className }: { className?: string }) => {
fill="none" fill="none"
className={className} className={className}
> >
<g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g <g
id="SVGRepo_tracerCarrier" id="SVGRepo_tracerCarrier"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
></g> ></g>
<g id="SVGRepo_iconCarrier"> <g id="SVGRepo_iconCarrier">
<path <path
fill="var(--color-err)" fill="var(--color-err)"
fill-rule="evenodd" fillRule="evenodd"
d="M16.293 17.707a1 1 0 001.414-1.414L11.414 10l6.293-6.293a1 1 0 00-1.414-1.414L10 8.586 3.707 2.293a1 1 0 00-1.414 1.414L8.586 10l-6.293 6.293a1 1 0 101.414 1.414L10 11.414l6.293 6.293z" d="M16.293 17.707a1 1 0 001.414-1.414L11.414 10l6.293-6.293a1 1 0 00-1.414-1.414L10 8.586 3.707 2.293a1 1 0 00-1.414 1.414L8.586 10l-6.293 6.293a1 1 0 101.414 1.414L10 11.414l6.293 6.293z"
></path> ></path>
</g> </g>

View File

@@ -9,7 +9,6 @@ import {
import { ItemCard } from "@/features/itemCard"; import { ItemCard } from "@/features/itemCard";
import React, { useRef } from "react"; import React, { useRef } from "react";
import { useState } from "react"; import { useState } from "react";
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
export const Grid = <U extends ItemType>({ export const Grid = <U extends ItemType>({
firstPage, firstPage,
@@ -30,7 +29,12 @@ export const Grid = <U extends ItemType>({
) => { ) => {
const { scrollTop, scrollHeight, clientHeight } = const { scrollTop, scrollHeight, clientHeight } =
e.target as HTMLDivElement; e.target as HTMLDivElement;
if ((scrollTop + clientHeight) / scrollHeight > 0.5) { if (
firstItemRef &&
firstItemRef.current &&
scrollHeight - (scrollTop + clientHeight) <
firstItemRef.current?.clientHeight * 3
) {
if (!loadingPage) { if (!loadingPage) {
changeLoadingPage(true); changeLoadingPage(true);
setTimeout(() => changeLoadingPage(false), 1000); setTimeout(() => changeLoadingPage(false), 1000);

View File

@@ -3,7 +3,6 @@
import { MobileMenu } from "./mobileMenu/mobileMenu"; import { MobileMenu } from "./mobileMenu/mobileMenu";
import Link from "next/link"; import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation"; import { useSelectedLayoutSegment } from "next/navigation";
import clsx from "clsx";
import { SectionService } from "@/features/sections"; import { SectionService } from "@/features/sections";
import { ColorSchemeSwitch } from "@/features/colorSchemeSwitch"; import { ColorSchemeSwitch } from "@/features/colorSchemeSwitch";
@@ -26,10 +25,10 @@ export const Header = () => {
{SectionService.sections.map((section) => ( {SectionService.sections.map((section) => (
<Link <Link
key={section} key={section}
className={clsx( className={
"px-5 cursor-pointer hover:underline underline-offset-2", "px-5 cursor-pointer hover:underline underline-offset-2 " +
currentPageName === section && "underline" (currentPageName === section ? "underline" : "")
)} }
href={"/" + section} href={"/" + section}
> >
{SectionService.sectionsConfiguration[section].sectionName} {SectionService.sectionsConfiguration[section].sectionName}
@@ -37,22 +36,6 @@ export const Header = () => {
))} ))}
</div> </div>
<ColorSchemeSwitch /> <ColorSchemeSwitch />
{/* <label className="flex flex-col items-start relative w-36">
<input
type="search"
className="peer/search w-full rounded-lg bg-bg4 px-2"
placeholder=" "
/>
<span
className="peer-focus/search:opacity-0
peer-[:not(:placeholder-shown)]/search:opacity-0
transition-opacity h-0 flex items-center relative bottom-3"
>
<SearchIcon className="w-4 h-4 mx-2" />
Поиск
</span>
</label> */}
</div> </div>
</header> </header>
); );

View File

@@ -1,7 +1,6 @@
"use client"; "use client";
import { SectionService } from "@/features/sections"; import { SectionService } from "@/features/sections";
import clsx from "clsx";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
@@ -19,20 +18,15 @@ export const MobileMenu = () => {
}} }}
onBlur={() => changeMenuOpen(false)} onBlur={() => changeMenuOpen(false)}
> >
<div <div className={open ? "rotate-45 top-4" : "top-0"}></div>
className={clsx(open && "rotate-45 top-4", !open && "top-0")} <div className={open ? "opacity-0" : ""}></div>
></div> <div className={open ? "-rotate-45 bottom-4" : "bottom-0"}></div>
<div className={clsx(open && "opacity-0")}></div>
<div
className={clsx(open && "-rotate-45 bottom-4", !open && "bottom-0")}
></div>
</button> </button>
<div <div
className={clsx( className={
"h-0 absolute transition-all duration-300 overflow-hidden\ "h-0 absolute transition-all duration-300 overflow-hidden\
bg-bg4 rounded-lg px-4 flex flex-col shadow-xl", bg-bg4 rounded-lg px-4 flex flex-col shadow-xl " + (open ? "h-56" : "")
open && "h-56" }
)}
onClick={() => changeMenuOpen(false)} onClick={() => changeMenuOpen(false)}
> >
{SectionService.sections.map((section) => ( {SectionService.sections.map((section) => (