diff --git a/packages/app/src/common/components/ErrorBoundary.tsx b/packages/app/src/common/components/ErrorBoundary.tsx new file mode 100644 index 00000000..3d82fc2a --- /dev/null +++ b/packages/app/src/common/components/ErrorBoundary.tsx @@ -0,0 +1,53 @@ +import React, { Component, type ReactNode } from "react"; + +interface Props { + children: ReactNode; + fallback?: (error: Error) => ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + console.error("ErrorBoundary caught an error:", error, errorInfo); + } + + render(): ReactNode { + if (this.state.hasError && this.state.error) { + if (this.props.fallback) { + return this.props.fallback(this.state.error); + } + + return ( +
+

+ Something went wrong +

+

{this.state.error.message}

+ +
+ ); + } + + return this.props.children; + } +} diff --git a/packages/app/src/common/components/index.ts b/packages/app/src/common/components/index.ts index 6a974e4b..fb9d7eb2 100644 --- a/packages/app/src/common/components/index.ts +++ b/packages/app/src/common/components/index.ts @@ -2,6 +2,7 @@ export * from "./AmountInput"; export * from "./Button"; export * from "./CarbonRecovered"; export * from "./DetailsBox"; +export * from "./ErrorBoundary"; export * from "./InfoBox"; export * from "./LockedGSol"; export * from "./Logo"; diff --git a/packages/app/src/common/context/forestContext.tsx b/packages/app/src/common/context/forestContext.tsx index d8f96d9d..fe6a7290 100644 --- a/packages/app/src/common/context/forestContext.tsx +++ b/packages/app/src/common/context/forestContext.tsx @@ -36,7 +36,7 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ const { connection } = useConnection(); const [service, setService] = useState(); const [neighbours] = useState([]); - const [myTree] = useState(); + const [myTree, setMyTree] = useState(); const address: PublicKey | null = useMemo( () => safeParsePublicKeyFromUrl() ?? wallet.publicKey, @@ -46,8 +46,74 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ const loadTree = useCallback( (reload = false) => { // Forest feature disabled - no neighbor retrieval + // Create a minimal tree object based on wallet connection status + if (address && details) { + try { + // Create a simple tree representation without MongoDB calls + const gsolBalance = Number(details.balances.gsolBalance.amount) || 0; + // lockDetails.amountLocked is in BN (lamports), convert to SOL + const lockedBalance = details.lockDetails?.amountLocked + ? Number(details.lockDetails.amountLocked.toString()) / 1e9 + : 0; + const totalBalance = gsolBalance + lockedBalance; + + // Calculate level based on balance thresholds + // Level 0: 0 gSOL + // Level 1: 0.001 - 1 gSOL + // Level 2: 1 - 10 gSOL + // Level 3: 10 - 50 gSOL + // Level 4: 50 - 100 gSOL + // Level 5: 100 - 500 gSOL + // Level 6: 500 - 1000 gSOL + // Level 7: 1000 - 5000 gSOL + // Level 8: 5000+ gSOL + let level = 0; + if (totalBalance > 0) { + if (totalBalance < 1) level = 1; + else if (totalBalance < 10) level = 2; + else if (totalBalance < 50) level = 3; + else if (totalBalance < 100) level = 4; + else if (totalBalance < 500) level = 5; + else if (totalBalance < 1000) level = 6; + else if (totalBalance < 5000) level = 7; + else level = 8; + } + + // Calculate species and instance from address for visual variety + const addressBytes = address.toBuffer(); + const instance = addressBytes[31] % 3; // Assuming 3 tree variations per species + const species = (addressBytes[30] % 3) + 1; // Assuming 3 species (1-3) + + const minimalTree: TreeComponent = { + address, + translate: { x: 0, y: 0, z: 0 }, + metadata: { + type: { + level, + species, + instance, + translucent: false, + }, + node: { + address, + balance: totalBalance, + startDate: new Date(), + mostRecentTransfer: new Date(), + children: [], + parents: [], + }, + layer: 0, + }, + }; + + setMyTree(minimalTree); + } catch (error) { + console.error("Error creating minimal tree:", error); + // Don't set tree on error to avoid breaking the UI + } + } }, - [service, address] + [address, details] ); // reload tree when details change. doesn't matter what changed. diff --git a/packages/app/src/common/context/sunriseStakeContext.tsx b/packages/app/src/common/context/sunriseStakeContext.tsx index 563f2d90..2622888a 100644 --- a/packages/app/src/common/context/sunriseStakeContext.tsx +++ b/packages/app/src/common/context/sunriseStakeContext.tsx @@ -67,7 +67,13 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { setLoading ) .then(updateClient) - .catch(console.error); + .catch((error) => { + console.error( + "Failed to initialize SunriseClient for wallet:", + error + ); + setLoading(false); + }); } else if (addressFromUrl !== null) { // we have an address in the url, but no wallet // this is a readonly client @@ -85,7 +91,7 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { ) .then(updateClient) .catch((e) => { - console.error(e); + console.error("Failed to initialize readonly client:", e); }); } else { // just get the details from the chain - no client available yet @@ -106,7 +112,9 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { return client.getDetails(); }) .then(initDetails) - .catch(console.error); + .catch((error) => { + console.error("Failed to get initial details:", error); + }); } }, [wallet?.publicKey, location.state?.address]); diff --git a/packages/app/src/hub/HubApp.tsx b/packages/app/src/hub/HubApp.tsx index 49b6b04c..fde7af11 100644 --- a/packages/app/src/hub/HubApp.tsx +++ b/packages/app/src/hub/HubApp.tsx @@ -9,7 +9,7 @@ import { useState, } from "react"; import { - // IoChevronBackOutline, + IoChevronBackOutline, IoChevronDownOutline, IoChevronForwardOutline, } from "react-icons/io5"; @@ -154,12 +154,11 @@ const _HubApp: ForwardRefRenderFunction< />
- {/* Forest navigation disabled - */} +
{myTree && ( - {/* Forest navigation disabled - + {/* Forest navigation - hidden but maintains layout */} +
Forest
- */} +
Grow