feat: create minimal version

This commit is contained in:
2025-11-05 23:11:30 +03:00
parent 5584a99600
commit e01aafdf01
22 changed files with 974 additions and 20558 deletions

152
README.md
View File

@@ -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)
- **shadcn/ui**: коллекция доступных и настраиваемых компонентов на базе Radix UI и Tailwind.
- Документация: [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**: утилитарные классы для быстрой стилизации интерфейсов.
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
- **anime.js**: легковесная библиотека для анимаций. В проекте используется с React-компонентами.
- Использование с React: [animejs.com/documentation/getting-started/using-with-react](https://animejs.com/documentation/getting-started/using-with-react)
- **framer-motion**: мощная библиотека для анимаций в React-компонентах.
- Документация: [www.framer.com/motion](https://www.framer.com/motion)
## Быстрый старт
1. Установите зависимости:
```bash
npm install
pnpm install
```
2. Создайте файл окружения на основе образца:
2. Запустите дев-сервер:
```bash
cp .env.example .env
pnpm dev
```
Отредактируйте `.env` под ваше окружение (ключи для Payload, базы данных, URL и т.д.).
3. Запустите дев-сервер:
```bash
npm run dev
```
4. Откройте `http://localhost:3000` в браузере.
3. Откройте `http://localhost:3000` в браузере.
## Структура проекта (основное)
- `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/lib`: утилиты и вспомогательные функции (включая загрузчик Payload).
- `src/shared/lib`: утилиты и вспомогательные функции.
## Архитектура: FeatureSliced Design (FSD)
@@ -73,28 +62,6 @@
- Руководство и примеры: [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 для быстрой и согласованной стилизации. Ключевые ресурсы:
@@ -102,106 +69,49 @@ Payload управляет схемами данных (коллекциями),
- Документация: [tailwindcss.com/docs](https://tailwindcss.com/docs)
- Руководства по 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)
Пример использования:
> прмер работы с анимацией
```
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;
});
};
```tsx
import { motion } from 'framer-motion';
function AnimatedComponent() {
return (
<div ref={root}>
<div className="large centered row">
<img src={reactLogo} className="logo react" alt="React logo" />
</div>
<div className="medium row">
<fieldset className="controls">
<button onClick={handleClick}>rotations: {rotations}</button>
</fieldset>
</div>
</div>
)
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Анимированный контент
</motion.div>
);
}
export default App;
```
Документация: [www.framer.com/motion](https://www.framer.com/motion)
## Команды
```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)
- 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)
- 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
View File

@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
};
export default nextConfig;

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -3,17 +3,13 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"@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-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.7",
@@ -41,21 +37,19 @@
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3",
"animejs": "^4.2.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"graphql": "^16.11.0",
"framer-motion": "^11.0.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.545.0",
"next": "15.5.5",
"next": "14.2.18",
"next-themes": "^0.4.6",
"payload": "^3.59.1",
"react": "19.1.0",
"react": "18.3.1",
"react-day-picker": "^9.11.1",
"react-dom": "19.1.0",
"react-dom": "18.3.1",
"react-hook-form": "^7.65.0",
"react-resizable-panels": "^3.0.6",
"recharts": "^2.15.4",
@@ -68,10 +62,10 @@
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"eslint": "^9",
"eslint-config-next": "15.5.5",
"eslint-config-next": "14.2.18",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"

6055
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,47 @@
"use client";
import { motion } from "framer-motion";
export default function Home() {
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">
sup
<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">
<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>
);
}

View File

@@ -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

View File

@@ -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

View File

@@ -1 +0,0 @@
export const importMap = {}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
}

View File

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

View File

@@ -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
],
}

View File

@@ -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 {}
}

View File

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

View File

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

View File

@@ -20,8 +20,7 @@
],
"baseUrl": ".",
"paths": {
"@/*": ["src/*", "./*"],
"@payload-config": ["src/payload.config.ts"]
"@/*": ["src/*", "./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],