diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..8b32d12
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,58 @@
+# Copilot Instructions for Vontor CZ
+
+## Overview
+This monorepo contains a Django backend and a Vite/React frontend, orchestrated via Docker Compose. The project is designed for a Czech e-marketplace, with custom payment integrations and real-time features.
+
+## Architecture
+- **backend/**: Django project (`vontor_cz`), custom `account` app, and third-party payment integrations (`thirdparty/`).
+ - Uses Django REST Framework, Channels (ASGI), Celery, and S3/static/media via `django-storages`.
+ - Custom user model: `account.CustomUser`.
+ - API docs: DRF Spectacular (`/api/schema/`).
+- **frontend/**: Vite + React + TypeScript app.
+ - Organized by `src/api/`, `components/`, `features/`, `layouts/`, `pages/`, `routes/`.
+ - Uses React Router layouts and nested routes (see `src/layouts/`, `src/routes/`).
+- **docker-compose.yml**: Orchestrates backend, frontend, Redis, and Postgres for local/dev.
+
+## Developer Workflows
+- **Backend**
+ - Local dev: `python manage.py runserver` (or use Docker Compose)
+ - Migrations: `python manage.py makemigrations && python manage.py migrate`
+ - Celery: `celery -A vontor_cz worker -l info`
+ - Channels: Daphne/ASGI (see Docker Compose command)
+ - Env config: `.env` files in `backend/` (see `.gitignore` for secrets)
+- **Frontend**
+ - Install: `npm install`
+ - Dev server: `npm run dev`
+ - Build: `npm run build`
+ - Preview: `npm run preview`
+ - Static assets: `src/assets/` (import in JS/CSS), `public/` (referenced in HTML)
+
+## Conventions & Patterns
+- **Backend**
+ - Use environment variables for secrets and config (see `settings.py`).
+ - Static/media files: S3 in production, local in dev (see `settings.py`).
+ - API versioning and docs: DRF Spectacular config in `settings.py`.
+ - Custom permissions, filters, and serializers in each app.
+- **Frontend**
+ - Use React Router layouts for shared UI (see `src/layouts/`, `LAYOUTS.md`).
+ - API calls and JWT handling in `src/api/`.
+ - Route definitions and guards in `src/routes/` (`ROUTES.md`).
+ - Use TypeScript strict mode (see `tsconfig.*.json`).
+ - Linting: ESLint config in `eslint.config.js`.
+
+## Integration Points
+- **Payments**: `thirdparty/` contains custom integrations for Stripe, GoPay, Trading212.
+- **Real-time**: Django Channels (ASGI, Redis) for websockets.
+- **Task queue**: Celery + Redis for async/background jobs.
+- **API**: REST endpoints, JWT auth, API key support.
+
+## References
+- [frontend/REACT.md](../frontend/REACT.md): Frontend structure, workflows, and conventions.
+- [frontend/src/layouts/LAYOUTS.md](../frontend/src/layouts/LAYOUTS.md): Layout/component patterns.
+- [frontend/src/routes/ROUTES.md](../frontend/src/routes/ROUTES.md): Routing conventions.
+- [backend/vontor_cz/settings.py](../backend/vontor_cz/settings.py): All backend config, env, and integration details.
+- [docker-compose.yml](../docker-compose.yml): Service orchestration and dev workflow.
+
+---
+
+**When in doubt, check the referenced markdown files and `settings.py` for project-specific logic and patterns.**
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 76e8993..e2320f2 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -3137,14 +3137,14 @@
}
},
"node_modules/tinyglobby": {
- "version": "0.2.14",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
- "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2"
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
@@ -3154,11 +3154,14 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -3300,18 +3303,18 @@
}
},
"node_modules/vite": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz",
- "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
+ "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
- "fdir": "^6.4.6",
+ "fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
- "tinyglobby": "^0.2.14"
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
@@ -3375,11 +3378,14 @@
}
},
"node_modules/vite/node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9db5a2d..0f8ddc0 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,9 +1,18 @@
-import { useState } from 'react'
-import './App.css'
+import { BrowserRouter as Router, Routes, Route, Link, Outlet } from "react-router-dom"
+import Home from "./pages/home/home";
+import HomeLayout from "./layouts/HomeLayout";
+
function App() {
return (
- /* */
+
+
+ {/* Layout route */}
+ }>
+ } />
+
+
+
)
}
diff --git a/frontend/src/components/Footer/footer.jsx b/frontend/src/components/Footer/footer.jsx
deleted file mode 100644
index 353ae76..0000000
--- a/frontend/src/components/Footer/footer.jsx
+++ /dev/null
@@ -1,28 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/components/Footer/footer.module.css b/frontend/src/components/Footer/footer.module.css
new file mode 100644
index 0000000..0be6ec2
--- /dev/null
+++ b/frontend/src/components/Footer/footer.module.css
@@ -0,0 +1,36 @@
+footer a{
+ color: var(--c-text);
+ text-decoration: none;
+}
+footer a i{
+ color: white;
+ text-decoration: none;
+}
+footer{
+ font-family: "Roboto Mono", monospace;
+
+ background-color: var(--c-boxes);
+
+ margin-top: 2em;
+ display: flex;
+
+ color: white;
+ align-items: center;
+ justify-content: space-evenly;
+}
+footer address{
+ padding: 1em;
+ font-style: normal;
+}
+footer .contacts{
+ font-size: 2em;
+
+}
+
+@media only screen and (max-width: 990px){
+ footer{
+ flex-direction: column;
+ padding-bottom: 1em;
+ padding-top: 1em;
+ }
+ }
\ No newline at end of file
diff --git a/frontend/src/components/Footer/footer.tsx b/frontend/src/components/Footer/footer.tsx
index e69de29..c602e53 100644
--- a/frontend/src/components/Footer/footer.tsx
+++ b/frontend/src/components/Footer/footer.tsx
@@ -0,0 +1,76 @@
+import styles from "./footer.module.css"
+
+export default function Footer() {
+ return (
+
+ )
+}
diff --git a/frontend/src/components/Forms/ContactMe/ContactMeForm.jsx b/frontend/src/components/Forms/ContactMe/ContactMeForm.jsx.a
similarity index 100%
rename from frontend/src/components/Forms/ContactMe/ContactMeForm.jsx
rename to frontend/src/components/Forms/ContactMe/ContactMeForm.jsx.a
diff --git a/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx b/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx
index e69de29..fe75414 100644
--- a/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx
+++ b/frontend/src/components/Forms/ContactMe/ContactMeForm.tsx
@@ -0,0 +1,115 @@
+import React, { useState } from "react"
+import styles from "./contact-me.module.css"
+
+interface ContactFormResponse {
+ success: boolean
+ [key: string]: any
+}
+
+export default function ContactMeForm() {
+ const [opened, setOpened] = useState(false)
+ const [formData, setFormData] = useState({ message: "", email: "" })
+ const [loading, setLoading] = useState(false)
+ const [success, setSuccess] = useState(false)
+ const [error, setError] = useState(null)
+
+ const toggleOpen = () => {
+ setOpened((prev) => !prev)
+ }
+
+ const handleChange = (
+ e: React.ChangeEvent
+ ) => {
+ const { name, value } = e.target
+ setFormData((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setLoading(true)
+ setError(null)
+
+ try {
+ const response = await fetch("/submit-contactme/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": getCSRFToken(),
+ },
+ body: JSON.stringify(formData),
+ })
+
+ const data: ContactFormResponse = await response.json()
+
+ if (data.success) {
+ setSuccess(true)
+ setFormData({ message: "", email: "" })
+ } else {
+ setError("Zpráva nebyla odeslaná.")
+ }
+ } catch (err) {
+ setError("Chyba připojení nebo serveru.")
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ // Utility to read CSRF token from cookies (Django)
+ const getCSRFToken = (): string => {
+ const match = document.cookie.match(/csrftoken=([^;]+)/)
+ return match ? match[1] : ""
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {success && (
+
+ Zpráva odeslaná!
+
+
+ )}
+ {error &&
{error}
}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/home/HomeNav.module.css b/frontend/src/components/navbar/HomeNav.module.css
similarity index 100%
rename from frontend/src/pages/home/HomeNav.module.css
rename to frontend/src/components/navbar/HomeNav.module.css
diff --git a/frontend/src/components/navbar/HomeNav.tsx b/frontend/src/components/navbar/HomeNav.tsx
new file mode 100644
index 0000000..6e89d9f
--- /dev/null
+++ b/frontend/src/components/navbar/HomeNav.tsx
@@ -0,0 +1,36 @@
+import React, { useState } from "react"
+import styles from "./HomeNav.module.css"
+
+export default function HomeNav() {
+ const [navOpen, setNavOpen] = useState(false)
+
+ const toggleNav = () => setNavOpen((prev) => !prev)
+
+ return (
+
+ )
+}
diff --git a/frontend/src/components/navbar/navbar.jsx b/frontend/src/components/navbar/navbar.jsx
deleted file mode 100644
index 3f3bce4..0000000
--- a/frontend/src/components/navbar/navbar.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/features/ads/Drone/Drone.jsx b/frontend/src/features/ads/Drone/Drone.jsx
deleted file mode 100644
index 1361c52..0000000
--- a/frontend/src/features/ads/Drone/Drone.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-{% load static %}
-
-
-
-
-
-
-
Letecké snímky dronem
-
-
-
-
-
Opravnění
-
-
- A1, A2, A3 a průkaz na vysílačku!
-
- Mohu garantovat bezpečný provoz dronu i ve složitějších podmínkách. Mám také možnost žádat o povolení k letu v blízkosti letišť!
-
-
-
-
Cena
-
- Nabízím letecké záběry dronem za cenu 3 000 Kč.
-
- Pokud se nacházíte v Ostravě, doprava je zdarma. Pro oblasti mimo Ostravu účtuji 10 Kč/km.
-
- Cena se může odvíjet ještě podle složitosti získaní povolení.*
-
-
-
Výstup
-
- Rád Vám připravím jednoduchý sestřih videa, který můžete rychle použít, nebo Vám mohu poskytnout samotné záběry k vlastní editaci.
-
-
-
-
-
-
-
-drone.js:
-
-$(document).ready(function () {
- function setVideoDroneQuality() {
- $sourceElement = $("#video-source");
-
- const videoSources = {
- fullHD: 'static/home/video/drone-background-video-1080p.mp4', // For desktops (1920x1080)
- hd: 'static/home/video/drone-background-video-720p.mp4', // For tablets/smaller screens (1280x720)
- lowRes: 'static/home/video/drone-background-video-480p.mp4' // For mobile devices or low performance (854x480)
- };
-
- const screenWidth = $(window).width(); // Get screen width
-
- // Determine the appropriate video source
- if (screenWidth >= 1920) {
- $sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.fullHD);
- } else if (screenWidth >= 1280) {
- $sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.hd);
- } else {
- $sourceElement.attr('src', "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.lowRes);
- }
-
- // Reload the video
- $('#drone-video')[0].load();
-
- console.log("video set!");
-
- }
-
- setTimeout(1000);
-
- setVideoDroneQuality();
- //$("#debug-drone").click(setVideoDroneQuality);
-});
diff --git a/frontend/src/features/ads/Drone/Drone.tsx b/frontend/src/features/ads/Drone/Drone.tsx
new file mode 100644
index 0000000..eb99062
--- /dev/null
+++ b/frontend/src/features/ads/Drone/Drone.tsx
@@ -0,0 +1,108 @@
+import React, { useEffect, useRef } from "react"
+import styles from "./drone.module.css"
+
+export default function Drone() {
+ const videoRef = useRef(null)
+ const sourceRef = useRef(null)
+
+ useEffect(() => {
+ function setVideoDroneQuality() {
+ if (!sourceRef.current || !videoRef.current) return
+
+ const videoSources = {
+ fullHD: "static/home/video/drone-background-video-1080p.mp4", // For desktops (1920x1080)
+ hd: "static/home/video/drone-background-video-720p.mp4", // For tablets/smaller screens (1280x720)
+ lowRes: "static/home/video/drone-background-video-480p.mp4" // For mobile devices or low performance (854x480)
+ }
+
+ const screenWidth = window.innerWidth
+
+ // Pick appropriate source
+ if (screenWidth >= 1920) {
+ sourceRef.current.src =
+ "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.fullHD
+ } else if (screenWidth >= 1280) {
+ sourceRef.current.src =
+ "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.hd
+ } else {
+ sourceRef.current.src =
+ "https://vontor-cz.s3.eu-central-1.amazonaws.com/" + videoSources.lowRes
+ }
+
+ // Reload video
+ videoRef.current.load()
+ console.log("Drone video set!")
+ }
+
+ // Run once on mount
+ setVideoDroneQuality()
+
+ // Optional: rerun on resize
+ window.addEventListener("resize", setVideoDroneQuality)
+ return () => {
+ window.removeEventListener("resize", setVideoDroneQuality)
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+
Letecké snímky dronem
+
+
+
+
+
Oprávnění
+
+ A1, A2, A3 a průkaz na vysílačku!
+
+ Mohu garantovat bezpečný provoz dronu i ve složitějších podmínkách.
+ Mám také možnost žádat o povolení k letu v blízkosti letišť!
+
+
+
+
+
Cena
+
+ Nabízím letecké záběry dronem
+ za cenu 3 000 Kč.
+
+
+ Pokud se nacházíte v Ostravě, doprava je zdarma. Pro oblasti mimo Ostravu účtuji 10 Kč/km.
+
+
+ Cena se může odvíjet ještě podle složitosti získaní povolení.*
+
+
+
+
+
Výstup
+
+ Rád Vám připravím jednoduchý sestřih videa, který můžete rychle použít,
+ nebo Vám mohu poskytnout samotné záběry k vlastní editaci.
+