From abb4424ba4674972f724fea098fb623eab1988fd Mon Sep 17 00:00:00 2001 From: Foxix Date: Tue, 24 Dec 2024 15:15:12 +0000 Subject: [PATCH] Update 9 files - /src/api.ts - /src/app/admin/login/AdminLoginClient.tsx - /src/app/admin/login/page.tsx - /src/app/api/auth/[...nextauth]/route.ts - /src/components/MovieCard.tsx - /src/components/MoviePlayer.tsx - /src/components/Navbar.tsx - /src/lib/api.ts - /src/lib/auth.ts --- src/api.ts | 9 +- src/app/admin/login/AdminLoginClient.tsx | 147 +++++++++++++++++++++++ src/app/admin/login/page.tsx | 5 + src/app/api/auth/[...nextauth]/route.ts | 17 ++- src/components/MovieCard.tsx | 2 +- src/components/MoviePlayer.tsx | 2 +- src/components/Navbar.tsx | 7 +- src/lib/api.ts | 12 +- src/lib/auth.ts | 1 - 9 files changed, 186 insertions(+), 16 deletions(-) create mode 100644 src/app/admin/login/AdminLoginClient.tsx create mode 100644 src/app/admin/login/page.tsx diff --git a/src/api.ts b/src/api.ts index bd1925b..e7dc46e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,12 +1,15 @@ import axios from 'axios'; const BASE_URL = 'https://api.themoviedb.org/3'; -const AUTH_TOKEN = 'Bearer process.env.TMDB_API_KEY'; + +if (typeof window === 'undefined' && !process.env.NEXT_PUBLIC_TMDB_ACCESS_TOKEN) { + throw new Error('TMDB_ACCESS_TOKEN is not defined in environment variables'); +} export const api = axios.create({ baseURL: BASE_URL, headers: { - 'Authorization': AUTH_TOKEN, + 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_TMDB_ACCESS_TOKEN}`, 'Content-Type': 'application/json' } }); @@ -147,4 +150,4 @@ export const moviesAPI = { return []; } } -}; +}; \ No newline at end of file diff --git a/src/app/admin/login/AdminLoginClient.tsx b/src/app/admin/login/AdminLoginClient.tsx new file mode 100644 index 0000000..5660e2d --- /dev/null +++ b/src/app/admin/login/AdminLoginClient.tsx @@ -0,0 +1,147 @@ +'use client'; + +import { useState } from 'react'; +import { signIn } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import styled from 'styled-components'; + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 2rem; + background: #1a1a1a; +`; + +const Form = styled.form` + width: 100%; + max-width: 400px; + padding: 2rem; + background: rgba(255, 255, 255, 0.1); + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +`; + +const Title = styled.h1` + color: white; + text-align: center; + margin-bottom: 2rem; + font-size: 1.5rem; +`; + +const Input = styled.input` + width: 100%; + padding: 0.75rem; + margin-bottom: 1rem; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + background: rgba(255, 255, 255, 0.05); + color: white; + font-size: 1rem; + + &:focus { + outline: none; + border-color: #3b82f6; + } + + &::placeholder { + color: rgba(255, 255, 255, 0.5); + } +`; + +const Button = styled.button` + width: 100%; + padding: 0.75rem; + background: #3b82f6; + color: white; + border: none; + border-radius: 4px; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background: #2563eb; + } + + &:disabled { + background: #64748b; + cursor: not-allowed; + } +`; + +const ErrorMessage = styled.div` + color: #ef4444; + margin-bottom: 1rem; + text-align: center; +`; + +export default function AdminLoginClient() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + const result = await signIn('credentials', { + 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'); + } + } catch (error) { + setError('Произошла ошибка при входе'); + } finally { + setIsLoading(false); + } + }; + + return ( + +
+ Вход в админ-панель + {error && {error}} + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + +
+
+ ); +} diff --git a/src/app/admin/login/page.tsx b/src/app/admin/login/page.tsx new file mode 100644 index 0000000..da44a9d --- /dev/null +++ b/src/app/admin/login/page.tsx @@ -0,0 +1,5 @@ +import AdminLoginClient from './AdminLoginClient'; + +export default function AdminLoginPage() { + return ; +} diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 219a708..3b5f4a7 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -12,6 +12,7 @@ declare module 'next-auth' { email: string; verified: boolean; isAdmin: boolean; + adminVerified?: boolean; } & DefaultSession['user'] } } @@ -23,7 +24,7 @@ const handler = NextAuth({ credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, - isAdminVerified: { label: 'isAdminVerified', type: 'checkbox' } + isAdminLogin: { label: 'isAdminLogin', type: 'boolean' } }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { @@ -48,9 +49,12 @@ const handler = NextAuth({ throw new Error('EMAIL_NOT_VERIFIED'); } - // Проверяем права администратора и флаг верификации для админ-панели - if (user.isAdmin && !credentials.isAdminVerified) { - throw new Error('ADMIN_NOT_VERIFIED'); + // Если это попытка входа в админ-панель + if (credentials.isAdminLogin === 'true') { + // Проверяем, является ли пользователь админом + if (!user.isAdmin) { + throw new Error('NOT_AN_ADMIN'); + } } return { @@ -58,7 +62,8 @@ const handler = NextAuth({ email: user.email, name: user.name, verified: user.verified, - isAdmin: user.isAdmin + isAdmin: user.isAdmin, + adminVerified: credentials.isAdminLogin === 'true' }; } }) @@ -73,6 +78,7 @@ const handler = NextAuth({ token.id = user.id; token.verified = user.verified; token.isAdmin = user.isAdmin; + token.adminVerified = user.adminVerified; } return token; }, @@ -81,6 +87,7 @@ const handler = NextAuth({ 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; } diff --git a/src/components/MovieCard.tsx b/src/components/MovieCard.tsx index 4700e77..fb5629c 100644 --- a/src/components/MovieCard.tsx +++ b/src/components/MovieCard.tsx @@ -87,7 +87,7 @@ const Rating = styled.div` right: 8px; padding: 4px 8px; border-radius: 6px; - font-size: 12px; + font-size: 17px; font-weight: 600; color: white; `; diff --git a/src/components/MoviePlayer.tsx b/src/components/MoviePlayer.tsx index e74400a..13b916c 100644 --- a/src/components/MoviePlayer.tsx +++ b/src/components/MoviePlayer.tsx @@ -210,7 +210,7 @@ export default function MoviePlayer({ id, title, poster, imdbId }: MoviePlayerPr {settings.defaultPlayer === 'lumex' && imdbId ? ( diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 951da19..3ee70de 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -216,7 +216,7 @@ const AuthButtons = styled.div` export default function Navbar() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false); - const { data: session } = useSession(); + const { data: session, status } = useSession(); const pathname = usePathname(); const router = useRouter(); @@ -225,6 +225,11 @@ export default function Navbar() { return null; } + // Если сессия загружается, показываем плейсхолдер + if (status === 'loading') { + return null; + } + const handleNavigation = (href: string, onClick?: () => void) => { if (onClick) { onClick(); diff --git a/src/lib/api.ts b/src/lib/api.ts index 31ede32..8ae72bd 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,13 +1,17 @@ import axios from 'axios'; const BASE_URL = 'https://api.themoviedb.org/3'; -const API_KEY = 'process.env.TMDB_API_KEY'; + +if (typeof window === 'undefined' && !process.env.NEXT_PUBLIC_TMDB_ACCESS_TOKEN) { + throw new Error('TMDB_ACCESS_TOKEN is not defined in environment variables'); +} export const api = axios.create({ baseURL: BASE_URL, - params: { - api_key: API_KEY, - }, + headers: { + 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_TMDB_ACCESS_TOKEN}`, + 'Content-Type': 'application/json' + } }); export interface Genre { diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 1799081..3f3fc79 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -88,4 +88,3 @@ declare module 'next-auth/jwt' { isAdmin: boolean; } } -