import { toPng } from "html-to-image"; import { useCallback, useRef, useState } from "react"; import { flushSync } from "react-dom"; // --- YOUR COMPONENT --- interface RawOGCProps { privateProviderHandle?: string; multiple?: boolean; a: string; b: string; c?: string; d?: string; expiry?: Date; } export function RawOGC({ privateProviderHandle, multiple = false, a, b, c, d, expiry, }: RawOGCProps) { const options = [a, b, c, d].filter((opt): opt is string => !!opt); const formattedDate = expiry?.toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "short", }); return ( // Note: Added explicit background color to ensure PNG isn't transparent where it shouldn't be
{/* Header */}
{/* Type Pill */}
{privateProviderHandle ? : } {privateProviderHandle ? "Private Poll" : "Public Poll"}
{/* Multiplicity */} {multiple || !privateProviderHandle ? ( ) : ( )} {multiple || !privateProviderHandle ? "Select one or more options" : "Select one option"} All votes are public
{/* Options List */}
{options.map((optionText, index) => (
{optionText}
))}
{/* Footer */}
{/* Expiry */}
{!formattedDate || !privateProviderHandle ? ( Never expires ) : ( Expires {formattedDate} )}
{/* Branding */}
{privateProviderHandle ? ( <> Private voting via
{/* provider pfp (gradient placeholder) */}
@{privateProviderHandle}
) : (
View all votes
)}
); } // --- THE GENERATOR HOOK --- export function useOGGenerator() { const ref = useRef(null); const [props, setProps] = useState(null); const generate = useCallback( async (renderProps: RawOGCProps): Promise => { return new Promise((resolve, reject) => { // 1. Mount the component with the new props // eslint-disable-next-line @eslint-react/dom/no-flush-sync flushSync(() => { setProps(renderProps); }); // 2. Wait a tick for styles/fonts to settle // A small timeout allows the DOM to fully paint the Tailwind classes setTimeout(async () => { if (!ref.current) { reject("Ref not found"); return; } try { // 3. Capture image const dataUrl = await toPng(ref.current, { cacheBust: true, pixelRatio: 1, // 1 = 1000px width. Set to 2 for Retina (2000px width) skipFonts: true, }); // 4. Cleanup (Unmount) setProps(null); resolve(dataUrl); } catch (error) { console.error("Generation failed", error); setProps(null); reject(error); } }, 100); }); }, [], ); // The hidden rendering area // We use fixed positioning way off-screen. Display:none causes capture failures. const element = props ? (
) : null; return { generate, element }; }