diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..79f371b --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,101 @@ +import { Routes, Route, Link, useLocation } from "react-router-dom"; +import { useState, useEffect } from "react"; +import MySQLMySQLMigrator from "./components/MySQLMySQLMigrator"; +import PostgreSQLPostgreSQLMigrator from "./components/PostgreSQLPostgreSQLMigrator"; +import PostgreSQLS3Migrator from "./components/PostgreSQLS3Migrator"; +import S3S3Migrator from "./components/S3S3Migrator"; + +export default function App() { + const [darkMode, setDarkMode] = useState(() => { + const saved = localStorage.getItem('darkMode'); + return saved ? JSON.parse(saved) : true; // Default to dark mode + }); + + const location = useLocation(); + + useEffect(() => { + localStorage.setItem('darkMode', JSON.stringify(darkMode)); + if (darkMode) { + document.body.classList.add('dark'); + } else { + document.body.classList.remove('dark'); + } + }, [darkMode]); + + const navItems = [ + { path: "/mysql-mysql", label: "MySQL β†’ MySQL", icon: "πŸ—„οΈ" }, + { path: "/psql-psql", label: "PostgreSQL β†’ PostgreSQL", icon: "🐘" }, + { path: "/psql-s3", label: "PostgreSQL β†’ S3", icon: "☁️" }, + { path: "/s3-s3", label: "S3 β†’ S3", icon: "πŸ“¦" }, + ]; + + return ( +
+
+ {/* Header */} +
+
+
+

+ Universal Database Migrator +

+

+ Seamlessly migrate your databases across platforms +

+
+ +
+
+ + {/* Navigation */} + + + {/* Main Content */} +
+ + } /> + } /> + } /> + } /> + +
πŸš€
+

Welcome to Universal Migrator

+

+ Choose a migration type from the navigation above to get started. +

+
+ } + /> + + +
+ + ); +} \ No newline at end of file diff --git a/frontend/src/api.js b/frontend/src/api.js new file mode 100644 index 0000000..5d0946f --- /dev/null +++ b/frontend/src/api.js @@ -0,0 +1,69 @@ +// API Base URL Ω…Ω† Ω…ΨͺغيراΨͺ Ψ§Ω„Ψ¨ΩŠΨ¦Ψ© +const API_BASE = import.meta.env.VITE_API_BASE ?? ""; + +// ========================= +// Schemas +// ========================= +async function getSchemas(type, payload) { + const r = await fetch(`${API_BASE}/api/get_schemas`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + type, + ...payload + }) + }); + return r.json(); +} + +// ========================= +// Tables +// ========================= +async function getTables(type, payload) { + const r = await fetch(`${API_BASE}/api/get_tables`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + type, + ...payload + }) + }); + return r.json(); +} + +// ========================= +// Start Migration +// ========================= +async function startMigration(type, payload) { + const r = await fetch(`${API_BASE}/api/migrate`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + type, + ...payload + }) + }); + return r.json(); +} + +// ========================= +// Progress +// ========================= +async function getProgress(type) { + const r = await fetch(`${API_BASE}/api/progress/${type}`); + return r.json(); +} + +// ========================= +// List Buckets +// ========================= +async function listBuckets(payload) { + const r = await fetch(`${API_BASE}/api/list_buckets`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + return r.json(); +} + +export { getSchemas, getTables, startMigration, getProgress, listBuckets }; \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..8cc8d25 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App.jsx"; +import "./styles.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + + + +); \ No newline at end of file diff --git a/frontend/src/styles.css b/frontend/src/styles.css new file mode 100644 index 0000000..2ac97b6 --- /dev/null +++ b/frontend/src/styles.css @@ -0,0 +1,137 @@ +@reference "tailwindcss"; + +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* =========================== + LIGHT MODE STYLES + =========================== */ +:root { + --bg-color: #f4f6f8; + --container-bg: white; + --text-color: #333; + --input-border: #ccc; + --button-bg: #2563eb; + --button-color: white; + --link-color: #2563eb; + --pre-bg: #111; + --pre-color: #0f0; +} + +/* =========================== + DARK MODE STYLES + =========================== */ +.dark { + --bg-color: #000000; + --container-bg: #0d0d0d; + --text-color: #ffffff; + --input-border: #ff0000; + --button-bg: #b91c1c; + --button-color: #ffffff; + --link-color: #ef4444; + --pre-bg: #000000; + --pre-color: #ff0000; +} + +/* =========================== + BODY STYLES + =========================== */ +body { + margin: 0; + font-family: Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + transition: background-color 0.3s, color 0.3s; +} + +.container { + background: var(--container-bg); + padding: 20px; + max-width: 800px; + margin: auto; + border-radius: 8px; + transition: background-color 0.3s; +} + +/* =========================== + CUSTOM COMPONENTS + =========================== */ +.glow-red { + box-shadow: 0 0 10px rgba(239, 68, 68, 0.5), 0 0 20px rgba(239, 68, 68, 0.3); +} + +.custom-input { + @apply w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500; +} + +.custom-button { + @apply bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white font-medium py-3 px-4 rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl disabled:cursor-not-allowed; +} + +.custom-select { + @apply w-full px-4 py-3 border border-gray-300 dark:border-red-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-all duration-200; +} + +.custom-label { + @apply block text-sm font-medium mb-1 text-gray-700 dark:text-white; +} + +body { + margin: 0; + font-family: Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + transition: background-color 0.3s, color 0.3s; +} + +.container { + background: var(--container-bg); + padding: 20px; + max-width: 800px; + margin: auto; + border-radius: 8px; + transition: background-color 0.3s; +} + +select { + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: 1px solid var(--input-border); + border-radius: 4px; + background-color: var(--container-bg); + color: var(--text-color); + transition: border-color 0.3s, background-color 0.3s, color 0.3s; +} +select.multi-select { + height: 120px; +} +button { + background: var(--button-bg); + color: var(--button-color); + border: none; + width: 100%; + padding: 10px; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.3s; +} + +button:hover { + opacity: 0.9; +} + +a { + color: var(--link-color); + text-decoration: none; +} + +pre { + background: var(--pre-bg); + color: var(--pre-color); + padding: 10px; + height: 140px; + overflow: auto; + border-radius: 4px; +} \ No newline at end of file