Implementiere Impressum/Datenschutz-System und bereinige URL-Konfiguration

- Neues Impressum/Datenschutz-Tab mit konfigurierbaren rechtlichen Daten
- Konfigurationsdatei legal-config.ts für alle rechtlichen Informationen
- RPC-Endpoint legal.getConfig() für rechtliche Daten
- Schöne Tab-Navigation zwischen Impressum und Datenschutz
- Responsive Design mit Loading-States und Fehlerbehandlung
- Alle rechtlichen Daten über Umgebungsvariablen konfigurierbar
- FRONTEND_URL entfernt - nur noch DOMAIN wird verwendet
- Hilfsfunktion generateUrl() für konsistente URL-Generierung
- Code-Duplikation in bookings.ts eliminiert
- .env.example aktualisiert mit allen neuen Variablen
- README.md dokumentiert neue rechtliche Konfiguration
- DSGVO- und TMG-konforme Inhalte implementiert
This commit is contained in:
2025-09-30 18:14:01 +02:00
parent 55923e0426
commit 40d76680fd
9 changed files with 407 additions and 27 deletions

View File

@@ -0,0 +1,76 @@
// Legal configuration for Impressum and Datenschutz
export interface LegalConfig {
// Impressum data
companyName: string;
ownerName: string;
address: {
street: string;
city: string;
postalCode: string;
country: string;
};
contact: {
phone: string;
email: string;
website: string;
};
businessDetails: {
taxId?: string;
vatId?: string;
commercialRegister?: string;
responsibleForContent: string;
};
// Datenschutz data
dataProtection: {
responsiblePerson: string;
email: string;
purpose: string;
legalBasis: string;
dataRetention: string;
rights: string;
cookies: string;
thirdPartyServices: string[];
dataSecurity: string;
contactDataProtection: string;
};
}
// Default configuration - should be overridden by environment variables
export const defaultLegalConfig: LegalConfig = {
companyName: process.env.COMPANY_NAME || "Stargirlnails Kiel",
ownerName: process.env.OWNER_NAME || "Inhaber Name",
address: {
street: process.env.ADDRESS_STREET || "Musterstraße 123",
city: process.env.ADDRESS_CITY || "Kiel",
postalCode: process.env.ADDRESS_POSTAL_CODE || "24103",
country: process.env.ADDRESS_COUNTRY || "Deutschland",
},
contact: {
phone: process.env.CONTACT_PHONE || "+49 431 123456",
email: process.env.CONTACT_EMAIL || "info@stargirlnails.de",
website: process.env.DOMAIN || "stargirlnails.de",
},
businessDetails: {
taxId: process.env.TAX_ID || "",
vatId: process.env.VAT_ID || "",
commercialRegister: process.env.COMMERCIAL_REGISTER || "",
responsibleForContent: process.env.RESPONSIBLE_FOR_CONTENT || "Inhaber Name",
},
dataProtection: {
responsiblePerson: process.env.DATA_PROTECTION_RESPONSIBLE || "Inhaber Name",
email: process.env.DATA_PROTECTION_EMAIL || "datenschutz@stargirlnails.de",
purpose: process.env.DATA_PROTECTION_PURPOSE || "Terminbuchung und Kundenbetreuung",
legalBasis: process.env.DATA_PROTECTION_LEGAL_BASIS || "Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung) und Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse)",
dataRetention: process.env.DATA_PROTECTION_RETENTION || "Buchungsdaten werden 3 Jahre nach Vertragsende gespeichert",
rights: process.env.DATA_PROTECTION_RIGHTS || "Auskunft, Berichtigung, Löschung, Einschränkung, Widerspruch, Beschwerde bei der Aufsichtsbehörde",
cookies: process.env.DATA_PROTECTION_COOKIES || "Wir verwenden technisch notwendige Cookies für die Funktionalität der Website",
thirdPartyServices: process.env.THIRD_PARTY_SERVICES ? process.env.THIRD_PARTY_SERVICES.split(',') : ["Resend (E-Mail-Versand)"],
dataSecurity: process.env.DATA_PROTECTION_SECURITY || "SSL-Verschlüsselung, sichere Server, regelmäßige Updates",
contactDataProtection: process.env.DATA_PROTECTION_CONTACT || "Bei Fragen zum Datenschutz wenden Sie sich an: datenschutz@stargirlnails.de",
},
};
export function getLegalConfig(): LegalConfig {
return defaultLegalConfig;
}

