Implement package limit notification system

This commit is contained in:
Codex Agent
2025-11-01 13:19:07 +01:00
parent 81cdee428e
commit 2c14493604
87 changed files with 4557 additions and 290 deletions

View File

@@ -31,7 +31,10 @@ import {
import { InviteLayoutCustomizerPanel, QrLayoutCustomization } from './components/InviteLayoutCustomizerPanel';
import { DesignerCanvas } from './components/invite-layout/DesignerCanvas';
import {
CANVAS_HEIGHT,
CANVAS_WIDTH,
buildDefaultElements,
clamp,
normalizeElements,
payloadToElements,
LayoutElement,
@@ -171,6 +174,8 @@ export default function EventInvitesPage(): JSX.Element {
const [exportDownloadBusy, setExportDownloadBusy] = React.useState<string | null>(null);
const [exportPrintBusy, setExportPrintBusy] = React.useState<string | null>(null);
const [exportError, setExportError] = React.useState<string | null>(null);
const exportPreviewContainerRef = React.useRef<HTMLDivElement | null>(null);
const [exportScale, setExportScale] = React.useState(0.34);
const load = React.useCallback(async () => {
if (!slug) {
@@ -190,10 +195,35 @@ export default function EventInvitesPage(): JSX.Element {
}
}, [slug]);
const recomputeExportScale = React.useCallback(() => {
const container = exportPreviewContainerRef.current;
if (!container) {
return;
}
const widthRatio = container.clientWidth / CANVAS_WIDTH;
const heightRatio = container.clientHeight ? container.clientHeight / CANVAS_HEIGHT : Number.POSITIVE_INFINITY;
const base = Math.min(widthRatio, heightRatio);
const safeBase = Number.isFinite(base) && base > 0 ? Math.min(base, 1) : 1;
const clampedScale = clamp(safeBase, 0.1, 1);
setExportScale((prev) => (Math.abs(prev - clampedScale) < 0.001 ? prev : clampedScale));
}, []);
React.useEffect(() => {
void load();
}, [load]);
React.useEffect(() => {
recomputeExportScale();
}, [recomputeExportScale]);
React.useEffect(() => {
const handleResize = () => recomputeExportScale();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [recomputeExportScale]);
React.useEffect(() => {
const param = searchParams.get('tab');
const nextTab = param === 'export' || param === 'links' ? (param as TabKey) : 'layout';
@@ -369,6 +399,28 @@ export default function EventInvitesPage(): JSX.Element {
);
}, [exportLayout, currentCustomization, selectedInvite?.url, eventName]);
React.useEffect(() => {
if (activeTab !== 'export') {
return;
}
recomputeExportScale();
}, [activeTab, recomputeExportScale, exportElements.length, exportLayout?.id, selectedInvite?.id]);
React.useEffect(() => {
if (typeof ResizeObserver !== 'function') {
return undefined;
}
const target = exportPreviewContainerRef.current;
if (!target) {
return undefined;
}
const observer = new ResizeObserver(() => recomputeExportScale());
observer.observe(target);
return () => observer.disconnect();
}, [recomputeExportScale, activeTab]);
const exportCanvasKey = React.useMemo(
() => `export:${selectedInvite?.id ?? 'none'}:${exportLayout?.id ?? 'layout'}:${exportPreview?.mode ?? 'standard'}`,
[selectedInvite?.id, exportLayout?.id, exportPreview?.mode]
@@ -789,7 +841,10 @@ export default function EventInvitesPage(): JSX.Element {
</div>
<div className="mt-6 flex justify-center">
{exportElements.length ? (
<div className="pointer-events-none">
<div
ref={exportPreviewContainerRef}
className="pointer-events-none w-full max-w-full"
>
<DesignerCanvas
elements={exportElements}
selectedId={null}
@@ -803,8 +858,9 @@ export default function EventInvitesPage(): JSX.Element {
badge={exportPreview.badgeColor}
qrCodeDataUrl={exportQr}
logoDataUrl={exportLogo}
scale={0.34}
scale={exportScale}
layoutKey={exportCanvasKey}
readOnly
/>
</div>
) : (