diff --git a/frontend/src/components/home/widgets/AppOfTheDayWidget.tsx b/frontend/src/components/home/widgets/AppOfTheDayWidget.tsx
new file mode 100644
index 00000000..6e5138c1
--- /dev/null
+++ b/frontend/src/components/home/widgets/AppOfTheDayWidget.tsx
@@ -0,0 +1,56 @@
+import { ExternalLinkIcon } from "lucide-react";
+import { Link } from "react-router-dom";
+import { suggestedApps } from "src/components/SuggestedAppData";
+import { Button } from "src/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+
+export function AppOfTheDayWidget() {
+ function seededRandom(seed: number) {
+ const x = Math.sin(seed++) * 10000;
+ return x - Math.floor(x);
+ }
+
+ const daysSinceEpoch = Math.floor(Date.now() / (1000 * 60 * 60 * 24));
+ const todayIndex = Math.floor(
+ seededRandom(daysSinceEpoch) * suggestedApps.length
+ );
+ const app = suggestedApps[todayIndex];
+
+ return (
+
+
+ App of the Day
+
+
+
+
+
+ {app.title}
+ {app.description}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/home/widgets/BlockHeightWidget.tsx b/frontend/src/components/home/widgets/BlockHeightWidget.tsx
new file mode 100644
index 00000000..c588f862
--- /dev/null
+++ b/frontend/src/components/home/widgets/BlockHeightWidget.tsx
@@ -0,0 +1,25 @@
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { useMempoolApi } from "src/hooks/useMempoolApi";
+
+export function BlockHeightWidget() {
+ const { data: blocks } = useMempoolApi<{ height: number }[]>("/v1/blocks");
+
+ if (!blocks?.length) {
+ return null;
+ }
+ return (
+
+
+ Block Height
+
+
+ {blocks[0].height}
+
+
+ );
+}
diff --git a/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx
new file mode 100644
index 00000000..d3c77784
--- /dev/null
+++ b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx
@@ -0,0 +1,55 @@
+import dayjs from "dayjs";
+import { ChevronRight } from "lucide-react";
+import { Link } from "react-router-dom";
+import AppAvatar from "src/components/AppAvatar";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { useApps } from "src/hooks/useApps";
+
+export function LatestUsedAppsWidget() {
+ const { data: apps } = useApps();
+ if (!apps?.length) {
+ return null;
+ }
+
+ return (
+
+
+ Latest Used Apps
+
+
+ {apps
+ .sort(
+ (a, b) =>
+ new Date(b.lastEventAt ?? 0).getTime() -
+ new Date(a.lastEventAt ?? 0).getTime()
+ )
+ .slice(0, 3)
+ .map((app) => (
+
+
+
+
+
+ {app.name === "getalby.com" ? "Alby Account" : app.name}
+
+
+
+
+ {app.lastEventAt
+ ? dayjs(app.lastEventAt).fromNow()
+ : "never"}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/home/widgets/NodeStatusWidget.tsx b/frontend/src/components/home/widgets/NodeStatusWidget.tsx
new file mode 100644
index 00000000..70c91e01
--- /dev/null
+++ b/frontend/src/components/home/widgets/NodeStatusWidget.tsx
@@ -0,0 +1,39 @@
+import { Link } from "react-router-dom";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { useChannels } from "src/hooks/useChannels";
+import { usePeers } from "src/hooks/usePeers";
+
+export function NodeStatusWidget() {
+ const { data: channels } = useChannels();
+ const { data: peers } = usePeers();
+
+ if (!channels || !peers) {
+ return null;
+ }
+ return (
+
+
+
+ Node
+
+
+ Channels Online
+
+ {channels.filter((c) => c.active).length || 0} /{" "}
+ {channels.filter((c) => c.active).length || 0}
+
+ Connected Peers
+
+ {peers.filter((p) => p.isConnected).length || 0} /{" "}
+ {peers.filter((p) => p.isConnected).length || 0}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/home/widgets/OnchainFeesWidget.tsx b/frontend/src/components/home/widgets/OnchainFeesWidget.tsx
new file mode 100644
index 00000000..1417f232
--- /dev/null
+++ b/frontend/src/components/home/widgets/OnchainFeesWidget.tsx
@@ -0,0 +1,55 @@
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { useMempoolApi } from "src/hooks/useMempoolApi";
+
+export function OnchainFeesWidget() {
+ const { data: recommendedFees } = useMempoolApi<{
+ fastestFee: number;
+ halfHourFee: number;
+ economyFee: number;
+ minimumFee: number;
+ }>("/v1/fees/recommended");
+
+ if (!recommendedFees) {
+ return null;
+ }
+
+ const entries = [
+ {
+ title: "No priority",
+ value: recommendedFees.minimumFee,
+ },
+ {
+ title: "Low priority",
+ value: recommendedFees.economyFee,
+ },
+ {
+ title: "Medium priority",
+ value: recommendedFees.halfHourFee,
+ },
+ {
+ title: "High priority",
+ value: recommendedFees.fastestFee,
+ },
+ ];
+
+ return (
+
+
+ On-Chain Fees
+
+
+ {entries.map((entry) => (
+
+
{entry.title}
+
{entry.value} sat/vB
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/home/widgets/WhatsNewWidget.tsx b/frontend/src/components/home/widgets/WhatsNewWidget.tsx
new file mode 100644
index 00000000..440d7fcb
--- /dev/null
+++ b/frontend/src/components/home/widgets/WhatsNewWidget.tsx
@@ -0,0 +1,42 @@
+import { compare } from "compare-versions";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "src/components/ui/card";
+import { useAlbyInfo } from "src/hooks/useAlbyInfo";
+import { useInfo } from "src/hooks/useInfo";
+
+export function WhatsNewWidget() {
+ const { data: info } = useInfo();
+ const { data: albyInfo } = useAlbyInfo();
+
+ if (!info || !albyInfo || !albyInfo.hub.latestReleaseNotes) {
+ return null;
+ }
+
+ const upToDate =
+ info.version &&
+ info.version.startsWith("v") &&
+ compare(info.version.substring(1), albyInfo.hub.latestVersion, ">=");
+
+ return (
+
+
+ What's New in {upToDate && "your "}Alby Hub?
+
+
+ v{albyInfo.hub.latestVersion}
+
+ {albyInfo.hub.latestReleaseNotes}
+
+ {!upToDate && (
+
+ Make sure to update! you're currently running {info.version}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/screens/Home.tsx b/frontend/src/screens/Home.tsx
index c7dc6384..b73befcc 100644
--- a/frontend/src/screens/Home.tsx
+++ b/frontend/src/screens/Home.tsx
@@ -18,8 +18,15 @@ import { useBalances } from "src/hooks/useBalances";
import { useInfo } from "src/hooks/useInfo";
import OnboardingChecklist from "src/screens/wallet/OnboardingChecklist";
+import React from "react";
import albyGo from "src/assets/suggested-apps/alby-go.png";
import zapplanner from "src/assets/suggested-apps/zapplanner.png";
+import { AppOfTheDayWidget } from "src/components/home/widgets/AppOfTheDayWidget";
+import { BlockHeightWidget } from "src/components/home/widgets/BlockHeightWidget";
+import { LatestUsedAppsWidget } from "src/components/home/widgets/LatestUsedAppsWidget";
+import { NodeStatusWidget } from "src/components/home/widgets/NodeStatusWidget";
+import { OnchainFeesWidget } from "src/components/home/widgets/OnchainFeesWidget";
+import { WhatsNewWidget } from "src/components/home/widgets/WhatsNewWidget";
function getGreeting(name: string | undefined) {
const hours = new Date().getHours();
@@ -40,6 +47,7 @@ function Home() {
const { data: info } = useInfo();
const { data: balances } = useBalances();
const { data: albyMe } = useAlbyMe();
+ const [isNerd, setNerd] = React.useState(false);
/* eslint-disable @typescript-eslint/no-explicit-any */
const extensionInstalled = (window as any).alby !== undefined;
@@ -51,122 +59,152 @@ function Home() {
return (
<>
-
-
- {info.albyAccountConnected && (
-
+
+ {/* LEFT */}
+
+
+
+ {info.albyAccountConnected && (
+
+
+
+
+
+
+
+
+ Alby Account
+
+
+
+ Get an Alby Account with a web wallet interface,
+ lightning address and other features.
+
+
+
+
+
+
+
+
+
+ )}
+
+
-
+
-
- Alby Account
+
+ ZapPlanner NEW
- Get an Alby Account with a web wallet interface, lightning
- address and other features.
+ Schedule automatic recurring lightning payments.
-
+
-
- )}
+
-
-
-
-
-
-
-
-
-
-
- ZapPlanner NEW
-
-
-
- Schedule automatic recurring lightning payments.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Alby Go
-
-
-
- The easiest Bitcoin mobile app that works great with Alby
- Hub.
-
-
-
-
-
-
-
-
-
- {!extensionInstalled && (
-
+
-
+
- Alby Browser Extension
+ Alby Go
- Seamless bitcoin payments in your favorite internet
- browser.
+ The easiest Bitcoin mobile app that works great with Alby
+ Hub.
-
+
-
- )}
+
+ {!extensionInstalled && (
+
+
+
+
+
+
+
+
+ Alby Browser Extension
+
+
+
+ Seamless bitcoin payments in your favorite internet
+ browser.
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {/* RIGHT */}
+
+
+
+
+
+
+
+ Stats for nerds
+
+
+
+ {isNerd && (
+
+
+
+
+
+
+
+ )}
+
+
>
);