Compare commits
4
Commits
b8b8ec7b7a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c505520f19 | ||
|
|
ab3a5da892 | ||
|
|
654f4455cc | ||
|
|
c9e4b0cdbb |
+1
-1
@@ -4,7 +4,7 @@ import sitemap from '@astrojs/sitemap';
|
||||
|
||||
// https://astro.build/config
|
||||
// TODO Phase 2: echte Domain eintragen, sobald Cloudflare-Domain steht (siehe Tech-Stack-Entscheidung.md)
|
||||
const SITE_URL = 'https://www.vans-diy-bastelbedarf.de';
|
||||
const SITE_URL = 'https://vans-diy-bastelbedarf.com';
|
||||
|
||||
export default defineConfig({
|
||||
site: SITE_URL,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://vans-diy-bastelbedarf.de/sitemap-index.xml
|
||||
Sitemap: https://vans-diy-bastelbedarf.com/sitemap-index.xml
|
||||
|
||||
@@ -7,6 +7,9 @@ DB_PATH=/var/lib/vandiy-shop/vandiy-shop.db
|
||||
# Port, auf dem der Node-Server lokal lauscht (Caddy leitet von außen dorthin weiter)
|
||||
PORT=4000
|
||||
|
||||
# Produktivbetrieb: sorgt u.a. dafuer, dass im Fehlerfall keine internen Details nach aussen gehen.
|
||||
NODE_ENV=production
|
||||
|
||||
# Zugangscode-Schranke (ganze Seite) — selbst gewählte, zufällige Werte
|
||||
SITE_ACCESS_SECRET=
|
||||
SITE_ACCESS_CODE_QCIGA=
|
||||
|
||||
+42
-3
@@ -4,11 +4,11 @@
|
||||
einem Deployment vereint hat. Die Zugangscode-Schranke (middleware/gate.js) läuft vor JEDER
|
||||
Anfrage, exakt wie functions/_middleware.js das vorher bei Cloudflare tat.
|
||||
|
||||
Umgebungsvariablen kommen aus einer .env-Datei — gestartet wird dieser Server mit
|
||||
"node --env-file=.env index.js" (Node 22 kann .env-Dateien nativ laden, kein zusätzliches
|
||||
Paket nötig).
|
||||
Umgebungsvariablen kommen aus einer .env-Datei im server/-Ordner (siehe .env.example), geladen
|
||||
über das "dotenv"-Paket direkt beim Start.
|
||||
===================================================================== */
|
||||
|
||||
import "dotenv/config";
|
||||
import express from "express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { dirname, join } from "node:path";
|
||||
@@ -35,6 +35,19 @@ const app = express();
|
||||
// Läuft hinter Caddy (Reverse Proxy) — nötig, damit req.protocol/req.get("host") und
|
||||
// "secure"-Cookies korrekt funktionieren, statt anzunehmen, die Verbindung sei unverschlüsselt.
|
||||
app.set("trust proxy", 1);
|
||||
app.disable("x-powered-by"); // verrät sonst unnötig den eingesetzten Technik-Stack
|
||||
|
||||
/* Sicherheits-Header, ergänzt 05.08.2026 (bei Cloudflare Pages kamen die teils von der Plattform;
|
||||
auf dem eigenen Server muss der Server sie selbst setzen). `payment=(self)` bleibt erlaubt,
|
||||
damit die PayPal-Zahlung im eigenen Fenster weiter funktioniert. */
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("X-Content-Type-Options", "nosniff");
|
||||
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
||||
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=(self)");
|
||||
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(express.json({ limit: "2mb" }));
|
||||
@@ -63,6 +76,32 @@ app.use((req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- Fehler-Handler (muss NACH allen Routen stehen), ergänzt 05.08.2026 ----------
|
||||
Express 4 fängt Fehler aus async-Handlern nicht selbst ab. Ohne diesen Handler landete kaputtes
|
||||
JSON in Express' Standard-Fehlerseite — inklusive komplettem Stacktrace mit Serverpfaden, solange
|
||||
NODE_ENV nicht auf "production" steht. Jetzt kommt in jedem Fall eine saubere JSON-Antwort. */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, req, res, next) => {
|
||||
if (err?.type === "entity.parse.failed") {
|
||||
return res.status(400).json({ ok: false, error: "Ungültige Anfrage." });
|
||||
}
|
||||
if (err?.type === "entity.too.large") {
|
||||
return res.status(413).json({ ok: false, error: "Die Anfrage ist zu groß." });
|
||||
}
|
||||
console.error(`[fehler] ${req.method} ${req.originalUrl}:`, err?.stack || err);
|
||||
if (res.headersSent) return;
|
||||
if (req.path.startsWith("/api/")) {
|
||||
return res.status(500).json({ ok: false, error: "Interner Fehler." });
|
||||
}
|
||||
return res.status(500).send("Es ist ein Fehler aufgetreten. Bitte später erneut versuchen.");
|
||||
});
|
||||
|
||||
/* Letztes Netz: Ein unbehandelter Fehler darf den Shop nicht offline nehmen — besonders wichtig
|
||||
direkt nach einer PayPal-Abbuchung, wo ein Absturz die Kundin ohne Rückmeldung zurücklassen
|
||||
würde, obwohl das Geld bereits eingezogen ist. */
|
||||
process.on("unhandledRejection", (grund) => console.error("[unhandledRejection]", grund));
|
||||
process.on("uncaughtException", (fehler) => console.error("[uncaughtException]", fehler?.stack || fehler));
|
||||
|
||||
app.listen(PORT, "127.0.0.1", () => {
|
||||
console.log(`Van's DIY & Bastelbedarf – Server läuft auf http://127.0.0.1:${PORT}`);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/* =====================================================================
|
||||
lib/async-wrap.js — kleiner Helfer, ergänzt am 05.08.2026.
|
||||
|
||||
Warum es das braucht: Express 4 fängt Fehler aus `async`-Route-Handlern NICHT selbst ab. Eine
|
||||
abgelehnte Promise wird zu einer "unhandled rejection" — und Node beendet daraufhin den ganzen
|
||||
Prozess. Bei Cloudflare Pages war ein Fehler dagegen immer auf die eine betroffene Anfrage
|
||||
begrenzt; dort konnte ein einzelner Datenbankfehler niemals den Shop offline nehmen.
|
||||
|
||||
Besonders kritisch bei /api/paypal/capture-order: dort würde ein Absturz genau in dem Moment
|
||||
passieren, in dem das Geld bei PayPal bereits abgebucht wurde — die Kundin bekäme statt der
|
||||
sorgfältig formulierten "bitte NICHT erneut bezahlen"-Meldung nur einen Verbindungsabbruch.
|
||||
|
||||
`wrap()` leitet jeden Fehler stattdessen an den zentralen Fehler-Handler in index.js weiter.
|
||||
===================================================================== */
|
||||
|
||||
export const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
||||
+10
-2
@@ -9,7 +9,15 @@
|
||||
|
||||
export const COOKIE_NAME = "vandiy_gate_session";
|
||||
export const ROLE_COOKIE_NAME = "vandiy_gate_role";
|
||||
export const SESSION_TAGE = 90;
|
||||
|
||||
/* Zugang gilt nur für die laufende Browser-Sitzung (Wunsch 05.08.2026: "jedes Mal wenn wir den
|
||||
Browser zu machen, soll der Code wieder gefragt werden"). Umgesetzt über ein reines
|
||||
Sitzungs-Cookie OHNE maxAge/expires in middleware/gate.js — das löscht der Browser beim
|
||||
Schließen selbst. SESSION_STUNDEN ist nur die zusätzliche Notbremse im signierten Token für
|
||||
den Fall, dass ein Browser tage-/wochenlang offen bleibt (dann läuft der Zugang trotzdem ab).
|
||||
Betrifft NUR die Zugangsschranke — Kundenkonten (customer-auth.js, 30 Tage) bleiben unberührt,
|
||||
echte Kund:innen sollen sich nicht bei jedem Besuch neu anmelden müssen. */
|
||||
export const SESSION_STUNDEN = 12;
|
||||
|
||||
function b64urlEncode(bytes) {
|
||||
let bin = "";
|
||||
@@ -34,7 +42,7 @@ async function hmacKey(secret) {
|
||||
}
|
||||
|
||||
export async function signSession(role, secret) {
|
||||
const payload = JSON.stringify({ role, exp: Date.now() + SESSION_TAGE * 24 * 60 * 60 * 1000 });
|
||||
const payload = JSON.stringify({ role, exp: Date.now() + SESSION_STUNDEN * 60 * 60 * 1000 });
|
||||
const payloadB64 = b64urlEncode(new TextEncoder().encode(payload));
|
||||
const key = await hmacKey(secret);
|
||||
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payloadB64));
|
||||
|
||||
@@ -42,7 +42,7 @@ function bestellBenachrichtigungHtml({ bestellnummer, name, email, land, artikel
|
||||
<p><strong>Artikel:</strong></p>
|
||||
<ul>${artikelZeilen}</ul>
|
||||
<p><strong>Summe: ${formatPreis(summe)}</strong></p>
|
||||
<p><a href="https://vans-diy-bastelbedarf.de/verwaltung/">→ Zur Bestellverwaltung</a></p>
|
||||
<p><a href="https://vans-diy-bastelbedarf.com/verwaltung/">→ Zur Bestellverwaltung</a></p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
- API-Routen bekommen bei fehlender Sitzung 401-JSON statt HTML-Redirect.
|
||||
===================================================================== */
|
||||
|
||||
import { COOKIE_NAME, ROLE_COOKIE_NAME, SESSION_TAGE, signSession, verifySession } from "../lib/auth.js";
|
||||
import { COOKIE_NAME, ROLE_COOKIE_NAME, signSession, verifySession } from "../lib/auth.js";
|
||||
import {
|
||||
VERWALTUNG_COOKIE_NAME,
|
||||
VERWALTUNG_SESSION_STUNDEN,
|
||||
signVerwaltungSession,
|
||||
verifyVerwaltungSession,
|
||||
} from "../lib/verwaltung-auth.js";
|
||||
@@ -44,9 +43,9 @@ export async function gateAuthHandler(req, res) {
|
||||
}
|
||||
|
||||
const session = await signSession(role, process.env.SITE_ACCESS_SECRET);
|
||||
const maxAge = SESSION_TAGE * 24 * 60 * 60 * 1000;
|
||||
res.cookie(COOKIE_NAME, session, { path: "/", maxAge, httpOnly: true, secure: true, sameSite: "lax" });
|
||||
res.cookie(ROLE_COOKIE_NAME, role, { path: "/", maxAge, secure: true, sameSite: "lax" });
|
||||
// Bewusst OHNE maxAge/expires → reines Sitzungs-Cookie, verfällt beim Schließen des Browsers.
|
||||
res.cookie(COOKIE_NAME, session, { path: "/", httpOnly: true, secure: true, sameSite: "lax" });
|
||||
res.cookie(ROLE_COOKIE_NAME, role, { path: "/", secure: true, sameSite: "lax" });
|
||||
return res.json({ ok: true, next });
|
||||
}
|
||||
|
||||
@@ -62,8 +61,8 @@ export async function verwaltungAuthHandler(req, res) {
|
||||
}
|
||||
|
||||
const session = await signVerwaltungSession(process.env.VERWALTUNG_SESSION_SECRET);
|
||||
const maxAge = VERWALTUNG_SESSION_STUNDEN * 60 * 60 * 1000;
|
||||
res.cookie(VERWALTUNG_COOKIE_NAME, session, { path: "/", maxAge, httpOnly: true, secure: true, sameSite: "lax" });
|
||||
// Ebenfalls reines Sitzungs-Cookie (siehe gateAuthHandler oben).
|
||||
res.cookie(VERWALTUNG_COOKIE_NAME, session, { path: "/", httpOnly: true, secure: true, sameSite: "lax" });
|
||||
return res.json({ ok: true, next });
|
||||
}
|
||||
|
||||
@@ -124,5 +123,5 @@ export function gateMiddleware(req, res, next) {
|
||||
|
||||
const nextParam = pfad + (req.originalUrl.includes("?") ? "?" + req.originalUrl.split("?")[1] : "");
|
||||
return res.redirect(302, `/gate?next=${encodeURIComponent(nextParam)}`);
|
||||
})();
|
||||
})().catch(next); // ergänzt 05.08.2026: verhindert Serverabsturz bei einem Fehler in der Prüfung
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Eigener Node.js/Express-Server für Van's DIY & Bastelbedarf – Ersatz für Cloudflare Pages Functions + D1, läuft auf dem eigenen Netcup-Server. Portiert 1:1 aus functions/ (Cloudflare Pages Functions), gleiche Sicherheitslogik, gleiche Datenbankstruktur (SQLite statt D1).",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,26 +4,27 @@
|
||||
===================================================================== */
|
||||
|
||||
import { Router } from "express";
|
||||
import { wrap } from "../lib/async-wrap.js";
|
||||
import { getCustomer, clearCustomerCookie } from "../lib/customer-auth.js";
|
||||
import { db } from "../db.js";
|
||||
|
||||
export const accountRouter = Router();
|
||||
|
||||
accountRouter.get("/api/account/me", async (req, res) => {
|
||||
accountRouter.get("/api/account/me", wrap(async (req, res) => {
|
||||
const customer = await getCustomer(req);
|
||||
if (!customer) return res.json({ ok: false });
|
||||
return res.json({
|
||||
ok: true,
|
||||
customer: { email: customer.email, name: customer.name, provider: customer.provider, seit: customer.created_at },
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
accountRouter.post("/api/account/logout", (req, res) => {
|
||||
clearCustomerCookie(res);
|
||||
return res.json({ ok: true });
|
||||
});
|
||||
|
||||
accountRouter.post("/api/account/delete", async (req, res) => {
|
||||
accountRouter.post("/api/account/delete", wrap(async (req, res) => {
|
||||
const customer = await getCustomer(req);
|
||||
if (!customer) return res.status(401).json({ ok: false, error: "Nicht angemeldet." });
|
||||
|
||||
@@ -35,9 +36,9 @@ accountRouter.post("/api/account/delete", async (req, res) => {
|
||||
|
||||
clearCustomerCookie(res);
|
||||
return res.json({ ok: true });
|
||||
});
|
||||
}));
|
||||
|
||||
accountRouter.get("/api/account/export", async (req, res) => {
|
||||
accountRouter.get("/api/account/export", wrap(async (req, res) => {
|
||||
const customer = await getCustomer(req);
|
||||
if (!customer) return res.status(401).json({ ok: false, error: "Nicht angemeldet." });
|
||||
|
||||
@@ -51,4 +52,4 @@ accountRouter.get("/api/account/export", async (req, res) => {
|
||||
|
||||
res.setHeader("Content-Disposition", 'attachment; filename="meine-daten.json"');
|
||||
return res.status(200).json(daten);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -9,7 +9,11 @@ import { starteOAuth, verarbeiteOAuthCallback } from "../lib/oauth-handlers.js";
|
||||
|
||||
export const authRouter = Router();
|
||||
|
||||
authRouter.get("/api/auth/google/start", (req, res) => starteOAuth(req, res, "google"));
|
||||
authRouter.get("/api/auth/google/callback", (req, res) => verarbeiteOAuthCallback(req, res, "google"));
|
||||
authRouter.get("/api/auth/paypal/start", (req, res) => starteOAuth(req, res, "paypal"));
|
||||
authRouter.get("/api/auth/paypal/callback", (req, res) => verarbeiteOAuthCallback(req, res, "paypal"));
|
||||
/* wrap() ergänzt 05.08.2026: Express 4 fängt Fehler aus async-Handlern nicht selbst ab — ohne das
|
||||
würde z.B. ein Netzwerkfehler beim Token-Austausch mit Google/PayPal den ganzen Shop beenden. */
|
||||
const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
||||
|
||||
authRouter.get("/api/auth/google/start", wrap((req, res) => starteOAuth(req, res, "google")));
|
||||
authRouter.get("/api/auth/google/callback", wrap((req, res) => verarbeiteOAuthCallback(req, res, "google")));
|
||||
authRouter.get("/api/auth/paypal/start", wrap((req, res) => starteOAuth(req, res, "paypal")));
|
||||
authRouter.get("/api/auth/paypal/callback", wrap((req, res) => verarbeiteOAuthCallback(req, res, "paypal")));
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
===================================================================== */
|
||||
|
||||
import { Router } from "express";
|
||||
import { wrap } from "../lib/async-wrap.js";
|
||||
import { erstelleBestellung } from "../lib/bestellung-erstellen.js";
|
||||
|
||||
export const ordersRouter = Router();
|
||||
|
||||
ordersRouter.post("/api/orders", async (req, res) => {
|
||||
ordersRouter.post("/api/orders", wrap(async (req, res) => {
|
||||
const body = req.body || {};
|
||||
const zahlungsart = String(body.zahlungsart || "");
|
||||
|
||||
@@ -28,4 +29,4 @@ ordersRouter.post("/api/orders", async (req, res) => {
|
||||
return res.status(ergebnis.httpStatus || 500).json({ ok: false, error: ergebnis.error });
|
||||
}
|
||||
return res.status(201).json({ ok: true, id: ergebnis.id, bestellnummer: ergebnis.bestellnummer });
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
===================================================================== */
|
||||
|
||||
import { Router } from "express";
|
||||
import { wrap } from "../lib/async-wrap.js";
|
||||
import { paypalKonfiguriert, erstellePaypalBestellung, erfassePaypalZahlung } from "../lib/paypal.js";
|
||||
import { erstelleBestellung } from "../lib/bestellung-erstellen.js";
|
||||
|
||||
@@ -13,7 +14,7 @@ export const paypalRouter = Router();
|
||||
|
||||
const BETRAG_TOLERANZ_EURO = 0.02;
|
||||
|
||||
paypalRouter.post("/api/paypal/create-order", async (req, res) => {
|
||||
paypalRouter.post("/api/paypal/create-order", wrap(async (req, res) => {
|
||||
if (!paypalKonfiguriert()) {
|
||||
return res.status(503).json({
|
||||
ok: false,
|
||||
@@ -33,9 +34,9 @@ paypalRouter.post("/api/paypal/create-order", async (req, res) => {
|
||||
} catch (err) {
|
||||
return res.status(502).json({ ok: false, error: err instanceof Error ? err.message : "PayPal-Bestellung fehlgeschlagen." });
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
paypalRouter.post("/api/paypal/capture-order", async (req, res) => {
|
||||
paypalRouter.post("/api/paypal/capture-order", wrap(async (req, res) => {
|
||||
if (!paypalKonfiguriert()) {
|
||||
return res.status(503).json({ ok: false, error: "PayPal ist auf dieser Seite noch nicht eingerichtet." });
|
||||
}
|
||||
@@ -72,4 +73,4 @@ paypalRouter.post("/api/paypal/capture-order", async (req, res) => {
|
||||
}
|
||||
|
||||
return res.status(201).json({ ok: true, id: ergebnis.id, bestellnummer: ergebnis.bestellnummer });
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
===================================================================== */
|
||||
|
||||
import { Router } from "express";
|
||||
import { wrap } from "../lib/async-wrap.js";
|
||||
import { getGateRole, unauthorizedJson } from "../lib/auth.js";
|
||||
import { hatGueltigeVerwaltungSitzung } from "../lib/verwaltung-auth.js";
|
||||
import { db } from "../db.js";
|
||||
@@ -27,7 +28,7 @@ async function pruefeSchranken(req, res) {
|
||||
return true;
|
||||
}
|
||||
|
||||
verwaltungRouter.get("/api/verwaltung/orders", async (req, res) => {
|
||||
verwaltungRouter.get("/api/verwaltung/orders", wrap(async (req, res) => {
|
||||
if (!(await pruefeSchranken(req, res))) return;
|
||||
|
||||
try {
|
||||
@@ -50,9 +51,9 @@ verwaltungRouter.get("/api/verwaltung/orders", async (req, res) => {
|
||||
} catch (err) {
|
||||
return res.status(500).json({ ok: false, error: "Bestellungen konnten nicht geladen werden." });
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
verwaltungRouter.patch("/api/verwaltung/orders/:id", async (req, res) => {
|
||||
verwaltungRouter.patch("/api/verwaltung/orders/:id", wrap(async (req, res) => {
|
||||
if (!(await pruefeSchranken(req, res))) return;
|
||||
|
||||
const id = Number(req.params.id);
|
||||
@@ -92,4 +93,4 @@ verwaltungRouter.patch("/api/verwaltung/orders/:id", async (req, res) => {
|
||||
} catch (err) {
|
||||
return res.status(500).json({ ok: false, error: "Bestellung konnte nicht aktualisiert werden." });
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"sections": [
|
||||
{
|
||||
"heading": "§ 1 Geltungsbereich",
|
||||
"body": "Diese Allgemeinen Geschäftsbedingungen gelten für alle Bestellungen, die Verbraucher über den Onlineshop von Van's DIY & Bastelbedarf (Einzelunternehmen, [Anschrift]) aufgeben."
|
||||
"body": "Diese Allgemeinen Geschäftsbedingungen gelten für alle Bestellungen, die Verbraucher über den Onlineshop von Van's DIY & Bastelbedarf (Einzelunternehmen, Vanessa Geske, Von-Arnim-Str. 40, 17309 Rollwitz) aufgeben."
|
||||
},
|
||||
{
|
||||
"heading": "§ 2 Vertragspartner, Vertragsschluss",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"sections": [
|
||||
{
|
||||
"heading": "1. Verantwortlicher",
|
||||
"body": "[Vorname Nachname], Van's DIY & Bastelbedarf, [Anschrift], [E-Mail-Adresse]"
|
||||
"body": "Vanessa Geske, Van's DIY & Bastelbedarf, Von-Arnim-Str. 40, 17309 Rollwitz, [email protected]"
|
||||
},
|
||||
{
|
||||
"heading": "2. Allgemeines zur Datenverarbeitung",
|
||||
@@ -10,11 +10,11 @@
|
||||
},
|
||||
{
|
||||
"heading": "3. Hosting",
|
||||
"body": "Diese Website wird bei einem externen Dienstleister gehostet (Hoster). Die personenbezogenen Daten, die auf dieser Website erfasst werden, werden auf den Servern des Hosters gespeichert. [Konkreten Hosting-Anbieter, z. B. Cloudflare Pages, hier ergänzen inkl. Standort der Server und ggf. Auftragsverarbeitungsvertrag.]"
|
||||
"body": "Diese Website wird auf einem eigenen Server der netcup GmbH (Daimlerstraße 25, 76185 Karlsruhe, Deutschland) im Rechenzentrum Nürnberg betrieben; mit netcup besteht ein Vertrag zur Auftragsverarbeitung. Die personenbezogenen Daten, die auf dieser Website erfasst werden, werden dort auf dem Server gespeichert. Dem Server vorgeschaltet ist als Content-Delivery-Network und Schutz vor Überlastungsangriffen Cloudflare (Cloudflare, Inc., 101 Townsend St, San Francisco, USA); dabei werden technisch notwendige Daten (u. a. IP-Adresse, Zeitstempel, aufgerufene URL) auch von Cloudflare verarbeitet. Eine Übermittlung in die USA ist dabei nicht ausgeschlossen, abgesichert über die EU-Standardvertragsklauseln bzw. das EU-US Data Privacy Framework. Näheres: cloudflare.com/privacypolicy."
|
||||
},
|
||||
{
|
||||
"heading": "4. Bestellabwicklung",
|
||||
"body": "Zur Abwicklung deiner Bestellung erheben und verarbeiten wir personenbezogene Daten, wenn dies zur Erfüllung des Vertrags erforderlich ist (Art. 6 Abs. 1 lit. b DSGVO). Dazu gehören Name, Anschrift, E-Mail-Adresse und Zahlungsdaten. Die Daten werden ausschließlich zur Vertragsabwicklung genutzt."
|
||||
"body": "Zur Abwicklung deiner Bestellung erheben und verarbeiten wir personenbezogene Daten, wenn dies zur Erfüllung des Vertrags erforderlich ist (Art. 6 Abs. 1 lit. b DSGVO). Dazu gehören Name, Anschrift, E-Mail-Adresse und Zahlungsdaten. Die Daten werden ausschließlich zur Vertragsabwicklung genutzt. Die interne Benachrichtigung über eine neue Bestellung wird über den E-Mail-Versanddienst Resend (Resend, Inc., USA) verschickt, der dabei technisch als Auftragsverarbeiter die für diese eine Nachricht nötigen Bestelldaten verarbeitet; Resend ist nach dem EU-US Data Privacy Framework zertifiziert."
|
||||
},
|
||||
{
|
||||
"heading": "5. Zahlungsdienstleister",
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
{
|
||||
"heading": "6. Kundenkonto (Anmeldung mit Google oder PayPal)",
|
||||
"body": "Für ein Kundenkonto bieten wir ausschließlich die Anmeldung über bestehende Google- oder PayPal-Konten an (OAuth 2.0 / OpenID Connect) — ein eigenes Passwort bei uns gibt es nicht. Beim ersten Login übermitteln uns Google bzw. PayPal deine E-Mail-Adresse, deinen Namen und eine eindeutige, anbieterseitige Kennung; wir speichern diese Angaben zusammen mit dem Anmeldezeitpunkt in unserer Datenbank (Cloudflare D1), um dich bei künftigen Besuchen wiederzuerkennen. Die Verarbeitung erfolgt auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO (Erfüllung des Nutzungsvertrags für das Kundenkonto) sowie deiner Einwilligung durch den aktiven Klick auf den jeweiligen Anmelde-Button (Art. 6 Abs. 1 lit. a DSGVO). Verantwortlich für die Verarbeitung deiner Daten bei Google ist die Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland; die Muttergesellschaft Google LLC (USA) ist nach unserer Kenntnis nach dem EU-US Data Privacy Framework zertifiziert, das ein angemessenes Datenschutzniveau sicherstellt. Für PayPal gelten dieselben Angaben wie unter Punkt 5 (PayPal (Europe) S.à r.l. et Cie, S.C.A., DPF-Zertifizierung der US-Muttergesellschaft). Es gelten zusätzlich die jeweiligen Datenschutzhinweise von Google (policies.google.com/privacy) bzw. PayPal (paypal.com/de/webapps/mpp/ua/privacy-full). Du kannst dein Kundenkonto jederzeit selbst und ohne Rückfrage in deinem Kontobereich unwiderruflich löschen (Art. 17 DSGVO) oder deine gespeicherten Kontodaten als Datei herunterladen (Art. 15, 20 DSGVO); bereits aufgegebene Bestellungen bleiben davon unberührt, da für sie eigenständige, gesetzliche Aufbewahrungspflichten gelten (siehe Punkt 10)."
|
||||
"body": "Für ein Kundenkonto bieten wir ausschließlich die Anmeldung über bestehende Google- oder PayPal-Konten an (OAuth 2.0 / OpenID Connect) — ein eigenes Passwort bei uns gibt es nicht. Beim ersten Login übermitteln uns Google bzw. PayPal deine E-Mail-Adresse, deinen Namen und eine eindeutige, anbieterseitige Kennung; wir speichern diese Angaben zusammen mit dem Anmeldezeitpunkt in unserer Datenbank auf dem eigenen Server (siehe Punkt 3), um dich bei künftigen Besuchen wiederzuerkennen. Die Verarbeitung erfolgt auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO (Erfüllung des Nutzungsvertrags für das Kundenkonto) sowie deiner Einwilligung durch den aktiven Klick auf den jeweiligen Anmelde-Button (Art. 6 Abs. 1 lit. a DSGVO). Verantwortlich für die Verarbeitung deiner Daten bei Google ist die Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland; die Muttergesellschaft Google LLC (USA) ist nach unserer Kenntnis nach dem EU-US Data Privacy Framework zertifiziert, das ein angemessenes Datenschutzniveau sicherstellt. Für PayPal gelten dieselben Angaben wie unter Punkt 5 (PayPal (Europe) S.à r.l. et Cie, S.C.A., DPF-Zertifizierung der US-Muttergesellschaft). Es gelten zusätzlich die jeweiligen Datenschutzhinweise von Google (policies.google.com/privacy) bzw. PayPal (paypal.com/de/webapps/mpp/ua/privacy-full). Du kannst dein Kundenkonto jederzeit selbst und ohne Rückfrage in deinem Kontobereich unwiderruflich löschen (Art. 17 DSGVO) oder deine gespeicherten Kontodaten als Datei herunterladen (Art. 15, 20 DSGVO); bereits aufgegebene Bestellungen bleiben davon unberührt, da für sie eigenständige, gesetzliche Aufbewahrungspflichten gelten (siehe Punkt 10)."
|
||||
},
|
||||
{
|
||||
"heading": "7. Kontaktformular",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"sections": [
|
||||
{
|
||||
"heading": "Angaben gemäß § 5 DDG",
|
||||
"body": "[Vorname Nachname] („VanVan\")\nVan's DIY & Bastelbedarf\nEinzelunternehmen\n[Straße und Hausnummer]\n[PLZ Ort]\nDeutschland"
|
||||
"body": "Vanessa Geske („VanVan\")\nVan's DIY & Bastelbedarf\nEinzelunternehmen\nVon-Arnim-Str. 40\n17309 Rollwitz\nDeutschland"
|
||||
},
|
||||
{
|
||||
"heading": "Kontakt",
|
||||
"body": "Telefon: [Telefonnummer]\nE-Mail: [E-Mail-Adresse]"
|
||||
"body": "Telefon: 015167505105\nE-Mail: [email protected]"
|
||||
},
|
||||
{
|
||||
"heading": "Umsatzsteuer",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
{
|
||||
"heading": "Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV",
|
||||
"body": "[Vorname Nachname], Anschrift wie oben."
|
||||
"body": "Vanessa Geske, Anschrift wie oben."
|
||||
},
|
||||
{
|
||||
"heading": "EU-Streitschlichtung",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"sections": [
|
||||
{
|
||||
"heading": "Widerrufsrecht",
|
||||
"body": "Du hast das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag, an dem du oder ein von dir benannter Dritter, der nicht der Beförderer ist, die Waren in Besitz genommen hast bzw. hat.\n\nUm dein Widerrufsrecht auszuüben, musst du uns ([Vorname Nachname], Van's DIY & Bastelbedarf, [Anschrift], [E-Mail-Adresse]) mittels einer eindeutigen Erklärung (z. B. ein mit der Post versandter Brief oder E-Mail) über deinen Entschluss, diesen Vertrag zu widerrufen, informieren. Ein Muster-Widerrufsformular findest du unten auf dieser Seite verlinkt, das jedoch nicht vorgeschrieben ist.\n\nZur Wahrung der Widerrufsfrist reicht es aus, dass du die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absendest."
|
||||
"body": "Du hast das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu widerrufen. Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag, an dem du oder ein von dir benannter Dritter, der nicht der Beförderer ist, die Waren in Besitz genommen hast bzw. hat.\n\nUm dein Widerrufsrecht auszuüben, musst du uns (Vanessa Geske, Van's DIY & Bastelbedarf, Von-Arnim-Str. 40, 17309 Rollwitz, [email protected]) mittels einer eindeutigen Erklärung (z. B. ein mit der Post versandter Brief oder E-Mail) über deinen Entschluss, diesen Vertrag zu widerrufen, informieren. Ein Muster-Widerrufsformular findest du unten auf dieser Seite verlinkt, das jedoch nicht vorgeschrieben ist.\n\nZur Wahrung der Widerrufsfrist reicht es aus, dass du die Mitteilung über die Ausübung des Widerrufsrechts vor Ablauf der Widerrufsfrist absendest."
|
||||
},
|
||||
{
|
||||
"heading": "Folgen des Widerrufs",
|
||||
|
||||
+7
-7
@@ -76,7 +76,7 @@ export const ui = {
|
||||
gdprPrefix: "Ich habe die", gdprLink: "Datenschutzerklärung", gdprSuffix: "gelesen und bin mit der Verarbeitung meiner Daten einverstanden.",
|
||||
send: "Nachricht senden", sentDemo: "Danke! (Demo – noch keine echte Übertragung in Phase 1)",
|
||||
todoNote: "TODO Phase 2: Formular an eine echte Backend-/E-Mail-Anbindung (Cloudflare Worker) anschließen. Aktuell ohne echten Versand.",
|
||||
directTitle: "Direkter Kontakt", emailPlaceholderNote: "(Platzhalter-Adresse)", orderNote: "Für Bestellfragen bitte die Bestellnummer angeben.",
|
||||
directTitle: "Direkter Kontakt", emailPlaceholderNote: "", orderNote: "Für Bestellfragen bitte die Bestellnummer angeben.",
|
||||
responseTitle: "Antwortzeit", responseNote: "In der Regel innerhalb von 1–2 Werktagen.",
|
||||
},
|
||||
about: {
|
||||
@@ -325,7 +325,7 @@ export const ui = {
|
||||
},
|
||||
},
|
||||
en: {
|
||||
nav: { shop: "Shop", sale: "Sale", creative: "About the Maker", faq: "FAQ", contact: "Contact", account: "My Account", login: "Sign in" },
|
||||
nav: { shop: "Shop", sale: "Sale", creative: "About the Maker", faq: "FAQ", contact: "Contact", account: "My Account", login: "Sign in", cart: "Cart" },
|
||||
accountMenu: { myAccount: "My Account", logout: "Sign out" },
|
||||
topbar: "Free shipping from €75 (DE) / €85 (AT, LU) / €100 (CH)",
|
||||
footer: {
|
||||
@@ -394,7 +394,7 @@ export const ui = {
|
||||
gdprPrefix: "I have read the", gdprLink: "privacy policy", gdprSuffix: "and agree to the processing of my data.",
|
||||
send: "Send message", sentDemo: "Thank you! (Demo – no real submission yet in Phase 1)",
|
||||
todoNote: "TODO Phase 2: connect this form to a real backend/email service (Cloudflare Worker). Currently no real sending happens.",
|
||||
directTitle: "Direct contact", emailPlaceholderNote: "(placeholder address)", orderNote: "Please include your order number for order-related questions.",
|
||||
directTitle: "Direct contact", emailPlaceholderNote: "", orderNote: "Please include your order number for order-related questions.",
|
||||
responseTitle: "Response time", responseNote: "Usually within 1–2 business days.",
|
||||
},
|
||||
about: {
|
||||
@@ -643,7 +643,7 @@ export const ui = {
|
||||
},
|
||||
},
|
||||
ch: {
|
||||
nav: { shop: "Shop", sale: "Aktion", creative: "Über d'Kreativi", faq: "FAQ", contact: "Kontakt", account: "Mis Konto", login: "Aamälde" },
|
||||
nav: { shop: "Shop", sale: "Aktion", creative: "Über d'Kreativi", faq: "FAQ", contact: "Kontakt", account: "Mis Konto", login: "Aamälde", cart: "Warechorb" },
|
||||
accountMenu: { myAccount: "Mis Konto", logout: "Abmälde" },
|
||||
topbar: "Gratisversand ab 75 € (DE) / 85 € (AT, LU) / 100 € (CH)",
|
||||
footer: {
|
||||
@@ -712,7 +712,7 @@ export const ui = {
|
||||
gdprPrefix: "Ich ha d'", gdprLink: "Datenschutzerklärig", gdprSuffix: "gläse und bin mit dr Verarbeitig vo mine Date iverstande.",
|
||||
send: "Nachricht abschicke", sentDemo: "Merci! (Demo – noch keine echte Übertragung in Phase 1)",
|
||||
todoNote: "TODO Phase 2: Formular a e echti Backend-/E-Mail-Aabindig (Cloudflare Worker) aaschliesse. Momentan ohni echte Versand.",
|
||||
directTitle: "Direkte Kontaktdate", emailPlaceholderNote: "(Platzhalter-Adrässe)", orderNote: "Für Bstellfrage bitte d'Bstellnummere aagäh.",
|
||||
directTitle: "Direkte Kontaktdate", emailPlaceholderNote: "", orderNote: "Für Bstellfrage bitte d'Bstellnummere aagäh.",
|
||||
responseTitle: "Antwortzyt", responseNote: "Meistens innerhalb vo 1–2 Werktage.",
|
||||
},
|
||||
about: {
|
||||
@@ -961,7 +961,7 @@ export const ui = {
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
nav: { shop: "Boutique", sale: "Soldes", creative: "À propos de la créatrice", faq: "FAQ", contact: "Contact", account: "Mon compte", login: "Se connecter" },
|
||||
nav: { shop: "Boutique", sale: "Soldes", creative: "À propos de la créatrice", faq: "FAQ", contact: "Contact", account: "Mon compte", login: "Se connecter", cart: "Panier" },
|
||||
accountMenu: { myAccount: "Mon compte", logout: "Se déconnecter" },
|
||||
topbar: "Livraison gratuite dès 75 € (DE) / 85 € (AT, LU) / 100 € (CH)",
|
||||
footer: {
|
||||
@@ -1030,7 +1030,7 @@ export const ui = {
|
||||
gdprPrefix: "J'ai lu la", gdprLink: "politique de confidentialité", gdprSuffix: "et j'accepte le traitement de mes données.",
|
||||
send: "Envoyer le message", sentDemo: "Merci ! (Démo – pas encore d'envoi réel en phase 1)",
|
||||
todoNote: "À faire en phase 2 : connecter ce formulaire à un vrai service backend/e-mail (Cloudflare Worker). Actuellement aucun envoi réel n'a lieu.",
|
||||
directTitle: "Contact direct", emailPlaceholderNote: "(adresse provisoire)", orderNote: "Pour toute question de commande, merci d'indiquer le numéro de commande.",
|
||||
directTitle: "Contact direct", emailPlaceholderNote: "", orderNote: "Pour toute question de commande, merci d'indiquer le numéro de commande.",
|
||||
responseTitle: "Délai de réponse", responseNote: "Généralement sous 1 à 2 jours ouvrés.",
|
||||
},
|
||||
about: {
|
||||
|
||||
@@ -51,7 +51,7 @@ const p = (href: string) => `${localePrefix(lang)}${href}`;
|
||||
<title>{title} · Van's DIY & Bastelbedarf</title>
|
||||
<meta name="description" content={description} />
|
||||
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
||||
<link rel="canonical" href={new URL(Astro.url.pathname, Astro.site ?? "https://vans-diy-bastelbedarf.de").toString()} />
|
||||
<link rel="canonical" href={new URL(Astro.url.pathname, Astro.site ?? "https://vans-diy-bastelbedarf.com").toString()} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="Van's DIY & Bastelbedarf" />
|
||||
<meta property="og:title" content={title} />
|
||||
|
||||
@@ -7,9 +7,9 @@ import { agb, rechtstextAlsHtml } from "../../data/legal";
|
||||
<div class="container">
|
||||
<h1>Allgemeine Geschäftsbedingungen</h1>
|
||||
<div class="legal-note">
|
||||
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
|
||||
Prüfung durch eine fachkundige Stelle vor Veröffentlichung — im Adminbereich unter
|
||||
„⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
⚠️ Enthält die echten Angaben von VanVan, wurde aber noch nicht von einer fachkundigen
|
||||
Stelle geprüft. Vor dem echten Verkaufsstart bitte fachlich prüfen lassen — im
|
||||
Adminbereich unter „⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
</div>
|
||||
|
||||
{agb.sections.map((s) => (
|
||||
|
||||
@@ -45,7 +45,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<div class="card contact-highlight">
|
||||
<h3><span aria-hidden="true">💌</span> {t.contact.directTitle}</h3>
|
||||
<p class="small">E-Mail: <a href="mailto:info@vans-diy-bastelbedarf.de">info@vans-diy-bastelbedarf.de</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">E-Mail: <a href="mailto:vansdiyundbastelbedarf@gmail.com">vansdiyundbastelbedarf@gmail.com</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">{t.contact.orderNote}</p>
|
||||
<h3><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h3>
|
||||
<p class="small">{t.contact.responseNote}</p>
|
||||
|
||||
@@ -7,10 +7,10 @@ import { datenschutz, rechtstextAlsHtml } from "../../data/legal";
|
||||
<div class="container">
|
||||
<h1>Datenschutzerklärung</h1>
|
||||
<div class="legal-note">
|
||||
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
|
||||
Prüfung. Vor Veröffentlichung an die tatsächlich eingesetzten Dienste (z. B. konkreter
|
||||
Hosting-Anbieter, Zahlungsdienstleister, Versandtool) anpassen und rechtlich prüfen
|
||||
lassen — im Adminbereich unter „⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
⚠️ Enthält die echten Angaben von VanVan und die tatsächlich eingesetzten Dienste
|
||||
(Hosting, Zahlungsdienstleister, E-Mail-Versand), wurde aber noch nicht von einer
|
||||
fachkundigen Stelle geprüft. Vor dem echten Verkaufsstart bitte fachlich prüfen lassen —
|
||||
im Adminbereich unter „⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
</div>
|
||||
|
||||
{datenschutz.sections.map((s) => (
|
||||
|
||||
@@ -45,7 +45,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<div class="card contact-highlight">
|
||||
<h3><span aria-hidden="true">💌</span> {t.contact.directTitle}</h3>
|
||||
<p class="small">E-Mail: <a href="mailto:info@vans-diy-bastelbedarf.de">info@vans-diy-bastelbedarf.de</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">E-Mail: <a href="mailto:vansdiyundbastelbedarf@gmail.com">vansdiyundbastelbedarf@gmail.com</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">{t.contact.orderNote}</p>
|
||||
<h3><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h3>
|
||||
<p class="small">{t.contact.responseNote}</p>
|
||||
|
||||
@@ -45,7 +45,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<div class="card contact-highlight">
|
||||
<h3><span aria-hidden="true">💌</span> {t.contact.directTitle}</h3>
|
||||
<p class="small">E-Mail: <a href="mailto:info@vans-diy-bastelbedarf.de">info@vans-diy-bastelbedarf.de</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">E-Mail: <a href="mailto:vansdiyundbastelbedarf@gmail.com">vansdiyundbastelbedarf@gmail.com</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">{t.contact.orderNote}</p>
|
||||
<h3><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h3>
|
||||
<p class="small">{t.contact.responseNote}</p>
|
||||
|
||||
@@ -7,11 +7,10 @@ import { impressum, rechtstextAlsHtml } from "../../data/legal";
|
||||
<div class="container">
|
||||
<h1>Impressum</h1>
|
||||
<div class="legal-note">
|
||||
⚠️ <strong>Muster-Text.</strong> Dies ist ein vorbereiteter Platzhalter gemäß Pflichtenheft
|
||||
und ersetzt keine rechtliche Prüfung. Vor Veröffentlichung bitte von einer fachkundigen
|
||||
Stelle (z. B. Anwalt/Anwältin oder IHK-Impressumsgenerator) prüfen und mit den echten
|
||||
Angaben von VanVan vervollständigen — im Adminbereich unter „⚖️ Rechtstexte & FAQ"
|
||||
bearbeitbar.
|
||||
⚠️ Enthält die echten Angaben von VanVan, wurde aber noch nicht von einer fachkundigen
|
||||
Stelle (z. B. Anwalt/Anwältin oder IHK-Impressumsgenerator) geprüft. Vor dem echten
|
||||
Verkaufsstart bitte fachlich prüfen lassen — im Adminbereich unter
|
||||
„⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
</div>
|
||||
|
||||
{impressum.sections.map((s) => (
|
||||
|
||||
@@ -45,7 +45,7 @@ const t = useTranslations(lang);
|
||||
|
||||
<div class="card contact-highlight">
|
||||
<h2><span aria-hidden="true">💌</span> {t.contact.directTitle}</h2>
|
||||
<p class="small">E-Mail: <a href="mailto:info@vans-diy-bastelbedarf.de">info@vans-diy-bastelbedarf.de</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">E-Mail: <a href="mailto:vansdiyundbastelbedarf@gmail.com">vansdiyundbastelbedarf@gmail.com</a> <span class="small">{t.contact.emailPlaceholderNote}</span></p>
|
||||
<p class="small">{t.contact.orderNote}</p>
|
||||
<h2><span aria-hidden="true">⏱️</span> {t.contact.responseTitle}</h2>
|
||||
<p class="small">{t.contact.responseNote}</p>
|
||||
|
||||
@@ -5,16 +5,10 @@ import Layout from "../../layouts/Layout.astro";
|
||||
<section class="section-tight">
|
||||
<div class="container">
|
||||
<h1>Muster-Widerrufsformular</h1>
|
||||
<div class="legal-note">
|
||||
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
|
||||
Prüfung vor Veröffentlichung. Dieses Formular ist nicht verpflichtend — du kannst deinen
|
||||
Widerruf auch formlos erklären.
|
||||
</div>
|
||||
|
||||
<p>(Wenn du den Vertrag widerrufen möchtest, fülle bitte dieses Formular aus und sende es zurück.)</p>
|
||||
<p>(Wenn du den Vertrag widerrufen möchtest, fülle bitte dieses Formular aus und sende es zurück. Dieses Formular ist nicht verpflichtend — du kannst deinen Widerruf auch formlos erklären.)</p>
|
||||
|
||||
<div class="card">
|
||||
<p>An<br />[Vorname Nachname]<br />Van's DIY & Bastelbedarf<br />[Anschrift]<br />[E-Mail-Adresse]</p>
|
||||
<p>An<br />Vanessa Geske<br />Van's DIY & Bastelbedarf<br />Von-Arnim-Str. 40, 17309 Rollwitz<br />[email protected]</p>
|
||||
<p>
|
||||
Hiermit widerrufe(n) ich/wir (*) den von mir/uns (*) abgeschlossenen Vertrag über den Kauf
|
||||
der folgenden Waren (*)/die Erbringung der folgenden Dienstleistung (*)
|
||||
|
||||
@@ -7,9 +7,9 @@ import { widerruf, rechtstextAlsHtml } from "../../data/legal";
|
||||
<div class="container">
|
||||
<h1>Widerrufsbelehrung</h1>
|
||||
<div class="legal-note">
|
||||
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
|
||||
Prüfung durch eine fachkundige Stelle vor Veröffentlichung — im Adminbereich unter
|
||||
„⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
⚠️ Enthält die echten Angaben von VanVan, wurde aber noch nicht von einer fachkundigen
|
||||
Stelle geprüft. Vor dem echten Verkaufsstart bitte fachlich prüfen lassen — im
|
||||
Adminbereich unter „⚖️ Rechtstexte & FAQ" bearbeitbar.
|
||||
</div>
|
||||
|
||||
{widerruf.sections.map((s) => (
|
||||
|
||||
Reference in New Issue
Block a user