View File

@@ -19,6 +19,13 @@ function formatDateGerman(dateString: string): string {
return `${day}.${month}.${year}`;
}
// Helper function to generate URLs from DOMAIN environment variable
function generateUrl(path: string = ''): string {
const domain = process.env.DOMAIN || 'localhost:5173';
const protocol = domain.includes('localhost') ? 'http' : 'https';
return `${protocol}://${domain}${path}`;
}
const BookingSchema = z.object({
id: z.string(),
treatmentId: z.string(),
@@ -119,9 +126,7 @@ const create = os
// Notify customer: request received (pending)
void (async () => {
const formattedDate = formatDateGerman(input.appointmentDate);
const domain = process.env.DOMAIN || 'localhost:5173';
const protocol = domain.includes('localhost') ? 'http' : 'https';
const homepageUrl = `${protocol}://${domain}`;
const homepageUrl = generateUrl();
const html = await renderBookingPendingHTML({ name: input.customerName, date: input.appointmentDate, time: input.appointmentTime });
await sendEmail({
to: input.customerEmail,
@@ -150,9 +155,7 @@ const create = os
hasInspirationPhoto: !!input.inspirationPhoto
});
const domain = process.env.DOMAIN || 'localhost:5173';
const protocol = domain.includes('localhost') ? 'http' : 'https';
const homepageUrl = `${protocol}://${domain}`;
const homepageUrl = generateUrl();
const adminText = `Neue Buchungsanfrage eingegangen:\n\n` +
`Name: ${input.customerName}\n` +
@@ -260,7 +263,8 @@ const updateStatus = os
const cancellationToken = await queryClient.cancellation.createToken({ bookingId: booking.id });
const formattedDate = formatDateGerman(booking.appointmentDate);
const cancellationUrl = `${process.env.FRONTEND_URL || 'http://localhost:5173'}/cancel/${cancellationToken.token}`;
const cancellationUrl = generateUrl(`/cancel/${cancellationToken.token}`);
const homepageUrl = generateUrl();
const html = await renderBookingConfirmedHTML({
name: booking.customerName,
@@ -269,10 +273,6 @@ const updateStatus = os
cancellationUrl
});
const domain = process.env.DOMAIN || 'localhost:5173';
const protocol = domain.includes('localhost') ? 'http' : 'https';
const homepageUrl = `${protocol}://${domain}`;
await sendEmailWithAGB({
to: booking.customerEmail,
subject: "Dein Termin wurde bestätigt - AGB im Anhang",
@@ -282,9 +282,7 @@ const updateStatus = os
});
} else if (input.status === "cancelled") {
const formattedDate = formatDateGerman(booking.appointmentDate);
const domain = process.env.DOMAIN || 'localhost:5173';
const protocol = domain.includes('localhost') ? 'http' : 'https';
const homepageUrl = `${protocol}://${domain}`;
const homepageUrl = generateUrl();
const html = await renderBookingCancelledHTML({ name: booking.customerName, date: booking.appointmentDate, time: booking.appointmentTime });
await sendEmail({
to: booking.customerEmail,

View File

@@ -4,6 +4,7 @@ import { router as bookings } from "./bookings";
import { router as auth } from "./auth";
import { router as availability } from "./availability";
import { router as cancellation } from "./cancellation";
import { router as legal } from "./legal";
export const router = {
demo,
@@ -12,4 +13,5 @@ export const router = {
auth,
availability,
cancellation,
legal,
};

8
src/server/rpc/legal.ts Normal file
View File

@@ -0,0 +1,8 @@
import { os } from "@orpc/server";
import { getLegalConfig } from "@/server/lib/legal-config";
export const router = {
getConfig: os.handler(async () => {
return getLegalConfig();
}),
};