Мы переходим на территорию визуала и реактивности! Бэкенд — это мотор, а Next.js — это крутой кузов, интерьер и руль. Поехали!

Чтобы проект был современным, быстрым и удобным для разработки, мы возьмем Next.js 14+ (App Router) + TypeScript + TailwindCSS. TypeScript сэкономит нам кучу нервов при работе с API (он подскажет, какие поля прилетели от Laravel), а Tailwind позволит верстать карточки со скоростью мысли.

ЭТАП 2: Веб-оболочка и SEO

Шаг 2.1: Создаем проект Next.js

Важно: Фронтенд — это отдельное приложение. Оно не лежит внутри папки Laravel. Это будет папка client с файлами проекта. Папки client и server должны лежать на одном уровне. Открой новое окно терминала, и выполни команду:

npx create-next-app@latest client

Установщик задаст вопросы. Отвечай строго так:

  • Would you like to use TypeScript? -> Yes
  • Would you like to use ESLint? -> Yes
  • Would you like to use Tailwind CSS? -> Yes
  • Would you like to use src/ directory? -> Yes
  • Would you like to use App Router? (recommended) -> Yes
  • Would you like to customize the default import alias? -> No (оставь @/*)

Эта команда скачаетNext.js и все зависимости. Это займет пару минут.

Шаг 2.2: Структура и Мост к Laravel (API Client)

Когда установка завершится, зайди в папку client в твоем редакторе кода (VS Code). Нам нужно создать “мост”, через который React будет общаться с нашим Laravel API. Для этого мы будем использовать библиотеку axios (она удобнее, чем встроенный fetch, так как позволяет автоматически цеплять токены) и js-cookie для корректной обработки заголовков и кук в запросах.

Установи Axios в папке фронтенда (client):

npm install axios js-cookie
npm install --save-dev @types/js-cookie

Создай структуру папок для API.

В корне создай папку lib, а в ней файл api.ts (lib/api.ts). Это будет наш единственный экземпляр Axios для всего приложения.

Напиши в lib/api.ts:

import axios, { InternalAxiosRequestConfig } from "axios"; // Добавили InternalAxiosRequestConfig
import Cookies from "js-cookie";

// Базовый клиент (для CSRF)
export const api = axios.create({
  baseURL: "http://localhost:8000",
  withCredentials: true,
  headers: {
    "Accept": "application/json",
    "X-Requested-With": "XMLHttpRequest", 
  },
});

// Клиент для API роутов
export const apiClient = axios.create({
  baseURL: "http://localhost:8000/api",
  withCredentials: true,
  headers: {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "X-Requested-With": "XMLHttpRequest", 
  },
});

// Типизируем config, чтобы TS не ругался на 'any'
const xsrfInterceptor = (config: InternalAxiosRequestConfig) => {
  // Проверяем, что мы в браузере (для Next.js SSR безопасности)
  if (typeof window !== 'undefined') {
    const xsrfToken = Cookies.get('XSRF-TOKEN');
    if (xsrfToken) {
      // Декодируем и вставляем в заголовок!
      config.headers['X-XSRF-TOKEN'] = decodeURIComponent(xsrfToken);
    }
  }
  return config;
};

api.interceptors.request.use(xsrfInterceptor);
apiClient.interceptors.request.use(xsrfInterceptor);

// Функция для получения CSRF-токена
export const getCsrfToken = async () => {
  await api.get("/sanctum/csrf-cookie");
};

Ставим Shadc/ui и создаем компоненты

shadcn/ui — это не просто библиотека, это генератор компонентов. Он копирует исходный код прямо в твой проект, и ты можешь их менять как хочешь.

  1. В папке dating-web выполни инициализацию shadcn:
npx shadcn@latest init

Ответь на вопросы:

  • Which style would you like to use? -> Default
  • Which color would you like to use as base color? -> Slate (или Neutral)
  • Do you want to use CSS variables for colors? -> yes
  1. Добавим нужные нам компоненты (Кнопки, Инпуты, Карточки, Формы):
npx shadcn@latest add button input label card

Установим библиотеки для удобной работы с формами и валидацией:

npm install react-hook-form @hookform/resolvers zod

(Zod – крутая библиотека для описания типов и валидации одновременно для фронтенда и бэкенда).

Публикуем на сервере файл CORS

В терминале Laravel выполни команду:

php artisan config:publish cors

Эта команда создаст файл config/cors.php, который мы теперь можем редактировать.

Открой только что созданный файл config/cors.php. Замени его содержимое на это (я убрал привязку к env(), чтобы исключить любые кэши и ошибки):

<?php

return [

    'paths' => ['api/*', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    // ЖЕСТКО ПРОПИСЫВАЕМ НАШ ФРОНТЕНД! Никаких env()
    'allowed_origins' => ['http://localhost:3000'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    // КРИТИЧЕСКИ ВАЖНО!
    'supports_credentials' => true,

];

обновялем на сервере bootsrap/app.php

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

use Illuminate\Http\Request;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        channels: __DIR__.'/../routes/channels.php',
        health: '/up',
    )
   ->withMiddleware(function (Middleware $middleware): void {      
         $middleware->statefulApi();
    })
    
    ->withExceptions(function (Exceptions $exceptions): void {
        $exceptions->shouldRenderJsonWhen(
            fn (Request $request) => $request->is('api/*'),
        );
    })->create();

добавили $middleware->statefulApi(); для обработки апи роутов как веб

Переписываем под csrf на сервере authController

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    // РЕГИСТРАЦИЯ
    public function register(Request $request)
    {
        $request->validate([
            'email' => 'required|email|unique:users',
            'password' => 'required|min:8',
            'name' => 'required|string|max:255', // Имя теперь обязательно при регистрации
        ]);

        $user = User::create([
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        // Обновляем профиль с именем
        $user->profile->update(['name' => $request->name]);

        // Логиним юзера автоматически (создает сессию и куку)
        Auth::login($user);

        return response()->json([
            'user' => $user->load('profile', 'photos')
        ], 201);
    }

    // АВТОРИЗАЦИЯ (Логин)
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        $user = User::where('email', $request->email)->first();

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['Неверный email или пароль.'],
            ]);
        }

        // Логиним через guard web (создает сессию и куку)
        Auth::login($user);

        return response()->json([
            'user' => $user->load('profile', 'photos')
        ]);
    }

    // ВЫХОД (Логаут)
    public function logout(Request $request)
    {
        Auth::guard('web')->logout(); // Разрушает сессию
        
        // Аннулируем CSRF-токен (безопасность)
        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return response()->json([
            'message' => 'Вы вышли из системы'
        ]);
    }
}

Проверяем routes/api.php на сервере

<?php

use App\Http\Controllers\AuthController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\LocationController;
use App\Http\Controllers\SwipeController;
use App\Http\Controllers\DiscoverController;
use App\Http\Controllers\ChatController;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\BlockController;
use App\Http\Controllers\ReportController;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;


// Публичные
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

// Защищенные
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/user', function (Request $request) {
        return $request->user()->load('profile', 'photos');
    });

    // Профиль и Геолокация
    Route::put('/profile', [ProfileController::class, 'update']);
    Route::post('/location', [LocationController::class, 'update']);

    // Фото
    Route::post('/photos', [PhotoController::class, 'upload']);
    Route::delete('/photos/{id}', [PhotoController::class, 'destroy']);

    // Лента и Свайпы
    Route::get('/discover', [DiscoverController::class, 'index']);
    Route::post('/swipe', [SwipeController::class, 'store']);

    // Чат
    Route::get('/chats', [ChatController::class, 'index']);
    Route::get('/chats/{conversationId}/messages', [ChatController::class, 'messages']);
    Route::post('/chats/{conversationId}/send', [ChatController::class, 'send']);

    // Безопасность
    Route::post('/block', [BlockController::class, 'store']);
    Route::post('/report', [ReportController::class, 'store']);

    // Удаление аккаунта
    Route::delete('/account', [UserController::class, 'destroy']);
});

Переходим к самому кайфу — созданию UI

Брат, это огромный шаг. Твой React на порту 3000 только что успешно создал человека в PostgreSQL на порту 8000, Laravel вернул токен, и React его принял. Мост работает! Бэкенд и Фронтенд теперь единый организм!

Теперь мы переходим к самому кайфу — созданию UI (пользовательского интерфейса). Мы будем верстать карточки свайпов, как в Tinder!

Прежде чем делать красивые карточки, нам нужно создать Глобальное состояние (Context), чтобы приложение знало, что пользователь авторизован, и имело доступ к его профилю из любого компонента. Иначе при обновлении страницы мы “забудем”, что мы залогинены.

Давай сделаем это по-быстрому и красиво!

Создаем нормальную страницу логина и регестрации

"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import axios from "axios"; // ИМПОРТИРУЕМ ДЛЯ ТИПИЗАЦИИ ОШИБОК

// Добавь импорт хука вверху файла:
import { useAuth } from "@/contexts/AuthContext";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

// ИМПОРТИРУЕМ ОБА КЛИЕНТА
import { apiClient, getCsrfToken } from "@/lib/api";

// Схемы валидации
const loginSchema = z.object({
  email: z.string().email("Введите корректный email"),
  password: z.string().min(8, "Минимум 8 символов"),
});

const registerSchema = z.object({
  name: z.string().min(2, "Имя должно быть не менее 2 символов"),
  email: z.string().email("Введите корректный email"),
  password: z.string().min(8, "Минимум 8 символов"),
});

type LoginFormValues = z.infer<typeof loginSchema>;
type RegisterFormValues = z.infer<typeof registerSchema>;

export default function AuthPage() {
  const [isLogin, setIsLogin] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();

  const { fetchUser } = useAuth();

  // Деструктуризируем хуки форм!
  const {
    register: registerLogin,
    handleSubmit: handleLoginSubmit,
    formState: { errors: loginErrors },
  } = useForm<LoginFormValues>({
    resolver: zodResolver(loginSchema),
    defaultValues: { email: "", password: "" },
  });

  const {
    register: registerReg,
    handleSubmit: handleRegSubmit,
    formState: { errors: regErrors },
  } = useForm<RegisterFormValues>({
    resolver: zodResolver(registerSchema),
    defaultValues: { name: "", email: "", password: "" },
  });

  // Вспомогательная функция для безопасного извлечения сообщения об ошибке
  const getErrorMessage = (err: unknown): string => {
    if (axios.isAxiosError(err)) {
      // Если ошибка от Axios, проверяем есть ли ответ от сервера
      return err.response?.data?.message || err.message || "Сетевая ошибка";
    }
    if (err instanceof Error) {
      // Если стандартная ошибка JS
      return err.message;
    }
    // Если это вообще что-то непонятное
    return "Произошла неизвестная ошибка";
  };

   const onLogin = async (data: LoginFormValues) => {
    setIsLoading(true);
    setError(null);
    try {
      await getCsrfToken();
      // ИСПРАВЛЕНО: используем api вместо apiClient, так как /login в web.php (без префикса /api)
      await apiClient.post("/login", data);
      await fetchUser(); // ВАЖНО: Обновляем глобальный стейт!
      router.push("/discover");
    } catch (err: unknown) {
      setError(getErrorMessage(err));
    } finally {
      setIsLoading(false);
    }
  };

  const onRegister = async (data: RegisterFormValues) => {
    setIsLoading(true);
    setError(null);
    try {
      await getCsrfToken();
      // ИСПРАВЛЕНО: используем api вместо apiClient, так как /register в web.php (без префикса /api)
      await apiClient.post("/register", data);
      await fetchUser(); // ВАЖНО: Обновляем глобальный стейт!
      router.push("/discover");
    } catch (err: unknown) {
      setError(getErrorMessage(err));
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="min-h-screen bg-slate-950 flex items-center justify-center p-4">
      <Card className="w-full max-w-md bg-slate-900 border-slate-800 text-white">
        <CardHeader>
          <CardTitle className="text-3xl font-bold text-center text-pink-500">
            {isLogin ? "С возвращением!" : "Добро пожаловать!"}
          </CardTitle>
          <CardDescription className="text-center text-slate-400">
            {isLogin
              ? "Войдите, чтобы найти свою пару"
              : "Создайте аккаунт и начните искать"}
          </CardDescription>
        </CardHeader>
        <CardContent>
          {error && (
            <div className="bg-red-900/50 border border-red-500 text-red-300 p-3 rounded mb-4 text-sm">
              {error}
            </div>
          )}

          {isLogin ? (
            <form onSubmit={handleLoginSubmit(onLogin)} className="space-y-4">
              <div className="space-y-2">
                <Label htmlFor="login-email">Email</Label>
                <Input
                  id="login-email"
                  type="email"
                  autoComplete="email" 
                  placeholder="dev@dating.com"
                  className="bg-slate-800 border-slate-700"
                  {...registerLogin("email")}
                />
                {loginErrors.email && (
                  <p className="text-red-400 text-xs">{loginErrors.email.message}</p>
                )}
              </div>

              <div className="space-y-2">
                <Label htmlFor="login-password">Пароль</Label>
                <Input
                  id="login-password"
                  type="password"
                  autoComplete="current-password" 
                  placeholder="••••••••"
                  className="bg-slate-800 border-slate-700"
                  {...registerLogin("password")}
                />
                {loginErrors.password && (
                  <p className="text-red-400 text-xs">{loginErrors.password.message}</p>
                )}
              </div>

              <Button type="submit" className="w-full bg-pink-500 hover:bg-pink-600" disabled={isLoading}>
                {isLoading ? "Загрузка..." : "Войти"}
              </Button>
            </form>
          ) : (
            <form onSubmit={handleRegSubmit(onRegister)} className="space-y-4">
              <div className="space-y-2">
                <Label htmlFor="reg-name">Ваше имя</Label>
                <Input
                  id="reg-name"
                  placeholder="Алексей"
                  className="bg-slate-800 border-slate-700"
                  {...registerReg("name")}
                />
                {regErrors.name && (
                  <p className="text-red-400 text-xs">{regErrors.name.message}</p>
                )}
              </div>

              <div className="space-y-2">
                <Label htmlFor="reg-email">Email</Label>
                <Input
                  id="reg-email"
                  type="email"
                  autoComplete="email"
                  placeholder="dev@dating.com"
                  className="bg-slate-800 border-slate-700"
                  {...registerReg("email")}
                />
                {regErrors.email && (
                  <p className="text-red-400 text-xs">{regErrors.email.message}</p>
                )}
              </div>

              <div className="space-y-2">
                <Label htmlFor="reg-password">Пароль</Label>
                <Input
                  id="reg-password"
                  type="password"
                  autoComplete="new-password"
                  placeholder="••••••••"
                  className="bg-slate-800 border-slate-700"
                  {...registerReg("password")}
                />
                {regErrors.password && (
                  <p className="text-red-400 text-xs">{regErrors.password.message}</p>
                )}
              </div>

              <Button type="submit" className="w-full bg-pink-500 hover:bg-pink-600" disabled={isLoading}>
                {isLoading ? "Загрузка..." : "Зарегистрироваться"}
              </Button>
            </form>
          )}

          <div className="mt-6 text-center text-sm">
            <span className="text-slate-400">
              {isLogin ? "Нет аккаунта? " : "Уже есть аккаунт? "}
            </span>
            <button
              type="button"
              onClick={() => {
                setIsLogin(!isLogin);
                setError(null);
              }}
              className="text-pink-500 hover:underline font-semibold"
            >
              {isLogin ? "Создать" : "Войти"}
            </button>
          </div>
        </CardContent>
      </Card>
    </div>
  );
}

Шаг 2.5: Глобальное состояние авторизации (Auth Context)

  1. В корне проекта (где папки app и lib) создай папку contexts.
  2. Внутри contexts создай файл AuthContext.tsx.

Вот код для contexts/AuthContext.tsx:

"use client";

import {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from "react";
import { apiClient } from "@/lib/api";

type User = {
  id: number;
  email: string;
  profile: unknown;
};

type AuthContextType = {
  user: User | null;
  isLoading: boolean;
  fetchUser: () => Promise<void>;
  logout: () => Promise<void>;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  // Функция для проверки, кто сейчас залогинен (через куки)
  const fetchUser = async () => {
    try {
      const response = await apiClient.get("/user");
      setUser(response.data);
    } catch (error) {
      setUser(null); // Куки нет или протухла
    } finally {
      setIsLoading(false);
    }
  };

  // При первой загрузке приложения проверяем сессию
  useEffect(() => {    
      fetchUser();    
  }, []);

  const logout = async () => {
    try {
      await apiClient.post("/logout");
    } catch (error) {
      console.error("Ошибка выхода", error);
    } finally {
      setUser(null);
    }
  };

  return (
    <AuthContext.Provider value={{ user, isLoading, fetchUser, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error("useAuth должен использоваться внутри AuthProvider");
  }
  return context;
}

Шаг 2.6: Оборачиваем приложение в AuthProvider

Открой app/layout.tsx (главная обертка всего сайта) и подключи наш контекст:

iimport type { Metadata } from "next";
import { Inter, Geist } from "next/font/google";
import "./globals.css";
import { AuthProvider } from "@/contexts/AuthContext";
import { cn } from "@/lib/utils";

const geist = Geist({subsets:['latin'],variable:'--font-sans'});

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Дейтинг",
  description: "Найди свою любовь",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ru" className={cn("font-sans", geist.variable)}>
      <body className={inter.className}>
        <AuthProvider> {/* ОБОРОЧИВАЕМ */}
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

