fixes
This commit is contained in:
parent
089f5064a3
commit
da29133989
|
|
@ -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
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"eslint-config-next": "15.5.5",
|
"eslint-config-next": "15.5.5",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"turbo": "^2.6.3",
|
"turbo": "^2.6.3",
|
||||||
"typescript": "^5"
|
"typescript": "5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,6 @@
|
||||||
"eslint-config-next": "15.5.5",
|
"eslint-config-next": "15.5.5",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"turbo": "^2.6.3",
|
"turbo": "^2.6.3",
|
||||||
"typescript": "^5"
|
"typescript": "5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
interface CatalogItemShortDto {
|
export interface CatalogItemShortDto {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
length: number;
|
length: number;
|
||||||
|
|
@ -12,12 +12,12 @@ interface CatalogItemShortDto {
|
||||||
isBestOffer?: boolean;
|
isBestOffer?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainPageCatalogResponseDto {
|
export interface MainPageCatalogResponseDto {
|
||||||
featuredYacht: CatalogItemShortDto;
|
featuredYacht: CatalogItemShortDto;
|
||||||
restYachts: CatalogItemShortDto[];
|
restYachts: CatalogItemShortDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CatalogFilteredResponseDto {
|
export interface CatalogFilteredResponseDto {
|
||||||
items: CatalogItemShortDto[];
|
items: CatalogItemShortDto[];
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ interface Review {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
export interface User {
|
||||||
userId?: number;
|
userId?: number;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
|
|
@ -51,7 +51,7 @@ interface User {
|
||||||
ogrn?: number;
|
ogrn?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CatalogItemLongDto extends CatalogItemShortDto {
|
export interface CatalogItemLongDto extends CatalogItemShortDto {
|
||||||
year: number;
|
year: number;
|
||||||
comfortCapacity: number;
|
comfortCapacity: number;
|
||||||
maxCapacity: number;
|
maxCapacity: number;
|
||||||
|
|
@ -75,3 +75,12 @@ interface Yacht {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReservationDto {
|
||||||
|
yachtId: number;
|
||||||
|
reservatorId: number;
|
||||||
|
startUtc: number;
|
||||||
|
endUtc: number;
|
||||||
|
id: number;
|
||||||
|
yacht: CatalogItemLongDto;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,40 @@ interface AuthDataType {
|
||||||
rememberMe: boolean;
|
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 useAuthentificate = () => {
|
||||||
const client = useApiClient();
|
const client = useApiClient();
|
||||||
const setToken = useAuthStore((state) => state.setToken);
|
const setToken = useAuthStore((state) => state.setToken);
|
||||||
|
const setUserId = useAuthStore((state) => state.setUserId);
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ["auth"],
|
mutationKey: ["auth"],
|
||||||
|
|
@ -25,6 +56,21 @@ const useAuthentificate = () => {
|
||||||
|
|
||||||
setToken(access_token, authData.rememberMe);
|
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;
|
return access_token;
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { DatePicker } from "@/components/ui/date-picker";
|
||||||
import { GuestPicker } from "@/components/form/guest-picker";
|
import { GuestPicker } from "@/components/form/guest-picker";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { calculateTotalPrice, formatPrice } from "@/lib/utils";
|
import { calculateTotalPrice, formatPrice } from "@/lib/utils";
|
||||||
|
import { CatalogItemLongDto } from "@/api/types";
|
||||||
|
|
||||||
interface BookingWidgetProps {
|
interface BookingWidgetProps {
|
||||||
price: string;
|
price: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { User } from "@/api/types";
|
||||||
|
|
||||||
export function ContactInfo({ firstName, companyName, inn, ogrn }: User) {
|
export function ContactInfo({ firstName, companyName, inn, ogrn }: User) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { CatalogItemLongDto } from "@/api/types";
|
||||||
|
|
||||||
interface YachtCharacteristicsProps {
|
interface YachtCharacteristicsProps {
|
||||||
yacht: CatalogItemLongDto;
|
yacht: CatalogItemLongDto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { GuestPicker } from "@/components/form/guest-picker";
|
||||||
import useApiClient from "@/hooks/useApiClient";
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
import { formatSpeed } from "@/lib/utils";
|
import { formatSpeed } from "@/lib/utils";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { CatalogItemLongDto } from "@/api/types";
|
||||||
|
|
||||||
export default function YachtDetailPage() {
|
export default function YachtDetailPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import useApiClient from "@/hooks/useApiClient";
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
import { formatMinCost, formatSpeed, formatWidth, getImageUrl } from "@/lib/utils";
|
import { formatMinCost, formatSpeed, formatWidth, getImageUrl } from "@/lib/utils";
|
||||||
import { useSearchParams, useRouter, usePathname } from "next/navigation";
|
import { useSearchParams, useRouter, usePathname } from "next/navigation";
|
||||||
|
import { CatalogFilteredResponseDto } from "@/api/types";
|
||||||
|
|
||||||
export interface CatalogFilters {
|
export interface CatalogFilters {
|
||||||
search: string;
|
search: string;
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,19 @@ import Image from "next/image";
|
||||||
import Icon from "@/components/ui/icon";
|
import Icon from "@/components/ui/icon";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { GuestDatePicker, GuestDatePickerValue } from "@/components/form/guest-date-picker";
|
import {
|
||||||
import { formatMinCost, formatWidth, getImageUrl, calculateTotalPrice, formatPrice } from "@/lib/utils";
|
GuestDatePicker,
|
||||||
|
GuestDatePickerValue,
|
||||||
|
} from "@/components/form/guest-date-picker";
|
||||||
|
import {
|
||||||
|
formatMinCost,
|
||||||
|
formatWidth,
|
||||||
|
getImageUrl,
|
||||||
|
calculateTotalPrice,
|
||||||
|
formatPrice,
|
||||||
|
} from "@/lib/utils";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { CatalogItemShortDto } from "@/api/types";
|
||||||
|
|
||||||
export default function FeaturedYacht({
|
export default function FeaturedYacht({
|
||||||
yacht,
|
yacht,
|
||||||
|
|
@ -38,7 +48,11 @@ export default function FeaturedYacht({
|
||||||
|
|
||||||
// Расчет итоговой стоимости
|
// Расчет итоговой стоимости
|
||||||
const getTotalPrice = () => {
|
const getTotalPrice = () => {
|
||||||
if (!bookingData.date || !bookingData.departureTime || !bookingData.arrivalTime) {
|
if (
|
||||||
|
!bookingData.date ||
|
||||||
|
!bookingData.departureTime ||
|
||||||
|
!bookingData.arrivalTime
|
||||||
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,24 +76,14 @@ export default function FeaturedYacht({
|
||||||
return;
|
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 с параметрами
|
// Формируем URL с параметрами
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
yachtId: yacht.id.toString(),
|
yachtId: yacht.id.toString(),
|
||||||
departureDate: dateString,
|
departureDate: format(bookingData.date, "yyyy-MM-dd"),
|
||||||
departureTime: encodedDepartureTime,
|
departureTime: bookingData.departureTime,
|
||||||
arrivalDate: dateString, // Используем ту же дату для arrival
|
arrivalDate: format(bookingData.date, "yyyy-MM-dd"),
|
||||||
arrivalTime: encodedArrivalTime,
|
arrivalTime: bookingData.arrivalTime,
|
||||||
guests: totalGuests.toString(),
|
guests: (bookingData.adults + bookingData.children).toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Переходим на страницу подтверждения
|
// Переходим на страницу подтверждения
|
||||||
|
|
@ -97,7 +101,8 @@ export default function FeaturedYacht({
|
||||||
<div
|
<div
|
||||||
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative lg:hidden"
|
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative lg:hidden"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: "url(/images/badge-bg.jpg)",
|
backgroundImage:
|
||||||
|
"url(/images/badge-bg.jpg)",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
|
|
@ -110,10 +115,14 @@ export default function FeaturedYacht({
|
||||||
|
|
||||||
{/* Header with yacht name and length */}
|
{/* Header with yacht name and length */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-3xl font-bold">{yacht.name}</h2>
|
<h2 className="text-3xl font-bold">
|
||||||
|
{yacht.name}
|
||||||
|
</h2>
|
||||||
<div className="flex items-center gap-2 text-gray-600">
|
<div className="flex items-center gap-2 text-gray-600">
|
||||||
<Icon size={16} name="width" />
|
<Icon size={16} name="width" />
|
||||||
<span className="text-lg">{formatWidth(yacht.length)}</span>
|
<span className="text-lg">
|
||||||
|
{formatWidth(yacht.length)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -148,15 +157,22 @@ export default function FeaturedYacht({
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
src={getImageUrl(thumb)}
|
src={getImageUrl(thumb)}
|
||||||
alt={`${yacht.name} view ${idx + 1}`}
|
alt={`${
|
||||||
|
yacht.name
|
||||||
|
} view ${idx + 1}`}
|
||||||
width={80}
|
width={80}
|
||||||
height={60}
|
height={60}
|
||||||
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${
|
className={`w-20 h-16 object-cover rounded-[8px] cursor-pointer border-2 transition-all ${
|
||||||
selectedImage === thumb
|
selectedImage ===
|
||||||
|
thumb
|
||||||
? "border-[#008299]"
|
? "border-[#008299]"
|
||||||
: "border-gray-200 hover:border-gray-400"
|
: "border-gray-200 hover:border-gray-400"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleThumbnailClick(thumb)}
|
onClick={() =>
|
||||||
|
handleThumbnailClick(
|
||||||
|
thumb
|
||||||
|
)
|
||||||
|
}
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -193,7 +209,8 @@ export default function FeaturedYacht({
|
||||||
<div
|
<div
|
||||||
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative hidden lg:flex"
|
className="text-white flex items-center justify-center py-2 rounded-full text-center mb-6 relative hidden lg:flex"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: "url(/images/badge-bg.jpg)",
|
backgroundImage:
|
||||||
|
"url(/images/badge-bg.jpg)",
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
|
|
@ -228,15 +245,21 @@ export default function FeaturedYacht({
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
className="font-bold text-white h-[64px] w-full px-8"
|
className="font-bold text-white h-[64px] w-full px-8"
|
||||||
onClick={handleBookClick}
|
onClick={handleBookClick}
|
||||||
disabled={!bookingData.date || !yacht.id}
|
disabled={
|
||||||
|
!bookingData.date || !yacht.id
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Забронировать
|
Забронировать
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Total price */}
|
{/* Total price */}
|
||||||
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
|
<div className="flex justify-between items-center text-l mt-6 font-bold text-gray-800">
|
||||||
<span className="font-normal">Итого:</span>
|
<span className="font-normal">
|
||||||
<span>{formatPrice(getTotalPrice())} ₽</span>
|
Итого:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{formatPrice(getTotalPrice())} ₽
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
formatWidth,
|
formatWidth,
|
||||||
getImageUrl,
|
getImageUrl,
|
||||||
} from "@/lib/utils";
|
} from "@/lib/utils";
|
||||||
|
import { CatalogItemShortDto, MainPageCatalogResponseDto } from "@/api/types";
|
||||||
|
|
||||||
export default function YachtGrid() {
|
export default function YachtGrid() {
|
||||||
const client = useApiClient();
|
const client = useApiClient();
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { User, ArrowUpRight, Map, ArrowLeft, Heart } from "lucide-react";
|
import { User, ArrowUpRight, Map, ArrowLeft, Heart } from "lucide-react";
|
||||||
import { useEffect, useState, Suspense } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import useApiClient from "@/hooks/useApiClient";
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
import { getImageUrl, formatPrice, calculateTotalPrice } from "@/lib/utils";
|
import { getImageUrl, formatPrice, calculateTotalPrice } from "@/lib/utils";
|
||||||
import { parseISO } from "date-fns";
|
import { parseISO } from "date-fns";
|
||||||
|
import { CatalogItemLongDto } from "@/api/types";
|
||||||
|
|
||||||
function ConfirmPageContent() {
|
function ConfirmPageContent() {
|
||||||
const [yacht, setYacht] = useState<CatalogItemLongDto | null>(null);
|
const [yacht, setYacht] = useState<CatalogItemLongDto | null>(null);
|
||||||
|
|
@ -154,6 +156,40 @@ function ConfirmPageContent() {
|
||||||
: `${guestCount} гостей`
|
: `${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) {
|
if (!yacht) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +340,11 @@ function ConfirmPageContent() {
|
||||||
Скидка (DISCOUNT50):
|
Скидка (DISCOUNT50):
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#2D908D] font-bold">
|
<span className="text-[#2D908D] font-bold">
|
||||||
-{formatPrice(totalPrice * 0.5)} Р
|
-
|
||||||
|
{formatPrice(
|
||||||
|
totalPrice * 0.5
|
||||||
|
)}{" "}
|
||||||
|
Р
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -358,7 +398,7 @@ function ConfirmPageContent() {
|
||||||
variant="default"
|
variant="default"
|
||||||
className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200"
|
className="w-full h-[56px] bg-[#2D908D] hover:bg-[#007088] text-white font-bold rounded-full transition-colors duration-200"
|
||||||
disabled={totalHours === 0}
|
disabled={totalHours === 0}
|
||||||
onClick={() => router.push("/profile/reservations")}
|
onClick={() => mutate()}
|
||||||
>
|
>
|
||||||
Отправить заявку
|
Отправить заявку
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -454,10 +494,12 @@ function ConfirmPageContent() {
|
||||||
{isPromocodeApplied && (
|
{isPromocodeApplied && (
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<span className="text-[#333333]">
|
<span className="text-[#333333]">
|
||||||
Скидка (DISCOUNT50):
|
Скидка
|
||||||
|
(DISCOUNT50):
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#2D908D] font-bold">
|
<span className="text-[#2D908D] font-bold">
|
||||||
-{formatPrice(
|
-
|
||||||
|
{formatPrice(
|
||||||
totalPrice *
|
totalPrice *
|
||||||
0.5
|
0.5
|
||||||
)}{" "}
|
)}{" "}
|
||||||
|
|
@ -591,7 +633,7 @@ function ConfirmPageContent() {
|
||||||
size="lg"
|
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"
|
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}
|
disabled={totalHours === 0}
|
||||||
onClick={() => router.push("/profile/reservations")}
|
onClick={() => mutate()}
|
||||||
>
|
>
|
||||||
Отправить заявку
|
Отправить заявку
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,48 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import ProfileSidebar from "@/app/profile/components/ProfileSidebar";
|
import ProfileSidebar from "@/app/profile/components/ProfileSidebar";
|
||||||
import { User, Clock, MoveHorizontal, Users } from "lucide-react";
|
import { User, Clock, MoveHorizontal, Users } from "lucide-react";
|
||||||
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
interface Reservation {
|
import useAuthStore from "@/stores/useAuthStore";
|
||||||
id: string;
|
import { ReservationDto } from "@/api/types";
|
||||||
yachtName: string;
|
import { formatWidth } from "@/lib/utils";
|
||||||
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<string, Reservation[]> = {
|
|
||||||
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: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ReservationsPage() {
|
export default function ReservationsPage() {
|
||||||
const [activeTab, setActiveTab] = useState<"new" | "active" | "confirmed" | "archive">("new");
|
const [activeTab, setActiveTab] = useState<
|
||||||
const reservations = mockReservations[activeTab];
|
"new" | "active" | "confirmed" | "archive"
|
||||||
|
>("new");
|
||||||
|
const [reservationsData, setReservationsData] = useState<ReservationDto[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const apiClient = useApiClient();
|
||||||
|
const { getUserId } = useAuthStore();
|
||||||
|
const userId = getUserId();
|
||||||
|
|
||||||
const formatPrice = (price: number): string => {
|
useEffect(() => {
|
||||||
return new Intl.NumberFormat("ru-RU").format(price) + " Р";
|
if (userId) {
|
||||||
|
apiClient
|
||||||
|
.get<ReservationDto[]>(`/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 (
|
return (
|
||||||
|
|
@ -103,16 +75,18 @@ export default function ReservationsPage() {
|
||||||
<div className="flex gap-2 mb-6">
|
<div className="flex gap-2 mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("new")}
|
onClick={() => setActiveTab("new")}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${activeTab === "new"
|
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${
|
||||||
|
activeTab === "new"
|
||||||
? "border-[#333333] bg-white text-[#333333] font-bold"
|
? "border-[#333333] bg-white text-[#333333] font-bold"
|
||||||
: "border-transparent text-[#999999] hover:text-[#333333]"
|
: "border-transparent text-[#999999] hover:text-[#333333]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Новые брони ({mockReservations.new.length})
|
Новые брони ({reservationsData.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("active")}
|
onClick={() => setActiveTab("active")}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${activeTab === "active"
|
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${
|
||||||
|
activeTab === "active"
|
||||||
? "border-[#333333] bg-white text-[#333333] font-bold"
|
? "border-[#333333] bg-white text-[#333333] font-bold"
|
||||||
: "border-transparent text-[#999999] hover:text-[#333333]"
|
: "border-transparent text-[#999999] hover:text-[#333333]"
|
||||||
}`}
|
}`}
|
||||||
|
|
@ -121,7 +95,8 @@ export default function ReservationsPage() {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("confirmed")}
|
onClick={() => setActiveTab("confirmed")}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${activeTab === "confirmed"
|
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${
|
||||||
|
activeTab === "confirmed"
|
||||||
? "border-[#333333] bg-white text-[#333333] font-bold"
|
? "border-[#333333] bg-white text-[#333333] font-bold"
|
||||||
: "border-transparent text-[#999999] hover:text-[#333333]"
|
: "border-transparent text-[#999999] hover:text-[#333333]"
|
||||||
}`}
|
}`}
|
||||||
|
|
@ -130,7 +105,8 @@ export default function ReservationsPage() {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("archive")}
|
onClick={() => setActiveTab("archive")}
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${activeTab === "archive"
|
className={`px-4 py-2 text-sm font-medium rounded-full border transition-colors ${
|
||||||
|
activeTab === "archive"
|
||||||
? "border-[#333333] bg-white text-[#333333] font-bold"
|
? "border-[#333333] bg-white text-[#333333] font-bold"
|
||||||
: "border-transparent text-[#999999] hover:text-[#333333]"
|
: "border-transparent text-[#999999] hover:text-[#333333]"
|
||||||
}`}
|
}`}
|
||||||
|
|
@ -141,23 +117,34 @@ export default function ReservationsPage() {
|
||||||
|
|
||||||
{/* Reservations List */}
|
{/* Reservations List */}
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{reservations.length === 0 ? (
|
{reservationsData.length === 0 ? (
|
||||||
<div className="text-center py-12 text-[#999999]">
|
<div className="text-center py-12 text-[#999999]">
|
||||||
Нет бронирований в этой категории
|
Нет бронирований в этой категории
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
reservations.map((reservation, index) => (
|
reservationsData.map((reservation, index) => (
|
||||||
<div
|
<div
|
||||||
key={reservation.id}
|
key={reservation.id}
|
||||||
className={`overflow-hidden bg-white ${index !== reservations.length - 1 ? 'pb-8 border-b border-gray-200' : ''}`}
|
className={`overflow-hidden bg-white ${
|
||||||
|
index !==
|
||||||
|
reservationsData.length - 1
|
||||||
|
? "pb-8 border-b border-gray-200"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col lg:flex-row">
|
<div className="flex flex-col lg:flex-row">
|
||||||
{/* Image Section */}
|
{/* Image Section */}
|
||||||
<div className="relative rounded-[12px] overflow-hidden w-90 h-90 flex-shrink-0">
|
<div className="relative rounded-[12px] overflow-hidden w-90 h-90 flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={reservation.yachtImage}
|
src={
|
||||||
alt={reservation.yachtName}
|
reservation.yacht
|
||||||
|
.mainImageUrl
|
||||||
|
}
|
||||||
|
alt={
|
||||||
|
reservation.yacht
|
||||||
|
.name
|
||||||
|
}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
@ -173,7 +160,12 @@ export default function ReservationsPage() {
|
||||||
Владелец
|
Владелец
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-[#333333] font-bold">
|
<span className="text-sm text-[#333333] font-bold">
|
||||||
{reservation.ownerName}
|
{
|
||||||
|
reservation
|
||||||
|
.yacht
|
||||||
|
.owner
|
||||||
|
.firstName
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,7 +179,11 @@ export default function ReservationsPage() {
|
||||||
className="text-white"
|
className="text-white"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{reservation.length} метров
|
{formatWidth(
|
||||||
|
reservation
|
||||||
|
.yacht
|
||||||
|
.length
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -198,7 +194,13 @@ export default function ReservationsPage() {
|
||||||
size={16}
|
size={16}
|
||||||
className="text-white"
|
className="text-white"
|
||||||
/>
|
/>
|
||||||
<span>{reservation.capacity}</span>
|
<span>
|
||||||
|
{
|
||||||
|
reservation
|
||||||
|
.yacht
|
||||||
|
.maxCapacity
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,8 +216,9 @@ export default function ReservationsPage() {
|
||||||
Выход:
|
Выход:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{reservation.departureDate} -{" "}
|
{formatUtcDate(
|
||||||
{reservation.departureTime}
|
reservation.startUtc
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[#333333] w-full flex justify-between">
|
<div className="text-[#333333] w-full flex justify-between">
|
||||||
|
|
@ -223,8 +226,9 @@ export default function ReservationsPage() {
|
||||||
Заход:
|
Заход:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{reservation.arrivalDate} -{" "}
|
{formatUtcDate(
|
||||||
{reservation.arrivalTime}
|
reservation.endUtc
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[#333333] w-full flex justify-between">
|
<div className="text-[#333333] w-full flex justify-between">
|
||||||
|
|
@ -232,24 +236,38 @@ export default function ReservationsPage() {
|
||||||
Гости:
|
Гости:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{reservation.guests}
|
{/* @TODO: Добавить количество гостей */}
|
||||||
|
{/* {
|
||||||
|
reservation.guests
|
||||||
|
} */}
|
||||||
|
-
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[#333333] w-full flex justify-between">
|
<div className="text-[#333333] w-full flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
Тип оплаты:
|
Тип
|
||||||
|
оплаты:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{reservation.paymentType}
|
{/* @TODO: Добавить тип оплаты */}
|
||||||
|
{/* {
|
||||||
|
reservation.paymentType
|
||||||
|
} */}
|
||||||
|
-
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm text-[#333333]">
|
<div className="flex items-center gap-2 text-sm text-[#333333]">
|
||||||
<Clock
|
<Clock
|
||||||
size={16}
|
size={
|
||||||
|
16
|
||||||
|
}
|
||||||
className="text-[#999999]"
|
className="text-[#999999]"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
По местному времени яхты
|
По
|
||||||
|
местному
|
||||||
|
времени
|
||||||
|
яхты
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,17 +275,26 @@ export default function ReservationsPage() {
|
||||||
<div className="pt-3 border-t border-[#DFDFDF]">
|
<div className="pt-3 border-t border-[#DFDFDF]">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-base font-bold text-[#333333]">
|
<span className="text-base font-bold text-[#333333]">
|
||||||
Итого:{" "}
|
{/* @TODO: Добавить итоговую стоимость */}
|
||||||
|
{/* Итого:{" "}
|
||||||
{formatPrice(
|
{formatPrice(
|
||||||
reservation.totalPrice
|
reservation.totalPrice
|
||||||
)}{" "}{reservation.paymentStatus ===
|
)}{" "}
|
||||||
|
{reservation.paymentStatus ===
|
||||||
"pending" && (
|
"pending" && (
|
||||||
<span className="text-base font-bold text-red-500">
|
<span className="text-base font-bold text-red-500">
|
||||||
(в ожидании оплаты)
|
(в
|
||||||
|
ожидании
|
||||||
|
оплаты)
|
||||||
|
</span>
|
||||||
|
)} */}
|
||||||
|
Итого: 78000{" "}
|
||||||
|
<span className="text-base font-bold text-red-500">
|
||||||
|
(в
|
||||||
|
ожидании
|
||||||
|
оплаты)
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,34 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import ProfileSidebar from "@/app/profile/components/ProfileSidebar";
|
import ProfileSidebar from "@/app/profile/components/ProfileSidebar";
|
||||||
import { MoveHorizontal, Users } from "lucide-react";
|
import { MoveHorizontal, Users } from "lucide-react";
|
||||||
import { getImageUrl, formatMinCost } from "@/lib/utils";
|
import { getImageUrl, formatMinCost } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import useAuthStore from "@/stores/useAuthStore";
|
||||||
interface Yacht {
|
import { useEffect, useState } from "react";
|
||||||
id: string;
|
import useApiClient from "@/hooks/useApiClient";
|
||||||
name: string;
|
import { CatalogItemShortDto } from "@/api/types";
|
||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function YachtsPage() {
|
export default function YachtsPage() {
|
||||||
const yachts = mockYachts;
|
const [yachts, setYachts] = useState<CatalogItemShortDto[]>([]);
|
||||||
|
const apiClient = useApiClient();
|
||||||
|
const { getUserId } = useAuthStore();
|
||||||
|
const userId = getUserId();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId) {
|
||||||
|
apiClient
|
||||||
|
.get<CatalogItemShortDto[]>(`/catalog/user/${userId}`)
|
||||||
|
.then((response) => {
|
||||||
|
setYachts(response.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Ошибка при загрузке яхт:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="bg-[#f4f4f4]">
|
<main className="bg-[#f4f4f4]">
|
||||||
|
|
@ -68,7 +58,9 @@ export default function YachtsPage() {
|
||||||
<div className="flex-1 bg-white rounded-[16px] p-8">
|
<div className="flex-1 bg-white rounded-[16px] p-8">
|
||||||
{/* Header with Add Button */}
|
{/* Header with Add Button */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-2xl font-bold text-[#333333]">Мои яхты</h2>
|
<h2 className="text-2xl font-bold text-[#333333]">
|
||||||
|
Мои яхты
|
||||||
|
</h2>
|
||||||
<Link href="/profile/yachts/add">
|
<Link href="/profile/yachts/add">
|
||||||
<Button variant="gradient" size="default">
|
<Button variant="gradient" size="default">
|
||||||
Добавить
|
Добавить
|
||||||
|
|
@ -98,7 +90,7 @@ export default function YachtsPage() {
|
||||||
<div className="relative rounded-[12px] overflow-hidden w-90 h-90 flex-shrink-0">
|
<div className="relative rounded-[12px] overflow-hidden w-90 h-90 flex-shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={getImageUrl(
|
src={getImageUrl(
|
||||||
yacht.image
|
yacht.mainImageUrl
|
||||||
)}
|
)}
|
||||||
alt={yacht.name}
|
alt={yacht.name}
|
||||||
fill
|
fill
|
||||||
|
|
@ -129,9 +121,7 @@ export default function YachtsPage() {
|
||||||
className="text-white"
|
className="text-white"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{
|
-
|
||||||
yacht.capacity
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -162,10 +152,7 @@ export default function YachtsPage() {
|
||||||
Вместимость:
|
Вместимость:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{
|
-
|
||||||
yacht.capacity
|
|
||||||
}{" "}
|
|
||||||
человек
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[#333333] w-full flex justify-between">
|
<div className="text-[#333333] w-full flex justify-between">
|
||||||
|
|
@ -183,6 +170,8 @@ export default function YachtsPage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-3 border-t border-[#DFDFDF]">
|
<div className="pt-3 border-t border-[#DFDFDF]">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
{yacht.id && (
|
||||||
|
<>
|
||||||
<Link
|
<Link
|
||||||
href={`/catalog/${yacht.id}`}
|
href={`/catalog/${yacht.id}`}
|
||||||
className="text-sm text-[#2D908D] hover:underline"
|
className="text-sm text-[#2D908D] hover:underline"
|
||||||
|
|
@ -196,6 +185,8 @@ export default function YachtsPage() {
|
||||||
>
|
>
|
||||||
Редактировать
|
Редактировать
|
||||||
</Link>
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ interface AuthStore {
|
||||||
setToken: (token: string, rememberMe?: boolean) => void;
|
setToken: (token: string, rememberMe?: boolean) => void;
|
||||||
getToken: () => string | null;
|
getToken: () => string | null;
|
||||||
clearToken: () => void;
|
clearToken: () => void;
|
||||||
|
setUserId: (userId: string | number, rememberMe?: boolean) => void;
|
||||||
|
getUserId: () => string | null;
|
||||||
|
clearUserId: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useAuthStore = create<AuthStore>((set) => ({
|
const useAuthStore = create<AuthStore>(() => ({
|
||||||
setToken: (token: string, rememberMe: boolean = false) => {
|
setToken: (token: string, rememberMe: boolean = false) => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
if (rememberMe) {
|
if (rememberMe) {
|
||||||
localStorage.setItem("token", token);
|
localStorage.setItem("token", token);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -16,6 +20,7 @@ const useAuthStore = create<AuthStore>((set) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
getToken: (): string | null => {
|
getToken: (): string | null => {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
const sessionToken = sessionStorage.getItem("token");
|
const sessionToken = sessionStorage.getItem("token");
|
||||||
if (sessionToken) {
|
if (sessionToken) {
|
||||||
return sessionToken;
|
return sessionToken;
|
||||||
|
|
@ -30,9 +35,41 @@ const useAuthStore = create<AuthStore>((set) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
clearToken: () => {
|
clearToken: () => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
sessionStorage.removeItem("token");
|
sessionStorage.removeItem("token");
|
||||||
localStorage.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;
|
export default useAuthStore;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue