feat: create minimal version
This commit is contained in:
152
README.md
152
README.md
@@ -1,6 +1,6 @@
|
|||||||
## О проекте
|
## О проекте
|
||||||
|
|
||||||
Это проект на **Next.js** (App Router), использующий UI-компоненты **shadcn/ui**, headless CMS **Payload**, утилитарную CSS-библиотеку **Tailwind CSS** и библиотеку анимаций **anime.js**.
|
Это проект на **Next.js** (App Router), использующий UI-компоненты **shadcn/ui**, утилитарную CSS-библиотеку **Tailwind CSS** и библиотеку анимаций **framer-motion**.
|
||||||
|
|
||||||
## Стек и роль технологий
|
## Стек и роль технологий
|
||||||
|
|
||||||
@@ -8,39 +8,28 @@
|
|||||||
- Документация: [nextjs.org/docs](https://nextjs.org/docs)
|
- Документация: [nextjs.org/docs](https://nextjs.org/docs)
|
||||||
- **shadcn/ui**: коллекция доступных и настраиваемых компонентов на базе Radix UI и Tailwind.
|
- **shadcn/ui**: коллекция доступных и настраиваемых компонентов на базе Radix UI и Tailwind.
|
||||||
- Документация: [ui.shadcn.com](https://ui.shadcn.com/)
|
- Документация: [ui.shadcn.com](https://ui.shadcn.com/)
|
||||||
- **Payload CMS**: headless CMS на Node.js для хранения контента, MongoDB/REST API и админ-панели.
|
|
||||||
- Что такое Payload: [payloadcms.com/docs/getting-started/what-is-payload](https://payloadcms.com/docs/getting-started/what-is-payload)
|
|
||||||
- Документация: [payloadcms.com/docs](https://payloadcms.com/docs)
|
|
||||||
- **Tailwind CSS**: утилитарные классы для быстрой стилизации интерфейсов.
|
- **Tailwind CSS**: утилитарные классы для быстрой стилизации интерфейсов.
|
||||||
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
||||||
- **anime.js**: легковесная библиотека для анимаций. В проекте используется с React-компонентами.
|
- **framer-motion**: мощная библиотека для анимаций в React-компонентах.
|
||||||
- Использование с React: [animejs.com/documentation/getting-started/using-with-react](https://animejs.com/documentation/getting-started/using-with-react)
|
- Документация: [www.framer.com/motion](https://www.framer.com/motion)
|
||||||
|
|
||||||
## Быстрый старт
|
## Быстрый старт
|
||||||
|
|
||||||
1. Установите зависимости:
|
1. Установите зависимости:
|
||||||
```bash
|
```bash
|
||||||
npm install
|
pnpm install
|
||||||
```
|
```
|
||||||
2. Создайте файл окружения на основе образца:
|
2. Запустите дев-сервер:
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
pnpm dev
|
||||||
```
|
```
|
||||||
Отредактируйте `.env` под ваше окружение (ключи для Payload, базы данных, URL и т.д.).
|
3. Откройте `http://localhost:3000` в браузере.
|
||||||
3. Запустите дев-сервер:
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
4. Откройте `http://localhost:3000` в браузере.
|
|
||||||
|
|
||||||
## Структура проекта (основное)
|
## Структура проекта (основное)
|
||||||
|
|
||||||
- `src/app/(frontend)`: публичные страницы, глобальные стили (`globals.css`), макеты и корневые компоненты фронтенда.
|
- `src/app/(frontend)`: публичные страницы, глобальные стили (`globals.css`), макеты и корневые компоненты фронтенда.
|
||||||
- `src/app/(payload)`: интеграция с Payload (админ-панель, GraphQL, REST API и т.д.).
|
|
||||||
> содержимое /(payload) не трогаем - автоматически генерируется и изменяется Payload
|
|
||||||
- `src/entities`: описания коллекций/сущностей для Payload (`users`, `media`, ...).
|
|
||||||
- `src/shared/ui`: библиотека переиспользуемых UI-компонентов на базе shadcn/ui.
|
- `src/shared/ui`: библиотека переиспользуемых UI-компонентов на базе shadcn/ui.
|
||||||
- `src/shared/lib`: утилиты и вспомогательные функции (включая загрузчик Payload).
|
- `src/shared/lib`: утилиты и вспомогательные функции.
|
||||||
|
|
||||||
## Архитектура: Feature‑Sliced Design (FSD)
|
## Архитектура: Feature‑Sliced Design (FSD)
|
||||||
|
|
||||||
@@ -73,28 +62,6 @@
|
|||||||
|
|
||||||
- Руководство и примеры: [ui.shadcn.com](https://ui.shadcn.com/)
|
- Руководство и примеры: [ui.shadcn.com](https://ui.shadcn.com/)
|
||||||
|
|
||||||
## Контент и API через Payload CMS
|
|
||||||
|
|
||||||
Payload управляет схемами данных (коллекциями), админ-панелью и API:
|
|
||||||
|
|
||||||
- Конфигурация Payload: `src/payload.config.ts`
|
|
||||||
- Коллекции: `src/entities/*/*.collection.ts`
|
|
||||||
- Админ-панель и API-роуты находятся в `src/app/(payload)` (например, GraphQL, REST и админ-маршруты)
|
|
||||||
|
|
||||||
Дополнительно:
|
|
||||||
|
|
||||||
- Конфиг Payload доступен по алиасу: `@/payload.config.ts` (алиас `@` указывает на `src`).
|
|
||||||
- Все сущности для Payload описываются в директории `src/entities` в файлах вида `*.collection.ts`.
|
|
||||||
- Пример: `@/entities/media/payload/media.collection.ts`.
|
|
||||||
- Загрузка/обработка данных сущностей выносится в файлы вида `*.loader.ts` рядом с коллекциями.
|
|
||||||
- Пример: `@/entities/media/payload/media.loader.ts`.
|
|
||||||
|
|
||||||
Полезные разделы документации:
|
|
||||||
|
|
||||||
- Что такое Payload: [payloadcms.com/docs/getting-started/what-is-payload](https://payloadcms.com/docs/getting-started/what-is-payload)
|
|
||||||
- Настройка коллекций: [payloadcms.com/docs/configuration/collections](https://payloadcms.com/docs/configuration/collections)
|
|
||||||
- GraphQL: [payloadcms.com/docs/graphql/overview](https://payloadcms.com/docs/graphql/overview)
|
|
||||||
|
|
||||||
## Стилизация: Tailwind CSS
|
## Стилизация: Tailwind CSS
|
||||||
|
|
||||||
Проект использует Tailwind для быстрой и согласованной стилизации. Ключевые ресурсы:
|
Проект использует Tailwind для быстрой и согласованной стилизации. Ключевые ресурсы:
|
||||||
@@ -102,106 +69,49 @@ Payload управляет схемами данных (коллекциями),
|
|||||||
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
||||||
- Руководства по best practices: [tailwindcss.com/blog](https://tailwindcss.com/blog)
|
- Руководства по best practices: [tailwindcss.com/blog](https://tailwindcss.com/blog)
|
||||||
|
|
||||||
## Анимации: anime.js
|
## Анимации: framer-motion
|
||||||
|
|
||||||
Для анимаций используется `anime.js`. Подход к интеграции в React-компоненты соответствует официальным рекомендациям:
|
Для анимаций используется `framer-motion`. Это мощная библиотека для создания плавных и производительных анимаций в React-компонентах.
|
||||||
|
|
||||||
- Гайд по React: [animejs.com/documentation/getting-started/using-with-react](https://animejs.com/documentation/getting-started/using-with-react)
|
Пример использования:
|
||||||
|
|
||||||
> прмер работы с анимацией
|
```tsx
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
```
|
|
||||||
import { animate, createScope, spring, createDraggable } from 'animejs';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
import reactLogo from './assets/react.svg';
|
|
||||||
import './App.css';
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const root = useRef(null);
|
|
||||||
const scope = useRef(null);
|
|
||||||
const [ rotations, setRotations ] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
|
|
||||||
scope.current = createScope({ root }).add( self => {
|
|
||||||
|
|
||||||
// Every anime.js instance declared here is now scoped to <div ref={root}>
|
|
||||||
|
|
||||||
// Created a bounce animation loop
|
|
||||||
animate('.logo', {
|
|
||||||
scale: [
|
|
||||||
{ to: 1.25, ease: 'inOut(3)', duration: 200 },
|
|
||||||
{ to: 1, ease: spring({ bounce: .7 }) }
|
|
||||||
],
|
|
||||||
loop: true,
|
|
||||||
loopDelay: 250,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make the logo draggable around its center
|
|
||||||
createDraggable('.logo', {
|
|
||||||
container: [0, 0, 0, 0],
|
|
||||||
releaseEase: spring({ bounce: .7 })
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register function methods to be used outside the useEffect
|
|
||||||
self.add('rotateLogo', (i) => {
|
|
||||||
animate('.logo', {
|
|
||||||
rotate: i * 360,
|
|
||||||
ease: 'out(4)',
|
|
||||||
duration: 1500,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Properly cleanup all anime.js instances declared inside the scope
|
|
||||||
return () => scope.current.revert()
|
|
||||||
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
setRotations(prev => {
|
|
||||||
const newRotations = prev + 1;
|
|
||||||
// Animate logo rotation on click using the method declared inside the scope
|
|
||||||
scope.current.methods.rotateLogo(newRotations);
|
|
||||||
return newRotations;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
function AnimatedComponent() {
|
||||||
return (
|
return (
|
||||||
<div ref={root}>
|
<motion.div
|
||||||
<div className="large centered row">
|
initial={{ opacity: 0, y: 20 }}
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
animate={{ opacity: 1, y: 0 }}
|
||||||
</div>
|
transition={{ duration: 0.5 }}
|
||||||
<div className="medium row">
|
>
|
||||||
<fieldset className="controls">
|
Анимированный контент
|
||||||
<button onClick={handleClick}>rotations: {rotations}</button>
|
</motion.div>
|
||||||
</fieldset>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Документация: [www.framer.com/motion](https://www.framer.com/motion)
|
||||||
|
|
||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Запуск дев-сервера
|
# Запуск дев-сервера
|
||||||
npm run dev
|
pnpm dev
|
||||||
|
|
||||||
# Билд продакшн-версии
|
# Билд продакшн-версии
|
||||||
npm run build
|
pnpm build
|
||||||
|
|
||||||
# Предпросмотр продакшн-сборки
|
# Предпросмотр продакшн-сборки
|
||||||
npm run start
|
pnpm start
|
||||||
|
|
||||||
|
# Линтинг
|
||||||
|
pnpm lint
|
||||||
```
|
```
|
||||||
|
|
||||||
## Полезные ссылки
|
## Полезные ссылки
|
||||||
|
|
||||||
- Next.js: [nextjs.org/docs](https://nextjs.org/docs)
|
- Next.js: [nextjs.org/docs](https://nextjs.org/docs)
|
||||||
- shadcn/ui: [ui.shadcn.com](https://ui.shadcn.com/)
|
- shadcn/ui: [ui.shadcn.com](https://ui.shadcn.com/)
|
||||||
- Payload CMS: [payloadcms.com/docs](https://payloadcms.com/docs)
|
|
||||||
- Tailwind CSS: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
- Tailwind CSS: [tailwindcss.com/docs](https://tailwindcss.com/docs)
|
||||||
- anime.js: [animejs.com/documentation](https://animejs.com/documentation)
|
- framer-motion: [www.framer.com/motion](https://www.framer.com/motion)
|
||||||
|
|||||||
6
next.config.mjs
Normal file
6
next.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { withPayload } from "@payloadcms/next/withPayload";
|
|
||||||
import type { NextConfig } from "next";
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
|
||||||
/* config options here */
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withPayload(nextConfig);
|
|
||||||
14650
package-lock.json
generated
14650
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -3,17 +3,13 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev",
|
||||||
"build": "next build --turbopack",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@payloadcms/db-mongodb": "^3.59.1",
|
|
||||||
"@payloadcms/next": "^3.59.1",
|
|
||||||
"@payloadcms/payload-cloud": "^3.59.1",
|
|
||||||
"@payloadcms/richtext-lexical": "^3.59.1",
|
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
||||||
@@ -41,21 +37,19 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"animejs": "^4.2.2",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"graphql": "^16.11.0",
|
"framer-motion": "^11.0.0",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.545.0",
|
"lucide-react": "^0.545.0",
|
||||||
"next": "15.5.5",
|
"next": "14.2.18",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"payload": "^3.59.1",
|
"react": "18.3.1",
|
||||||
"react": "19.1.0",
|
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "18.3.1",
|
||||||
"react-hook-form": "^7.65.0",
|
"react-hook-form": "^7.65.0",
|
||||||
"react-resizable-panels": "^3.0.6",
|
"react-resizable-panels": "^3.0.6",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
@@ -68,10 +62,10 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "18.2.48",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "18.2.18",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.5.5",
|
"eslint-config-next": "14.2.18",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
6055
pnpm-lock.yaml
generated
6055
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
<div className="min-h-screen flex items-center justify-center p-8 pb-20 sm:p-20 bg-gradient-to-br from-background via-background to-muted/20">
|
||||||
sup
|
<div className="max-w-4xl w-full space-y-8">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="text-center space-y-6"
|
||||||
|
>
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.2 }}
|
||||||
|
className="text-6xl sm:text-7xl md:text-8xl font-bold bg-gradient-to-r from-foreground via-foreground to-foreground/40 bg-clip-text text-transparent tracking-tight"
|
||||||
|
>
|
||||||
|
Tungulov.space
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||||||
|
className="text-xl sm:text-2xl md:text-3xl text-muted-foreground font-medium"
|
||||||
|
>
|
||||||
|
Избавляем вас от головной боли разработки
|
||||||
|
</motion.p>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.6 }}
|
||||||
|
className="text-center"
|
||||||
|
>
|
||||||
|
<p className="text-lg sm:text-xl md:text-2xl text-foreground/80 leading-relaxed max-w-2xl mx-auto">
|
||||||
|
Сконцентрируйтесь на бизнесе, а мы позаботимся о продукте
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import type { Metadata } from 'next'
|
|
||||||
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
|
||||||
import { importMap } from '../importMap'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
params: Promise<{
|
|
||||||
segments: string[]
|
|
||||||
}>
|
|
||||||
searchParams: Promise<{
|
|
||||||
[key: string]: string | string[]
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
|
||||||
generatePageMetadata({ config, params, searchParams })
|
|
||||||
|
|
||||||
const NotFound = ({ params, searchParams }: Args) =>
|
|
||||||
NotFoundPage({ config, params, searchParams, importMap })
|
|
||||||
|
|
||||||
export default NotFound
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import type { Metadata } from 'next'
|
|
||||||
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
|
||||||
import { importMap } from '../importMap'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
params: Promise<{
|
|
||||||
segments: string[]
|
|
||||||
}>
|
|
||||||
searchParams: Promise<{
|
|
||||||
[key: string]: string | string[]
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
|
||||||
generatePageMetadata({ config, params, searchParams })
|
|
||||||
|
|
||||||
const Page = ({ params, searchParams }: Args) =>
|
|
||||||
RootPage({ config, params, searchParams, importMap })
|
|
||||||
|
|
||||||
export default Page
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const importMap = {}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import config from '@payload-config'
|
|
||||||
import '@payloadcms/next/css'
|
|
||||||
import {
|
|
||||||
REST_DELETE,
|
|
||||||
REST_GET,
|
|
||||||
REST_OPTIONS,
|
|
||||||
REST_PATCH,
|
|
||||||
REST_POST,
|
|
||||||
REST_PUT,
|
|
||||||
} from '@payloadcms/next/routes'
|
|
||||||
|
|
||||||
export const GET = REST_GET(config)
|
|
||||||
export const POST = REST_POST(config)
|
|
||||||
export const DELETE = REST_DELETE(config)
|
|
||||||
export const PATCH = REST_PATCH(config)
|
|
||||||
export const PUT = REST_PUT(config)
|
|
||||||
export const OPTIONS = REST_OPTIONS(config)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import config from '@payload-config'
|
|
||||||
import '@payloadcms/next/css'
|
|
||||||
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
|
|
||||||
|
|
||||||
export const GET = GRAPHQL_PLAYGROUND_GET(config)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { GRAPHQL_POST, REST_OPTIONS } from '@payloadcms/next/routes'
|
|
||||||
|
|
||||||
export const POST = GRAPHQL_POST(config)
|
|
||||||
|
|
||||||
export const OPTIONS = REST_OPTIONS(config)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
|
||||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
|
||||||
import config from "@payload-config";
|
|
||||||
import "@payloadcms/next/css";
|
|
||||||
import type { ServerFunctionClient } from "payload";
|
|
||||||
import { handleServerFunctions, RootLayout } from "@payloadcms/next/layouts";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { importMap } from "./admin/importMap.js";
|
|
||||||
import "./custom.scss";
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverFunction: ServerFunctionClient = async function (args) {
|
|
||||||
"use server";
|
|
||||||
return handleServerFunctions({
|
|
||||||
...args,
|
|
||||||
config,
|
|
||||||
importMap,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const Layout = ({ children }: Args) => (
|
|
||||||
<RootLayout
|
|
||||||
config={config}
|
|
||||||
importMap={importMap}
|
|
||||||
serverFunction={serverFunction}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</RootLayout>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Layout;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
|
||||||
|
|
||||||
export const Media: CollectionConfig = {
|
|
||||||
slug: 'media',
|
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'alt',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
upload: true,
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import type { Media } from "@/payload-types";
|
|
||||||
|
|
||||||
import { getPayloadClient } from "@/shared/lib/payload-loader";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает медиа файл по ID
|
|
||||||
*/
|
|
||||||
export async function getMediaById(id: string) {
|
|
||||||
const payload = await getPayloadClient();
|
|
||||||
const media = await payload.findByID({
|
|
||||||
collection: "media",
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает несколько медиа файлов по ID
|
|
||||||
*/
|
|
||||||
export async function getMediaByIds(ids: string[]) {
|
|
||||||
const payload = await getPayloadClient();
|
|
||||||
const mediaItems = await payload.find({
|
|
||||||
collection: "media",
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
in: ids,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return mediaItems.docs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает URL медиа файла
|
|
||||||
*/
|
|
||||||
export function getMediaUrl(media: Media): string {
|
|
||||||
return media.url || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает thumbnail URL медиа файла
|
|
||||||
*/
|
|
||||||
export function getMediaThumbnailUrl(media: Media): string {
|
|
||||||
return media.thumbnailURL || media.url || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверяет, является ли файл изображением
|
|
||||||
*/
|
|
||||||
export function isImage(media: Media): boolean {
|
|
||||||
return media.mimeType?.startsWith("image/") || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает размеры изображения
|
|
||||||
*/
|
|
||||||
export function getImageDimensions(media: Media) {
|
|
||||||
if (!isImage(media)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: media.width,
|
|
||||||
height: media.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
|
||||||
|
|
||||||
export const Users: CollectionConfig = {
|
|
||||||
slug: 'users',
|
|
||||||
admin: {
|
|
||||||
useAsTitle: 'email',
|
|
||||||
},
|
|
||||||
auth: true,
|
|
||||||
fields: [
|
|
||||||
// Email added by default
|
|
||||||
// Add more fields as needed
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/**
|
|
||||||
* This file was automatically generated by Payload.
|
|
||||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
|
||||||
* and re-run `payload generate:types` to regenerate this file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported timezones in IANA format.
|
|
||||||
*
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "supportedTimezones".
|
|
||||||
*/
|
|
||||||
export type SupportedTimezones =
|
|
||||||
| 'Pacific/Midway'
|
|
||||||
| 'Pacific/Niue'
|
|
||||||
| 'Pacific/Honolulu'
|
|
||||||
| 'Pacific/Rarotonga'
|
|
||||||
| 'America/Anchorage'
|
|
||||||
| 'Pacific/Gambier'
|
|
||||||
| 'America/Los_Angeles'
|
|
||||||
| 'America/Tijuana'
|
|
||||||
| 'America/Denver'
|
|
||||||
| 'America/Phoenix'
|
|
||||||
| 'America/Chicago'
|
|
||||||
| 'America/Guatemala'
|
|
||||||
| 'America/New_York'
|
|
||||||
| 'America/Bogota'
|
|
||||||
| 'America/Caracas'
|
|
||||||
| 'America/Santiago'
|
|
||||||
| 'America/Buenos_Aires'
|
|
||||||
| 'America/Sao_Paulo'
|
|
||||||
| 'Atlantic/South_Georgia'
|
|
||||||
| 'Atlantic/Azores'
|
|
||||||
| 'Atlantic/Cape_Verde'
|
|
||||||
| 'Europe/London'
|
|
||||||
| 'Europe/Berlin'
|
|
||||||
| 'Africa/Lagos'
|
|
||||||
| 'Europe/Athens'
|
|
||||||
| 'Africa/Cairo'
|
|
||||||
| 'Europe/Moscow'
|
|
||||||
| 'Asia/Riyadh'
|
|
||||||
| 'Asia/Dubai'
|
|
||||||
| 'Asia/Baku'
|
|
||||||
| 'Asia/Karachi'
|
|
||||||
| 'Asia/Tashkent'
|
|
||||||
| 'Asia/Calcutta'
|
|
||||||
| 'Asia/Dhaka'
|
|
||||||
| 'Asia/Almaty'
|
|
||||||
| 'Asia/Jakarta'
|
|
||||||
| 'Asia/Bangkok'
|
|
||||||
| 'Asia/Shanghai'
|
|
||||||
| 'Asia/Singapore'
|
|
||||||
| 'Asia/Tokyo'
|
|
||||||
| 'Asia/Seoul'
|
|
||||||
| 'Australia/Brisbane'
|
|
||||||
| 'Australia/Sydney'
|
|
||||||
| 'Pacific/Guam'
|
|
||||||
| 'Pacific/Noumea'
|
|
||||||
| 'Pacific/Auckland'
|
|
||||||
| 'Pacific/Fiji';
|
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
auth: {
|
|
||||||
users: UserAuthOperations;
|
|
||||||
};
|
|
||||||
blocks: {};
|
|
||||||
collections: {
|
|
||||||
users: User;
|
|
||||||
media: Media;
|
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
|
||||||
'payload-preferences': PayloadPreference;
|
|
||||||
'payload-migrations': PayloadMigration;
|
|
||||||
};
|
|
||||||
collectionsJoins: {};
|
|
||||||
collectionsSelect: {
|
|
||||||
users: UsersSelect<false> | UsersSelect<true>;
|
|
||||||
media: MediaSelect<false> | MediaSelect<true>;
|
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
|
||||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
|
||||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
|
||||||
};
|
|
||||||
db: {
|
|
||||||
defaultIDType: string;
|
|
||||||
};
|
|
||||||
globals: {};
|
|
||||||
globalsSelect: {};
|
|
||||||
locale: null;
|
|
||||||
user: User & {
|
|
||||||
collection: 'users';
|
|
||||||
};
|
|
||||||
jobs: {
|
|
||||||
tasks: unknown;
|
|
||||||
workflows: unknown;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export interface UserAuthOperations {
|
|
||||||
forgotPassword: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
login: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
registerFirstUser: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
unlock: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "users".
|
|
||||||
*/
|
|
||||||
export interface User {
|
|
||||||
id: string;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
email: string;
|
|
||||||
resetPasswordToken?: string | null;
|
|
||||||
resetPasswordExpiration?: string | null;
|
|
||||||
salt?: string | null;
|
|
||||||
hash?: string | null;
|
|
||||||
loginAttempts?: number | null;
|
|
||||||
lockUntil?: string | null;
|
|
||||||
sessions?:
|
|
||||||
| {
|
|
||||||
id: string;
|
|
||||||
createdAt?: string | null;
|
|
||||||
expiresAt: string;
|
|
||||||
}[]
|
|
||||||
| null;
|
|
||||||
password?: string | null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "media".
|
|
||||||
*/
|
|
||||||
export interface Media {
|
|
||||||
id: string;
|
|
||||||
alt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
url?: string | null;
|
|
||||||
thumbnailURL?: string | null;
|
|
||||||
filename?: string | null;
|
|
||||||
mimeType?: string | null;
|
|
||||||
filesize?: number | null;
|
|
||||||
width?: number | null;
|
|
||||||
height?: number | null;
|
|
||||||
focalX?: number | null;
|
|
||||||
focalY?: number | null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-locked-documents".
|
|
||||||
*/
|
|
||||||
export interface PayloadLockedDocument {
|
|
||||||
id: string;
|
|
||||||
document?:
|
|
||||||
| ({
|
|
||||||
relationTo: 'users';
|
|
||||||
value: string | User;
|
|
||||||
} | null)
|
|
||||||
| ({
|
|
||||||
relationTo: 'media';
|
|
||||||
value: string | Media;
|
|
||||||
} | null);
|
|
||||||
globalSlug?: string | null;
|
|
||||||
user: {
|
|
||||||
relationTo: 'users';
|
|
||||||
value: string | User;
|
|
||||||
};
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-preferences".
|
|
||||||
*/
|
|
||||||
export interface PayloadPreference {
|
|
||||||
id: string;
|
|
||||||
user: {
|
|
||||||
relationTo: 'users';
|
|
||||||
value: string | User;
|
|
||||||
};
|
|
||||||
key?: string | null;
|
|
||||||
value?:
|
|
||||||
| {
|
|
||||||
[k: string]: unknown;
|
|
||||||
}
|
|
||||||
| unknown[]
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| null;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-migrations".
|
|
||||||
*/
|
|
||||||
export interface PayloadMigration {
|
|
||||||
id: string;
|
|
||||||
name?: string | null;
|
|
||||||
batch?: number | null;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "users_select".
|
|
||||||
*/
|
|
||||||
export interface UsersSelect<T extends boolean = true> {
|
|
||||||
updatedAt?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
email?: T;
|
|
||||||
resetPasswordToken?: T;
|
|
||||||
resetPasswordExpiration?: T;
|
|
||||||
salt?: T;
|
|
||||||
hash?: T;
|
|
||||||
loginAttempts?: T;
|
|
||||||
lockUntil?: T;
|
|
||||||
sessions?:
|
|
||||||
| T
|
|
||||||
| {
|
|
||||||
id?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
expiresAt?: T;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "media_select".
|
|
||||||
*/
|
|
||||||
export interface MediaSelect<T extends boolean = true> {
|
|
||||||
alt?: T;
|
|
||||||
updatedAt?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
url?: T;
|
|
||||||
thumbnailURL?: T;
|
|
||||||
filename?: T;
|
|
||||||
mimeType?: T;
|
|
||||||
filesize?: T;
|
|
||||||
width?: T;
|
|
||||||
height?: T;
|
|
||||||
focalX?: T;
|
|
||||||
focalY?: T;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-locked-documents_select".
|
|
||||||
*/
|
|
||||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
|
||||||
document?: T;
|
|
||||||
globalSlug?: T;
|
|
||||||
user?: T;
|
|
||||||
updatedAt?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-preferences_select".
|
|
||||||
*/
|
|
||||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
|
||||||
user?: T;
|
|
||||||
key?: T;
|
|
||||||
value?: T;
|
|
||||||
updatedAt?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "payload-migrations_select".
|
|
||||||
*/
|
|
||||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
|
||||||
name?: T;
|
|
||||||
batch?: T;
|
|
||||||
updatedAt?: T;
|
|
||||||
createdAt?: T;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
|
||||||
* via the `definition` "auth".
|
|
||||||
*/
|
|
||||||
export interface Auth {
|
|
||||||
[k: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
declare module 'payload' {
|
|
||||||
export interface GeneratedTypes extends Config {}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// storage-adapter-import-placeholder
|
|
||||||
import { mongooseAdapter } from "@payloadcms/db-mongodb"; // database-adapter-import
|
|
||||||
import { payloadCloudPlugin } from "@payloadcms/payload-cloud";
|
|
||||||
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
|
||||||
import path from "path";
|
|
||||||
import { buildConfig } from "payload";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import sharp from "sharp";
|
|
||||||
|
|
||||||
import { Users } from "@/entities/users/payload/users.collection";
|
|
||||||
import { Media } from "@/entities/media/payload/media.collection";
|
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
|
||||||
const dirname = path.dirname(filename);
|
|
||||||
|
|
||||||
export default buildConfig({
|
|
||||||
admin: {
|
|
||||||
user: Users.slug,
|
|
||||||
importMap: {
|
|
||||||
baseDir: path.resolve(dirname),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
collections: [Users, Media],
|
|
||||||
editor: lexicalEditor(),
|
|
||||||
secret: process.env.PAYLOAD_SECRET || "",
|
|
||||||
typescript: {
|
|
||||||
outputFile: path.resolve(dirname, "payload-types.ts"),
|
|
||||||
},
|
|
||||||
// database-adapter-config-start
|
|
||||||
db: mongooseAdapter({
|
|
||||||
url: process.env.DATABASE_URI || "",
|
|
||||||
}),
|
|
||||||
// database-adapter-config-end
|
|
||||||
sharp,
|
|
||||||
plugins: [
|
|
||||||
payloadCloudPlugin(),
|
|
||||||
// storage-adapter-placeholder
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { headers as getHeaders } from "next/headers.js";
|
|
||||||
import { getPayload } from "payload";
|
|
||||||
|
|
||||||
import config from "@/payload.config";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Инициализирует и возвращает экземпляр Payload
|
|
||||||
* Использует кэширование для оптимизации производительности
|
|
||||||
*/
|
|
||||||
export async function getPayloadClient() {
|
|
||||||
// const headers = await getHeaders()
|
|
||||||
const payloadConfig = await config;
|
|
||||||
return await getPayload({ config: payloadConfig });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает текущего пользователя
|
|
||||||
*/
|
|
||||||
export async function getCurrentUser() {
|
|
||||||
const payload = await getPayloadClient();
|
|
||||||
const headers = await getHeaders();
|
|
||||||
const { user } = await payload.auth({ headers });
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Проверяет, аутентифицирован ли пользователь
|
|
||||||
*/
|
|
||||||
export async function isAuthenticated() {
|
|
||||||
const user = await getCurrentUser();
|
|
||||||
return !!user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Получает конфигурацию Payload
|
|
||||||
*/
|
|
||||||
export async function getPayloadConfig() {
|
|
||||||
return await config;
|
|
||||||
}
|
|
||||||
@@ -20,8 +20,7 @@
|
|||||||
],
|
],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*", "./*"],
|
"@/*": ["src/*", "./*"]
|
||||||
"@payload-config": ["src/payload.config.ts"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user