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)
|
||||
- **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`: утилиты и вспомогательные функции.
|
||||
|
||||
## Архитектура: Feature‑Sliced 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
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",
|
||||
"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
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() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*", "./*"],
|
||||
"@payload-config": ["src/payload.config.ts"]
|
||||
"@/*": ["src/*", "./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
||||
Reference in New Issue
Block a user