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 ( +
+
+ +
+ +
+
+ +