Update readme
@@ -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
|
||||||
73
README.md
@@ -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+)
|
||||||
|
|||
|
||||||
|
|-|-|
|
||||||
|
|||
|
||||||
|
|||
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
#### Tablet (iPad)
|
||||||
|
|||
|
||||||
|
|-|-|
|
||||||
|
|||
|
||||||
|
|||
|
||||||
|
|
||||||
- [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.
|
||||
|
||||||
|
|-|-|-|
|
||||||
|
||||
|
||||||
|
|
||||||
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
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 |
@@ -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 |
BIN
screenshots/HD+_bean_light.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
screenshots/HD+_beans_light.png
Normal file
|
After Width: | Height: | Size: 458 KiB |
BIN
screenshots/HD+_recipe_dark.png
Normal file
|
After Width: | Height: | Size: 971 KiB |
BIN
screenshots/HD+_recipes_dark.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
screenshots/HD+_root_dark.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
screenshots/HD+_root_light.png
Normal file
|
After Width: | Height: | Size: 447 KiB |
BIN
screenshots/iPad_bean_light.png
Normal file
|
After Width: | Height: | Size: 586 KiB |
BIN
screenshots/iPad_beans_light.png
Normal file
|
After Width: | Height: | Size: 758 KiB |
BIN
screenshots/iPad_facts_dark.png
Normal file
|
After Width: | Height: | Size: 446 KiB |
BIN
screenshots/iPad_history_light.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
screenshots/iPad_recipe_dark.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
screenshots/iPad_recipes_dark.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
screenshots/iPhoneX_bean_light.png
Normal file
|
After Width: | Height: | Size: 528 KiB |
BIN
screenshots/iPhoneX_beans_light.png
Normal file
|
After Width: | Height: | Size: 623 KiB |
BIN
screenshots/iPhoneX_combinations_light.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
screenshots/iPhoneX_facts_dark.png
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
screenshots/iPhoneX_recipe_dark.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
screenshots/iPhoneX_recipes_dark.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
@@ -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
|
After Width: | Height: | Size: 3.3 KiB |
@@ -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<{
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -39,3 +39,4 @@ export const BeanCard = React.forwardRef<HTMLDivElement, { item: BeanType }>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
BeanCard.displayName = "BeanCard";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -37,3 +37,4 @@ export const ItemCard = React.forwardRef<HTMLDivElement, { item: ItemType }>(
|
|||||||
return ItemTypeToCard(item, ref);
|
return ItemTypeToCard(item, ref);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
ItemCard.displayName = "ItemCard";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||