diff --git a/package-lock.json b/package-lock.json index d46d86c..e790c10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "@react-three/fiber": "^8.18.0", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.83.0", + "appwrite": "^22.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -137,6 +138,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -3051,8 +3053,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3200,6 +3201,7 @@ "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3215,6 +3217,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3226,6 +3229,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3291,6 +3295,7 @@ "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", @@ -3653,6 +3658,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3770,6 +3776,16 @@ "node": ">= 8" } }, + "node_modules/appwrite": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-22.0.0.tgz", + "integrity": "sha512-iFlfshYttuQheIyar6m789+Z/gvfKWQxWQCDhHzH9cEkFkn+laJZV8nMvGRH+1rTYNfAcFuycWKBGZiEDFxXug==", + "license": "BSD-3-Clause", + "dependencies": { + "bignumber.js": "9.0.0", + "json-bigint": "1.0.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3886,6 +3902,15 @@ ], "license": "MIT" }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3941,6 +3966,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -4418,6 +4444,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -4514,8 +4541,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -4573,7 +4599,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -4762,6 +4789,7 @@ "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5689,6 +5717,7 @@ "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -5742,6 +5771,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6344,7 +6382,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -6792,6 +6829,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6932,7 +6970,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -6948,7 +6985,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -6959,7 +6995,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6972,8 +7007,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", @@ -7047,6 +7081,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -7073,6 +7108,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -7086,6 +7122,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.61.1.tgz", "integrity": "sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -7811,6 +7848,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -7877,7 +7915,8 @@ "version": "0.182.0", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -7940,6 +7979,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8062,6 +8102,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8262,6 +8303,7 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index 6bcdbc7..dd1850d 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@react-three/fiber": "^8.18.0", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.83.0", + "appwrite": "^22.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/scripts/create-appwrite-collection.mjs b/scripts/create-appwrite-collection.mjs new file mode 100644 index 0000000..baea263 --- /dev/null +++ b/scripts/create-appwrite-collection.mjs @@ -0,0 +1,64 @@ +/** + * Script zum Erstellen der Appwrite-Collection für Kontaktformulare. + * Einmalig ausführen mit: node scripts/create-appwrite-collection.mjs + * + * Benötigt: API-Key aus Appwrite Console (Settings > API Keys) + * Umgebungsvariable: APPWRITE_API_KEY=your-secret-key + */ + +import { Client, Databases, Permission, Role } from "appwrite"; + +const ENDPOINT = "https://appwrite.webklar.com/v1"; +const PROJECT_ID = "696b82270034001dab69"; +const DATABASE_ID = "698124a20035e8f6dc42"; +const COLLECTION_ID = "contact_submissions"; + +const apiKey = process.env.APPWRITE_API_KEY; +if (!apiKey) { + console.error( + "Fehler: APPWRITE_API_KEY Umgebungsvariable fehlt.\n" + + "Beispiel: $env:APPWRITE_API_KEY='your-key'; node scripts/create-appwrite-collection.mjs" + ); + process.exit(1); +} + +const client = new Client().setEndpoint(ENDPOINT).setProject(PROJECT_ID).setKey(apiKey); +const databases = new Databases(client); + +async function createCollection() { + try { + // 1. Collection erstellen + // Permission: "any" darf Dokumente erstellen (für öffentliches Kontaktformular) + await databases.createCollection( + DATABASE_ID, + COLLECTION_ID, + "Kontaktanfragen", + [Permission.create(Role.any())], + true + ); + console.log("✓ Collection erstellt:", COLLECTION_ID); + + // 2. Attribute hinzufügen (Appwrite erfordert kleine Wartezeit zwischen den Schritten) + const delay = (ms) => new Promise((r) => setTimeout(r, ms)); + + await databases.createStringAttribute(DATABASE_ID, COLLECTION_ID, "name", 256, true); + await delay(500); + await databases.createStringAttribute(DATABASE_ID, COLLECTION_ID, "email", 512, true); + await delay(500); + await databases.createStringAttribute(DATABASE_ID, COLLECTION_ID, "company", 256, false); + await delay(500); + await databases.createStringAttribute(DATABASE_ID, COLLECTION_ID, "message", 4096, true); + + console.log("✓ Attribute erstellt: name, email, company, message"); + console.log("\nCollection ist bereit. Kontaktformular kann jetzt genutzt werden."); + } catch (err) { + if (err.code === 409) { + console.log("Collection existiert bereits:", COLLECTION_ID); + } else { + console.error("Fehler:", err.message || err); + process.exit(1); + } + } +} + +createCollection(); diff --git a/src/lib/appwrite.ts b/src/lib/appwrite.ts new file mode 100644 index 0000000..1bae7a0 --- /dev/null +++ b/src/lib/appwrite.ts @@ -0,0 +1,14 @@ +import { Client, Databases } from "appwrite"; + +const APPWRITE_ENDPOINT = "https://appwrite.webklar.com/v1"; +const APPWRITE_PROJECT = "696b82270034001dab69"; +const DATABASE_ID = "698124a20035e8f6dc42"; +export const CONTACTS_COLLECTION_ID = "contact_submissions"; + +const client = new Client() + .setEndpoint(APPWRITE_ENDPOINT) + .setProject(APPWRITE_PROJECT); + +export const databases = new Databases(client); + +export { DATABASE_ID }; diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index eed2979..dc02a0f 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -6,6 +6,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { ArrowLeft, Send } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; +import { ID, databases, DATABASE_ID, CONTACTS_COLLECTION_ID } from "@/lib/appwrite"; const Contact = () => { const { toast } = useToast(); @@ -30,16 +31,39 @@ const Contact = () => { e.preventDefault(); setIsSubmitting(true); - // Simulate form submission - await new Promise((resolve) => setTimeout(resolve, 1000)); + try { + const doc: Record = { + name: formData.name.trim(), + email: formData.email.trim(), + message: formData.message.trim(), + }; + if (formData.company.trim()) { + doc.company = formData.company.trim(); + } - toast({ - title: "Nachricht gesendet!", - description: "Wir melden uns innerhalb von 24 Stunden bei Ihnen.", - }); + await databases.createDocument( + DATABASE_ID, + CONTACTS_COLLECTION_ID, + ID.unique(), + doc + ); - setFormData({ name: "", email: "", company: "", message: "" }); - setIsSubmitting(false); + toast({ + title: "Nachricht gesendet!", + description: "Wir melden uns innerhalb von 24 Stunden bei Ihnen.", + }); + + setFormData({ name: "", email: "", company: "", message: "" }); + } catch (err) { + console.error("Fehler beim Senden:", err); + toast({ + title: "Fehler", + description: "Die Nachricht konnte nicht gesendet werden. Bitte versuchen Sie es später erneut oder kontaktieren Sie uns direkt per E-Mail.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } }; return (