Files
mintel.me/apps/web/src/components/LinkedInEmbed.tsx
Marc Mintel b15c8408ff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 14s
Build & Deploy / 🧪 QA (push) Failing after 1m48s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(blog): optimize component share logic, typography, and modal layouts
2026-02-22 11:41:28 +01:00

89 lines
3.5 KiB
TypeScript

'use client';
import * as React from 'react';
import { useEffect, useState, useRef } from 'react';
interface LinkedInEmbedProps {
/** The post URL, e.g. "https://www.linkedin.com/posts/xyz" or raw URN */
url: string;
className?: string;
width?: string | number;
}
export function LinkedInEmbed({
url,
className = "",
width = 504
}: LinkedInEmbedProps) {
const [isMounted, setIsMounted] = useState(false);
const [hasError, setHasError] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(null);
// Extract the 19-digit ID from the URL or URN
const match = url.match(/(\d{19})/);
const embedId = match ? match[1] : null;
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted || !embedId) return null;
// LinkedIn technically supports share, ugcPost, and activity. We start with share and natively cycle.
const initialSrc = `https://www.linkedin.com/embed/feed/update/urn:li:share:${embedId}`;
const handleError = () => {
if (!iframeRef.current) return;
const currentSrc = iframeRef.current.src;
// If the 'share' URN 404s (e.g., restricted post type), fallback to 'activity', then 'ugcPost'
if (currentSrc.includes('urn:li:share:')) {
iframeRef.current.src = `https://www.linkedin.com/embed/feed/update/urn:li:activity:${embedId}`;
} else if (currentSrc.includes('urn:li:activity:')) {
iframeRef.current.src = `https://www.linkedin.com/embed/feed/update/urn:li:ugcPost:${embedId}`;
} else {
// All fallbacks exhausted. The post is truly dead or private.
setHasError(true);
}
};
if (hasError) {
return (
<div className={`not-prose flex w-full justify-center my-8 ${className}`}>
<div
className="flex flex-col items-center justify-center text-center p-8 w-full max-w-[504px] border border-slate-200 border-dashed rounded-lg bg-slate-50 text-slate-500"
style={{ minHeight: '150px' }}
>
<svg className="w-8 h-8 mb-3 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm font-medium">Beitrag nicht verfügbar</span>
<span className="text-xs mt-1 text-slate-400">Dieser LinkedIn-Post wurde gelöscht oder die Privatsphäre-Einstellungen verhindern eine Einbettung.</span>
</div>
</div>
);
}
return (
<div className={`not-prose flex w-full justify-center my-8 ${className}`}>
<div
className="linkedin-embed-container w-full max-w-[504px] border border-slate-200 rounded-lg overflow-hidden shadow-sm bg-white"
style={{ width, minHeight: '500px' }}
>
{/* We use onLoad to detect successful rendering, relying on onError for explicit iframe load crashes */}
<iframe
ref={iframeRef}
src={initialSrc}
height="100%"
width="100%"
frameBorder="0"
allowFullScreen
title="Embedded post"
className="w-full min-h-[500px]"
onError={handleError}
/>
</div>
</div>
);
}