mirror of
https://gitlab.com/foxixus/neomovies.git
synced 2025-10-28 01:48:50 +05:00
Authorization, favorites and players have been moved to the API server
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { api } from '@/lib/api';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -91,29 +92,27 @@ export default function AdminLoginClient() {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const result = await signIn('credentials', {
|
||||
const response = await api.post('/auth/login', {
|
||||
email,
|
||||
password,
|
||||
isAdminLogin: 'true',
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
switch (result.error) {
|
||||
case 'NOT_AN_ADMIN':
|
||||
setError('У вас нет прав администратора');
|
||||
break;
|
||||
case 'EMAIL_NOT_VERIFIED':
|
||||
setError('Пожалуйста, подтвердите свой email');
|
||||
break;
|
||||
default:
|
||||
setError('Неверный email или пароль');
|
||||
}
|
||||
} else {
|
||||
router.push('/admin');
|
||||
const { token, user } = response.data;
|
||||
|
||||
if (user?.role !== 'admin') {
|
||||
setError('У вас нет прав администратора.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
setError('Произошла ошибка при входе');
|
||||
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('userName', user.name);
|
||||
localStorage.setItem('userEmail', user.email);
|
||||
|
||||
router.push('/admin');
|
||||
} catch (err) {
|
||||
const axiosError = err as AxiosError<{ message: string }>;
|
||||
setError(axiosError.response?.data?.message || 'Неверный email или пароль');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { User } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, secret } = await req.json();
|
||||
|
||||
// Проверяем секретный ключ
|
||||
const adminSecret = process.env.ADMIN_SECRET;
|
||||
if (!adminSecret || secret !== adminSecret) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный секретный ключ' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
await connectDB();
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Назначаем пользователя администратором
|
||||
user.isAdmin = true;
|
||||
await user.save();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Пользователь успешно назначен администратором'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating admin:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Произошла ошибка при назначении администратора' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { Movie } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await connectDB();
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const movies = await Movie.find().sort({ createdAt: -1 });
|
||||
return NextResponse.json(movies);
|
||||
} catch (error) {
|
||||
console.error('Error fetching movies:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch movies' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { Movie } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
await connectDB();
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const { movieId } = await request.json();
|
||||
if (!movieId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Movie ID is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const movie = await Movie.findById(movieId);
|
||||
if (!movie) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Movie not found' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
movie.isVisible = !movie.isVisible;
|
||||
await movie.save();
|
||||
|
||||
return NextResponse.json({ success: true, movie });
|
||||
} catch (error) {
|
||||
console.error('Error toggling movie visibility:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to toggle movie visibility' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { User } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
import { sendVerificationEmail } from '@/lib/email';
|
||||
import { generateVerificationToken } from '@/lib/utils';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
|
||||
await connectDB();
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
|
||||
if (!user || !user.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Доступ запрещен' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const token = generateVerificationToken();
|
||||
await sendVerificationEmail(email, token);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error sending verification email:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to send verification email' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { User } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Доступ запрещен' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const { userId } = await req.json();
|
||||
|
||||
await connectDB();
|
||||
|
||||
const targetUser = await User.findById(userId);
|
||||
if (!targetUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем, что это не последний администратор
|
||||
if (targetUser.isAdmin) {
|
||||
const adminCount = await User.countDocuments({ isAdmin: true });
|
||||
if (adminCount <= 1) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Нельзя отозвать права у последнего администратора' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Переключаем статус администратора
|
||||
targetUser.isAdmin = !targetUser.isAdmin;
|
||||
await targetUser.save();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
isAdmin: targetUser.isAdmin,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error toggling admin status:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Произошла ошибка при изменении прав администратора' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { User } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Доступ запрещен' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const { userId } = await req.json();
|
||||
|
||||
await connectDB();
|
||||
|
||||
const targetUser = await User.findById(userId);
|
||||
if (!targetUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем, что это не последний администратор
|
||||
if (targetUser.isAdmin) {
|
||||
const adminCount = await User.countDocuments({ isAdmin: true });
|
||||
if (adminCount <= 1) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Нельзя отозвать права у последнего администратора' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Переключаем статус администратора
|
||||
targetUser.isAdmin = !targetUser.isAdmin;
|
||||
await targetUser.save();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
isAdmin: targetUser.isAdmin,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error toggling admin status:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Произошла ошибка при изменении прав администратора' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { User } from '@/models';
|
||||
import { connectDB } from '@/lib/db';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, code } = await req.json();
|
||||
|
||||
await connectDB();
|
||||
|
||||
const user = await User.findOne({ email });
|
||||
|
||||
if (!user || !user.isAdmin) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Доступ запрещен' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем код
|
||||
if (!user.adminVerificationCode ||
|
||||
user.adminVerificationCode.code !== code ||
|
||||
new Date() > new Date(user.adminVerificationCode.expiresAt)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный или устаревший код подтверждения' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Очищаем код после успешной проверки
|
||||
user.adminVerificationCode = undefined;
|
||||
await user.save();
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error verifying code:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Произошла ошибка при проверке кода' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const revalidate = 0; // always fresh
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const imdbId = searchParams.get('imdb_id');
|
||||
const tmdbId = searchParams.get('tmdb_id');
|
||||
|
||||
if (!imdbId && !tmdbId) {
|
||||
return NextResponse.json({ error: 'imdb_id or tmdb_id query param is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const token = process.env.ALLOHA_TOKEN;
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Server misconfiguration: ALLOHA_TOKEN missing' }, { status: 500 });
|
||||
}
|
||||
|
||||
const idParam = imdbId ? `imdb=${encodeURIComponent(imdbId)}` : `tmdb=${encodeURIComponent(tmdbId!)}`;
|
||||
const apiUrl = `https://api.alloha.tv/?token=${token}&${idParam}`;
|
||||
const apiRes = await fetch(apiUrl, { next: { revalidate: 0 } });
|
||||
|
||||
if (!apiRes.ok) {
|
||||
return NextResponse.json({ error: 'Failed to fetch from Alloha' }, { status: apiRes.status });
|
||||
}
|
||||
|
||||
const json = await apiRes.json();
|
||||
if (json.status !== 'success' || !json.data?.iframe) {
|
||||
return NextResponse.json({ error: 'Video not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ iframe: json.data.iframe });
|
||||
} catch (e) {
|
||||
console.error('Alloha API route error:', e);
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import NextAuth, { DefaultSession } from 'next-auth';
|
||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||
import { compare } from 'bcrypt';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
// Расширяем тип User в сессии
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
verified: boolean;
|
||||
isAdmin: boolean;
|
||||
adminVerified?: boolean;
|
||||
} & DefaultSession['user']
|
||||
}
|
||||
}
|
||||
|
||||
const handler = NextAuth({
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: 'credentials',
|
||||
credentials: {
|
||||
email: { label: 'Email', type: 'email' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
isAdminLogin: { label: 'isAdminLogin', type: 'boolean' }
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
throw new Error('Необходимо указать email и пароль');
|
||||
}
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email: credentials.email });
|
||||
|
||||
if (!user) {
|
||||
throw new Error('Пользователь не найден');
|
||||
}
|
||||
|
||||
const isPasswordValid = await compare(credentials.password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Неверный пароль');
|
||||
}
|
||||
|
||||
// Проверяем верификацию
|
||||
if (!user.verified) {
|
||||
throw new Error('EMAIL_NOT_VERIFIED');
|
||||
}
|
||||
|
||||
// Если это попытка входа в админ-панель
|
||||
if (credentials.isAdminLogin === 'true') {
|
||||
// Проверяем, является ли пользователь админом
|
||||
if (!user.isAdmin) {
|
||||
throw new Error('NOT_AN_ADMIN');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: user.verified,
|
||||
isAdmin: user.isAdmin,
|
||||
adminVerified: credentials.isAdminLogin === 'true'
|
||||
};
|
||||
}
|
||||
})
|
||||
],
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
error: '/login'
|
||||
},
|
||||
callbacks: {
|
||||
jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
token.verified = user.verified;
|
||||
token.isAdmin = user.isAdmin;
|
||||
token.adminVerified = user.adminVerified;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session({ session, token }) {
|
||||
if (session.user) {
|
||||
session.user.id = token.id as string;
|
||||
session.user.verified = token.verified as boolean;
|
||||
session.user.isAdmin = token.isAdmin as boolean;
|
||||
session.user.adminVerified = token.adminVerified as boolean;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
@@ -1,23 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Пользователь не найден' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ verified: user.verified ?? false });
|
||||
} catch (error) {
|
||||
console.error('Error checking verification status:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Внутренняя ошибка сервера' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { hash } from 'bcryptjs';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password, name } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
// Проверяем, существует ли пользователь
|
||||
const existingUser = await db.collection('users').findOne({ email });
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email уже зарегистрирован' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Хешируем пароль
|
||||
const hashedPassword = await hash(password, 12);
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Создаем пользователя
|
||||
await db.collection('users').insertOne({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
verified: false,
|
||||
verificationCode,
|
||||
verificationExpires,
|
||||
isAdmin: false, // Явно указываем, что новый пользователь не админ
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
// Отправляем код подтверждения
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
email,
|
||||
message: 'Пользователь успешно зарегистрирован',
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ message: 'Ошибка при регистрации' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Генерируем новый код
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Обновляем код в базе
|
||||
await db.collection('users').updateOne(
|
||||
{ email },
|
||||
{
|
||||
$set: {
|
||||
verificationCode,
|
||||
verificationExpires,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Отправляем новый код
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ message: 'Ошибка при отправке кода' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, code } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
if (user.verificationCode !== code) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный код подтверждения' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (user.verificationExpires < new Date()) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Код подтверждения истек' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Подтверждаем аккаунт
|
||||
await db.collection('users').updateOne(
|
||||
{ email },
|
||||
{
|
||||
$set: {
|
||||
verified: true,
|
||||
verificationCode: null,
|
||||
verificationExpires: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ message: 'Ошибка при подтверждении' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
// DELETE /api/favorites/[mediaId] - удалить из избранного
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: { mediaId: string } }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const mediaType = searchParams.get('mediaType');
|
||||
const mediaId = params.mediaId;
|
||||
|
||||
if (!mediaType || !mediaId) {
|
||||
return NextResponse.json({ error: 'Missing mediaType or mediaId' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
const result = await db.collection('favorites').deleteOne({
|
||||
userId: session.user.email,
|
||||
mediaId,
|
||||
mediaType
|
||||
});
|
||||
|
||||
if (result.deletedCount === 0) {
|
||||
return NextResponse.json({ error: 'Favorite not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: 'Removed from favorites' });
|
||||
} catch (error) {
|
||||
console.error('Error removing from favorites:', error);
|
||||
return NextResponse.json({ error: 'Failed to remove from favorites' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
|
||||
// GET /api/favorites/check/[mediaId] - проверить есть ли в избранном
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { mediaId: string } }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const mediaType = searchParams.get('mediaType');
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
const favorite = await db.collection('favorites').findOne({
|
||||
userId: session.user.email,
|
||||
mediaId: params.mediaId,
|
||||
mediaType
|
||||
});
|
||||
|
||||
return NextResponse.json({ isFavorite: !!favorite });
|
||||
} catch (error) {
|
||||
console.error('Error checking favorite:', error);
|
||||
return NextResponse.json({ error: 'Failed to check favorite status' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { connectToDatabase, resetIndexes } from '@/lib/mongodb';
|
||||
|
||||
// Флаг для отслеживания инициализации
|
||||
let isInitialized = false;
|
||||
|
||||
// GET /api/favorites - получить все избранные
|
||||
export async function GET() {
|
||||
try {
|
||||
// Инициализируем индексы при первом запросе
|
||||
if (!isInitialized) {
|
||||
await resetIndexes();
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const favorites = await db.collection('favorites')
|
||||
.find({ userId: session.user.email })
|
||||
.sort({ createdAt: -1 })
|
||||
.toArray();
|
||||
|
||||
return NextResponse.json(favorites);
|
||||
} catch (error) {
|
||||
console.error('Error getting favorites:', error);
|
||||
return NextResponse.json({ error: 'Failed to get favorites' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/favorites - добавить в избранное
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
// Инициализируем индексы при первом запросе
|
||||
if (!isInitialized) {
|
||||
await resetIndexes();
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
const session = await getServerSession();
|
||||
if (!session?.user?.email) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { mediaId, mediaType, title, posterPath } = await request.json();
|
||||
|
||||
if (!mediaId || !mediaType || !title) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
const favorite = {
|
||||
userId: session.user.email,
|
||||
mediaId: mediaId.toString(), // Преобразуем в строку для консистентности
|
||||
mediaType,
|
||||
title,
|
||||
posterPath,
|
||||
createdAt: new Date()
|
||||
};
|
||||
|
||||
// Используем updateOne с upsert вместо insertOne
|
||||
const result = await db.collection('favorites').updateOne(
|
||||
{
|
||||
userId: session.user.email,
|
||||
mediaId: favorite.mediaId,
|
||||
mediaType
|
||||
},
|
||||
{ $set: favorite },
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
// Если документ был обновлен (уже существовал)
|
||||
if (result.matchedCount > 0) {
|
||||
return NextResponse.json({ message: 'Already in favorites' }, { status: 200 });
|
||||
}
|
||||
|
||||
// Если документ был создан (новый)
|
||||
return NextResponse.json(favorite);
|
||||
} catch (error) {
|
||||
console.error('Error adding to favorites:', error);
|
||||
return NextResponse.json({ error: 'Failed to add to favorites' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { compare } from 'bcryptjs';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const isPasswordValid = await compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный пароль' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.verified) {
|
||||
return NextResponse.json(
|
||||
{ error: 'EMAIL_NOT_VERIFIED' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Создаем JWT токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: user.verified,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
process.env.NEXTAUTH_SECRET!,
|
||||
{ expiresIn: '30d' }
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
user: {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: user.verified,
|
||||
isAdmin: user.isAdmin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка входа' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { hash } from 'bcryptjs';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password, name } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
// Проверяем, существует ли пользователь
|
||||
const existingUser = await db.collection('users').findOne({ email });
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email уже зарегистрирован' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Хешируем пароль
|
||||
const hashedPassword = await hash(password, 12);
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Создаем пользователя
|
||||
const result = await db.collection('users').insertOne({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
verified: false,
|
||||
verificationCode,
|
||||
verificationExpires,
|
||||
isAdmin: false,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
// Отправляем код подтверждения
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({
|
||||
id: result.insertedId.toString(),
|
||||
email,
|
||||
name,
|
||||
verified: false,
|
||||
isAdmin: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка регистрации' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
if (user.verified) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email уже подтвержден' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Генерируем новый код
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Обновляем код в базе
|
||||
await db.collection('users').updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: {
|
||||
verificationCode,
|
||||
verificationExpires
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Отправляем новый код
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Resend code error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка отправки кода' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, code } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({
|
||||
email,
|
||||
verificationCode: code,
|
||||
verificationExpires: { $gt: new Date() }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный код или код истек' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Подтверждаем email
|
||||
await db.collection('users').updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: { verified: true },
|
||||
$unset: { verificationCode: "", verificationExpires: "" }
|
||||
}
|
||||
);
|
||||
|
||||
// Создаем JWT токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: true,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
process.env.NEXTAUTH_SECRET!,
|
||||
{ expiresIn: '30d' }
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
user: {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: true,
|
||||
isAdmin: user.isAdmin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Verification error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка верификации' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { favoritesAPI } from '@/lib/favoritesApi';
|
||||
import { getImageUrl } from '@/lib/neoApi';
|
||||
|
||||
@@ -87,14 +87,15 @@ interface Favorite {
|
||||
}
|
||||
|
||||
export default function FavoritesPage() {
|
||||
const { data: session } = useSession();
|
||||
const [favorites, setFavorites] = useState<Favorite[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFavorites = async () => {
|
||||
if (!session?.user) {
|
||||
setLoading(false);
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,14 +103,19 @@ export default function FavoritesPage() {
|
||||
const response = await favoritesAPI.getFavorites();
|
||||
setFavorites(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching favorites:', error);
|
||||
console.error('Failed to fetch favorites:', error);
|
||||
// If token is invalid, clear it and redirect to login
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userName');
|
||||
localStorage.removeItem('userEmail');
|
||||
router.push('/login');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFavorites();
|
||||
}, [session?.user]);
|
||||
}, [router]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -120,16 +126,7 @@ export default function FavoritesPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!session?.user) {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Избранное</Title>
|
||||
<EmptyState>
|
||||
Для доступа к избранному необходимо авторизоваться
|
||||
</EmptyState>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (favorites.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const Container = styled.div`
|
||||
@@ -173,6 +173,14 @@ export default function LoginClient() {
|
||||
const [name, setName] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const router = useRouter();
|
||||
const { login, register } = useAuth();
|
||||
|
||||
// Redirect authenticated users away from /login
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && localStorage.getItem('token')) {
|
||||
router.replace('/');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -180,38 +188,11 @@ export default function LoginClient() {
|
||||
|
||||
try {
|
||||
if (isLogin) {
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
if (result.error === 'EMAIL_NOT_VERIFIED') {
|
||||
router.push(`/verify?email=${encodeURIComponent(email)}`);
|
||||
return;
|
||||
}
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
router.push('/');
|
||||
await login(email, password);
|
||||
} else {
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password, name }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || 'Ошибка при регистрации');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
await register(email, password, name);
|
||||
// Сохраняем пароль для автовхода после верификации
|
||||
localStorage.setItem('password', password);
|
||||
|
||||
router.push(`/verify?email=${encodeURIComponent(email)}`);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const LoginClient = dynamic(() => import('./LoginClient'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
export default function LoginPage() {
|
||||
return (
|
||||
<Container>
|
||||
<GlowingBackground>
|
||||
<Glow1 />
|
||||
<Glow2 />
|
||||
<Glow3 />
|
||||
</GlowingBackground>
|
||||
|
||||
<Content>
|
||||
<Logo>
|
||||
<span>Neo</span> Movies
|
||||
</Logo>
|
||||
|
||||
<GlassCard>
|
||||
<LoginClient />
|
||||
</GlassCard>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
background-color: #0a0a0a;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const Content = styled.main`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
padding: 2rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const Logo = styled.h1`
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 2rem;
|
||||
color: white;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
color: #2196f3;
|
||||
}
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 3rem;
|
||||
border-radius: 24px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-shadow:
|
||||
0 8px 32px 0 rgba(0, 0, 0, 0.3),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.05);
|
||||
margin: 0 auto;
|
||||
`;
|
||||
|
||||
const GlowingBackground = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
`;
|
||||
|
||||
const Glow = styled.div`
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(100px);
|
||||
opacity: 0.3;
|
||||
animation: float 20s infinite ease-in-out;
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0); }
|
||||
50% { transform: translate(-30px, 30px); }
|
||||
}
|
||||
`;
|
||||
|
||||
const Glow1 = styled(Glow)`
|
||||
background: #2196f3;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
top: -200px;
|
||||
left: -200px;
|
||||
animation-delay: 0s;
|
||||
`;
|
||||
|
||||
const Glow2 = styled(Glow)`
|
||||
background: #9c27b0;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
bottom: -150px;
|
||||
right: -150px;
|
||||
animation-delay: -5s;
|
||||
`;
|
||||
|
||||
const Glow3 = styled(Glow)`
|
||||
background: #00bcd4;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
bottom: 100px;
|
||||
left: 30%;
|
||||
animation-delay: -10s;
|
||||
`;
|
||||
@@ -9,14 +9,13 @@ interface PageProps {
|
||||
}
|
||||
|
||||
// Генерация метаданных для страницы
|
||||
export async function generateMetadata(
|
||||
props: { params: { id: string }}
|
||||
): Promise<Metadata> {
|
||||
export async function generateMetadata(props: Promise<PageProps>): Promise<Metadata> {
|
||||
const { params } = await props;
|
||||
// В Next.js 14, нужно сначала получить данные фильма,
|
||||
// а затем использовать их для метаданных
|
||||
try {
|
||||
// Получаем id для использования в запросе
|
||||
const movieId = props.params.id;
|
||||
const movieId = params.id;
|
||||
|
||||
// Запрашиваем данные фильма
|
||||
const { data: movie } = await moviesAPI.getMovie(movieId);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import styled from 'styled-components';
|
||||
import GlassCard from '@/components/GlassCard';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const Container = styled.div`
|
||||
min-height: 100vh;
|
||||
@@ -70,16 +70,28 @@ const SignOutButton = styled.button`
|
||||
`;
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { data: session, status } = useSession();
|
||||
const { logout } = useAuth();
|
||||
const router = useRouter();
|
||||
const [userName, setUserName] = useState<string | null>(null);
|
||||
const [userEmail, setUserEmail] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'unauthenticated') {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
router.push('/login');
|
||||
} else {
|
||||
setUserName(localStorage.getItem('userName'));
|
||||
setUserEmail(localStorage.getItem('userEmail'));
|
||||
setLoading(false);
|
||||
}
|
||||
}, [status, router]);
|
||||
}, [router]);
|
||||
|
||||
if (status === 'loading') {
|
||||
const handleSignOut = () => {
|
||||
logout();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container>
|
||||
<Content>
|
||||
@@ -91,7 +103,7 @@ export default function ProfilePage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
if (!userName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -101,14 +113,12 @@ export default function ProfilePage() {
|
||||
<GlassCard>
|
||||
<ProfileHeader>
|
||||
<Avatar>
|
||||
{session.user?.name?.split(' ').map(n => n[0]).join('').toUpperCase() || ''}
|
||||
{userName?.split(' ').map(n => n[0]).join('').toUpperCase() || ''}
|
||||
</Avatar>
|
||||
<Name>{session.user?.name}</Name>
|
||||
<Email>{session.user?.email}</Email>
|
||||
<Name>{userName}</Name>
|
||||
<Email>{userEmail}</Email>
|
||||
<SignOutButton onClick={handleSignOut}>Выйти</SignOutButton>
|
||||
</ProfileHeader>
|
||||
<SignOutButton onClick={() => router.push('/settings')}>
|
||||
Настройки
|
||||
</SignOutButton>
|
||||
</GlassCard>
|
||||
</Content>
|
||||
</Container>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
|
||||
const theme = {
|
||||
colors: {
|
||||
primary: '#2196f3',
|
||||
background: '#121212',
|
||||
surface: '#1e1e1e',
|
||||
text: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.7)',
|
||||
error: '#f44336',
|
||||
success: '#4caf50',
|
||||
},
|
||||
breakpoints: {
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
},
|
||||
};
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<SessionProvider refetchInterval={0} refetchOnWindowFocus={false}>
|
||||
{children}
|
||||
</SessionProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
import { authAPI } from '@/lib/authApi';
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
@@ -40,7 +41,7 @@ const CodeInput = styled.input`
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${({ theme }) => theme.colors.primary};
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 0 0 4px rgba(33, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ const VerifyButton = styled.button`
|
||||
const ResendButton = styled.button`
|
||||
background: none;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.colors.primary};
|
||||
color: #2196f3;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
@@ -110,6 +111,7 @@ export function VerificationClient({ email }: { email: string }) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const router = useRouter();
|
||||
const { verifyCode, login } = useAuth();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -134,32 +136,7 @@ export function VerificationClient({ email }: { email: string }) {
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, code }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Ошибка верификации');
|
||||
}
|
||||
|
||||
// Выполняем вход после успешной верификации
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email,
|
||||
password: localStorage.getItem('password'),
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
router.push('/');
|
||||
await verifyCode(code);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Произошла ошибка');
|
||||
} finally {
|
||||
@@ -169,18 +146,7 @@ export function VerificationClient({ email }: { email: string }) {
|
||||
|
||||
const handleResend = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/auth/resend-code', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Не удалось отправить код');
|
||||
}
|
||||
|
||||
await authAPI.resendCode(email);
|
||||
setCountdown(60);
|
||||
} catch (err) {
|
||||
setError('Не удалось отправить код');
|
||||
|
||||
Reference in New Issue
Block a user