Harden import and paste validation, fix duplicate exports and variable declarations
This commit is contained in:
49
src/App.tsx
49
src/App.tsx
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user