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 (
+
+
+
+ );
+}
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;
}
}
-