diff --git a/components.json b/components.json new file mode 100644 index 0000000..3edf7c2 --- /dev/null +++ b/components.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + }, + "registries": { + "@fancy": "https://www.fancycomponents.dev/r/{name}.json" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 3048d58..0ae8b37 100644 --- a/package.json +++ b/package.json @@ -10,18 +10,28 @@ "preview": "vite preview" }, "dependencies": { + "@types/matter-js": "^0.20.2", + "clsx": "^2.1.1", "i18next": "^25.8.4", "i18next-browser-languagedetector": "^8.2.0", + "lodash": "^4.17.23", "lucide-react": "^0.563.0", + "matter-js": "^0.20.0", + "motion": "^12.34.2", + "poly-decomp": "^0.3.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-i18next": "^16.5.4", - "react-router-dom": "^7.13.0" + "react-router-dom": "^7.13.0", + "shadcn": "^3.8.5", + "svg-path-commander": "^2.1.11", + "tailwind-merge": "^3.5.0" }, "devDependencies": { "@eslint/js": "^9.39.1", "@tailwindcss/vite": "^4.1.18", - "@types/react": "^19.2.5", + "@types/node": "^25.3.0", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", "autoprefixer": "^10.4.23", @@ -30,7 +40,8 @@ "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "postcss": "^8.5.6", - "tailwindcss": "^4.1.18", + "tailwindcss": "^4.2.0", + "typescript": "^5.9.3", "vite": "^7.2.4" } } diff --git a/public/api/contact.php b/public/api/contact.php index f870a9c..c392a51 100644 --- a/public/api/contact.php +++ b/public/api/contact.php @@ -1,68 +1,52 @@ load(); - header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST'); header('Access-Control-Allow-Headers: Content-Type'); -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $data = json_decode(file_get_contents('php://input'), true); - - // Check honeypot - if (!empty($data['website']) || !empty($data['phone_check'])) { - http_response_code(200); - echo json_encode(['success' => true]); - exit; - } - - // Validate and sanitize - $name = htmlspecialchars($data['name']); - $email = filter_var($data['email'], FILTER_SANITIZE_EMAIL); - $company = htmlspecialchars($data['company']); - $type = htmlspecialchars($data['type']); - $message = htmlspecialchars($data['message']); - - try { - $mail = new PHPMailer(true); - - // SMTP Configuration - $mail->isSMTP(); - $mail->Host = $_ENV['SMTP_HOST']; - $mail->SMTPAuth = true; - $mail->Username = $_ENV['SMTP_USERNAME']; - $mail->Password = $_ENV['SMTP_PASSWORD']; - $mail->SMTPSecure = $_ENV['SMTP_ENCRYPTION']; - $mail->Port = $_ENV['SMTP_PORT']; - - // Recipients - $mail->setFrom($_ENV['SMTP_FROM_EMAIL'], $_ENV['SMTP_FROM_NAME']); - $mail->addAddress($_ENV['CONTACT_EMAIL']); - $mail->addReplyTo($email, $name); - - // Content - $mail->isHTML(false); - $mail->Subject = 'New Contact Form: ' . $type; - $mail->Body = "Name: $name\nEmail: $email\nCompany: $company\nType: $type\n\nMessage:\n$message"; - - $mail->send(); - - http_response_code(200); - echo json_encode(['success' => true]); - } catch (Exception $e) { - http_response_code(500); - echo json_encode(['success' => false, 'error' => $mail->ErrorInfo]); - } -} else { +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); + exit; } -?> \ No newline at end of file + +$data = json_decode(file_get_contents('php://input'), true); + +// Check honeypot +if (!empty($data['website']) || !empty($data['phone_check'])) { + http_response_code(200); + echo json_encode(['success' => true]); + exit; +} + +// Validate and sanitize +$name = htmlspecialchars($data['name'] ?? ''); +$email = filter_var($data['email'] ?? '', FILTER_SANITIZE_EMAIL); +$company = htmlspecialchars($data['company'] ?? ''); +$type = htmlspecialchars($data['type'] ?? ''); +$message = htmlspecialchars($data['message'] ?? ''); + +if (!$name || !$email || !$message || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + http_response_code(422); + echo json_encode(['success' => false, 'error' => 'Invalid input']); + exit; +} + +$to = 'contact@ahojsvet.eu'; // TODO: replace with your actual address +$subject = 'New Contact Form: ' . $type; +$body = "Name: $name\nEmail: $email\nCompany: $company\nType: $type\n\nMessage:\n$message"; +$headers = implode("\r\n", [ + 'From: noreply@ahojsvet.eu', + 'Reply-To: ' . $name . ' <' . $email . '>', + 'X-Mailer: PHP/' . phpversion(), + 'Content-Type: text/plain; charset=UTF-8', +]); + +if (mail($to, $subject, $body, $headers)) { + http_response_code(200); + echo json_encode(['success' => true]); +} else { + http_response_code(500); + echo json_encode(['success' => false, 'error' => 'mail() failed']); +} +?> diff --git a/public/screenshots/admin.png b/public/screenshots/admin.png new file mode 100644 index 0000000..2e7719c Binary files /dev/null and b/public/screenshots/admin.png differ diff --git a/public/screenshots/cms.png b/public/screenshots/cms.png new file mode 100644 index 0000000..492b28f Binary files /dev/null and b/public/screenshots/cms.png differ diff --git a/public/screenshots/obchod.png b/public/screenshots/obchod.png new file mode 100644 index 0000000..208b68c Binary files /dev/null and b/public/screenshots/obchod.png differ diff --git a/public/screenshots/objednavky.png b/public/screenshots/objednavky.png new file mode 100644 index 0000000..5e0113b Binary files /dev/null and b/public/screenshots/objednavky.png differ diff --git a/src/App.jsx b/src/App.tsx similarity index 77% rename from src/App.jsx rename to src/App.tsx index 4b88ef4..215fac0 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -25,16 +25,18 @@ export default function App() { return (
+
{contact.subtitle} @@ -59,8 +113,9 @@ export default function ContactForm() { required value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} - className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-3 text-white focus:border-red-700 focus:outline-none transition-colors" + className={`w-full bg-gray-900 border rounded-lg px-4 py-3 text-white focus:outline-none transition-colors ${errors.name ? 'border-red-500 focus:border-red-500' : 'border-gray-700 focus:border-red-700'}`} /> + {errors.name &&
{errors.name}
}{errors.message}
} {/* Honeypot fields - hidden from users */} @@ -140,9 +197,10 @@ export default function ContactForm() { {formSent && ( @@ -150,6 +208,12 @@ export default function ContactForm() {- {t('features.subtitle')} -
-{feature.desc}
-+ {feature.desc} +
+
+
- {t('hero.description')} -
- -- {t('iceberg.subtitle')} -
-{t('iceberg.ahojsvet.subtitle')}
-+ {t("iceberg.subtitle")} +
++ {t("iceberg.ahojsvet.subtitle")} +
+- {wild.subtitle} -
-{project.desc}
-
+
{project.desc}
+- {tabs[activeTab]} screenshot -
-- {t('speed.subtitle')} -
-
+
- {t('problem.subtitle')} -
-{card.desc}
-{card.desc}
++ {subtitle} +
++ {card.desc} +
+
+