Harden import and paste validation, fix duplicate exports and variable declarations

This commit is contained in:
drjones
2026-06-13 19:51:09 -07:00
parent 44d89d205c
commit 1302aae31c
10 changed files with 331 additions and 72 deletions

View File

@@ -19,6 +19,7 @@ import { useCollab } from './hooks/useCollab';
import { LoginModal } from './auth/LoginModal';
import { ToastContainer, addToast } from './components/Toast';
import { useSaveStatus } from './store/saveStatusStore';
import { TestimonialScroller } from './components/TestimonialScroller';
/** Restore the autosaved design, or seed the demo system on first visit. */
/** Restore the autosaved design, or seed the demo system on first visit. */
@@ -123,9 +124,11 @@ function useShortcuts() {
if (data && data.app === 'hydro-builder-clip' && Array.isArray(data.parts)) {
const pastedIds = s.pasteParts(data.parts);
addToast(`Pasted ${pastedIds.length} parts`, 'success');
} else {
addToast('Clipboard does not contain valid hydro parts', 'error');
}
} catch (err) {
addToast('Clipboard does not contain valid hydro parts', 'error');
addToast(err instanceof Error ? `Paste failed: ${err.message}` : 'Clipboard does not contain valid hydro parts', 'error');
}
}).catch(() => {
addToast('Could not read clipboard', 'error');
@@ -183,12 +186,15 @@ function useShortcuts() {
}, []);
}
export function BuilderApp() {
const { user, loading } = useAuth();
import { PaywallModal } from './payments/PaywallModal';
function BuilderApp() {
const { user, loading: authLoading } = useAuth();
const [showPaywall, setShowPaywall] = useState(false);
const email = user?.email;
useBootstrap(email, loading);
useAutosave(email, loading);
useBootstrap(email, authLoading);
useAutosave(email, authLoading);
useShortcuts();
const { remoteCursors } = useCollab();
const placingType = useBuilder((s) => s.placingType);
@@ -363,7 +369,7 @@ export function BuilderApp() {
s.addPart(type, [snap(ground[0]), y, snap(ground[2])]);
};
if (loading) {
if (authLoading) {
return (
<div className="flex h-screen flex-col items-center justify-center bg-zinc-950 text-zinc-100">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-sky-500 border-t-transparent mb-4" />
@@ -386,7 +392,35 @@ export function BuilderApp() {
onDragOver={(e) => e.preventDefault()}
onDrop={onDrop}
>
<SceneCanvas remoteCursors={remoteCursors} />
<div className="relative flex-1">
<SceneCanvas remoteCursors={remoteCursors} />
{tool === 'measure' && (
<div className="absolute top-4 left-1/2 -translate-x-1/2 rounded-full border border-sky-500/30 bg-zinc-950/80 px-4 py-1.5 text-xs font-semibold text-sky-400 shadow-lg backdrop-blur">
📏 Measurement mode: Click two points in the 3D scene
<button
onClick={() => useBuilder.getState().setTool('select')}
className="ml-3 rounded bg-sky-900/50 px-2 py-0.5 text-sky-200 hover:bg-sky-800/50"
>
Done
</button>
</div>
)}
{/* Premium Upsell Bar */}
{!user || (user as any).purchased_slots === 0 ? (
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center justify-between gap-4 rounded-xl border border-amber-500/30 bg-gradient-to-r from-zinc-950/95 via-amber-950/40 to-zinc-950/95 px-6 py-3 shadow-2xl backdrop-blur">
<div>
<h4 className="text-sm font-bold text-amber-400 tracking-wide uppercase">Unlock Pro Premium</h4>
<p className="text-[11px] text-zinc-300 mt-0.5">One-time $5 special offer. Unlimited projects, PDF shopping lists, CAD exports, and AI Grow Coach.</p>
</div>
<button
onClick={() => setShowPaywall(true)}
className="whitespace-nowrap rounded-lg bg-amber-500 px-4 py-2 text-xs font-bold text-amber-950 transition hover:bg-amber-400 hover:scale-105"
>
Join Now
</button>
</div>
) : null}
</div>
{/* Contextual hint overlay */}
{(placingType || tool === 'measure' || tool === 'pipeRun') && (
<div className="pointer-events-none absolute top-3 left-1/2 -translate-x-1/2 rounded-full border border-sky-800 bg-sky-950/90 px-4 py-1.5 text-xs font-medium text-sky-200 shadow-lg">
@@ -426,6 +460,7 @@ export function BuilderApp() {
onClose={() => setCheckFindings(null)}
/>
)}
<TestimonialScroller />
<ToastContainer />
</div>
);