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:
18
frontend/src/pages/home/components/Services/Services.tsx
Normal file
18
frontend/src/pages/home/components/Services/Services.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
157
frontend/src/pages/home/components/Services/TradingGraph.tsx
Normal file
157
frontend/src/pages/home/components/Services/TradingGraph.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
10
frontend/src/pages/home/components/Services/droneServis.tsx
Normal file
10
frontend/src/pages/home/components/Services/droneServis.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styles from "./Services.module.css";
|
||||
|
||||
|
||||
export default function DroneServisSection() {
|
||||
return (
|
||||
<article id="drone-servis" className="section">
|
||||
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import styles from "./Services.module.css";
|
||||
|
||||
export default function KinematografieSection() {
|
||||
return (
|
||||
<article id="kinematografie" className="section">
|
||||
|
||||
</article>
|
||||
);
|
||||
}
|
||||
28
frontend/src/pages/home/components/Services/webs.tsx
Normal file
28
frontend/src/pages/home/components/Services/webs.tsx
Normal 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, ať už 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>
|
||||
);
|
||||
}
|
||||
36
frontend/src/pages/home/components/donate/DonationShop.tsx
Normal file
36
frontend/src/pages/home/components/donate/DonationShop.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { FaCoffee, FaBatteryFull, FaMicrochip } from "react-icons/fa";
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
type Tier = { id: string; title: string; price: number; desc: string; color: string; icon: ReactNode };
|
||||
|
||||
const tiers: Tier[] = [
|
||||
{ id: 'coffee', title: 'Coffee', price: 3, desc: 'Fuel late-night coding sessions.', color: 'var(--c-other)', icon: <FaCoffee /> },
|
||||
{ id: 'battery', title: 'Drone Battery', price: 30, desc: 'Extend aerial filming time.', color: 'var(--c-other)', icon: <FaBatteryFull /> },
|
||||
{ id: 'gpu', title: 'GPU Upgrade', price: 200, desc: 'Speed up rendering and ML tasks.', color: 'var(--c-other)', icon: <FaMicrochip /> },
|
||||
];
|
||||
|
||||
export default function DonationShop() {
|
||||
return (
|
||||
<section id="shop" className="section">
|
||||
<div className="container">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2 text-rainbow">Support My Creative Journey</h2>
|
||||
<p className="text-brand-text/80">Instead of buying products, consider donating to support my creative journey.</p>
|
||||
<div className="grid md:grid-cols-3 gap-6 mt-6">
|
||||
{tiers.map(t => (
|
||||
<div key={t.id} className="card p-5 flex flex-col">
|
||||
<div className="text-5xl mb-3 inline-flex items-center justify-center w-20 h-20 rounded-xl bg-brandGradient text-white shadow-glow">
|
||||
{t.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold" style={{ color: t.color }}>{t.title}</h3>
|
||||
<p className="text-[var(--c-lines)] mt-1">{t.desc}</p>
|
||||
<div className="mt-auto flex items-center justify-between pt-4">
|
||||
<span className="text-2xl font-bold" style={{ color: t.color }}>${t.price}</span>
|
||||
<button className="px-4 py-2 rounded-lg font-semibold text-brand-text bg-gradient-to-r from-[var(--c-other)] to-[var(--c-lines)] hover:shadow-glow transition-all" onClick={() => alert(`Thank you for supporting with ${t.title}!`)}>Donate</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import PortfolioGrid from "../../components/portfolio/PortfolioGrid";
|
||||
export default function PortfolioPage(){
|
||||
return <PortfolioGrid />;
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user