From da2913398911609b83f2db3acc587358b8fdd2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD?= Date: Fri, 19 Dec 2025 14:22:38 +0300 Subject: [PATCH] fixes --- .gitlab-ci.yml | 22 - package-lock.json | 2 +- package.json | 2 +- src/api/types.ts | 127 ++--- src/api/useAuthentificate.ts | 46 ++ .../catalog/[id]/components/BookingWidget.tsx | 1 + .../catalog/[id]/components/ContactInfo.tsx | 1 + .../[id]/components/YachtCharacteristics.tsx | 2 + src/app/catalog/[id]/page.tsx | 1 + src/app/catalog/page.tsx | 1 + src/app/components/FeaturedYacht.tsx | 467 +++++++++--------- src/app/components/YachtGrid.tsx | 1 + src/app/confirm/page.tsx | 52 +- src/app/profile/reservations/page.tsx | 237 +++++---- src/app/profile/yachts/page.tsx | 101 ++-- src/stores/useAuthStore.ts | 39 +- 16 files changed, 631 insertions(+), 471 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 737a3fb..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -stages: - - build - - deploy - -workflow: - rules: - - if: '$CI_COMMIT_BRANCH == "main"' - -build: - stage: build - script: - - echo "Building Docker image..." - - docker build -t travelmarine-frontend:latest . - -deploy: - stage: deploy - needs: ["build"] - script: - - echo "Restarting container..." - - docker ps -a --filter 'name=^/travelmarine-frontend$' --format '{{.Names}}' | grep -q '^travelmarine-frontend$' && docker rm -f travelmarine-frontend || true - - docker run -d --name travelmarine-frontend --restart unless-stopped -p 127.0.0.1:3000:3000 travelmarine-frontend:latest - when: on_success diff --git a/package-lock.json b/package-lock.json index ba22092..035b791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "eslint-config-next": "15.5.5", "tailwindcss": "^4", "turbo": "^2.6.3", - "typescript": "^5" + "typescript": "5.9.3" } }, "node_modules/@alloc/quick-lru": { diff --git a/package.json b/package.json index 9c8a5b5..b8ee94c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,6 @@ "eslint-config-next": "15.5.5", "tailwindcss": "^4", "turbo": "^2.6.3", - "typescript": "^5" + "typescript": "5.9.3" } } diff --git a/src/api/types.ts b/src/api/types.ts index 34a55c3..258e1f2 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,77 +1,86 @@ -interface CatalogItemShortDto { - id?: number; - name: string; - length: number; - speed: number; - minCost: number; - mainImageUrl: string; - galleryUrls: string[]; - hasQuickRent: boolean; - isFeatured: boolean; - topText?: string; - isBestOffer?: boolean; +export interface CatalogItemShortDto { + id?: number; + name: string; + length: number; + speed: number; + minCost: number; + mainImageUrl: string; + galleryUrls: string[]; + hasQuickRent: boolean; + isFeatured: boolean; + topText?: string; + isBestOffer?: boolean; } -interface MainPageCatalogResponseDto { - featuredYacht: CatalogItemShortDto; - restYachts: CatalogItemShortDto[]; +export interface MainPageCatalogResponseDto { + featuredYacht: CatalogItemShortDto; + restYachts: CatalogItemShortDto[]; } -interface CatalogFilteredResponseDto { - items: CatalogItemShortDto[]; - total: number; +export interface CatalogFilteredResponseDto { + items: CatalogItemShortDto[]; + total: number; } interface Reservation { - id: number; - yachtId: number; - reservatorId: number; - startUtc: number; - endUtc: number; + id: number; + yachtId: number; + reservatorId: number; + startUtc: number; + endUtc: number; } interface Review { - id: number; - reviewerId: number; - yachtId: number; - starsCount: number; - description: string; + id: number; + reviewerId: number; + yachtId: number; + starsCount: number; + description: string; } -interface User { - userId?: number; - firstName?: string; - lastName?: string; - phone?: string; - email?: string; - password?: string; - yachts?: Yacht[]; - companyName?: string; - inn?: number; - ogrn?: number; +export interface User { + userId?: number; + firstName?: string; + lastName?: string; + phone?: string; + email?: string; + password?: string; + yachts?: Yacht[]; + companyName?: string; + inn?: number; + ogrn?: number; } -interface CatalogItemLongDto extends CatalogItemShortDto { - year: number; - comfortCapacity: number; - maxCapacity: number; - width: number; - cabinsCount: number; - matherial: string; - power: number; - description: string; - owner: User; - reviews: Review[]; - reservations: Reservation[]; +export interface CatalogItemLongDto extends CatalogItemShortDto { + year: number; + comfortCapacity: number; + maxCapacity: number; + width: number; + cabinsCount: number; + matherial: string; + power: number; + description: string; + owner: User; + reviews: Review[]; + reservations: Reservation[]; } interface Yacht { - yachtId: number; - name: string; - model: string; - year: number; - length: number; - userId: number; - createdAt: Date; - updatedAt: Date; + yachtId: number; + name: string; + model: string; + year: number; + length: number; + userId: number; + createdAt: Date; + updatedAt: Date; +} + +export interface ReservationDto { + yachtId: number; + reservatorId: number; + startUtc: number; + endUtc: number; + id: number; + yacht: CatalogItemLongDto; } diff --git a/src/api/useAuthentificate.ts b/src/api/useAuthentificate.ts index 49041a9..570c7b3 100644 --- a/src/api/useAuthentificate.ts +++ b/src/api/useAuthentificate.ts @@ -8,9 +8,40 @@ interface AuthDataType { rememberMe: boolean; } +interface JWTPayload { + user_id?: number; + userId?: number; + sub?: string; + id?: number; + [key: string]: unknown; +} + +const parseJWT = (token: string): JWTPayload | null => { + try { + const base64Url = token.split(".")[1]; + if (!base64Url) { + return null; + } + + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const jsonPayload = decodeURIComponent( + atob(base64) + .split("") + .map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)) + .join("") + ); + + return JSON.parse(jsonPayload); + } catch (error) { + console.error("Ошибка при парсинге JWT токена:", error); + return null; + } +}; + const useAuthentificate = () => { const client = useApiClient(); const setToken = useAuthStore((state) => state.setToken); + const setUserId = useAuthStore((state) => state.setUserId); return useMutation({ mutationKey: ["auth"], @@ -25,6 +56,21 @@ const useAuthentificate = () => { setToken(access_token, authData.rememberMe); + // Парсим JWT токен и извлекаем userId + const payload = parseJWT(access_token); + if (payload) { + const userId = + payload.user_id || + payload.userId || + payload.sub || + payload.id || + null; + + if (userId) { + setUserId(userId, authData.rememberMe); + } + } + return access_token; }, onError: () => { diff --git a/src/app/catalog/[id]/components/BookingWidget.tsx b/src/app/catalog/[id]/components/BookingWidget.tsx index af4a12c..c9e1254 100644 --- a/src/app/catalog/[id]/components/BookingWidget.tsx +++ b/src/app/catalog/[id]/components/BookingWidget.tsx @@ -7,6 +7,7 @@ import { DatePicker } from "@/components/ui/date-picker"; import { GuestPicker } from "@/components/form/guest-picker"; import { format } from "date-fns"; import { calculateTotalPrice, formatPrice } from "@/lib/utils"; +import { CatalogItemLongDto } from "@/api/types"; interface BookingWidgetProps { price: string; diff --git a/src/app/catalog/[id]/components/ContactInfo.tsx b/src/app/catalog/[id]/components/ContactInfo.tsx index b4d6915..7c7106f 100644 --- a/src/app/catalog/[id]/components/ContactInfo.tsx +++ b/src/app/catalog/[id]/components/ContactInfo.tsx @@ -1,6 +1,7 @@ "use client"; import Image from "next/image"; +import { User } from "@/api/types"; export function ContactInfo({ firstName, companyName, inn, ogrn }: User) { return ( diff --git a/src/app/catalog/[id]/components/YachtCharacteristics.tsx b/src/app/catalog/[id]/components/YachtCharacteristics.tsx index a82b57f..92aba9f 100644 --- a/src/app/catalog/[id]/components/YachtCharacteristics.tsx +++ b/src/app/catalog/[id]/components/YachtCharacteristics.tsx @@ -1,5 +1,7 @@ "use client"; +import { CatalogItemLongDto } from "@/api/types"; + interface YachtCharacteristicsProps { yacht: CatalogItemLongDto; } diff --git a/src/app/catalog/[id]/page.tsx b/src/app/catalog/[id]/page.tsx index 078a942..ecba56a 100644 --- a/src/app/catalog/[id]/page.tsx +++ b/src/app/catalog/[id]/page.tsx @@ -14,6 +14,7 @@ import { GuestPicker } from "@/components/form/guest-picker"; import useApiClient from "@/hooks/useApiClient"; import { formatSpeed } from "@/lib/utils"; import { format } from "date-fns"; +import { CatalogItemLongDto } from "@/api/types"; export default function YachtDetailPage() { const { id } = useParams(); diff --git a/src/app/catalog/page.tsx b/src/app/catalog/page.tsx index 8bb893c..8f76854 100644 --- a/src/app/catalog/page.tsx +++ b/src/app/catalog/page.tsx @@ -18,6 +18,7 @@ import { Button } from "@/components/ui/button"; import useApiClient from "@/hooks/useApiClient"; import { formatMinCost, formatSpeed, formatWidth, getImageUrl } from "@/lib/utils"; import { useSearchParams, useRouter, usePathname } from "next/navigation"; +import { CatalogFilteredResponseDto } from "@/api/types"; export interface CatalogFilters { search: string; diff --git a/src/app/components/FeaturedYacht.tsx b/src/app/components/FeaturedYacht.tsx index 5edac8f..aedca28 100644 --- a/src/app/components/FeaturedYacht.tsx +++ b/src/app/components/FeaturedYacht.tsx @@ -3,247 +3,270 @@ import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { - Carousel, - CarouselContent, - CarouselItem, - CarouselNext, - CarouselPrevious, + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, } from "@/components/ui/carousel"; import Image from "next/image"; import Icon from "@/components/ui/icon"; import { useState } from "react"; import { useRouter } from "next/navigation"; -import { GuestDatePicker, GuestDatePickerValue } from "@/components/form/guest-date-picker"; -import { formatMinCost, formatWidth, getImageUrl, calculateTotalPrice, formatPrice } from "@/lib/utils"; +import { + GuestDatePicker, + GuestDatePickerValue, +} from "@/components/form/guest-date-picker"; +import { + formatMinCost, + formatWidth, + getImageUrl, + calculateTotalPrice, + formatPrice, +} from "@/lib/utils"; import { format } from "date-fns"; +import { CatalogItemShortDto } from "@/api/types"; export default function FeaturedYacht({ - yacht, + yacht, }: { - yacht: CatalogItemShortDto; + yacht: CatalogItemShortDto; }) { - const router = useRouter(); - const [selectedImage, setSelectedImage] = useState(yacht.mainImageUrl); - const [bookingData, setBookingData] = useState({ - date: undefined, - departureTime: "12:00", - arrivalTime: "13:00", - adults: 1, - children: 0, - }); - - const handleThumbnailClick = (imageSrc: string) => { - setSelectedImage(imageSrc); - }; - - // Расчет итоговой стоимости - const getTotalPrice = () => { - if (!bookingData.date || !bookingData.departureTime || !bookingData.arrivalTime) { - return 0; - } - - // Форматируем дату в ISO строку для calculateTotalPrice - const dateString = format(bookingData.date, "yyyy-MM-dd"); - - const { totalPrice } = calculateTotalPrice( - dateString, - bookingData.departureTime, - dateString, // Используем ту же дату для arrival - bookingData.arrivalTime, - yacht.minCost - ); - - return totalPrice; - }; - - // Обработчик нажатия на кнопку "Забронировать" - const handleBookClick = () => { - if (!bookingData.date || !yacht.id) { - return; - } - - // Форматируем дату в формат yyyy-MM-dd - const dateString = format(bookingData.date, "yyyy-MM-dd"); - - // Кодируем время для URL (00:00 -> 00%3A00) - const encodedDepartureTime = encodeURIComponent(bookingData.departureTime); - const encodedArrivalTime = encodeURIComponent(bookingData.arrivalTime); - - // Вычисляем общее количество гостей - const totalGuests = bookingData.adults + bookingData.children; - - // Формируем URL с параметрами - const params = new URLSearchParams({ - yachtId: yacht.id.toString(), - departureDate: dateString, - departureTime: encodedDepartureTime, - arrivalDate: dateString, // Используем ту же дату для arrival - arrivalTime: encodedArrivalTime, - guests: totalGuests.toString(), + const router = useRouter(); + const [selectedImage, setSelectedImage] = useState(yacht.mainImageUrl); + const [bookingData, setBookingData] = useState({ + date: undefined, + departureTime: "12:00", + arrivalTime: "13:00", + adults: 1, + children: 0, }); - - // Переходим на страницу подтверждения - router.push(`/confirm?${params.toString()}`); - }; - return ( -
- - -
- {/* Left side - Yacht details and images */} -
- {/* Promoted banner - Mobile only */} -
- - Заметнее других — бронируют быстрее - -
+ const handleThumbnailClick = (imageSrc: string) => { + setSelectedImage(imageSrc); + }; - {/* Header with yacht name and length */} -
-

{yacht.name}

-
- - {formatWidth(yacht.length)} -
-
+ // Расчет итоговой стоимости + const getTotalPrice = () => { + if ( + !bookingData.date || + !bookingData.departureTime || + !bookingData.arrivalTime + ) { + return 0; + } - {/* Main yacht image */} -
- {yacht.name} -
+ // Форматируем дату в ISO строку для calculateTotalPrice + const dateString = format(bookingData.date, "yyyy-MM-dd"); - {/* Thumbnail images carousel */} -
- - - {yacht.galleryUrls.map((thumb, idx) => ( - -
- {`${yacht.name} handleThumbnailClick(thumb)} - unoptimized - /> + const { totalPrice } = calculateTotalPrice( + dateString, + bookingData.departureTime, + dateString, // Используем ту же дату для arrival + bookingData.arrivalTime, + yacht.minCost + ); + + return totalPrice; + }; + + // Обработчик нажатия на кнопку "Забронировать" + const handleBookClick = () => { + if (!bookingData.date || !yacht.id) { + return; + } + + // Формируем URL с параметрами + const params = new URLSearchParams({ + yachtId: yacht.id.toString(), + departureDate: format(bookingData.date, "yyyy-MM-dd"), + departureTime: bookingData.departureTime, + arrivalDate: format(bookingData.date, "yyyy-MM-dd"), + arrivalTime: bookingData.arrivalTime, + guests: (bookingData.adults + bookingData.children).toString(), + }); + + // Переходим на страницу подтверждения + router.push(`/confirm?${params.toString()}`); + }; + + return ( +
+ + +
+ {/* Left side - Yacht details and images */} +
+ {/* Promoted banner - Mobile only */} +
+ + Заметнее других — бронируют быстрее + +
+ + {/* Header with yacht name and length */} +
+

+ {yacht.name} +

+
+ + + {formatWidth(yacht.length)} + +
+
+ + {/* Main yacht image */} +
+ {yacht.name} +
+ + {/* Thumbnail images carousel */} +
+ + + {yacht.galleryUrls.map((thumb, idx) => ( + +
+ {`${ + handleThumbnailClick( + thumb + ) + } + unoptimized + /> +
+
+ ))} +
+ + +
+
+ + {/* Promoted badge */} + {yacht.isFeatured && ( +
+ + + Это объявление продвигается.{" "} + + Хотите так же? + + +
+ )}
- - ))} - - - - -
- {/* Promoted badge */} - {yacht.isFeatured && ( -
- - - Это объявление продвигается.{" "} - - Хотите так же? - - -
- )} -
+ {/* Right side - Booking form */} +
+
+ {/* Promoted banner - Desktop only */} +
+ + Заметнее других — бронируют быстрее + +
- {/* Right side - Booking form */} -
-
- {/* Promoted banner - Desktop only */} -
- - Заметнее других — бронируют быстрее - -
+
+ {/* Price */} +
+

+ {formatMinCost(yacht.minCost)} + + / час + +

+
-
- {/* Price */} -
-

- {formatMinCost(yacht.minCost)} - - / час - -

-
+ {/* Booking form */} +
+ +
- {/* Booking form */} -
- -
+ {/* Book button */} + - {/* Book button */} - - - {/* Total price */} -
- Итого: - {formatPrice(getTotalPrice())} ₽ -
-
-
-
-
- - -
- ); + {/* Total price */} +
+ + Итого: + + + {formatPrice(getTotalPrice())} ₽ + +
+
+
+
+
+ + +
+ ); } diff --git a/src/app/components/YachtGrid.tsx b/src/app/components/YachtGrid.tsx index ebe893c..c5453c1 100644 --- a/src/app/components/YachtGrid.tsx +++ b/src/app/components/YachtGrid.tsx @@ -14,6 +14,7 @@ import { formatWidth, getImageUrl, } from "@/lib/utils"; +import { CatalogItemShortDto, MainPageCatalogResponseDto } from "@/api/types"; export default function YachtGrid() { const client = useApiClient(); diff --git a/src/app/confirm/page.tsx b/src/app/confirm/page.tsx index e048e8d..c71398c 100644 --- a/src/app/confirm/page.tsx +++ b/src/app/confirm/page.tsx @@ -3,12 +3,14 @@ import { Button } from "@/components/ui/button"; import Image from "next/image"; import Link from "next/link"; +import { useMutation } from "@tanstack/react-query"; import { User, ArrowUpRight, Map, ArrowLeft, Heart } from "lucide-react"; import { useEffect, useState, Suspense } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import useApiClient from "@/hooks/useApiClient"; import { getImageUrl, formatPrice, calculateTotalPrice } from "@/lib/utils"; import { parseISO } from "date-fns"; +import { CatalogItemLongDto } from "@/api/types"; function ConfirmPageContent() { const [yacht, setYacht] = useState(null); @@ -154,6 +156,40 @@ function ConfirmPageContent() { : `${guestCount} гостей` : "Не выбрано"; + const { mutate } = useMutation({ + mutationKey: ["create-reservation", yachtId], + mutationFn: async () => { + if ( + !departureDate || + !departureTime || + !yachtId || + !arrivalDate || + !arrivalTime + ) { + throw new Error("Ошибка получения данных бронирования"); + } + + const departureDateTime = new Date( + `${departureDate}T${departureTime}` + ); + const arrivalDateTime = new Date(`${arrivalDate}T${arrivalTime}`); + + const startUtc = Math.floor(departureDateTime.getTime() / 1000); + const endUtc = Math.floor(arrivalDateTime.getTime() / 1000); + + const body = { + startUtc, + endUtc, + yachtId: Number(yachtId), + reservatorId: Number("userId"), // TODO + }; + + await client.post("/reservations", body); + + router.push("/profile/reservations"); + }, + }); + if (!yacht) { return
; } @@ -304,7 +340,11 @@ function ConfirmPageContent() { Скидка (DISCOUNT50): - -{formatPrice(totalPrice * 0.5)} Р + - + {formatPrice( + totalPrice * 0.5 + )}{" "} + Р
)} @@ -358,7 +398,7 @@ function ConfirmPageContent() { variant="default" className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200" disabled={totalHours === 0} - onClick={() => router.push("/profile/reservations")} + onClick={() => mutate()} > Отправить заявку @@ -454,10 +494,12 @@ function ConfirmPageContent() { {isPromocodeApplied && (
- Скидка (DISCOUNT50): + Скидка + (DISCOUNT50): - -{formatPrice( + - + {formatPrice( totalPrice * 0.5 )}{" "} @@ -591,7 +633,7 @@ function ConfirmPageContent() { size="lg" className="flex-shrink-0 h-[64px] w-[270px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full p-0 transition-colors duration-200 hover:shadow-lg" disabled={totalHours === 0} - onClick={() => router.push("/profile/reservations")} + onClick={() => mutate()} > Отправить заявку diff --git a/src/app/profile/reservations/page.tsx b/src/app/profile/reservations/page.tsx index 78a3d4f..fe24500 100644 --- a/src/app/profile/reservations/page.tsx +++ b/src/app/profile/reservations/page.tsx @@ -1,76 +1,48 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Link from "next/link"; import Image from "next/image"; import ProfileSidebar from "@/app/profile/components/ProfileSidebar"; import { User, Clock, MoveHorizontal, Users } from "lucide-react"; - -interface Reservation { - id: string; - yachtName: string; - yachtImage: string; - ownerName: string; - ownerAvatar?: string; - length: number; - capacity: number; - departureDate: string; - departureTime: string; - arrivalDate: string; - arrivalTime: string; - guests: number; - paymentType: string; - totalPrice: number; - paymentStatus: "pending" | "paid" | "confirmed"; -} - -// Моковые данные для демонстрации -const mockReservations: Record = { - new: [ - { - id: "1", - yachtName: "KALLISTE", - yachtImage: "/images/yachts/yacht1.jpg", - ownerName: "Денис", - length: 14, - capacity: 10, - departureDate: "9 Авг, 2025", - departureTime: "00:00", - arrivalDate: "9 Авг, 2025", - arrivalTime: "02:00", - guests: 1, - paymentType: "Полная оплата", - totalPrice: 52800, - paymentStatus: "pending", - }, - { - id: "2", - yachtName: "Señorita", - yachtImage: "/images/yachts/yacht2.jpg", - ownerName: "Денис", - length: 14, - capacity: 10, - departureDate: "17 Авг, 2025", - departureTime: "00:00", - arrivalDate: "17 Авг, 2025", - arrivalTime: "03:00", - guests: 1, - paymentType: "Полная оплата", - totalPrice: 75240, - paymentStatus: "pending", - }, - ], - active: [], - confirmed: [], - archive: [], -}; +import useApiClient from "@/hooks/useApiClient"; +import useAuthStore from "@/stores/useAuthStore"; +import { ReservationDto } from "@/api/types"; +import { formatWidth } from "@/lib/utils"; export default function ReservationsPage() { - const [activeTab, setActiveTab] = useState<"new" | "active" | "confirmed" | "archive">("new"); - const reservations = mockReservations[activeTab]; + const [activeTab, setActiveTab] = useState< + "new" | "active" | "confirmed" | "archive" + >("new"); + const [reservationsData, setReservationsData] = useState( + [] + ); + const apiClient = useApiClient(); + const { getUserId } = useAuthStore(); + const userId = getUserId(); - const formatPrice = (price: number): string => { - return new Intl.NumberFormat("ru-RU").format(price) + " Р"; + useEffect(() => { + if (userId) { + apiClient + .get(`/reservations/user/${userId}`) + .then((response) => { + setReservationsData(response.data); + }) + .catch((error) => { + console.error("Ошибка при загрузке бронирований:", error); + }); + } + }, [userId]); + + // @TODO: Залупа с годом, надо скачать dayjs + const formatUtcDate = (timestamp: number): string => { + const date = new Date(timestamp); + const day = String(date.getUTCDate()).padStart(2, "0"); + const month = String(date.getUTCMonth() + 1).padStart(2, "0"); + const year = date.getUTCFullYear(); + const hours = String(date.getUTCHours()).padStart(2, "0"); + const minutes = String(date.getUTCMinutes()).padStart(2, "0"); + return `${day}.${month}.${year} - ${hours}:${minutes}`; }; return ( @@ -103,37 +75,41 @@ export default function ReservationsPage() {
@@ -141,23 +117,34 @@ export default function ReservationsPage() { {/* Reservations List */}
- {reservations.length === 0 ? ( + {reservationsData.length === 0 ? (
Нет бронирований в этой категории
) : ( - reservations.map((reservation, index) => ( + reservationsData.map((reservation, index) => (
{/* Image Section */}
{reservation.yachtName} @@ -173,7 +160,12 @@ export default function ReservationsPage() { Владелец - {reservation.ownerName} + { + reservation + .yacht + .owner + .firstName + }
@@ -187,7 +179,11 @@ export default function ReservationsPage() { className="text-white" /> - {reservation.length} метров + {formatWidth( + reservation + .yacht + .length + )}
@@ -198,7 +194,13 @@ export default function ReservationsPage() { size={16} className="text-white" /> - {reservation.capacity} + + { + reservation + .yacht + .maxCapacity + } +
@@ -214,8 +216,9 @@ export default function ReservationsPage() { Выход:
- {reservation.departureDate} -{" "} - {reservation.departureTime} + {formatUtcDate( + reservation.startUtc + )}
@@ -223,8 +226,9 @@ export default function ReservationsPage() { Заход:
- {reservation.arrivalDate} -{" "} - {reservation.arrivalTime} + {formatUtcDate( + reservation.endUtc + )}
@@ -232,24 +236,38 @@ export default function ReservationsPage() { Гости:
- {reservation.guests} + {/* @TODO: Добавить количество гостей */} + {/* { + reservation.guests + } */} + -
- Тип оплаты: + Тип + оплаты:
- {reservation.paymentType} + {/* @TODO: Добавить тип оплаты */} + {/* { + reservation.paymentType + } */} + -
- По местному времени яхты + По + местному + времени + яхты
@@ -257,17 +275,26 @@ export default function ReservationsPage() {
- Итого:{" "} + {/* @TODO: Добавить итоговую стоимость */} + {/* Итого:{" "} {formatPrice( reservation.totalPrice - )}{" "}{reservation.paymentStatus === + )}{" "} + {reservation.paymentStatus === "pending" && ( - - (в ожидании оплаты) - - )} + + (в + ожидании + оплаты) + + )} */} + Итого: 78000{" "} + + (в + ожидании + оплаты) + -
diff --git a/src/app/profile/yachts/page.tsx b/src/app/profile/yachts/page.tsx index 54c5e35..4838bd8 100644 --- a/src/app/profile/yachts/page.tsx +++ b/src/app/profile/yachts/page.tsx @@ -1,44 +1,34 @@ +"use client"; + import Link from "next/link"; import Image from "next/image"; import ProfileSidebar from "@/app/profile/components/ProfileSidebar"; import { MoveHorizontal, Users } from "lucide-react"; import { getImageUrl, formatMinCost } from "@/lib/utils"; import { Button } from "@/components/ui/button"; - -interface Yacht { - id: string; - name: string; - image: string; - length: number; - capacity: number; - minCost: number; - status: "active" | "moderation" | "archive"; -} - -// Моковые данные для демонстрации -const mockYachts: Yacht[] = [ - { - id: "1", - name: "KALLISTE", - image: "/images/yachts/yacht1.jpg", - length: 14, - capacity: 10, - minCost: 26400, - status: "active", - }, - { - id: "2", - name: "Señorita", - image: "/images/yachts/yacht2.jpg", - length: 14, - capacity: 10, - minCost: 37620, - status: "active", - }, -]; +import useAuthStore from "@/stores/useAuthStore"; +import { useEffect, useState } from "react"; +import useApiClient from "@/hooks/useApiClient"; +import { CatalogItemShortDto } from "@/api/types"; export default function YachtsPage() { - const yachts = mockYachts; + const [yachts, setYachts] = useState([]); + const apiClient = useApiClient(); + const { getUserId } = useAuthStore(); + const userId = getUserId(); + + useEffect(() => { + if (userId) { + apiClient + .get(`/catalog/user/${userId}`) + .then((response) => { + setYachts(response.data); + }) + .catch((error) => { + console.error("Ошибка при загрузке яхт:", error); + }); + } + }, [userId]); return (
@@ -68,7 +58,9 @@ export default function YachtsPage() {
{/* Header with Add Button */}
-

Мои яхты

+

+ Мои яхты +

@@ -162,10 +152,7 @@ export default function YachtsPage() { Вместимость:
- { - yacht.capacity - }{" "} - человек + -
@@ -183,19 +170,23 @@ export default function YachtsPage() {
- - Посмотреть - объявление - - - Редактировать - + {yacht.id && ( + <> + + Посмотреть + объявление + + + Редактировать + + + )}
diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts index 8bb666d..4f33580 100644 --- a/src/stores/useAuthStore.ts +++ b/src/stores/useAuthStore.ts @@ -4,10 +4,14 @@ interface AuthStore { setToken: (token: string, rememberMe?: boolean) => void; getToken: () => string | null; clearToken: () => void; + setUserId: (userId: string | number, rememberMe?: boolean) => void; + getUserId: () => string | null; + clearUserId: () => void; } -const useAuthStore = create((set) => ({ +const useAuthStore = create(() => ({ setToken: (token: string, rememberMe: boolean = false) => { + if (typeof window === "undefined") return; if (rememberMe) { localStorage.setItem("token", token); } else { @@ -16,6 +20,7 @@ const useAuthStore = create((set) => ({ }, getToken: (): string | null => { + if (typeof window === "undefined") return null; const sessionToken = sessionStorage.getItem("token"); if (sessionToken) { return sessionToken; @@ -30,9 +35,41 @@ const useAuthStore = create((set) => ({ }, clearToken: () => { + if (typeof window === "undefined") return; sessionStorage.removeItem("token"); localStorage.removeItem("token"); }, + + setUserId: (userId: string | number, rememberMe: boolean = false) => { + if (typeof window === "undefined") return; + const userIdString = String(userId); + if (rememberMe) { + localStorage.setItem("userId", userIdString); + } else { + sessionStorage.setItem("userId", userIdString); + } + }, + + getUserId: (): string | null => { + if (typeof window === "undefined") return null; + const sessionUserId = sessionStorage.getItem("userId"); + if (sessionUserId) { + return sessionUserId; + } + + const localUserId = localStorage.getItem("userId"); + if (localUserId) { + return localUserId; + } + + return null; + }, + + clearUserId: () => { + if (typeof window === "undefined") return; + sessionStorage.removeItem("userId"); + localStorage.removeItem("userId"); + }, })); export default useAuthStore;