Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions packages/app/src/common/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -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<Props, State> {
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 (
<div className="flex flex-col items-center justify-center min-h-screen p-4 text-center">
<h1 className="text-2xl font-bold text-red-600 mb-4">
Something went wrong
</h1>
<p className="text-gray-600 mb-2">{this.state.error.message}</p>
<button
className="mt-4 px-4 py-2 bg-green text-white rounded"
onClick={() => {
window.location.reload();
}}
>
Reload Page
</button>
</div>
);
}

return this.props.children;
}
}
1 change: 1 addition & 0 deletions packages/app/src/common/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
70 changes: 68 additions & 2 deletions packages/app/src/common/context/forestContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({
const { connection } = useConnection();
const [service, setService] = useState<ForestService | undefined>();
const [neighbours] = useState<TreeComponent[]>([]);
const [myTree] = useState<TreeComponent>();
const [myTree, setMyTree] = useState<TreeComponent>();

const address: PublicKey | null = useMemo(
() => safeParsePublicKeyFromUrl() ?? wallet.publicKey,
Expand All @@ -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.
Expand Down
14 changes: 11 additions & 3 deletions packages/app/src/common/context/sunriseStakeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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]);

Expand Down
19 changes: 9 additions & 10 deletions packages/app/src/hub/HubApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
useState,
} from "react";
import {
// IoChevronBackOutline,
IoChevronBackOutline,
IoChevronDownOutline,
IoChevronForwardOutline,
} from "react-icons/io5";
Expand Down Expand Up @@ -154,12 +154,11 @@ const _HubApp: ForwardRefRenderFunction<
/>
<div className={showHub ? "block" : "hidden"}>
<div className="flex">
{/* Forest navigation disabled
<LinkWithQuery
to="/forest"
{/* Forest navigation - hidden but maintains layout */}
<div
className={clx(
"hidden md:flex flex-col justify-center transition-opacity ease-in duration-500",
showHubNav ? "opacity-100" : "opacity-0"
"hidden md:flex flex-col justify-center transition-opacity ease-in duration-500 opacity-0 pointer-events-none",
showHubNav ? "opacity-0" : "opacity-0"
)}
>
<div className="flex items-center nowrap text-2xl">
Expand All @@ -169,7 +168,7 @@ const _HubApp: ForwardRefRenderFunction<
/>
<span>Forest</span>
</div>
</LinkWithQuery> */}
</div>
{myTree && (
<DynamicTree
details={myTree}
Expand Down Expand Up @@ -228,16 +227,16 @@ const _HubApp: ForwardRefRenderFunction<
showHubNav ? "opacity-100" : "opacity-0"
)}
>
{/* Forest navigation disabled
<LinkWithQuery to="/forest" className="flex items-center">
{/* Forest navigation - hidden but maintains layout */}
<div className="flex items-center opacity-0 pointer-events-none">
<div className="flex items-center nowrap text-2xl">
<IoChevronBackOutline
className="inline"
size={LINK_CHEVRON_SIZE}
/>
<span>Forest</span>
</div>
</LinkWithQuery> */}
</div>
<LinkWithQuery to="/grow" className="flex items-center">
<div className="flex items-center nowrap text-2xl relative">
<span>Grow</span>
Expand Down
Loading