feat: implement device-specific configurations for desktop, tablet, and mobile
All checks were successful
Production Build & Deploy / build-and-deploy (push) Successful in 57s
All checks were successful
Production Build & Deploy / build-and-deploy (push) Successful in 57s
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"siteName": "N&D IT Solutions",
|
"siteName": "N&D IT Solutions",
|
||||||
"metaDescription": "High-Performance Webdesign & Digital Solutions - 100% DSGVO-konform.",
|
"metaDescription": "Webdesign & Digital Solutions",
|
||||||
"hero": {
|
"hero": {
|
||||||
"badge": "⚡ WaaS Enterprise Baukastensystem",
|
"badge": "WaaS",
|
||||||
"title": "Maßgeschneiderte Webseiten. Digital im Griff.",
|
"title": "Maßgeschneiderte Webseiten. Digital im Griff.",
|
||||||
"subtitle": "Wir entwickeln blitzschnelle, DSGVO-konforme High-End Webseiten für Ihr Unternehmen zum monatlichen Festpreis – inklusive Wartung & Hosting.",
|
"subtitle": "Wir machen Zeugs",
|
||||||
"ctaPrimaryText": "Projekt anfragen",
|
"ctaPrimaryText": "Projekt anfragen",
|
||||||
"ctaPrimaryLink": "#contact",
|
"ctaPrimaryLink": "#contact",
|
||||||
"ctaSecondaryText": "Mehr erfahren",
|
"ctaSecondaryText": "Mehr erfahren",
|
||||||
@@ -71,9 +71,9 @@
|
|||||||
"address": "Deutschland"
|
"address": "Deutschland"
|
||||||
},
|
},
|
||||||
"sectionsOrder": [
|
"sectionsOrder": [
|
||||||
"hero",
|
"contact",
|
||||||
"bento",
|
"bento",
|
||||||
"contact"
|
"hero"
|
||||||
],
|
],
|
||||||
"sectionsDetails": [
|
"sectionsDetails": [
|
||||||
{
|
{
|
||||||
|
|||||||
1
app/data/site.desktop.config.json
Normal file
1
app/data/site.desktop.config.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"activePreset": "sunsetCoral",
|
"activePreset": "amberGlow",
|
||||||
"customColors": {
|
"customColors": {
|
||||||
"bg": "#0B0F19",
|
"bg": "#0B0F19",
|
||||||
"accent": "#38BDF8"
|
"accent": "#38BDF8"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// src/components/Footer.astro
|
// src/components/Footer.astro
|
||||||
import { loadWaasConfigs } from '../utils/configLoader';
|
import { loadWaasConfigs } from '../utils/configLoader';
|
||||||
|
|
||||||
const { siteConfig } = loadWaasConfigs();
|
const { siteConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const footer = siteConfig.footer || {
|
const footer = siteConfig.footer || {
|
||||||
brandDescription: 'Ihr Partner für professionelles Webdesign, Website-Entwicklung und lokale SEO für Kleinbetriebe & KMU.',
|
brandDescription: 'Ihr Partner für professionelles Webdesign, Website-Entwicklung und lokale SEO für Kleinbetriebe & KMU.',
|
||||||
copyrightText: 'N&D i-t SOLUTIONS. Alle Rechte vorbehalten.',
|
copyrightText: 'N&D i-t SOLUTIONS. Alle Rechte vorbehalten.',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { FaBars, FaTimes } from 'react-icons/fa';
|
import { FaBars, FaTimes } from 'react-icons/fa';
|
||||||
import { loadWaasConfigs } from '../utils/configLoader';
|
import { loadWaasConfigs } from '../utils/configLoader';
|
||||||
|
|
||||||
const { siteConfig } = loadWaasConfigs();
|
const { siteConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const header = siteConfig.header || {
|
const header = siteConfig.header || {
|
||||||
brandName: 'N&D',
|
brandName: 'N&D',
|
||||||
brandHighlight: 'i-t SOLUTIONS',
|
brandHighlight: 'i-t SOLUTIONS',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface Props {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { siteConfig, themeConfig } = loadWaasConfigs();
|
const { siteConfig, themeConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
||||||
const colors = activePreset.colors;
|
const colors = activePreset.colors;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Layout from '../../layouts/Layout.astro';
|
|||||||
import { loadWaasConfigs } from '../../utils/configLoader';
|
import { loadWaasConfigs } from '../../utils/configLoader';
|
||||||
import { THEME_PRESETS } from '../../utils/themePresets';
|
import { THEME_PRESETS } from '../../utils/themePresets';
|
||||||
|
|
||||||
const { siteConfig, themeConfig } = loadWaasConfigs();
|
const { siteConfig, themeConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Live Visual Editor | WaaS Engine">
|
<Layout title="Live Visual Editor | WaaS Engine">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Layout from '../../layouts/Layout.astro';
|
|||||||
import { loadWaasConfigs } from '../../utils/configLoader';
|
import { loadWaasConfigs } from '../../utils/configLoader';
|
||||||
import { THEME_PRESETS } from '../../utils/themePresets';
|
import { THEME_PRESETS } from '../../utils/themePresets';
|
||||||
|
|
||||||
const { siteConfig, themeConfig, smtpConfig } = loadWaasConfigs();
|
const { siteConfig, themeConfig, smtpConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const presets = Object.values(THEME_PRESETS);
|
const presets = Object.values(THEME_PRESETS);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
fs.mkdirSync(dataDir, { recursive: true });
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { siteConfig: currentSite, themeConfig: currentTheme, smtpConfig: currentSmtp } = loadWaasConfigs();
|
const { siteConfig: currentSite, themeConfig: currentTheme, smtpConfig: currentSmtp } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
|
|
||||||
if (body.siteConfig) {
|
if (body.siteConfig) {
|
||||||
const newSite = {
|
const newSite = {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
return new Response(JSON.stringify({ success: false, message: 'Bitte füllen Sie alle Pflichtfelder aus.' }), { status: 400, headers: { 'Content-Type': 'application/json' } });
|
return new Response(JSON.stringify({ success: false, message: 'Bitte füllen Sie alle Pflichtfelder aus.' }), { status: 400, headers: { 'Content-Type': 'application/json' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { smtpConfig, siteConfig } = loadWaasConfigs();
|
const { smtpConfig, siteConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
|
|
||||||
if (!smtpConfig.isConfigured || smtpConfig.host === 'smtp.placeholder.local') {
|
if (!smtpConfig.isConfigured || smtpConfig.host === 'smtp.placeholder.local') {
|
||||||
console.log('[WaaS Contact API] (Demo Mode) Received Submission:', { name, email, message });
|
console.log('[WaaS Contact API] (Demo Mode) Received Submission:', { name, email, message });
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import FallbackSection from '../components/sections/FallbackSection.astro';
|
|||||||
import { loadWaasConfigs } from '../utils/configLoader';
|
import { loadWaasConfigs } from '../utils/configLoader';
|
||||||
import { THEME_PRESETS } from '../utils/themePresets';
|
import { THEME_PRESETS } from '../utils/themePresets';
|
||||||
|
|
||||||
const { siteConfig, themeConfig, smtpConfig } = loadWaasConfigs();
|
const { siteConfig, themeConfig, smtpConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
||||||
const colors = activePreset.colors;
|
const colors = activePreset.colors;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { detectDeviceType } from './deviceDetect';
|
||||||
export interface UIComponent {
|
export interface UIComponent {
|
||||||
id: string; // Eindeutige ID (z.B. uuid / timestamp)
|
id: string; // Eindeutige ID (z.B. uuid / timestamp)
|
||||||
slotId: string; // Platz-ID (z.B. 'hero-badge-slot', 'hero-actions-slot', 'bento-slot-1')
|
slotId: string; // Platz-ID (z.B. 'hero-badge-slot', 'hero-actions-slot', 'bento-slot-1')
|
||||||
@@ -206,11 +206,20 @@ function readJsonFile<T>(filePath: string, fallback: T): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadWaasConfigs() {
|
export function loadWaasConfigs(userAgent: string = '') {
|
||||||
const dataDir = process.env.DATA_DIR || path.join(process.cwd(), 'app', 'data');
|
const dataDir = process.env.DATA_DIR || path.join(process.cwd(), 'app', 'data');
|
||||||
const sitePath = path.join(dataDir, 'site.config.json');
|
const device = detectDeviceType(userAgent);
|
||||||
const themePath = path.join(dataDir, 'theme.config.json');
|
const deviceSitePath = path.join(dataDir, `site.${device}.config.json`);
|
||||||
const smtpPath = path.join(dataDir, 'smtp.config.json');
|
const genericSitePath = path.join(dataDir, 'site.config.json');
|
||||||
|
const sitePath = fs.existsSync(deviceSitePath) ? deviceSitePath : genericSitePath;
|
||||||
|
|
||||||
|
const deviceThemePath = path.join(dataDir, `theme.${device}.config.json`);
|
||||||
|
const genericThemePath = path.join(dataDir, 'theme.config.json');
|
||||||
|
const themePath = fs.existsSync(deviceThemePath) ? deviceThemePath : genericThemePath;
|
||||||
|
|
||||||
|
const deviceSmtpPath = path.join(dataDir, `smtp.${device}.config.json`);
|
||||||
|
const genericSmtpPath = path.join(dataDir, 'smtp.config.json');
|
||||||
|
const smtpPath = fs.existsSync(deviceSmtpPath) ? deviceSmtpPath : genericSmtpPath;
|
||||||
|
|
||||||
const rawSite = readJsonFile<Partial<SiteConfig>>(sitePath, FALLBACK_SITE_CONFIG);
|
const rawSite = readJsonFile<Partial<SiteConfig>>(sitePath, FALLBACK_SITE_CONFIG);
|
||||||
const siteConfig: SiteConfig = {
|
const siteConfig: SiteConfig = {
|
||||||
|
|||||||
14
src/utils/deviceDetect.ts
Normal file
14
src/utils/deviceDetect.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type DeviceType = 'desktop' | 'tablet' | 'mobile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple User-Agent based device detection.
|
||||||
|
* Returns 'desktop' by default when detection fails.
|
||||||
|
*/
|
||||||
|
export function detectDeviceType(userAgent: string): DeviceType {
|
||||||
|
const ua = userAgent.toLowerCase();
|
||||||
|
// Tablet detection (including iPad which reports as Mac on iOS 13+)
|
||||||
|
if (/tablet|ipad/.test(ua)) return 'tablet';
|
||||||
|
// Mobile detection (phones and Android)
|
||||||
|
if (/mobile|iphone|android/.test(ua)) return 'mobile';
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ describe('WaaS Engine Core Unit Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('1. Should return fallbacks when JSON files do not exist (Zero-Crash Guarantee)', () => {
|
it('1. Should return fallbacks when JSON files do not exist (Zero-Crash Guarantee)', () => {
|
||||||
const configs = loadWaasConfigs();
|
const configs = loadWaasConfigs('');
|
||||||
expect(configs.siteConfig.siteName).toBe(FALLBACK_SITE_CONFIG.siteName);
|
expect(configs.siteConfig.siteName).toBe(FALLBACK_SITE_CONFIG.siteName);
|
||||||
expect(configs.themeConfig.activePreset).toBe(FALLBACK_THEME_CONFIG.activePreset);
|
expect(configs.themeConfig.activePreset).toBe(FALLBACK_THEME_CONFIG.activePreset);
|
||||||
expect(configs.smtpConfig.host).toBe(FALLBACK_SMTP_CONFIG.host);
|
expect(configs.smtpConfig.host).toBe(FALLBACK_SMTP_CONFIG.host);
|
||||||
@@ -32,7 +32,7 @@ describe('WaaS Engine Core Unit Tests', () => {
|
|||||||
const customSite = { siteName: 'Custom Test Agency', hero: { title: 'Custom Title' } };
|
const customSite = { siteName: 'Custom Test Agency', hero: { title: 'Custom Title' } };
|
||||||
fs.writeFileSync(path.join(testDataDir, 'site.config.json'), JSON.stringify(customSite), 'utf8');
|
fs.writeFileSync(path.join(testDataDir, 'site.config.json'), JSON.stringify(customSite), 'utf8');
|
||||||
|
|
||||||
const configs = loadWaasConfigs();
|
const configs = loadWaasConfigs('');
|
||||||
expect(configs.siteConfig.siteName).toBe('Custom Test Agency');
|
expect(configs.siteConfig.siteName).toBe('Custom Test Agency');
|
||||||
expect(configs.siteConfig.hero.title).toBe('Custom Title');
|
expect(configs.siteConfig.hero.title).toBe('Custom Title');
|
||||||
expect(configs.siteConfig.hero.badge).toBe(FALLBACK_SITE_CONFIG.hero.badge);
|
expect(configs.siteConfig.hero.badge).toBe(FALLBACK_SITE_CONFIG.hero.badge);
|
||||||
@@ -50,4 +50,20 @@ describe('WaaS Engine Core Unit Tests', () => {
|
|||||||
expect(heroElements.length).toBeGreaterThan(0);
|
expect(heroElements.length).toBeGreaterThan(0);
|
||||||
expect(heroElements[0].type).toBe('badge');
|
expect(heroElements[0].type).toBe('badge');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('5. Should load device-specific configs for desktop, mobile, tablet with fallback', () => {
|
||||||
|
fs.mkdirSync(testDataDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(testDataDir, 'site.mobile.config.json'), JSON.stringify({ siteName: 'Mobile Site' }), 'utf8');
|
||||||
|
fs.writeFileSync(path.join(testDataDir, 'site.tablet.config.json'), JSON.stringify({ siteName: 'Tablet Site' }), 'utf8');
|
||||||
|
fs.writeFileSync(path.join(testDataDir, 'site.desktop.config.json'), JSON.stringify({ siteName: 'Desktop Site' }), 'utf8');
|
||||||
|
|
||||||
|
const mobileConfig = loadWaasConfigs('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)');
|
||||||
|
expect(mobileConfig.siteConfig.siteName).toBe('Mobile Site');
|
||||||
|
|
||||||
|
const tabletConfig = loadWaasConfigs('Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)');
|
||||||
|
expect(tabletConfig.siteConfig.siteName).toBe('Tablet Site');
|
||||||
|
|
||||||
|
const desktopConfig = loadWaasConfigs('Mozilla/5.0 (Windows NT 10.0; Win64; x64)');
|
||||||
|
expect(desktopConfig.siteConfig.siteName).toBe('Desktop Site');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user