New readme, screenshots and small fixes
72
README.md
@@ -1,36 +1,54 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
# .Torrent frontend
|
||||
> .Torrent - сервис обмена .torrent файлами видеоигр, фильмов и аудиокниг
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
## Стек
|
||||
- TypeScript
|
||||
- React 18
|
||||
- Next.js 14 (App Router)
|
||||
- Tailwind CSS
|
||||
- Zod
|
||||
- SWR
|
||||
- clsx
|
||||
- React Hook Form
|
||||
- и другие
|
||||
- next-themes
|
||||
- js-cookie
|
||||
- jwt-decode
|
||||
- react-dropzone
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
## Возможности
|
||||
- Главная страница со списком популярных видеоигр, фильмов, аудиокниг
|
||||
- Страницы со списками по категориям (отдельно видеоигры, фильмы, аудиокниги)
|
||||
- Страница просмотра, редактирования или добавления сущности
|
||||
- Форма входа или регистрации в виде модального окна с помощью Parallel и Intercepting маршрутов в Next.js
|
||||
- Адаптивная верстка. Корректное отображение на мобильных устройствах, планшетах, ноутбуках, десктопах
|
||||
- SEO оптимизация. SSR, метаданные к страницам
|
||||
- Валидация данных с помощью Zod. Некорректные (или неполные) данные вырезаются (если некорректна одна сущность из списка, то остальные отображаются)
|
||||
- Структура проекта в соответствии с Feature-Sliced Design
|
||||
- Цветовая схема Gruvbox. Возможность переключения тёмной и светлой темы
|
||||
- Вся конфигурация через файл .env (или переменные среды), для удобного запуска в Docker контейнере
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
## Скриншоты
|
||||
|||
|
||||
|-|-|
|
||||
|||
|
||||
|||
|
||||
|||
|
||||
|||
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
## Запуск
|
||||
### Локально
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
## ToDo
|
||||
- [ ] Dockerfile
|
||||
- [ ] Добавить ссылку на общий репозиторий
|
||||
- [ ] Теги жанров для сущностей
|
||||
- [ ] Поиск
|
||||
- [ ] Динамические метаданные к страницам [section]/*
|
||||
- [ ] Страница "О проекте"
|
||||
BIN
screenshots/game.png
Normal file
|
After Width: | Height: | Size: 656 KiB |
BIN
screenshots/game_create.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
screenshots/game_editing.png
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
screenshots/login.png
Normal file
|
After Width: | Height: | Size: 363 KiB |
BIN
screenshots/main.png
Normal file
|
After Width: | Height: | Size: 613 KiB |
BIN
screenshots/main_mobile.png
Normal file
|
After Width: | Height: | Size: 439 KiB |
BIN
screenshots/movie.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
BIN
screenshots/movie_mobile.png
Normal file
|
After Width: | Height: | Size: 547 KiB |
BIN
screenshots/movies.png
Normal file
|
After Width: | Height: | Size: 549 KiB |
BIN
screenshots/registration.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
5
src/app/registration/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Registration() {
|
||||
redirect("/");
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
TypesOfItems,
|
||||
UnionItemType,
|
||||
} from "./types";
|
||||
import { EraseCacheByTag } from "@/shared/utils/http";
|
||||
import { EraseCacheByTags } from "@/shared/utils/http";
|
||||
|
||||
export abstract class ItemService {
|
||||
private static get itemsConfiguration(): {
|
||||
@@ -122,13 +122,7 @@ export abstract class ItemService {
|
||||
const item = await this.itemsConfiguration[itemInfo.type].service.Add(
|
||||
itemInfo
|
||||
);
|
||||
|
||||
if (item)
|
||||
EraseCacheByTag(
|
||||
`/${this.itemsConfiguration[itemInfo.type].service.urlPrefix}/${
|
||||
item.id
|
||||
}`
|
||||
);
|
||||
this.UpdateCachedData(item);
|
||||
return item;
|
||||
}
|
||||
public static async ChangeItem(id: number, itemInfo: ItemCreateType) {
|
||||
@@ -136,12 +130,14 @@ export abstract class ItemService {
|
||||
id,
|
||||
itemInfo
|
||||
);
|
||||
if (item)
|
||||
EraseCacheByTag(
|
||||
`/${this.itemsConfiguration[itemInfo.type].service.urlPrefix}/${
|
||||
item.id
|
||||
}`
|
||||
);
|
||||
this.UpdateCachedData(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static UpdateCachedData(item: ItemType | null) {
|
||||
if (item) {
|
||||
const tagPrefix = this.itemsConfiguration[item.type].service.urlPrefix;
|
||||
EraseCacheByTags([`/${tagPrefix}/${item.id}`, `/${tagPrefix}/cards`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,24 @@ type RequestOptions = GetRequestOptions & {
|
||||
};
|
||||
|
||||
export abstract class HTTPService {
|
||||
private static deepUndefinedToNull(o?: object): object | undefined {
|
||||
if (o)
|
||||
return Object.fromEntries(
|
||||
Object.entries(o).map(([k, v]) => {
|
||||
if (v === undefined) return [k, null];
|
||||
if (typeof v === "object") return [k, this.deepUndefinedToNull(v)];
|
||||
return [k, v];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public static async request<Z extends z.ZodTypeAny>(
|
||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||
url: string,
|
||||
schema: Z,
|
||||
options?: RequestOptions
|
||||
) {
|
||||
console.log(options?.body);
|
||||
return await fetch(process.env.NEXT_PUBLIC_BASE_URL + url, {
|
||||
method: method,
|
||||
headers: {
|
||||
@@ -35,7 +47,9 @@ export abstract class HTTPService {
|
||||
body:
|
||||
(options?.stringify ?? true) != true
|
||||
? (options?.body as BodyInit)
|
||||
: JSON.stringify(options?.body),
|
||||
: JSON.stringify(
|
||||
this.deepUndefinedToNull(options?.body as object | undefined)
|
||||
),
|
||||
cache: options?.cache ?? options?.next ? undefined : "no-cache",
|
||||
next: options?.next ?? {},
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ItemFragment = ({
|
||||
className="relative w-full flex items-center justify-around pt-4"
|
||||
{...(editable ? getFragmentDropRootProps() : {})}
|
||||
>
|
||||
<div className="flex flex-col items-center w-[80%] h-20">
|
||||
<div className="flex flex-col items-center w-[80%] h-14 lp:h-20">
|
||||
<audio
|
||||
controls
|
||||
controlsList="nodownload"
|
||||
|
||||
@@ -84,6 +84,7 @@ export const ItemInfo = <T extends ItemType | ItemCreateType>({
|
||||
|
||||
const onSubmit = async (formData: ItemCreateType) => {
|
||||
changeSavedTimeout(null);
|
||||
console.log(formData);
|
||||
const updatedItem = ItemService.isExistingItem(item)
|
||||
? await ItemService.ChangeItem(item.id, formData)
|
||||
: await ItemService.AddItem(formData);
|
||||
|
||||