Начат фронтенд
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
<title>ospab.host - первый хостинг в Великом Новгороде</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
1526
ospabhost/frontend/package-lock.json
generated
1526
ospabhost/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,19 +10,24 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.9.1"
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"postcss": "^8.4.21",
|
||||
"react-router-dom": "^7.9.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2"
|
||||
|
||||
6
ospabhost/frontend/postcss.config.js
Normal file
6
ospabhost/frontend/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
import PageTmpl from './components/pagetempl';
|
||||
import HomePage from './pages/index';
|
||||
import MainPage from './pages/dashboard/mainpage';
|
||||
import LogoutPage from './pages/dashboard/logout';
|
||||
import LoginPage from './pages/login';
|
||||
import RegisterPage from './pages/register';
|
||||
import DashboardPage from './pages/dashboard/index';
|
||||
import TariffsPage from './pages/tariffs';
|
||||
import AboutPage from './pages/about';
|
||||
import PrivateRoute from './components/privateroute';
|
||||
|
||||
const App = () => {
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<header className="bg-gray-800 text-white p-4">
|
||||
<nav className="container mx-auto flex justify-between items-center">
|
||||
<Link to="/" className="text-2xl font-bold">ospab.host</Link>
|
||||
<div>
|
||||
<Link to="/login" className="px-4 py-2 hover:bg-gray-700 rounded-md">Вход</Link>
|
||||
<Link to="/register" className="px-4 py-2 ml-2 hover:bg-gray-700 rounded-md">Регистрация</Link>
|
||||
<Link to="/dashboard" className="px-4 py-2 ml-2 hover:bg-gray-700 rounded-md">Дашборд</Link>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main className="p-4">
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegisterPage />} />
|
||||
<Route path="/dashboard" element={<DashboardPage />} />
|
||||
</Routes>
|
||||
</main>
|
||||
<Routes>
|
||||
<Route path="/" element={<PageTmpl><HomePage /></PageTmpl>} />
|
||||
<Route path="/tariffs" element={<PageTmpl><TariffsPage /></PageTmpl>} />
|
||||
<Route path="/about" element={<PageTmpl><AboutPage /></PageTmpl>} />
|
||||
<Route path="/dashboard" element={<PageTmpl><PrivateRoute><MainPage /></PrivateRoute></PageTmpl>} />
|
||||
<Route path="/login" element={<PageTmpl><LoginPage /></PageTmpl>} />
|
||||
<Route path="/register" element={<PageTmpl><RegisterPage /></PageTmpl>} />
|
||||
<Route path="/logout" element={<PageTmpl><LogoutPage /></PageTmpl>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default App;
|
||||
49
ospabhost/frontend/src/components/footer.tsx
Normal file
49
ospabhost/frontend/src/components/footer.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Footer = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-800 text-white py-12">
|
||||
<div className="container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center md:text-left">
|
||||
{/* About Section */}
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4">О нас</h3>
|
||||
<p className="text-sm text-gray-400">
|
||||
ospab.host - это надежный хостинг для ваших проектов. Мы предлагаем высокую производительность и круглосуточную поддержку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4">Навигация</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><Link to="/" className="text-gray-400 hover:text-white transition-colors">Главная</Link></li>
|
||||
<li><Link to="/tariffs" className="text-gray-400 hover:text-white transition-colors">Тарифы</Link></li>
|
||||
<li><Link to="/about" className="text-gray-400 hover:text-white transition-colors">О нас</Link></li>
|
||||
<li><Link to="/login" className="text-gray-400 hover:text-white transition-colors">Войти</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Legal Documents */}
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4">Документы</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li><a href="#" className="text-gray-400 hover:text-white transition-colors">Политика конфиденциальности</a></li>
|
||||
<li><a href="#" className="text-gray-400 hover:text-white transition-colors">Условия использования</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-gray-700 text-center">
|
||||
<p className="text-sm text-gray-400">
|
||||
© {currentYear} ospab.host. Все права защищены.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
58
ospabhost/frontend/src/components/header.tsx
Normal file
58
ospabhost/frontend/src/components/header.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const Header = () => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkLoginStatus = () => {
|
||||
setIsLoggedIn(localStorage.getItem('isLoggedIn') === 'true');
|
||||
};
|
||||
|
||||
checkLoginStatus();
|
||||
window.addEventListener('storage', checkLoginStatus);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('storage', checkLoginStatus);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav className="bg-white shadow-lg fixed w-full z-10 top-0">
|
||||
<div className="container mx-auto px-6 py-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-xl font-bold text-gray-800">
|
||||
<Link to="/" className="font-mono text-2xl">ospab.host</Link>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link to="/tariffs" className="text-gray-600 hover:text-ospab-primary transition-colors">Тарифы</Link>
|
||||
<Link to="/about" className="text-gray-600 hover:text-ospab-primary transition-colors">О нас</Link>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<Link to="/dashboard" className="text-gray-600 hover:text-ospab-primary transition-colors">Личный кабинет</Link>
|
||||
<Link
|
||||
to="/logout"
|
||||
className="px-4 py-2 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-gray-500 hover:bg-red-500"
|
||||
>
|
||||
Выйти
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link to="/login" className="text-gray-600 hover:text-ospab-primary transition-colors">Войти</Link>
|
||||
<Link
|
||||
to="/register"
|
||||
className="px-4 py-2 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
21
ospabhost/frontend/src/components/pagetempl.tsx
Normal file
21
ospabhost/frontend/src/components/pagetempl.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import Header from './header';
|
||||
import Footer from './footer';
|
||||
import React from 'react';
|
||||
|
||||
interface PageTmplProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PageTmpl: React.FC<PageTmplProps> = ({ children }) => {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Header />
|
||||
<main className="flex-grow pt-16">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTmpl;
|
||||
13
ospabhost/frontend/src/components/privateroute.tsx
Normal file
13
ospabhost/frontend/src/components/privateroute.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
interface PrivateRouteProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children }) => {
|
||||
const isAuthenticated = localStorage.getItem('isLoggedIn') === 'true';
|
||||
return isAuthenticated ? children : <Navigate to="/login" replace />;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
@@ -1,68 +1,3 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
17
ospabhost/frontend/src/pages/about.tsx
Normal file
17
ospabhost/frontend/src/pages/about.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
const AboutPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center py-20 px-4">
|
||||
<div className="bg-white p-10 rounded-3xl shadow-2xl max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6">О компании ospab.host</h1>
|
||||
<p className="text-lg text-gray-700 mb-4">
|
||||
Мы предоставляем надежные и масштабируемые хостинг-решения для разработчиков, стартапов и крупных компаний. Наша миссия — дать вам инструменты, необходимые для реализации ваших идей в интернете, обеспечивая при этом высокую производительность и безопасность.
|
||||
</p>
|
||||
<p className="text-lg text-gray-700">
|
||||
Наша инфраструктура построена на современных технологиях, а круглосуточная поддержка всегда готова помочь вам. Мы верим, что качественный хостинг должен быть доступен каждому.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPage;
|
||||
@@ -1,3 +0,0 @@
|
||||
const DashboardPage = () => <div>Дашборд (заглушка)</div>;
|
||||
|
||||
export default DashboardPage;
|
||||
24
ospabhost/frontend/src/pages/dashboard/logout.tsx
Normal file
24
ospabhost/frontend/src/pages/dashboard/logout.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const LogoutPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Удаляем все токены и флаг входа из localStorage
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
console.log('Выполняется выход из системы...');
|
||||
// После выхода перенаправляем пользователя на главную страницу
|
||||
navigate('/');
|
||||
}, [navigate]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<h1 className="text-xl font-bold text-gray-800">Выполняется выход...</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoutPage;
|
||||
26
ospabhost/frontend/src/pages/dashboard/mainpage.tsx
Normal file
26
ospabhost/frontend/src/pages/dashboard/mainpage.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const MainPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col items-center justify-center p-4">
|
||||
<div className="bg-white p-10 rounded-3xl shadow-2xl text-center max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-900 leading-tight">
|
||||
Добро пожаловать в личный кабинет!
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Здесь будет информация о твоих проектах и статистика.
|
||||
</p>
|
||||
<div className="mt-8">
|
||||
<Link
|
||||
to="/logout"
|
||||
className="px-6 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Выйти
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainPage;
|
||||
@@ -1,8 +1,88 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FaServer, FaCloud, FaShieldAlt } from 'react-icons/fa';
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div className="text-center mt-20">
|
||||
<h1 className="text-4xl font-bold">Добро пожаловать на ospab.host!</h1>
|
||||
<p className="text-lg mt-4">Мы работаем над нашим сайтом.</p>
|
||||
<div className="min-h-screen bg-gray-50 text-gray-800">
|
||||
{/* Hero Section */}
|
||||
<section className="relative bg-gradient-to-b from-blue-100 to-white pt-24 pb-32">
|
||||
<div className="container mx-auto text-center px-4">
|
||||
<h1 className="text-5xl md:text-6xl font-extrabold leading-tight tracking-tighter text-gray-900">
|
||||
Масштабируемый хостинг <br /> для ваших идей
|
||||
</h1>
|
||||
<p className="mt-6 text-lg md:text-xl max-w-2xl mx-auto text-gray-700">
|
||||
Запускайте, масштабируйте и управляйте своими проектами с надёжной и высокопроизводительной инфраструктурой.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col sm:flex-row justify-center space-y-4 sm:space-y-0 sm:space-x-4">
|
||||
<Link
|
||||
to="/register"
|
||||
className="px-8 py-4 rounded-full text-white font-bold text-lg transition-transform transform hover:scale-105 shadow-lg bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Начать бесплатно
|
||||
</Link>
|
||||
<Link
|
||||
to="/login"
|
||||
className="px-8 py-4 rounded-full text-gray-800 font-bold text-lg border-2 border-gray-400 transition-colors hover:bg-gray-200 hover:border-gray-300"
|
||||
>
|
||||
Войти в аккаунт
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section className="py-20 px-4">
|
||||
<div className="container mx-auto">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-center mb-12 text-gray-900">Наши возможности</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl hover:shadow-2xl transition-shadow duration-300 transform hover:scale-105">
|
||||
<div className="flex justify-center mb-4">
|
||||
<FaServer className="text-5xl text-blue-500" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-center text-gray-900">Высокая производительность</h3>
|
||||
<p className="mt-2 text-center text-gray-700">
|
||||
Оптимизированные серверы для максимальной скорости загрузки вашего сайта.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl hover:shadow-2xl transition-shadow duration-300 transform hover:scale-105">
|
||||
<div className="flex justify-center mb-4">
|
||||
<FaCloud className="text-5xl text-blue-500" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-center text-gray-900">Масштабируемость</h3>
|
||||
<p className="mt-2 text-center text-gray-700">
|
||||
Легко увеличивайте или уменьшайте ресурсы по мере роста вашего проекта.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl hover:shadow-2xl transition-shadow duration-300 transform hover:scale-105">
|
||||
<div className="flex justify-center mb-4">
|
||||
<FaShieldAlt className="text-5xl text-blue-500" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-center text-gray-900">Надежность и безопасность</h3>
|
||||
<p className="mt-2 text-center text-gray-700">
|
||||
Ваши данные и приложения всегда под надёжной защитой.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Call to Action Section */}
|
||||
<section className="bg-gray-800 py-20 px-4 text-white text-center">
|
||||
<h2 className="text-4xl md:text-5xl font-bold leading-tight">
|
||||
Готовы начать?
|
||||
</h2>
|
||||
<p className="mt-4 text-lg md:text-xl max-w-2xl mx-auto text-gray-400">
|
||||
Присоединяйтесь к тысячам разработчиков, которые доверяют нам.
|
||||
</p>
|
||||
<div className="mt-8">
|
||||
<Link
|
||||
to="/register"
|
||||
className="px-8 py-4 rounded-full text-white font-bold text-lg transition-transform transform hover:scale-105 shadow-lg bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Начать бесплатно
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,75 +1,69 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
const response = await axios.post('http://localhost:5000/api/auth/login', {
|
||||
email: email,
|
||||
password: password,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setMessage(data.message);
|
||||
localStorage.setItem('token', data.token);
|
||||
navigate('/dashboard'); // Перенаправляем на дашборд после успешного входа
|
||||
} else {
|
||||
setMessage(data.message || 'Ошибка входа. Проверьте email и пароль.');
|
||||
}
|
||||
// Сохраняем токен в localStorage
|
||||
localStorage.setItem('access_token', response.data.token);
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
console.log('Успешный вход:', response.data);
|
||||
navigate('/dashboard'); // Перенаправляем на личный кабинет
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
setMessage('Не удалось подключиться к серверу.');
|
||||
let errMsg = 'Ошибка входа. Проверьте правильность email и пароля.';
|
||||
if (axios.isAxiosError(error)) {
|
||||
errMsg = error.response?.data?.message || errMsg;
|
||||
console.error('Ошибка входа:', error.response?.data || error.message);
|
||||
} else {
|
||||
console.error('Ошибка входа:', error);
|
||||
}
|
||||
alert(errMsg);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
||||
<h1 className="text-3xl font-bold mb-4">Вход</h1>
|
||||
<form onSubmit={handleLogin} className="bg-white p-8 rounded-lg shadow-md w-96">
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700">Email</label>
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white p-8 md:p-10 rounded-3xl shadow-2xl w-full max-w-md text-center">
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-6">Вход в аккаунт</h1>
|
||||
<form onSubmit={handleLogin}>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
placeholder="Электронная почта"
|
||||
className="w-full px-5 py-3 mb-4 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700">Пароль</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
placeholder="Пароль"
|
||||
className="w-full px-5 py-3 mb-6 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Войти
|
||||
</button>
|
||||
</form>
|
||||
{message && <p className="mt-4 text-center text-red-500">{message}</p>}
|
||||
<p className="mt-4">
|
||||
Нет аккаунта? <a href="/register" className="text-blue-500 hover:underline">Зарегистрироваться</a>
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full px-5 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Войти
|
||||
</button>
|
||||
</form>
|
||||
<p className="mt-6 text-gray-600">
|
||||
Нет аккаунта?{' '}
|
||||
<Link to="/register" className="text-ospab-primary font-bold hover:underline">
|
||||
Зарегистрироваться
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,86 +1,75 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
|
||||
const RegisterPage = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleRegister = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setMessage('');
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:5000/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, email, password }),
|
||||
const response = await axios.post('http://localhost:5000/api/auth/register', {
|
||||
username: username,
|
||||
email: email,
|
||||
password: password
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
setMessage(data.message);
|
||||
localStorage.setItem('token', data.token);
|
||||
navigate('/dashboard'); // Перенаправляем на дашборд после успешной регистрации
|
||||
} else {
|
||||
setMessage(data.message || 'Ошибка регистрации.');
|
||||
}
|
||||
console.log('Успешная регистрация:', response.data);
|
||||
navigate('/login'); // Перенаправляем пользователя на страницу входа
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
setMessage('Не удалось подключиться к серверу.');
|
||||
let errMsg = 'Ошибка регистрации. Пожалуйста, попробуйте снова.';
|
||||
if (axios.isAxiosError(error)) {
|
||||
errMsg = error.response?.data?.message || errMsg;
|
||||
console.error('Ошибка регистрации:', error.response?.data || error.message);
|
||||
} else {
|
||||
console.error('Ошибка регистрации:', error);
|
||||
}
|
||||
alert(errMsg);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
||||
<h1 className="text-3xl font-bold mb-4">Регистрация</h1>
|
||||
<form onSubmit={handleRegister} className="bg-white p-8 rounded-lg shadow-md w-96">
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700">Имя пользователя</label>
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white p-8 md:p-10 rounded-3xl shadow-2xl w-full max-w-md text-center">
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-6">Регистрация</h1>
|
||||
<form onSubmit={handleRegister}>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
placeholder="Имя пользователя"
|
||||
className="w-full px-5 py-3 mb-4 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
placeholder="Электронная почта"
|
||||
className="w-full px-5 py-3 mb-4 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700">Пароль</label>
|
||||
<input
|
||||
type="password"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
placeholder="Пароль"
|
||||
className="w-full px-5 py-3 mb-6 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-ospab-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
</form>
|
||||
{message && <p className="mt-4 text-center text-red-500">{message}</p>}
|
||||
<p className="mt-4">
|
||||
Уже есть аккаунт? <a href="/login" className="text-blue-500 hover:underline">Войти</a>
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full px-5 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent"
|
||||
>
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
</form>
|
||||
<p className="mt-6 text-gray-600">
|
||||
Уже есть аккаунт?{' '}
|
||||
<Link to="/login" className="text-ospab-primary font-bold hover:underline">
|
||||
Войти
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
130
ospabhost/frontend/src/pages/tariffs.tsx
Normal file
130
ospabhost/frontend/src/pages/tariffs.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
const TariffsPage = () => {
|
||||
const [cpu, setCpu] = useState(1);
|
||||
const [ram, setRam] = useState(1);
|
||||
const [storage, setStorage] = useState(50);
|
||||
|
||||
const cpuPrice = 500;
|
||||
const ramPrice = 300;
|
||||
const storagePrice = 5;
|
||||
|
||||
const total = cpu * cpuPrice + ram * ramPrice + storage * storagePrice;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-20">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-center mb-16 text-gray-900">Выберите подходящий тариф</h1>
|
||||
|
||||
{/* Basic Tariffs Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-20">
|
||||
{/* Tariff Card 1 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl text-center transition-transform hover:scale-105 duration-300">
|
||||
<h2 className="text-3xl font-bold text-gray-800">Базовый</h2>
|
||||
<p className="mt-4 text-4xl font-extrabold text-ospab-primary">₽1500<span className="text-lg font-normal text-gray-500">/мес</span></p>
|
||||
<ul className="mt-4 text-gray-600 space-y-2">
|
||||
<li>1 ядро CPU</li>
|
||||
<li>2 ГБ RAM</li>
|
||||
<li>100 ГБ SSD</li>
|
||||
<li>Неограниченный трафик</li>
|
||||
</ul>
|
||||
<button className="mt-8 px-6 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent">
|
||||
Выбрать
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tariff Card 2 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl text-center border-4 border-ospab-primary transition-transform hover:scale-105 duration-300">
|
||||
<h2 className="text-3xl font-bold text-gray-800">Профессиональный</h2>
|
||||
<p className="mt-4 text-4xl font-extrabold text-ospab-primary">₽4000<span className="text-lg font-normal text-gray-500">/мес</span></p>
|
||||
<ul className="mt-4 text-gray-600 space-y-2">
|
||||
<li>4 ядра CPU</li>
|
||||
<li>8 ГБ RAM</li>
|
||||
<li>250 ГБ SSD</li>
|
||||
<li>Приоритетная поддержка</li>
|
||||
</ul>
|
||||
<button className="mt-8 px-6 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent">
|
||||
Выбрать
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tariff Card 3 */}
|
||||
<div className="bg-white p-8 rounded-2xl shadow-xl text-center transition-transform hover:scale-105 duration-300">
|
||||
<h2 className="text-3xl font-bold text-gray-800">Бизнес</h2>
|
||||
<p className="mt-4 text-4xl font-extrabold text-ospab-primary">₽8000<span className="text-lg font-normal text-gray-500">/мес</span></p>
|
||||
<ul className="mt-4 text-gray-600 space-y-2">
|
||||
<li>8 ядер CPU</li>
|
||||
<li>16 ГБ RAM</li>
|
||||
<li>500 ГБ SSD</li>
|
||||
<li>24/7 Мониторинг</li>
|
||||
</ul>
|
||||
<button className="mt-8 px-6 py-3 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent">
|
||||
Выбрать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Server Constructor Section */}
|
||||
<div className="bg-white p-10 rounded-3xl shadow-2xl max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-center mb-8 text-gray-900">Соберите свой сервер</h2>
|
||||
<div className="space-y-6">
|
||||
{/* CPU Slider */}
|
||||
<div>
|
||||
<label htmlFor="cpu" className="block text-lg font-medium text-gray-700">Ядра CPU: {cpu}</label>
|
||||
<input
|
||||
type="range"
|
||||
id="cpu"
|
||||
min="1"
|
||||
max="16"
|
||||
value={cpu}
|
||||
onChange={(e) => setCpu(Number(e.target.value))}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<p className="text-sm text-gray-500 mt-1">Цена: ₽{cpu * cpuPrice}</p>
|
||||
</div>
|
||||
|
||||
{/* RAM Slider */}
|
||||
<div>
|
||||
<label htmlFor="ram" className="block text-lg font-medium text-gray-700">Оперативная память (ГБ): {ram}</label>
|
||||
<input
|
||||
type="range"
|
||||
id="ram"
|
||||
min="1"
|
||||
max="32"
|
||||
value={ram}
|
||||
onChange={(e) => setRam(Number(e.target.value))}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<p className="text-sm text-gray-500 mt-1">Цена: ₽{ram * ramPrice}</p>
|
||||
</div>
|
||||
|
||||
{/* Storage Slider */}
|
||||
<div>
|
||||
<label htmlFor="storage" className="block text-lg font-medium text-gray-700">Диск (ГБ): {storage}</label>
|
||||
<input
|
||||
type="range"
|
||||
id="storage"
|
||||
min="50"
|
||||
max="2000"
|
||||
step="50"
|
||||
value={storage}
|
||||
onChange={(e) => setStorage(Number(e.target.value))}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<p className="text-sm text-gray-500 mt-1">Цена: ₽{storage * storagePrice}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-2xl font-bold text-gray-800">Итого: ₽{total}<span className="text-lg font-normal text-gray-500">/мес</span></p>
|
||||
<button className="mt-4 px-8 py-4 rounded-full text-white font-bold transition-colors transform hover:scale-105 bg-ospab-primary hover:bg-ospab-accent">
|
||||
Собрать сервер
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TariffsPage;
|
||||
19
ospabhost/frontend/tailwind.config.js
Normal file
19
ospabhost/frontend/tailwind.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'ospab-primary': '#3B82F6', // Голубой
|
||||
'ospab-accent': '#FF13F0', // Розовый
|
||||
},
|
||||
fontFamily: {
|
||||
mono: ['Share Tech Mono', 'monospace'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user