страницы профиля

This commit is contained in:
Sergey Bolshakov 2025-12-15 00:44:44 +03:00
parent b45f9885ab
commit d177eee970
4 changed files with 890 additions and 0 deletions

View File

@ -0,0 +1,55 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
interface MenuItem {
label: string;
href: string;
icon?: string;
}
const menuItems: MenuItem[] = [
{ label: "Дашборд", href: "/profile/dashboard" },
{ label: "Мои яхты", href: "/profile/yachts" },
{ label: "Мои брони", href: "/profile/reservations" },
{ label: "Заказы", href: "/profile/orders" },
{ label: "Календарь", href: "/profile/calendar" },
{ label: "Избранное", href: "/profile/favorites" },
{ label: "Аккаунт", href: "/profile/account" },
{ label: "Выйти", href: "/profile/logout" },
];
export default function ProfileSidebar() {
const pathname = usePathname();
return (
<aside className="w-[292px] bg-white h-min rounded-[16px] flex-shrink-0 bg-[#f4f4f4]">
<nav>
<ul>
{menuItems.map((item) => {
const isActive = pathname === item.href ||
(item.href === "/profile/yachts" && pathname?.startsWith("/profile/yachts"));
return (
<li key={item.href}>
<Link
href={item.href}
className={cn(
"block p-6 border-b border-[#EDEDED] font-regular text-sm",
isActive
? "text-[#2D908D]"
: "text-[#333333]"
)}
>
{item.label}
</Link>
</li>
);
})}
</ul>
</nav>
</aside>
);
}

View File

