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 };
}