Добавлена система регистрации, небезопасная
This commit is contained in:
43
ospabhost/package-lock.json
generated
43
ospabhost/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.9.1",
|
"react-router-dom": "^7.9.1",
|
||||||
@@ -8424,6 +8425,33 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.12.tgz",
|
||||||
|
"integrity": "sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.12",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@@ -11524,6 +11552,21 @@
|
|||||||
"mkdirp": "bin/cmd.js"
|
"mkdirp": "bin/cmd.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.12.tgz",
|
||||||
|
"integrity": "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.9.1",
|
"react-router-dom": "^7.9.1",
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import Index from './pages/index';
|
import { AuthProvider } from "./context/AuthContext";
|
||||||
import Pricing from './pages/pricing';
|
import Navbar from "./components/Navbar";
|
||||||
import Login from './pages/login';
|
import Footer from "./components/Footer";
|
||||||
import Dashboard from './pages/dashboard/index';
|
import Home from "./pages/index";
|
||||||
import './styles/tailwind.css';
|
import Login from "./pages/login";
|
||||||
|
import Register from "./pages/register";
|
||||||
|
import Dashboard from "./pages/dashboard";
|
||||||
|
|
||||||
export default function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<AuthProvider>
|
||||||
<Routes>
|
<Router>
|
||||||
<Route path="/" element={<Index />} />
|
<div className="flex flex-col min-h-screen">
|
||||||
<Route path="/pricing" element={<Pricing />} />
|
{/* Навигация сверху */}
|
||||||
<Route path="/login" element={<Login />} />
|
<Navbar />
|
||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
|
||||||
</Routes>
|
{/* Основной контент */}
|
||||||
</BrowserRouter>
|
<main className="flex-grow">
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route path="/register" element={<Register />} />
|
||||||
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
|
</Routes>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Подвал */}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-gray-900 text-white p-4 text-center">
|
<footer className="bg-gray-800 text-white py-6 mt-12">
|
||||||
© {new Date().getFullYear()} osapab.host. Все права защищены.
|
<div className="container mx-auto text-center">
|
||||||
|
© 2025 ospab.host. Все права защищены.
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,47 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
|
||||||
interface NavbarProps {
|
export default function Navbar() {
|
||||||
user: { email: string } | null;
|
const { user, logout } = useAuth();
|
||||||
logout: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Navbar: React.FC<NavbarProps> = ({ user, logout }) => {
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-gray-800 text-white p-4 flex justify-between items-center">
|
<nav className="bg-white shadow-md px-8 py-4 fixed w-full z-50 rounded-b-3xl">
|
||||||
<div className="text-xl font-bold">ospab.host</div>
|
<div className="container mx-auto flex justify-between items-center">
|
||||||
<div className="space-x-4">
|
<Link to="/" className="text-2xl font-bold text-indigo-700 hover:text-pink-500 transition">
|
||||||
<Link to="/">Главная</Link>
|
ospab.host
|
||||||
<Link to="/pricing">Цены</Link>
|
</Link>
|
||||||
{user ? (
|
<div className="space-x-6">
|
||||||
<>
|
<Link to="/" className="hover:text-pink-500 transition">Главная</Link>
|
||||||
<Link to="/dashboard">ЛК</Link>
|
<Link to="/pricing" className="hover:text-pink-500 transition">Цены</Link>
|
||||||
<button onClick={logout} className="bg-red-600 px-2 py-1 rounded">
|
{user ? (
|
||||||
Выйти
|
<>
|
||||||
</button>
|
<Link to="/dashboard" className="hover:text-pink-500 transition">Личный кабинет</Link>
|
||||||
</>
|
<span className="text-gray-700 font-semibold">Привет, {user.name}</span>
|
||||||
) : (
|
<button
|
||||||
<Link to="/login">Вход</Link>
|
onClick={logout}
|
||||||
)}
|
className="bg-red-500 text-white px-4 py-2 rounded-full hover:bg-pink-500 transition-all"
|
||||||
|
>
|
||||||
|
Выйти
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="bg-indigo-700 text-white px-4 py-2 rounded-full hover:bg-pink-500 transition-all"
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/register"
|
||||||
|
className="bg-white text-indigo-700 px-4 py-2 rounded-full border border-indigo-700 hover:bg-pink-500 hover:text-white transition-all"
|
||||||
|
>
|
||||||
|
Регистрация
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Navbar;
|
|
||||||
|
|||||||
48
ospabhost/src/context/AuthContext.tsx
Normal file
48
ospabhost/src/context/AuthContext.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React, { createContext, useContext, useState, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
user: User | null;
|
||||||
|
login: (email: string, password: string) => boolean;
|
||||||
|
register: (name: string, email: string, password: string, password2: string) => boolean;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
const login = (email: string, password: string) => {
|
||||||
|
if (email && password) {
|
||||||
|
setUser({ name: "Пользователь", email });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = (name: string, email: string, password: string, password2: string) => {
|
||||||
|
if (!name || !email || !password || !password2) return false;
|
||||||
|
if (password !== password2) return false;
|
||||||
|
setUser({ name, email });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => setUser(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, login, register, logout }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuth = (): AuthContextType => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -9,18 +9,13 @@ import './styles/tailwind.css';
|
|||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||||
|
|
||||||
// Заглушка для функции login
|
|
||||||
const login = (email: string) => {
|
|
||||||
// Здесь может быть логика авторизации
|
|
||||||
console.log('Вход для:', email);
|
|
||||||
};
|
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Index />} />
|
<Route path="/" element={<Index />} />
|
||||||
<Route path="/pricing" element={<Pricing />} />
|
<Route path="/pricing" element={<Pricing />} />
|
||||||
<Route path="/login" element={<Login login={login} />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import React from 'react';
|
import Navbar from "../../components/Navbar";
|
||||||
|
|
||||||
export default function Billing() {
|
const Billing: React.FC = () => {
|
||||||
return <div>Billing details</div>;
|
return (
|
||||||
}
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<Navbar />
|
||||||
|
<section className="pt-32 px-4 container mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">Баланс и платежи</h1>
|
||||||
|
<div className="bg-white rounded-3xl shadow p-6">
|
||||||
|
<p>Информация о платежах появится здесь.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Billing;
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
import { Link, Routes, Route } from "react-router-dom";
|
import Navbar from "../../components/Navbar";
|
||||||
import Servers from "./servers";
|
import { Link } from "react-router-dom";
|
||||||
import Billing from "./billing";
|
|
||||||
import Support from "./support";
|
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="text-gray-800 min-h-screen bg-gray-50">
|
||||||
<h1 className="text-3xl font-bold mb-4">Личный кабинет</h1>
|
<Navbar />
|
||||||
<div className="flex space-x-4 mb-4">
|
|
||||||
<Link className="underline" to="servers">Сервера</Link>
|
<section className="pt-32 px-4 container mx-auto">
|
||||||
<Link className="underline" to="billing">Биллинг</Link>
|
<h1 className="text-4xl font-bold mb-6">Личный кабинет</h1>
|
||||||
<Link className="underline" to="support">Поддержка</Link>
|
<div className="grid md:grid-cols-3 gap-6">
|
||||||
</div>
|
<Link
|
||||||
<Routes>
|
to="/dashboard/servers"
|
||||||
<Route path="servers" element={<Servers />} />
|
className="bg-white rounded-3xl p-6 shadow hover:shadow-2xl transition-all text-center"
|
||||||
<Route path="billing" element={<Billing />} />
|
>
|
||||||
<Route path="support" element={<Support />} />
|
Мои серверы
|
||||||
<Route path="/" element={<div>Выберите раздел</div>} />
|
</Link>
|
||||||
</Routes>
|
<Link
|
||||||
|
to="/dashboard/billing"
|
||||||
|
className="bg-white rounded-3xl p-6 shadow hover:shadow-2xl transition-all text-center"
|
||||||
|
>
|
||||||
|
Баланс и платежи
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/dashboard/support"
|
||||||
|
className="bg-white rounded-3xl p-6 shadow hover:shadow-2xl transition-all text-center"
|
||||||
|
>
|
||||||
|
Поддержка
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
import Navbar from "../../components/Navbar";
|
||||||
|
|
||||||
const Servers: React.FC = () => {
|
const Servers: React.FC = () => {
|
||||||
return <div>Здесь будут ваши VPS</div>;
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<Navbar />
|
||||||
|
<section className="pt-32 px-4 container mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">Мои VPS</h1>
|
||||||
|
<div className="bg-white rounded-3xl shadow p-6">
|
||||||
|
<p>Список серверов пока пуст...</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Servers;
|
export default Servers;
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import React from 'react';
|
import Navbar from "../../components/Navbar";
|
||||||
|
|
||||||
export default function Support() {
|
const Support: React.FC = () => {
|
||||||
return <div>Support tickets</div>;
|
return (
|
||||||
}
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<Navbar />
|
||||||
|
<section className="pt-32 px-4 container mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">Поддержка</h1>
|
||||||
|
<div className="bg-white rounded-3xl shadow p-6">
|
||||||
|
<p>Здесь будет форма обращения в поддержку.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Support;
|
||||||
|
|||||||
@@ -1,16 +1,110 @@
|
|||||||
import React from 'react';
|
import Navbar from "../components/Navbar";
|
||||||
import Navbar from '../components/Navbar';
|
import { Link } from "react-router-dom";
|
||||||
import Footer from '../components/Footer';
|
import { motion } from "framer-motion"; // анимации
|
||||||
|
|
||||||
export default function Index() {
|
const Home: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-white">
|
<div className="text-gray-800">
|
||||||
<Navbar user={null} logout={() => {}} />
|
<Navbar />
|
||||||
<main className="flex-grow p-10">
|
|
||||||
<h1 className="text-4xl font-bold">Добро пожаловать в ospab.host</h1>
|
{/* Hero section */}
|
||||||
<p className="mt-4 text-lg">Ваша панель управления VPS-хостингом</p>
|
<section className="bg-gradient-to-r from-blue-600 to-indigo-700 text-white py-36">
|
||||||
</main>
|
<div className="container mx-auto text-center px-4">
|
||||||
<Footer />
|
<motion.h1
|
||||||
|
className="text-5xl font-bold mb-4 rounded-lg"
|
||||||
|
initial={{ opacity: 0, y: -50 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 1 }}
|
||||||
|
>
|
||||||
|
ospab.host — Надёжный VPS и хостинг
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
className="text-xl mb-8"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 1, delay: 0.5 }}
|
||||||
|
>
|
||||||
|
Создавайте серверы, управляйте VPS и следите за метриками прямо из панели.
|
||||||
|
</motion.p>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.5, delay: 1 }}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="bg-white text-blue-700 font-bold py-3 px-6 rounded-full shadow-lg hover:bg-pink-500 hover:text-white transition-all"
|
||||||
|
>
|
||||||
|
Начать бесплатно
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Features section */}
|
||||||
|
<section className="py-20 bg-gray-100">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<h2 className="text-3xl font-bold text-center mb-12">Почему выбирают ospab.host</h2>
|
||||||
|
<div className="grid md:grid-cols-3 gap-8 text-center">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
title: "VPS на Proxmox",
|
||||||
|
desc: "Мгновенное создание и управление VPS, автоматические snapshot'ы и бэкапы.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Мониторинг и графики",
|
||||||
|
desc: "Полная информация о нагрузке CPU, RAM и дисков в реальном времени через ЛК.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Безопасность",
|
||||||
|
desc: "SSL, защита API, role-based access и безопасное хранение данных.",
|
||||||
|
},
|
||||||
|
].map((feature, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={idx}
|
||||||
|
className="bg-white rounded-3xl shadow p-6 hover:shadow-2xl transition-all"
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
>
|
||||||
|
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
|
||||||
|
<p>{feature.desc}</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing teaser */}
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="container mx-auto px-4 text-center">
|
||||||
|
<h2 className="text-3xl font-bold mb-6">Прозрачные тарифы</h2>
|
||||||
|
<p className="mb-8">Выбирайте тариф под свои задачи, платите только за то, что используете.</p>
|
||||||
|
<Link
|
||||||
|
to="/pricing"
|
||||||
|
className="bg-blue-600 text-white font-bold py-3 px-6 rounded-full shadow hover:bg-pink-500 transition-all"
|
||||||
|
>
|
||||||
|
Посмотреть тарифы
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Call-to-action */}
|
||||||
|
<section className="py-8 bg-indigo-700 text-white text-center rounded-t-3xl">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<h2 className="text-3xl font-bold mb-4">Готовы начать?</h2>
|
||||||
|
<p className="mb-6">
|
||||||
|
Создайте аккаунт и получите доступ к панели управления VPS уже сегодня.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="bg-white text-indigo-700 font-bold py-3 px-6 rounded-full shadow hover:bg-pink-500 hover:text-white transition-all"
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|||||||
@@ -1,45 +1,53 @@
|
|||||||
|
import Navbar from "../components/Navbar";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useAuth } from "../context/AuthContext";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface LoginProps {
|
const Login: React.FC = () => {
|
||||||
login: (email: string) => void;
|
const { login } = useAuth();
|
||||||
}
|
|
||||||
|
|
||||||
const Login: React.FC<LoginProps> = ({ login }) => {
|
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
login(email);
|
if (login(email, password)) {
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
|
} else {
|
||||||
|
alert("Неверный логин или пароль");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto mt-20 p-6 bg-gray-800 text-white rounded">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<h1 className="text-2xl font-bold mb-4">Вход</h1>
|
<Navbar />
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<section className="flex justify-center items-center h-screen">
|
||||||
<input
|
<div className="bg-white rounded-3xl shadow-lg p-10 w-full max-w-md">
|
||||||
type="email"
|
<h2 className="text-3xl font-bold mb-6 text-center">Вход в ЛК</h2>
|
||||||
placeholder="Email"
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
value={email}
|
<input
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
type="email"
|
||||||
className="w-full p-2 rounded text-black"
|
placeholder="Email"
|
||||||
required
|
value={email}
|
||||||
/>
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
<input
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
type="password"
|
/>
|
||||||
placeholder="Пароль"
|
<input
|
||||||
value={password}
|
type="password"
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
placeholder="Пароль"
|
||||||
className="w-full p-2 rounded text-black"
|
value={password}
|
||||||
required
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
/>
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
<button className="w-full bg-blue-600 py-2 rounded font-bold hover:bg-blue-700">
|
/>
|
||||||
Войти
|
<button
|
||||||
</button>
|
type="submit"
|
||||||
</form>
|
className="w-full bg-indigo-700 text-white py-2 rounded-full hover:bg-pink-500 transition-all"
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,36 +1,44 @@
|
|||||||
import React from 'react';
|
import Navbar from "../components/Navbar";
|
||||||
import Navbar from '../components/Navbar';
|
import { motion } from "framer-motion";
|
||||||
import Footer from '../components/Footer';
|
|
||||||
import Button from '../components/Button';
|
|
||||||
|
|
||||||
export default function Pricing() {
|
const Pricing: React.FC = () => {
|
||||||
const plans = [
|
const plans = [
|
||||||
{ name: "Мини", price: "200 р/мес", features: ["1 vCPU", "1GB RAM", "25GB SSD"] },
|
{ title: "Basic VPS", price: "$5/мес", features: ["1 CPU", "1 GB RAM", "20 GB SSD"] },
|
||||||
{ name: "Стандарт", price: "500 р/мес", features: ["2 vCPU", "2GB RAM", "50GB SSD"] },
|
{ title: "Pro VPS", price: "$15/мес", features: ["2 CPU", "4 GB RAM", "50 GB SSD"] },
|
||||||
{ name: "Профессионал", price: "700 р/мес", features: ["4 vCPU", "8GB RAM", "100GB SSD"] },
|
{ title: "Enterprise", price: "$30/мес", features: ["4 CPU", "8 GB RAM", "100 GB SSD"] },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-gray-50">
|
<div className="text-gray-800">
|
||||||
<Navbar user={null} logout={() => {}} />
|
<Navbar />
|
||||||
|
|
||||||
<main className="flex-grow px-6 py-12 text-center">
|
<section className="py-24 bg-gray-100">
|
||||||
<h1 className="text-4xl font-bold mb-6">Выбери свой тариф</h1>
|
<div className="container mx-auto text-center px-4">
|
||||||
<div className="grid md:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
<h1 className="text-4xl font-bold mb-12">Наши тарифы</h1>
|
||||||
{plans.map(plan => (
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
<div key={plan.name} className="bg-white shadow-md rounded-xl p-6 hover:shadow-xl transition">
|
{plans.map((plan, idx) => (
|
||||||
<h2 className="text-2xl font-bold mb-4">{plan.name}</h2>
|
<motion.div
|
||||||
<p className="text-3xl font-extrabold text-blue-600 mb-4">{plan.price}</p>
|
key={idx}
|
||||||
<ul className="text-gray-600 mb-6 space-y-2">
|
className="bg-white rounded-3xl shadow p-6 hover:shadow-2xl transition-all"
|
||||||
{plan.features.map(f => <li key={f}>✅ {f}</li>)}
|
whileHover={{ scale: 1.05 }}
|
||||||
</ul>
|
>
|
||||||
<Button>Заказать</Button>
|
<h2 className="text-2xl font-bold mb-4">{plan.title}</h2>
|
||||||
</div>
|
<p className="text-xl mb-4">{plan.price}</p>
|
||||||
))}
|
<ul className="mb-6">
|
||||||
|
{plan.features.map((f, i) => (
|
||||||
|
<li key={i} className="mb-1">{f}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<button className="bg-indigo-700 text-white py-2 px-6 rounded-full hover:bg-pink-500 transition-all">
|
||||||
|
Выбрать
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</section>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Pricing;
|
||||||
|
|||||||
77
ospabhost/src/pages/register.tsx
Normal file
77
ospabhost/src/pages/register.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Navbar from "../components/Navbar";
|
||||||
|
|
||||||
|
export default function Register() {
|
||||||
|
const { register } = useAuth();
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [password2, setPassword2] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!name || !email || !password || !password2) {
|
||||||
|
alert("Заполните все поля");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password !== password2) {
|
||||||
|
alert("Пароли не совпадают");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (register(name, email, password, password2)) {
|
||||||
|
navigate("/dashboard");
|
||||||
|
} else {
|
||||||
|
alert("Ошибка регистрации");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<Navbar />
|
||||||
|
<section className="flex justify-center items-center h-screen">
|
||||||
|
<div className="bg-white rounded-3xl shadow-lg p-10 w-full max-w-md">
|
||||||
|
<h2 className="text-3xl font-bold mb-6 text-center">Регистрация</h2>
|
||||||
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Имя"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Пароль"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="Повторите пароль"
|
||||||
|
value={password2}
|
||||||
|
onChange={(e) => setPassword2(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 rounded-lg border focus:ring-2 focus:ring-pink-500 outline-none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-indigo-700 text-white py-2 rounded-full hover:bg-pink-500 transition-all"
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user