@ -0,0 +1,286 @@
"use client";
import { useState } 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<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() {
const [activeTab, setActiveTab] = useState<"new" | "active" | "confirmed" | "archive">("new");
const reservations = mockReservations[activeTab];
const formatPrice = (price: number): string => {
return new Intl.NumberFormat("ru-RU").format(price) + " Р";
};
return (
<main className="bg-[#f4f4f4]">
<div className="container max-w-6xl mx-auto px-4 py-6">
{/* Breadcrumbs */}
<div className="hidden lg:flex mb-4 text-sm text-[#999999] items-center gap-[16px]">
<Link href="/">
<span className="cursor-pointer hover:text-[#333333] transition-colors">
Аренда яхты
</span>
</Link>
<span>&gt;</span>
<Link href="/profile">
<span className="cursor-pointer hover:text-[#333333] transition-colors">
Личный кабинет
</span>
</Link>
<span>&gt;</span>
<span className="text-[#333333]">Мои брони</span>
</div>
<div className="flex gap-6">
{/* Sidebar */}
<ProfileSidebar />
{/* Main Content */}
<div className="flex-1 bg-white rounded-[16px] p-8">
{/* Tabs */}
<div className="flex gap-2 mb-6">
<button
onClick={() => setActiveTab("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-transparent text-[#999999] hover:text-[#333333]"
}`}
>
Новые брони ({mockReservations.new.length})
</button>
<button
onClick={() => setActiveTab("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-transparent text-[#999999] hover:text-[#333333]"
}`}
>
Активные
</button>
<button
onClick={() => setActiveTab("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-transparent text-[#999999] hover:text-[#333333]"
}`}
>
Подтвержденные
</button>
<button
onClick={() => setActiveTab("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-transparent text-[#999999] hover:text-[#333333]"
}`}
>
Архив
</button>
</div>
{/* Reservations List */}
<div className="space-y-8">
{reservations.length === 0 ? (
<div className="text-center py-12 text-[#999999]">
Нет бронирований в этой категории
</div>
) : (
reservations.map((reservation, index) => (
<div
key={reservation.id}
className={`overflow-hidden bg-white ${index !== reservations.length - 1 ? 'pb-8 border-b border-gray-200' : ''}`}
>
<div>
<div className="flex flex-col lg:flex-row">
{/* Image Section */}
<div className="relative rounded-[12px] overflow-hidden w-90 h-90 flex-shrink-0">
<Image
src={reservation.yachtImage}
alt={reservation.yachtName}
fill
className="object-cover"
/>
{/* Owner Info Overlay */}
<div className="absolute top-2 left-2">
<div className="bg-white p-2 rounded-[8px] flex items-center gap-2">
<User
size={20}
className="text-[#999999]"
/>
<div className="flex flex-col">
<span className="text-xs text-[#999999]">
Владелец
</span>
<span className="text-sm text-[#333333] font-bold">
{reservation.ownerName}
</span>
</div>
</div>
</div>
{/* Yacht Details Overlay */}
<div className="absolute bottom-2 left-2 flex gap-2">
<div className="bg-black/50 text-white px-3 py-1.5 rounded-lg flex items-center gap-3 text-sm">
<div className="flex items-center gap-1">
<MoveHorizontal
size={16}
className="text-white"
/>
<span>
{reservation.length} метров
</span>
</div>
</div>
<div className="bg-black/50 text-white px-3 py-1.5 rounded-lg flex items-center gap-3 text-sm">
<div className="flex items-center gap-1">
<Users
size={16}
className="text-white"
/>
<span>{reservation.capacity}</span>
</div>
</div>
</div>
</div>
{/* Details Section */}
<div className="flex-1 px-6">
<div className="space-y-3">
<div className="flex items-start justify-between">
<div className="space-y-3 w-full">
<div className="text-[#333333] w-full flex justify-between">
<div>
Выход:
</div>
<div>
{reservation.departureDate} -{" "}
{reservation.departureTime}
</div>
</div>
<div className="text-[#333333] w-full flex justify-between">
<div>
Заход:
</div>
<div>
{reservation.arrivalDate} -{" "}
{reservation.arrivalTime}
</div>
</div>
<div className="text-[#333333] w-full flex justify-between">
<div>
Гости:
</div>
<div>
{reservation.guests}
</div>
</div>
<div className="text-[#333333] w-full flex justify-between">
<div>
Тип оплаты:
</div>
<div>
{reservation.paymentType}
</div>
</div>
<div className="flex items-center gap-2 text-sm text-[#333333]">
<Clock
size={16}
className="text-[#999999]"
/>
<span>
По местному времени яхты
</span>
</div>
</div>
</div>
<div className="pt-3 border-t border-[#DFDFDF]">
<div className="flex items-center justify-between">
<span className="text-base font-bold text-[#333333]">
Итого:{" "}
{formatPrice(
reservation.totalPrice
)}{" "}{reservation.paymentStatus ===
"pending" && (
<span className="text-base font-bold text-red-500">
(в ожидании оплаты)
</span>
)}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
</div>
</main>
);
}

View File

@ -0,0 +1,525 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import ProfileSidebar from "@/app/profile/components/ProfileSidebar";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Info, X, Plus, Minus } from "lucide-react";
interface Cabin {
id: string;
name: string;
count: number;
type: string;
}
export default function AddYachtPage() {
const [cabins, setCabins] = useState<Cabin[]>([
{ id: "1", name: "Мастер Каюта", count: 1, type: "Односпальная" },
{ id: "2", name: "Гостевая каюта 1", count: 1, type: "" },
]);
const addCabin = () => {
const newCabin: Cabin = {
id: Date.now().toString(),
name: `Гостевая каюта ${cabins.length}`,
count: 1,
type: "",
};
setCabins([...cabins, newCabin]);
};
const removeCabin = (id: string) => {
setCabins(cabins.filter((cabin) => cabin.id !== id));
};
const updateCabinCount = (id: string, delta: number) => {
setCabins(
cabins.map((cabin) =>
cabin.id === id
? { ...cabin, count: Math.max(1, cabin.count + delta) }
: cabin
)
);
};
const updateCabinType = (id: string, type: string) => {
setCabins(
cabins.map((cabin) =>
cabin.id === id ? { ...cabin, type } : cabin
)
);
};
return (
<main className="bg-[#f4f4f4] min-h-screen">
<div className="container max-w-6xl mx-auto px-4 py-6">
{/* Breadcrumbs */}
<div className="hidden lg:flex mb-4 text-sm text-[#999999] items-center gap-[16px]">
<Link href="/">
<span className="cursor-pointer hover:text-[#333333] transition-colors">
Аренда яхты
</span>
</Link>
<span>&gt;</span>
<Link href="/profile">
<span className="cursor-pointer hover:text-[#333333] transition-colors">
Личный кабинет
</span>
</Link>
<span>&gt;</span>
<Link href="/profile/yachts">
<span className="cursor-pointer hover:text-[#333333] transition-colors">
Мои яхты
</span>
</Link>
<span>&gt;</span>
<span className="text-[#333333]">Добавление яхты</span>
</div>
<div className="flex gap-6">
{/* Sidebar */}
<ProfileSidebar />
{/* Main Content */}
<div className="flex-1 bg-white rounded-lg p-8">
<h1 className="text-2xl font-bold text-[#333333] mb-8">
Добавление яхты
</h1>
{/* Выберите тип судна */}
<div className="mb-6">
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Выберите тип судна*
</Label>
<Select>
<SelectTrigger className="w-full h-12 rounded-lg">
<SelectValue placeholder="Выберите тип судна" />
</SelectTrigger>
<SelectContent>
<SelectItem value="motor">Моторная яхта</SelectItem>
<SelectItem value="sail">Парусная яхта</SelectItem>
<SelectItem value="catamaran">Катамаран</SelectItem>
</SelectContent>
</Select>
</div>
{/* Основная информация */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Основная информация*
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Название судна*
</Label>
<Input
placeholder="Название судна"
className="h-12"
/>
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Верфь*
</Label>
<Input placeholder="Верфь" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Модель*
</Label>
<Select>
<SelectTrigger className="w-full h-12 rounded-lg">
<SelectValue placeholder="Модель" />
</SelectTrigger>
<SelectContent>
<SelectItem value="model1">Модель 1</SelectItem>
<SelectItem value="model2">Модель 2</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Выберите марину*
</Label>
<Select>
<SelectTrigger className="w-full h-12 rounded-lg">
<SelectValue placeholder="Выберите марину" />
</SelectTrigger>
<SelectContent>
<SelectItem value="marina1">Марина 1</SelectItem>
<SelectItem value="marina2">Марина 2</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* Прибыль и время аренды */}
<div className="mb-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Прибыль (за час)*
</Label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-[#333333]">
$
</span>
<Input
placeholder="Прибыль (за час)"
className="h-12 pl-8 pr-10"
/>
<Info className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#999999]" />
</div>
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Минимальное время аренды (в часах)*
</Label>
<Input
placeholder="Минимальное время (в часах)"
className="h-12"
/>
</div>
</div>
</div>
{/* Тип оплаты */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Тип оплаты
</h2>
<div className="space-y-3">
<div className="flex items-center gap-3">
<Checkbox id="yookassa" />
<Label
htmlFor="yookassa"
className="flex-1 cursor-pointer flex items-center justify-between"
>
<span>Оплата через Yookassa</span>
<Info className="h-4 w-4 text-[#999999]" />
</Label>
</div>
<div className="flex items-center gap-3">
<Checkbox id="prepayment" defaultChecked />
<Label
htmlFor="prepayment"
className="flex-1 cursor-pointer flex items-center justify-between"
>
<span>Предоплата</span>
<Info className="h-4 w-4 text-[#999999]" />
</Label>
</div>
</div>
</div>
{/* Промоцены */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-[#333333]">
Промоцены
</h2>
<Button variant="outline" size="sm">
Добавить промоцену
</Button>
</div>
</div>
{/* Синхронизация Google Календаря */}
<div className="mb-8">
<Label className="text-sm font-medium text-[#333333] mb-2 block">
ID Google Календаря для синхронизации
</Label>
<Input
placeholder="ID Google Календаря"
className="h-12"
/>
</div>
{/* Загрузка изображений */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<Label className="text-sm font-medium text-[#333333]">
Загрузите изображения судна (в высоком разрешении)*
</Label>
<Button variant="outline" size="sm">
Загрузить
</Button>
</div>
<div className="border-2 border-dashed border-[#999999] rounded-lg p-12 text-center">
<div className="flex flex-col items-center gap-4">
<div className="w-16 h-16 bg-[#f4f4f4] rounded-lg flex items-center justify-center">
<svg
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
</div>
<p className="text-sm text-[#333333]">
Загрузите изображения судна (в высоком разрешении)*
</p>
</div>
</div>
</div>
{/* Характеристики */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Характеристики*
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Год*
</Label>
<Input placeholder="Год" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Мощность (л/с)
</Label>
<Input placeholder="Мощность (л/с)" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Длина (м)*
</Label>
<Input placeholder="Длина (м)" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Ширина (м)
</Label>
<Input placeholder="Ширина (м)" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Макс. вместимость (без экипажа)*
</Label>
<Input
placeholder="Макс. вместимость (без экипажа)"
className="h-12"
/>
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Комфортная вместимость (человек)*
</Label>
<Input
placeholder="Комфортная вместимость (человек)"
className="h-12"
/>
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Каюты
</Label>
<Input placeholder="Каюты" className="h-12" />
</div>
<div>
<Label className="text-sm font-medium text-[#333333] mb-2 block">
Материал
</Label>
<Select>
<SelectTrigger className="w-full h-12 rounded-lg">
<SelectValue placeholder="Материал" />
</SelectTrigger>
<SelectContent>
<SelectItem value="fiberglass">Стеклопластик</SelectItem>
<SelectItem value="aluminum">Алюминий</SelectItem>
<SelectItem value="steel">Сталь</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{/* Удобства */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Удобства
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Здесь будут чекбоксы для удобств */}
<div className="flex items-center gap-2">
<Checkbox id="wifi" />
<Label htmlFor="wifi" className="cursor-pointer">
Wi-Fi
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="aircon" />
<Label htmlFor="aircon" className="cursor-pointer">
Кондиционер
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="kitchen" />
<Label htmlFor="kitchen" className="cursor-pointer">
Кухня
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="toilet" />
<Label htmlFor="toilet" className="cursor-pointer">
Туалет
</Label>
</div>
</div>
</div>
{/* Описание */}
<div className="mb-8">
<div className="flex items-center gap-2 mb-4">
<h2 className="text-lg font-bold text-[#333333]">
Описание (5000)
</h2>
<Info className="h-4 w-4 text-[#999999]" />
</div>
<textarea
placeholder="Введите описание"
className="w-full h-32 p-4 border border-gray-300 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-[#008299]"
/>
</div>
{/* Добавить каюты */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Добавить каюты
</h2>
<div className="space-y-3 mb-4">
{cabins.map((cabin) => (
<div
key={cabin.id}
className="flex items-center gap-4 p-4 border border-gray-200 rounded-lg"
>
<button
onClick={() => removeCabin(cabin.id)}
className="text-[#999999] hover:text-[#333333]"
>
<X className="h-5 w-5" />
</button>
<div className="flex items-center gap-2">
<button
onClick={() =>
updateCabinCount(cabin.id, -1)
}
className="w-8 h-8 flex items-center justify-center border border-gray-300 rounded"
>
<Minus className="h-4 w-4" />
</button>
<span className="w-8 text-center">
{cabin.count}
</span>
<button
onClick={() =>
updateCabinCount(cabin.id, 1)
}
className="w-8 h-8 flex items-center justify-center border border-gray-300 rounded"
>
<Plus className="h-4 w-4" />
</button>
</div>
<span className="flex-1 font-medium">
{cabin.name}
</span>
<Select
value={cabin.type}
onValueChange={(value) =>
updateCabinType(cabin.id, value)
}
>
<SelectTrigger className="w-48 rounded-lg">
<SelectValue
placeholder="Выберите..."
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="Односпальная">
Односпальная
</SelectItem>
<SelectItem value="Двуспальная">
Двуспальная
</SelectItem>
<SelectItem value="Двухъярусная">
Двухъярусная
</SelectItem>
</SelectContent>
</Select>
</div>
))}
</div>
<Button
variant="outline"
onClick={addCabin}
className="bg-[#333333] text-white hover:bg-[#444444] border-[#333333]"
>
Добавить каюту
</Button>
</div>
{/* Услуги на яхте */}
<div className="mb-8">
<h2 className="text-lg font-bold text-[#333333] mb-4">
Какие есть услуги на вашей яхте?
</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div className="flex items-center gap-2">
<Checkbox id="service1" />
<Label htmlFor="service1" className="cursor-pointer">
Капитан
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="service2" />
<Label htmlFor="service2" className="cursor-pointer">
Повар
</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="service3" />
<Label htmlFor="service3" className="cursor-pointer">
Стюард
</Label>
</div>
</div>
</div>
{/* Кнопки внизу */}
<div className="flex items-center justify-between pt-6 border-t border-gray-200">
<Button variant="outline" size="lg">
Режим предпросмотра
</Button>
<Button
variant="gradient"
size="lg"
className="bg-[#008299] hover:bg-[#006d7a] text-white"
>
Добавить судно
</Button>
</div>
</div>
</div>
</div>
</main>
);
}

View File

@ -0,0 +1,24 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#008299] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = "Input";
export { Input };