Refactor frontend components and backend migrations

- Removed TradingGraph component from frontend/src/components/trading.
- Updated home page to import Services component and TradingGraph from new path.
- Modified PortfolioPage to return null instead of PortfolioGrid.
- Added initial migrations for account, advertisement, commerce, configuration, downloader, gopay, stripe, trading212, and zasilkovna apps in the backend.
- Created Services component with subcomponents for Kinematografie, Drone Service, and Website Service.
- Implemented TradingGraph component with dynamic data generation and canvas rendering.
- Updated DonationShop component to display donation tiers with icons and descriptions.
This commit is contained in:
2025-12-14 03:49:16 +01:00
parent 564418501c
commit 1751badb90
40 changed files with 796 additions and 165 deletions

View File

@@ -1,79 +0,0 @@
import { useState } from "react";
import Modal from "../common/Modal";
type Project = {
id: string;
title: string;
image: string; // public/ path
description: string;
link?: string;
};
const projects: Project[] = [
{
id: "perlica",
title: "Perlica",
image: "/portfolio/perlica.png",
description: "E-commerce redesign with modern UI and Django backend integration.",
link: "#",
},
{
id: "epinger",
title: "Epinger",
image: "/portfolio/epinger.png",
description: "Landing page with responsive layout and animation system.",
link: "#",
},
{
id: "davo1",
title: "Davo",
image: "/portfolio/davo1.png",
description: "Portfolio template and component library built with Vite + Tailwind.",
link: "#",
},
];
export default function PortfolioGrid() {
const [active, setActive] = useState<Project | null>(null);
return (
<section id="portfolio" className="section">
<div className="container">
<h2 className="text-2xl md:text-3xl font-bold mb-2 text-rainbow">My Work</h2>
<p className="text-[var(--c-lines)] mb-6 max-w-2xl">Selected projects combining engineering, design systems, performance optimization and infrastructure. Click a tile for details.</p>
<div className="grid gap-6 md:grid-cols-3">
{projects.map((p) => (
<button
key={p.id}
className="card overflow-hidden text-left"
onClick={() => setActive(p)}
>
<div className="aspect-[16/10] w-full overflow-hidden">
<img src={p.image} alt={p.title} className="w-full h-full object-cover hover:scale-105 transition-transform" />
</div>
<div className="p-4">
<h3 className="text-lg font-semibold tracking-wide text-rainbow">{p.title}</h3>
<p className="text-xs text-[var(--c-lines)] mt-1 uppercase">View details </p>
</div>
</button>
))}
</div>
</div>
<Modal open={!!active} onClose={() => setActive(null)} title={active?.title}>
<div className="space-y-3">
{active && (
<>
<img src={active.image} alt={active.title} className="w-full rounded" />
<p>{active.description}</p>
{active.link && (
<a href={active.link} target="_blank" rel="noreferrer" className="inline-block px-4 py-2 glow border rounded">
Visit project
</a>
)}
</>
)}
</div>
</Modal>
</section>
);
}

View File

