Loading repository data…
Loading repository data…
Brossend / repository
Тестовое задание для Upjet - frontend сервиса бронирования билетов в кино с временным резервированием мест.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Frontend для тестового задания Fullstack-разработчика. Приложение показывает будущие киносеансы, позволяет временно забронировать место, оплатить билет и автоматически освобождает место при отмене или истечении срока брони.
Backend-репозиторий реализован отдельно на Laravel и предоставляет REST API.
409 Conflict и Laravel validation errors 422npm install
Создайте файл .env.local в корне проекта:
NEXT_PUBLIC_API_URL=http://localhost:8080/api/v1
Шаблон также находится в .env.example.
npm run dev
Приложение будет доступно по адресу:
http://localhost:3000
npm run build
npm run start
npm run lint
Пользователь нажимает "Оформить билет"
↓
POST /screenings/{screeningId}/reservations
↓
Backend атомарно проверяет доступность места и создаёт временную бронь
↓
Frontend сохраняет id, токен и expiresAt только в React state
↓
Открывается модальное окно с формой и обратным отсчётом
↓
Пользователь оплачивает билет или закрывает модальное окно
↓
POST /reservations/{reservationId}/pay
или
DELETE /reservations/{reservationId}
↓
TanStack Query обновляет список сеансов
Если пользователь просто закроет вкладку, запрос отмены может не успеть уйти. Это допустимо: backend автоматически перестанет учитывать временную бронь после expiresAt.
Базовый URL задаётся через NEXT_PUBLIC_API_URL.
GET /screenings
Accept: application/json
Ответ:
[
{
"id": 1,
"title": "Интерстеллар",
"startsAt": "2026-06-26T14:00:00Z",
"totalSeats": 10,
"availableSeats": 7
}
]
availableSeats уже учитывает оплаченные и активные временные брони.
POST /screenings/{screeningId}/reservations
Accept: application/json
Ответ 201 Created:
{
"id": "56f96ec8-8bd9-41dc-9c34-a785dfdb9f6e",
"reservationToken": "secret-token",
"expiresAt": "2026-06-26T14:02:00Z"
}
Модальное окно открывается только после успешного ответа 201.
POST /reservations/{reservationId}/pay
Accept: application/json
Content-Type: application/json
X-Reservation-Token: {reservationToken}
{
"name": "Иван Иванов",
"email": "ivan@example.com"
}
После успешной оплаты бронь не отменяется через DELETE.
DELETE /reservations/{reservationId}
Accept: application/json
X-Reservation-Token: {reservationToken}
Отправляется при обычном закрытии модального окна до успешной оплаты.
Проект использует адаптированный Feature-Sliced Design.
src/
├── app/ # Next App Router, root layout, провайдеры, глобальные стили
├── views/ # композиция экранов
├── widgets/ # крупные блоки интерфейса
├── features/ # пользовательские сценарии
├── entities/ # доменные модели и UI сущностей
└── shared/ # общий UI-kit, HTTP-клиент, конфигурация
| Слой | Ответственность |
|---|---|
app | Next.js routing, глобальные стили, Query Provider |
views | Экран киносеансов и его композиция |
widgets | Список киносеансов и orchestration UI-состояния |
features | Создание, оплата и отмена временной брони |
entities | Модель киносеанса, типы брони, карточка киносеанса |
shared | Button, Input, Modal, HTTP-клиент, обработка ошибок, env-конфигурация |
Зависимости направлены сверху вниз:
app
↓
views
↓
widgets
↓
features
↓
entities
↓
shared
Нижние слои не импортируют верхние.
TanStack Query используется для данных backend API:
invalidateQueriesСписок сеансов обновляется:
React state хранит:
reservationToken хранится только в памяти браузера, пока открыта модалка. Он не передаётся через URL и не сохраняется в localStorage.
Backend возвращает даты в ISO 8601 и UTC. Frontend создаёт объект через new Date(startsAt) и форматирует его через Intl.DateTimeFormat, поэтому пользователь видит время в локальном часовом поясе браузера.
HTTP-клиент приводит ошибки Laravel к единому классу ApiError.
Поддерживаются:
422 - ошибки валидации полей формы403 - токен не соответствует брони404 - сеанс или бронь не найдены409 - мест нет, сеанс уже начался или срок брони истёкОшибки 422 для name и email выводятся под соответствующими полями формы.
src/shared/api/http-client.ts
Базовый HTTP-клиент и единый формат ошибок
src/entities/screening/model/use-screenings-query.ts
Query списка киносеансов
src/features/create-reservation/api
Запросы на создание, оплату и отмену брони
src/features/create-reservation/model
Mutation hooks, Zod-схема формы, таймер брони
src/widgets/screenings-list/ui/screenings-list.tsx
Координация полного booking-flow