Compare commits
15 Commits
bb5828cbfc
...
v1.3.22
| Author | SHA1 | Date | |
|---|---|---|---|
| e8b8a4073e | |||
| 5821a5dee4 | |||
| dfc7536268 | |||
| 9f26a179fa | |||
| 5043058660 | |||
| a37091ad71 | |||
| 9539aa35eb | |||
| 3ad24ef615 | |||
| 3018ae5412 | |||
| fd3f4c82c5 | |||
| 3906838dc1 | |||
| 785e36af08 | |||
| d4c32476d6 | |||
| 9b9d4634dd | |||
| c9e48cd5a4 |
@@ -185,6 +185,8 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: 🐳 Set up Docker Buildx
|
- name: 🐳 Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: 🧹 Pre-Build Cache Cleanup
|
||||||
|
run: docker builder prune -f --filter "until=24h"
|
||||||
- name: 🔐 Registry Login
|
- name: 🔐 Registry Login
|
||||||
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
- name: 🏗️ Build and Push
|
- name: 🏗️ Build and Push
|
||||||
|
|||||||
@@ -2,65 +2,46 @@ import { Download } from "lucide-react";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export default function AGB() {
|
export default function AVB() {
|
||||||
const filePath = path.join(process.cwd(), "context/avbs.md");
|
const filePath = path.join(process.cwd(), "context/avbs.md");
|
||||||
const fileContent = fs.readFileSync(filePath, "utf8");
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
||||||
|
|
||||||
// Split by double newlines to get major blocks (headers + their first paragraphs, or subsequent paragraphs)
|
// Split by double newlines to get major blocks
|
||||||
const blocks = fileContent
|
const rawBlocks = fileContent
|
||||||
.split(/\n\s*\n/)
|
.split(/\n\s*\n/)
|
||||||
.map((b) => b.trim())
|
.map((b) => b.trim())
|
||||||
.filter((b) => b !== "");
|
.filter((b) => b !== "");
|
||||||
|
|
||||||
const title = blocks[0] || "Liefer- und Zahlungsbedingungen";
|
// Extract title and stand more robustly
|
||||||
const stand = blocks[1] || "Stand Januar 2026";
|
const title = rawBlocks.find(b => b.startsWith("# "))?.replace(/^#\s+/, "") || "Allgemeine Verkaufsbedingungen";
|
||||||
|
const stand = rawBlocks.find(b => b.toLowerCase().startsWith("stand:")) || "Stand: April 2026";
|
||||||
|
|
||||||
const sections: { title: string; content: string[] }[] = [];
|
const sections: { title: string; content: string[] }[] = [];
|
||||||
let currentSection: { title: string; content: string[] } | null = null;
|
let currentSection: { title: string; content: string[] } | null = null;
|
||||||
|
|
||||||
// Skip title and stand
|
// Process sections, skipping title and stand blocks
|
||||||
blocks.slice(2).forEach((block) => {
|
rawBlocks.forEach((block) => {
|
||||||
const lines = block
|
if (block.startsWith("# ") || block.toLowerCase().startsWith("stand:")) return;
|
||||||
.split("\n")
|
|
||||||
.map((l) => l.trim())
|
|
||||||
.filter((l) => l !== "");
|
|
||||||
if (lines.length === 0) return;
|
|
||||||
|
|
||||||
const firstLine = lines[0];
|
if (block.startsWith("##")) {
|
||||||
|
// New section header: e.g. "## 1. Geltungsbereich"
|
||||||
if (/^\d+\./.test(firstLine)) {
|
|
||||||
// New section
|
|
||||||
if (currentSection) sections.push(currentSection);
|
if (currentSection) sections.push(currentSection);
|
||||||
|
currentSection = {
|
||||||
currentSection = { title: firstLine, content: [] };
|
title: block.replace(/^##\s+/, "").trim(),
|
||||||
|
content: []
|
||||||
// If there are more lines in this block, they form the first paragraph(s)
|
};
|
||||||
if (lines.length > 1) {
|
|
||||||
// Join subsequent lines as they might be part of the same paragraph
|
|
||||||
// In this MD, we'll assume lines in the same block belong together
|
|
||||||
// unless they are clearly separate paragraphs (but we already split by double newline)
|
|
||||||
const remainingText = lines.slice(1).join(" ");
|
|
||||||
if (remainingText) currentSection.content.push(remainingText);
|
|
||||||
}
|
|
||||||
} else if (currentSection) {
|
} else if (currentSection) {
|
||||||
// Continuation of current section
|
// Clean up bold markers for better display
|
||||||
const blockText = lines.join(" ");
|
const cleanedBlock = block.replace(/\*\*(.*?)\*\*/g, "$1");
|
||||||
if (blockText) currentSection.content.push(blockText);
|
currentSection.content.push(cleanedBlock);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (currentSection) sections.push(currentSection);
|
if (currentSection) sections.push(currentSection);
|
||||||
|
|
||||||
// The last block is the footer
|
// The very last block might be a footer/schlussbestimmung if it's not in a section
|
||||||
const footer = blocks[blocks.length - 1];
|
// In our MD, everything is in a section, so we just use the last block of the last section as potential footer if we want,
|
||||||
if (sections.length > 0) {
|
// but the current UI expects a separate 'footer' variable.
|
||||||
const lastSection = sections[sections.length - 1];
|
const footer = "MB Grid Solutions & Services";
|
||||||
if (lastSection.content.includes(footer) || lastSection.title === footer) {
|
|
||||||
lastSection.content = lastSection.content.filter((c) => c !== footer);
|
|
||||||
if (sections[sections.length - 1].title === footer) {
|
|
||||||
sections.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-slate-50 min-h-screen pt-40 pb-20">
|
<div className="bg-slate-50 min-h-screen pt-40 pb-20">
|
||||||
@@ -114,8 +95,9 @@ export default function AGB() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="pt-8 border-t border-slate-100">
|
<div className="pt-8 border-t border-slate-100 flex justify-between items-center text-slate-400 text-sm italic">
|
||||||
<p className="font-bold text-primary">{footer}</p>
|
<p>{footer}</p>
|
||||||
|
<p>{stand}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
href="/agb"
|
href="/agb"
|
||||||
className="hover:text-accent transition-colors"
|
className="hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
{t("footer.agb")}
|
{t("footer.avb")}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
"legal": "Rechtliches",
|
"legal": "Rechtliches",
|
||||||
"impressum": "Impressum",
|
"impressum": "Impressum",
|
||||||
"datenschutz": "Datenschutz",
|
"datenschutz": "Datenschutz",
|
||||||
"agb": "AVB",
|
"avb": "AVB",
|
||||||
"rights": "Alle Rechte vorbehalten.",
|
"rights": "Alle Rechte vorbehalten.",
|
||||||
"madeWith": "Entwickelt mit",
|
"madeWith": "Entwickelt mit",
|
||||||
"precision": "Präzision",
|
"precision": "Präzision",
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mb-grid-solutions.com",
|
"name": "mb-grid-solutions.com",
|
||||||
"version": "1.3.14",
|
"version": "1.3.22",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.18.3",
|
"packageManager": "pnpm@10.18.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
799
pnpm-lock.yaml
generated
799
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -47,8 +47,9 @@ async function main() {
|
|||||||
const others = urls.filter((u) => !homeEN.includes(u) && !homeDE.includes(u));
|
const others = urls.filter((u) => !homeEN.includes(u) && !homeDE.includes(u));
|
||||||
urls = [...homeDE, ...homeEN, ...others.slice(0, limit - (homeEN.length + homeDE.length))];
|
urls = [...homeDE, ...homeEN, ...others.slice(0, limit - (homeEN.length + homeDE.length))];
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error(`❌ Failed to fetch sitemap: ${err.message}`);
|
const errorBody = err instanceof Error ? err.message : String(err);
|
||||||
|
console.error(`❌ Failed to fetch sitemap: ${errorBody}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,17 +79,19 @@ async function main() {
|
|||||||
const consoleErrorsList: Array<{ type: string; error: string; page: string }> = [];
|
const consoleErrorsList: Array<{ type: string; error: string; page: string }> = [];
|
||||||
|
|
||||||
// Listen for unhandled exceptions natively in the page
|
// Listen for unhandled exceptions natively in the page
|
||||||
page.on('pageerror', (err: any) => {
|
page.on('pageerror', (err: unknown) => {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
consoleErrorsList.push({
|
consoleErrorsList.push({
|
||||||
type: 'PAGE_ERROR',
|
type: 'PAGE_ERROR',
|
||||||
error: err.message,
|
error: errorMessage,
|
||||||
page: page.url(),
|
page: page.url(),
|
||||||
});
|
});
|
||||||
hasConsoleErrors = true;
|
hasConsoleErrors = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for console.error and console.warn messages (like Next.js Image warnings, hydration errors, CSP blocks)
|
// Listen for console.error and console.warn messages (like Next.js Image warnings, hydration errors, CSP blocks)
|
||||||
page.on('console', (msg) => {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
page.on('console', (msg: any) => {
|
||||||
const type = msg.type();
|
const type = msg.type();
|
||||||
if (type === 'error' || type === 'warn') {
|
if (type === 'error' || type === 'warn') {
|
||||||
const text = msg.text();
|
const text = msg.text();
|
||||||
@@ -163,8 +166,9 @@ async function main() {
|
|||||||
|
|
||||||
// Wait a tiny bit more for final lazy loads
|
// Wait a tiny bit more for final lazy loads
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error(`⚠️ Timeout or navigation error on ${u}: ${err.message}`);
|
const errorBody = err instanceof Error ? err.message : String(err);
|
||||||
|
console.error(`⚠️ Timeout or navigation error on ${u}: ${errorBody}`);
|
||||||
// Don't fail the whole script just because one page timed out, but flag it
|
// Don't fail the whole script just because one page timed out, but flag it
|
||||||
hasBrokenAssets = true;
|
hasBrokenAssets = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ async function main() {
|
|||||||
const filename = `${safePath || 'index'}.html`;
|
const filename = `${safePath || 'index'}.html`;
|
||||||
|
|
||||||
fs.writeFileSync(path.join(outputDir, filename), res.data);
|
fs.writeFileSync(path.join(outputDir, filename), res.data);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error(`❌ HTTP Error fetching ${u}: ${err.message}`);
|
const errorBody = err instanceof Error ? err.message : String(err);
|
||||||
throw new Error(`Failed to fetch page: ${u} - ${err.message}`);
|
console.error(`❌ HTTP Error fetching ${u}: ${errorBody}`);
|
||||||
|
throw new Error(`Failed to fetch page: ${u} - ${errorBody}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +68,13 @@ async function main() {
|
|||||||
try {
|
try {
|
||||||
execSync(`npx html-validate .htmlvalidate-tmp/*.html`, { stdio: 'inherit' });
|
execSync(`npx html-validate .htmlvalidate-tmp/*.html`, { stdio: 'inherit' });
|
||||||
console.log(`✅ HTML Validation passed perfectly!`);
|
console.log(`✅ HTML Validation passed perfectly!`);
|
||||||
} catch (e) {
|
} catch {
|
||||||
console.error(`❌ HTML Validation found issues.`);
|
console.error(`❌ HTML Validation found issues.`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error(`\n❌ Error during HTML Validation:`, error.message);
|
const errorBody = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`\n❌ Error during HTML Validation:`, errorBody);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} finally {
|
} finally {
|
||||||
const outputDir = path.join(process.cwd(), '.htmlvalidate-tmp');
|
const outputDir = path.join(process.cwd(), '.htmlvalidate-tmp');
|
||||||
|
|||||||
@@ -3,6 +3,30 @@ import * as cheerio from 'cheerio';
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
interface Pa11yConfig {
|
||||||
|
defaults: {
|
||||||
|
threshold?: number;
|
||||||
|
runners?: string[];
|
||||||
|
ignore?: string[];
|
||||||
|
chromeLaunchConfig?: {
|
||||||
|
executablePath?: string;
|
||||||
|
args?: string[];
|
||||||
|
};
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
|
urls?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Pa11yResult {
|
||||||
|
type?: string;
|
||||||
|
message?: string;
|
||||||
|
code?: string;
|
||||||
|
context?: string;
|
||||||
|
selector?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WCAG Audit Script
|
* WCAG Audit Script
|
||||||
@@ -65,14 +89,15 @@ async function main() {
|
|||||||
|
|
||||||
// 2. Prepare pa11y-ci config
|
// 2. Prepare pa11y-ci config
|
||||||
const baseConfigPath = path.join(process.cwd(), '.pa11yci.json');
|
const baseConfigPath = path.join(process.cwd(), '.pa11yci.json');
|
||||||
let baseConfig: any = { defaults: {} };
|
let baseConfig: Pa11yConfig = { defaults: {} };
|
||||||
if (fs.existsSync(baseConfigPath)) {
|
if (fs.existsSync(baseConfigPath)) {
|
||||||
baseConfig = JSON.parse(fs.readFileSync(baseConfigPath, 'utf8'));
|
baseConfig = JSON.parse(fs.readFileSync(baseConfigPath, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract domain for cookie
|
// Extract domain for cookie (not currently used)
|
||||||
const urlObj = new URL(targetUrl);
|
// const urlObj = new URL(targetUrl);
|
||||||
const domain = urlObj.hostname;
|
// domain is not used, so remove or keep if needed later? Linter says unused.
|
||||||
|
// const domain = urlObj.hostname;
|
||||||
|
|
||||||
// Update config with discovered URLs and gatekeeper cookie
|
// Update config with discovered URLs and gatekeeper cookie
|
||||||
const tempConfig = {
|
const tempConfig = {
|
||||||
@@ -118,7 +143,7 @@ async function main() {
|
|||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch {
|
||||||
// pa11y-ci exits with non-zero if issues are found, which is expected
|
// pa11y-ci exits with non-zero if issues are found, which is expected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,10 +161,10 @@ async function main() {
|
|||||||
|
|
||||||
if (Array.isArray(results)) {
|
if (Array.isArray(results)) {
|
||||||
// pa11y action execution errors come as objects with a message but no type
|
// pa11y action execution errors come as objects with a message but no type
|
||||||
const actionErrors = results.filter((r: any) => !r.type && r.message).length;
|
const actionErrors = results.filter((r: Pa11yResult) => !r.type && r.message).length;
|
||||||
errors = results.filter((r: any) => r.type === 'error').length + actionErrors;
|
errors = results.filter((r: Pa11yResult) => r.type === 'error').length + actionErrors;
|
||||||
warnings = results.filter((r: any) => r.type === 'warning').length;
|
warnings = results.filter((r: Pa11yResult) => r.type === 'warning').length;
|
||||||
notices = results.filter((r: any) => r.type === 'notice').length;
|
notices = results.filter((r: Pa11yResult) => r.type === 'notice').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean URL for display
|
// Clean URL for display
|
||||||
@@ -168,13 +193,14 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n✨ WCAG Audit completed!`);
|
console.log(`\n✨ WCAG Audit completed!`);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error(`\n❌ Error during WCAG Audit:`);
|
console.error(`\n❌ Error during WCAG Audit:`);
|
||||||
if (axios.isAxiosError(error)) {
|
if (error instanceof AxiosError) {
|
||||||
console.error(`Status: ${error.response?.status}`);
|
console.error(`Status: ${error.response?.status}`);
|
||||||
console.error(`URL: ${error.config?.url}`);
|
console.error(`URL: ${error.config?.url}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(error.message);
|
const errorBody = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(errorBody);
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
".next/dev/types/**/*.ts"
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules", "tests", "tests_bak"]
|
"exclude": ["node_modules", "tests", "tests_bak", "scripts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
@@ -16,11 +17,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
globals: true,
|
globals: true,
|
||||||
setupFiles: ['./tests/setup.tsx'],
|
setupFiles: ['./tests/setup.tsx'],
|
||||||
alias: {
|
alias: [
|
||||||
'next/server': 'next/server.js',
|
{ find: 'next/server', replacement: 'next/server.js' },
|
||||||
'@payload-config': new URL('./tests/__mocks__/payload-config.ts', import.meta.url).pathname,
|
{ find: '@payload-config', replacement: fileURLToPath(new URL('./tests/__mocks__/payload-config.ts', import.meta.url)) },
|
||||||
'@': new URL('./', import.meta.url).pathname,
|
{ find: '@', replacement: fileURLToPath(new URL('./', import.meta.url)) },
|
||||||
},
|
],
|
||||||
exclude: ['**/node_modules/**', '**/.next/**'],
|
exclude: ['**/node_modules/**', '**/.next/**'],
|
||||||
server: {
|
server: {
|
||||||
deps: {
|
deps: {
|
||||||
|
|||||||
Reference in New Issue
Block a user