@@ -1,15 +0,0 @@
export default function TradingGraph() {
return (
<section id="live" className="py-20 bg-gradient-to-b from-brand-bg to-brand-bgLight">
<div className="container mx-auto px-4">
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-rainbow">Live Performance</h2>
<div className="card p-4 md:p-6">
<div className="mb-3 text-sm text-brand-text/60">Trading212 graph placeholder</div>
<div className="aspect-[16/9] w-full rounded border border-brand-lines/50 bg-brand-bg/40 grid place-items-center">
<span className="text-brand-text/60">Graph will appear here</span>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,18 @@
import { useState } from "react";
import KinematografieSection from "./kinematografie";
import DroneServisSection from "./droneServis";
import WebsiteServiceSection from "./webs";
import styles from "./Services.module.css";
export default function Services() {
const [active, setActive] = useState<Project | null>(null);
return (
<article id="services" className="section">
<WebsiteServiceSection />
<KinematografieSection />
<DroneServisSection />
</article>
);
}

View File

@@ -0,0 +1,157 @@
import { useEffect, useRef, useState } from "react";
export default function TradingGraph() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [data, setData] = useState<number[]>([]);
// Generate sample data for demo
useEffect(() => {
const generateData = () => {
const points = 50;
const newData: number[] = [];
let value = 100;
for (let i = 0; i < points; i++) {
value += (Math.random() - 0.45) * 5;
newData.push(Math.max(50, Math.min(150, value)));
}
setData(newData);
};
generateData();
const interval = setInterval(generateData, 5000);
return () => clearInterval(interval);
}, []);
// Draw the graph
useEffect(() => {
if (!canvasRef.current || data.length === 0) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Get CSS variables
const styles = getComputedStyle(document.documentElement);
const colorOther = styles.getPropertyValue("--c-other").trim();
const colorLines = styles.getPropertyValue("--c-lines").trim();
const colorBoxes = styles.getPropertyValue("--c-boxes").trim();
const colorBg = styles.getPropertyValue("--c-background").trim();
// Set canvas size
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * window.devicePixelRatio;
canvas.height = rect.height * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
const width = rect.width;
const height = rect.height;
const padding = 40;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Draw grid
ctx.strokeStyle = colorLines;
ctx.lineWidth = 0.5;
ctx.globalAlpha = 0.2;
// Horizontal grid lines
for (let i = 0; i <= 5; i++) {
const y = padding + (height - 2 * padding) * (i / 5);
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(width - padding, y);
ctx.stroke();
}
// Vertical grid lines
for (let i = 0; i <= 10; i++) {
const x = padding + (width - 2 * padding) * (i / 10);
ctx.beginPath();
ctx.moveTo(x, padding);
ctx.lineTo(x, height - padding);
ctx.stroke();
}
ctx.globalAlpha = 1;
// Find min and max values
const minValue = Math.min(...data);
const maxValue = Math.max(...data);
const range = maxValue - minValue || 1;
// Draw the line graph
ctx.strokeStyle = colorOther;
ctx.lineWidth = 3; // Thicker line
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
data.forEach((value, index) => {
const x = padding + (width - 2 * padding) * (index / (data.length - 1));
const y = height - padding - (height - 2 * padding) * ((value - minValue) / range);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
// Draw area under the line with gradient
const gradient = ctx.createLinearGradient(0, padding, 0, height - padding);
gradient.addColorStop(0, colorBoxes + "80");
gradient.addColorStop(1, colorBoxes + "00");
ctx.fillStyle = gradient;
ctx.lineTo(width - padding, height - padding);
ctx.lineTo(padding, height - padding);
ctx.closePath();
ctx.fill();
// Draw data points
ctx.fillStyle = colorOther;
data.forEach((value, index) => {
const x = padding + (width - 2 * padding) * (index / (data.length - 1));
const y = height - padding - (height - 2 * padding) * ((value - minValue) / range);
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fill();
});
// Draw labels
ctx.fillStyle = colorLines;
ctx.font = "12px Inter, sans-serif";
ctx.textAlign = "right";
// Y-axis labels
for (let i = 0; i <= 5; i++) {
const value = maxValue - (range * i / 5);
const y = padding + (height - 2 * padding) * (i / 5);
ctx.fillText(value.toFixed(0), padding - 10, y + 4);
}
}, [data]);
return (
<section id="live" className="py-20 bg-gradient-to-b from-brand-bg to-brand-bgLight">
<div className="container mx-auto px-4">
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-rainbow">Live Performance</h2>
<div className="card p-4 md:p-6">
<div className="mb-3 text-sm text-brand-text/60">Trading212 Performance Chart</div>
<div className="aspect-[16/9] w-full rounded border border-brand-lines/50 bg-brand-bg/40 overflow-hidden">
<canvas
ref={canvasRef}
className="w-full h-full"
style={{ display: "block" }}
/>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,10 @@
import styles from "./Services.module.css";
export default function DroneServisSection() {
return (
<article id="drone-servis" className="section">
</article>
);
}

View File

@@ -0,0 +1,9 @@
import styles from "./Services.module.css";
export default function KinematografieSection() {
return (
<article id="kinematografie" className="section">
</article>
);
}

View File

@@ -0,0 +1,28 @@
import styles from "./Services.module.css";
import TradingGraph from "./TradingGraph";
export default function WebsiteServiceSection() {
return (
<article id="web-services" className="section">
<h1>Weby</h1>
<p>
Udělám weby na míru podle vašich představ, jde o jednoduché statické stránky, e-shopy, správy systémů, nebo komplexní webové aplikace.
Používám moderní technologie jako React, Next.js, a další, abych zajistil rychlý a responzivní design.
Web lze hostovat na mém hostingu s rychlou odezvou a atraktivní cenou.
</p>
<section>
<h2>Trading 212 (API)</h2>
<small>(realné data z Tradingu 212)</small>
<div id="T212Graph">
{/* Trading graph component or placeholder */}
</div>
</section>
<a href="">Příklady</a> {/* reálné ukázky webů + dema + apps */}
<a href="">Ceník</a>
</article>
);
}

View File

@@ -1,10 +1,11 @@
import { useEffect } from "react";
import PortfolioGrid from "../../components/portfolio/PortfolioGrid";
import TradingGraph from "../../components/trading/TradingGraph";
import DonationShop from "../../components/donate/DonationShop";
import PortfolioGrid from "./components/Services/Services";
import TradingGraph from "./components/Services/TradingGraph";
import DonationShop from "./components/donate/DonationShop";
import ContactMeForm from "../../components/Forms/ContactMe/ContactMeForm";
import Services from "../../components/services/Services";
export default function Home() {
// Optional: keep spark effect for fun
useEffect(() => {

View File

@@ -1,4 +1,3 @@
import PortfolioGrid from "../../components/portfolio/PortfolioGrid";
export default function PortfolioPage(){
return <PortfolioGrid />;
return null;
}