feat: project scaffold (FastAPI backend, React frontend, aiogram bot, docker-compose, CI/CD)
This commit is contained in:
1 parent
066deaca47
commit
71460ab56a
37 files changed
+936
No files matched your search
@@ -0,0 +1,13 @@
|
||||
# build
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# serve
|
||||
FROM nginx:1.27-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#091C5A" />
|
||||
<title>ОПОРА РОССИИ</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "opora-frontend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"recharts": "^2.12.7",
|
||||
"lucide-react": "^0.446.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
"vite-plugin-pwa": "^0.20.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Link, Route, Routes } from "react-router-dom";
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<main className="mx-auto max-w-[430px] p-4">
|
||||
<header className="mb-6">
|
||||
<h1 className="font-heading text-2xl font-bold text-primary">ОПОРА РОССИИ</h1>
|
||||
<p className="text-sm text-muted">Платформа</p>
|
||||
</header>
|
||||
<nav className="grid gap-3">
|
||||
<Link className="card" to="/goals">Цели организации</Link>
|
||||
<Link className="card" to="/ratings">Рейтинг регионов</Link>
|
||||
<Link className="card" to="/tasks">Задачи</Link>
|
||||
<Link className="card" to="/artifacts">Артефакты</Link>
|
||||
</nav>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function Placeholder({ title }: { title: string }) {
|
||||
return (
|
||||
<main className="mx-auto max-w-[430px] p-4">
|
||||
<Link to="/" className="text-accent">← Назад</Link>
|
||||
<h1 className="font-heading mt-4 text-xl font-bold">{title}</h1>
|
||||
<p className="mt-2 text-muted">Раздел в разработке.</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/goals" element={<Placeholder title="Цели организации" />} />
|
||||
<Route path="/ratings" element={<Placeholder title="Рейтинг регионов" />} />
|
||||
<Route path="/tasks" element={<Placeholder title="Задачи" />} />
|
||||
<Route path="/artifacts" element={<Placeholder title="Артефакты" />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-primary: #091c5a;
|
||||
--color-primary-light: #1b3a8c;
|
||||
--color-accent: #0480b9;
|
||||
--color-accent-red: #e30613;
|
||||
--color-zone-green: #2e7d32;
|
||||
--color-zone-red: #c62828;
|
||||
--color-zone-neutral: #9e9e9e;
|
||||
--bg: #f7f8fc;
|
||||
--surface: #ffffff;
|
||||
--border: #e2e6ef;
|
||||
--text: #1a1a1a;
|
||||
--text-muted: #666666;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #0e1220;
|
||||
--surface: #1a2033;
|
||||
--border: #2a3350;
|
||||
--text: #e8eaed;
|
||||
--text-muted: #a0a6b5;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: Inter, system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.font-heading {
|
||||
font-family: Manrope, Inter, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: block;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 2px rgba(9, 28, 90, 0.08);
|
||||
}
|
||||
|
||||
.text-primary { color: var(--color-primary); }
|
||||
.text-accent { color: var(--color-accent); }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#091C5A",
|
||||
"primary-light": "#1B3A8C",
|
||||
accent: "#0480B9",
|
||||
"accent-red": "#E30613",
|
||||
"zone-green": "#2E7D32",
|
||||
"zone-red": "#C62828",
|
||||
"zone-neutral": "#9E9E9E",
|
||||
},
|
||||
fontFamily: {
|
||||
heading: ["Manrope", "Inter", "system-ui", "sans-serif"],
|
||||
body: ["Inter", "system-ui", "sans-serif"],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
manifest: {
|
||||
name: "ОПОРА РОССИИ",
|
||||
short_name: "ОПОРА",
|
||||
lang: "ru",
|
||||
theme_color: "#091C5A",
|
||||
background_color: "#F7F8FC",
|
||||
display: "standalone",
|
||||
start_url: "/",
|
||||
icons: [
|
||||
{ src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
|
||||
{ src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
host: true,
|
||||
port: 5173,
|
||||
proxy: {
|
||||
"/api": { target: "http://api:8000", changeOrigin: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user