refactor: komplettsanierung
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 1m26s
Build & Deploy / 🏗️ Build (push) Failing after 3m19s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 1m26s
Build & Deploy / 🏗️ Build (push) Failing after 3m19s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
@@ -1,19 +1,68 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import * as React from 'react';
|
||||
import { Modal } from './Modal';
|
||||
import { Copy, Check, Share2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import * as React from "react";
|
||||
import { Modal } from "./Modal";
|
||||
import { Copy, Check, Share2, Download } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface ShareModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
url: string;
|
||||
qrCodeData?: string;
|
||||
title?: string;
|
||||
diagramImage?: string;
|
||||
}
|
||||
|
||||
export function ShareModal({ isOpen, onClose, url, qrCodeData }: ShareModalProps) {
|
||||
export function ShareModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
url,
|
||||
qrCodeData,
|
||||
title,
|
||||
diagramImage,
|
||||
}: ShareModalProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||
const [timestamp] = useState(
|
||||
new Date().toLocaleTimeString("de-DE", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (diagramImage && isOpen) {
|
||||
// Convert SVG to PNG for preview with higher resolution (3x)
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([diagramImage], {
|
||||
type: "image/svg+xml;charset=utf-8",
|
||||
});
|
||||
const svgUrl = URL.createObjectURL(svgBlob);
|
||||
|
||||
img.onload = () => {
|
||||
const scale = 3; // 3x scaling for sharpness
|
||||
canvas.width = img.width * scale;
|
||||
canvas.height = img.height * scale;
|
||||
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw image with scaling
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
setImagePreview(canvas.toDataURL("image/png"));
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
};
|
||||
|
||||
img.src = svgUrl;
|
||||
}
|
||||
}, [diagramImage, isOpen]);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(url);
|
||||
@@ -24,56 +73,236 @@ export function ShareModal({ isOpen, onClose, url, qrCodeData }: ShareModalProps
|
||||
const handleNativeShare = async () => {
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: 'Meine Projekt-Konfiguration',
|
||||
url: url
|
||||
});
|
||||
const shareData: ShareData = {
|
||||
title: title || "Mintel Diagramm",
|
||||
url: url,
|
||||
};
|
||||
|
||||
// If we have a diagram image, try to include it as a file
|
||||
if (imagePreview) {
|
||||
try {
|
||||
// Convert base64 preview back to a File object
|
||||
const response = await fetch(imagePreview);
|
||||
const blob = await response.blob();
|
||||
const file = new File(
|
||||
[blob],
|
||||
`${title?.replace(/\s+/g, "-").toLowerCase() || "diagram"}.png`,
|
||||
{ type: "image/png" },
|
||||
);
|
||||
|
||||
// Check if sharing files is supported
|
||||
if (navigator.canShare && navigator.canShare({ files: [file] })) {
|
||||
shareData.files = [file];
|
||||
}
|
||||
} catch (fileError) {
|
||||
console.error("Could not prepare file for sharing", fileError);
|
||||
}
|
||||
}
|
||||
|
||||
await navigator.share(shareData);
|
||||
} catch (e) {
|
||||
console.error("Share failed", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Konfiguration teilen">
|
||||
<div className="space-y-8">
|
||||
<p className="text-slate-500 leading-relaxed">
|
||||
Speichern Sie diesen Link, um Ihre Konfiguration später fortzusetzen oder teilen Sie ihn mit anderen.
|
||||
</p>
|
||||
const handleDownloadImage = () => {
|
||||
if (!imagePreview) return;
|
||||
|
||||
{qrCodeData && (
|
||||
<div className="flex flex-col items-center gap-4 p-8 bg-slate-50 rounded-[2rem]">
|
||||
<img src={qrCodeData} alt="QR Code" className="w-48 h-48 rounded-xl shadow-sm" />
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">QR-Code scannen</p>
|
||||
const link = document.createElement("a");
|
||||
link.download = `${title?.replace(/\s+/g, "-").toLowerCase() || "diagram"}.png`;
|
||||
link.href = imagePreview;
|
||||
link.click();
|
||||
};
|
||||
|
||||
const handleShareX = () => {
|
||||
const text = encodeURIComponent(title || "Mintel Diagramm");
|
||||
const shareUrl = `https://twitter.com/intent/tweet?text=${text}&url=${encodeURIComponent(url)}`;
|
||||
window.open(shareUrl, "_blank", "width=550,height=420");
|
||||
};
|
||||
|
||||
const handleShareLinkedIn = () => {
|
||||
const shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`;
|
||||
window.open(shareUrl, "_blank", "width=550,height=420");
|
||||
};
|
||||
|
||||
const modalTitle = diagramImage
|
||||
? "Diagramm teilen"
|
||||
: title
|
||||
? "Artikel teilen"
|
||||
: "Konfiguration teilen";
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={modalTitle}
|
||||
maxWidth={diagramImage ? "max-w-3xl" : "max-w-lg"}
|
||||
>
|
||||
<div className="space-y-8">
|
||||
{imagePreview ? (
|
||||
<div className="space-y-6">
|
||||
{/* Social Post Preview Section */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-400 ml-1">
|
||||
Social Preview
|
||||
</label>
|
||||
<div className="bg-slate-50 rounded-2xl border border-slate-100 overflow-hidden shadow-inner">
|
||||
<div className="p-4 flex items-center gap-3 border-b border-white/50">
|
||||
<div className="w-8 h-8 rounded-full bg-slate-200 animate-pulse" />
|
||||
<div className="space-y-1">
|
||||
<div className="h-2 w-24 bg-slate-200 rounded animate-pulse" />
|
||||
<div className="h-1.5 w-16 bg-slate-100 rounded animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="h-2.5 w-full bg-slate-200 rounded animate-pulse" />
|
||||
<div className="h-2.5 w-3/4 bg-slate-200 rounded animate-pulse" />
|
||||
</div>
|
||||
{/* Industrial Blueprint Preview */}
|
||||
<div className="relative group overflow-hidden bg-white border border-slate-200 rounded-xl shadow-sm transition-all duration-500 hover:shadow-md hover:border-slate-300">
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03] pointer-events-none"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(#1e293b 1px, transparent 0)",
|
||||
backgroundSize: "15px 15px",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative p-6 flex flex-col items-center">
|
||||
<img
|
||||
src={imagePreview}
|
||||
alt={title || "Diagram"}
|
||||
className="max-w-full max-h-[30vh] object-contain transition-transform duration-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50/80 border-t border-slate-100 px-3 py-1.5 flex items-center justify-between text-[6px] font-mono text-slate-400 uppercase tracking-widest">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>
|
||||
SYS:{" "}
|
||||
{Math.random()
|
||||
.toString(36)
|
||||
.substring(7)
|
||||
.toUpperCase()}
|
||||
</span>
|
||||
<span className="w-0.5 h-0.5 bg-slate-300 rounded-full" />
|
||||
<span>MINTEL.ME</span>
|
||||
</div>
|
||||
<span>{timestamp}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-[10px] font-bold uppercase tracking-[0.3em] text-slate-300 flex items-center justify-center gap-4">
|
||||
<span className="w-8 h-px bg-slate-100" />
|
||||
Diagramm Vorschau
|
||||
<span className="w-8 h-px bg-slate-100" />
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
title && (
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-bold uppercase tracking-widest text-slate-400 ml-1">
|
||||
Artikel Titel
|
||||
</label>
|
||||
<p className="text-slate-600 leading-relaxed font-medium bg-slate-50/50 p-5 rounded-2xl border border-slate-100 italic transition-all hover:bg-white hover:shadow-sm">
|
||||
"{title}"
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{!diagramImage && qrCodeData && (
|
||||
<div className="flex flex-col items-center gap-4 p-8 bg-slate-50 rounded-[2rem] border border-slate-100 shadow-inner">
|
||||
<img
|
||||
src={qrCodeData}
|
||||
alt="QR Code"
|
||||
className="w-48 h-48 rounded-xl shadow-sm"
|
||||
/>
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-slate-400">
|
||||
QR-Code scannen
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={url}
|
||||
className="w-full p-6 pr-20 bg-slate-50 border border-slate-100 rounded-2xl text-sm font-mono text-slate-600 focus:outline-none"
|
||||
/>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="absolute right-2 top-2 bottom-2 px-4 bg-white border border-slate-100 rounded-xl text-slate-900 hover:bg-slate-900 hover:text-white transition-all flex items-center gap-2"
|
||||
>
|
||||
{copied ? <Check size={18} /> : <Copy size={18} />}
|
||||
<span className="text-xs font-bold uppercase tracking-wider">{copied ? 'Kopiert' : 'Kopieren'}</span>
|
||||
</button>
|
||||
<div className="relative group">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-slate-200 to-slate-100 rounded-2xl blur opacity-25 group-hover:opacity-100 transition duration-1000 group-focus-within:opacity-100" />
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={url}
|
||||
className="w-full p-5 pr-32 bg-white border border-slate-200 rounded-2xl text-xs font-mono text-slate-600 focus:outline-none focus:border-slate-400 focus:ring-4 focus:ring-slate-50 transition-all"
|
||||
/>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="absolute right-1.5 top-1.5 bottom-1.5 px-4 bg-slate-900 border border-slate-800 rounded-xl text-white hover:bg-black transition-all flex items-center gap-2"
|
||||
>
|
||||
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider">
|
||||
{copied ? "Kopiert" : "Kopieren"}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{typeof navigator !== 'undefined' && !!navigator.share && (
|
||||
<button
|
||||
onClick={handleNativeShare}
|
||||
className="w-full p-6 bg-slate-900 text-white rounded-2xl font-bold flex items-center justify-center gap-3 hover:bg-slate-800 transition-all"
|
||||
>
|
||||
<Share2 size={20} />
|
||||
<span>System-Dialog öffnen</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4 pt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleShareX}
|
||||
className="w-12 h-12 bg-black text-white rounded-xl flex items-center justify-center hover:bg-slate-900 transition-all border border-slate-800 active:scale-95"
|
||||
title="Auf X teilen"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleShareLinkedIn}
|
||||
className="w-12 h-12 bg-[#0A66C2] text-white rounded-xl flex items-center justify-center hover:bg-[#004182] transition-all active:scale-95"
|
||||
title="Auf LinkedIn teilen"
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{typeof navigator !== "undefined" && !!navigator.share && (
|
||||
<button
|
||||
onClick={handleNativeShare}
|
||||
className="w-12 h-12 bg-white border border-slate-200 text-slate-900 rounded-xl flex items-center justify-center hover:bg-slate-50 transition-all active:scale-95"
|
||||
title="System-Dialog öffnen"
|
||||
>
|
||||
<Share2 size={20} className="text-slate-400" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{imagePreview && (
|
||||
<button
|
||||
onClick={handleDownloadImage}
|
||||
className="flex-1 w-full p-3 bg-slate-100 text-slate-900 rounded-xl font-bold flex items-center justify-center gap-3 hover:bg-slate-200 transition-all border border-slate-200 active:scale-[0.98]"
|
||||
>
|
||||
<Download size={18} />
|
||||
<span className="text-xs">Als Bild speichern (PNG)</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user