Шаг 2.7: Создаем страницу Ленты (Discover)

Теперь сделаем страницу, где будут карточки! Создай папку app/discover и внутри неё файл page.tsx (app/discover/page.tsx).

Пока мы будем делать простой вывод первой анкеты из нашей умной ленты.

"use client";

import { useEffect, useState } from "react";
import { useAuth } from "@/contexts/AuthContext";
import { apiClient } from "@/lib/api";
import { useRouter } from "next/navigation"; // Для редиректа

type Profile = {
  id: number;  
  name: string;
  age: number;
  gender: string;
  city: string;
  bio: string | null;
  distance_km: number;
  photos: { id: number; path: string; is_main: boolean }[];
};

export default function DiscoverPage() {
  const { user, isLoading, logout } = useAuth();
  const router = useRouter();
  const [profiles, setProfiles] = useState<Profile[]>([]);
  const [currentIndex, setCurrentIndex] = useState(0);

  // Редирект на главную, если не авторизован
  useEffect(() => {
    if (!isLoading && !user) {
      router.push("/");
    }
  }, [user, isLoading, router]);

  // Функция для получения и отправки геолокации браузера
  const sendLocation = async () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        async (position) => {
          try {
            await apiClient.post("/location", {
              latitude: position.coords.latitude,
              longitude: position.coords.longitude,
            });
            // После сохранения координат сразу грузим ленту
            fetchProfiles();
          } catch (error) {
            console.error("Ошибка отправки геолокации", error);
          }
        },
        (error) => {
          console.error("Пользователь запретил доступ к геолокации", error);
          // Тут можно показать модалку: "Без геолокации мы не можем искать людей"
        },
      );
    }
  };

  const fetchProfiles = async () => {
    try {
       const response = await apiClient.get("/discover");
      setProfiles(response.data.data); 
    } catch (error) {
      console.error("Ошибка загрузки ленты", error);
    }
  };

  // Загружаем ленту
  useEffect(() => {
    if (user) {
      sendLocation();
    }
  }, [user]);

  const handleSwipe = async (action: "like" | "dislike", swipedId: number) => {
    try {
      await apiClient.post("/swipe", { swiped_id: swipedId, action });      
      // Переключаем на следующую карточку
      setCurrentIndex((prev) => prev + 1);
    } catch (error) {
      console.error("Ошибка свайпа", error);
    }
  };

  if (isLoading)
    return (
      <div className="min-h-screen bg-gray-900 text-white flex items-center justify-center">
        Загрузка...
      </div>
    );
  if (!user) return null;

  const currentProfile = profiles[currentIndex];

  return (
    <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4">
      <h1 className="text-4xl font-bold text-pink-500 mb-4">Лента рекомендаций</h1>
      <p className="text-slate-300 mb-8">Привет, { user.email}! Ты успешно авторизовался через куки!</p>
      
      <div className="w-full max-w-md">
        <div className="flex justify-between items-center mb-6">
          <h1 className="text-2xl font-bold text-pink-500">Знакомства</h1>
          <button
            onClick={logout}
            className="text-sm text-gray-400 hover:text-white"
          >
            Выйти
          </button>
        </div>

        {/* Карточка профиля */}
        {currentProfile ? (
          <div className="bg-gray-800 rounded-3xl overflow-hidden shadow-2xl relative h-[500px]">
            {/* Фото-заглушка (позже заменим на реальное фото) */}
            <div className="w-full h-full bg-gradient-to-t from-black/80 via-transparent to-transparent absolute bottom-0 z-10"></div>
            <div className="w-full h-full bg-gray-700 flex items-center justify-center text-9xl">
              {currentProfile.gender === "female" ? "👩" : "👨"}
            </div>

            {/* Информация на карточке */}
            <div className="absolute bottom-0 left-0 right-0 p-6 z-20">
              <h2 className="text-3xl font-bold">
                {currentProfile.name}, {currentProfile.age}
              </h2>
              <p className="text-gray-300 mt-1">
                📍 {currentProfile.distance_km} км от вас •{" "}
                {currentProfile.city || "Не указан"}
              </p>
              {currentProfile.bio && (
                <p className="text-gray-400 mt-2 text-sm line-clamp-2">
                  {currentProfile.bio}
                </p>
              )}
            </div>
          </div>
        ) : (
          <div className="bg-gray-800 rounded-3xl h-[500px] flex items-center justify-center">
            <p className="text-gray-400">Людей рядом больше нет... 😢</p>
          </div>
        )}

        {/* Кнопки Свайпов */}
        {currentProfile && (
          <div className="flex justify-center gap-6 mt-6">
            <button
              onClick={() => handleSwipe("dislike", currentProfile.id)}
              className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center text-3xl hover:bg-red-500 transition shadow-lg"
            >
              ✕
            </button>
            <button
              onClick={() => handleSwipe("like", currentProfile.id)}
              className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center text-3xl hover:bg-green-500 transition shadow-lg"
            >
              ♥
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

Тестим ! Авторизация должна работать (логин и регистрация).