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 + + +
+ logo +
+ {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 && ( + +
+ + + +
+
+ )} +
+
);