Skip to content

Commit

Permalink
feat: onboarding sidebar (#293)
Browse files Browse the repository at this point in the history
* feat: onboarding sidebar

* fix: don't show checklist hint on channel current order page

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
  • Loading branch information
reneaaron and rolznz authored Jul 19, 2024
1 parent 0718ca0 commit d58f21b
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 215 deletions.
116 changes: 35 additions & 81 deletions frontend/src/components/SidebarHint.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link2, LucideIcon, Plane, ShieldAlert, Zap } from "lucide-react";
import { ListTodo, LucideIcon, Zap } from "lucide-react";
import { ReactElement } from "react";
import { Link, useLocation } from "react-router-dom";
import { Button } from "src/components/ui/button";
import {
Expand All @@ -8,33 +9,21 @@ import {
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { ALBY_MIN_BALANCE, ALBY_SERVICE_FEE } from "src/constants";
import { useAlbyBalance } from "src/hooks/useAlbyBalance";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useChannels } from "src/hooks/useChannels";
import { useInfo } from "src/hooks/useInfo";
import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
import { Progress } from "src/components/ui/progress";
import { useOnboardingData } from "src/hooks/useOnboardingData";
import useChannelOrderStore from "src/state/ChannelOrderStore";

function SidebarHint() {
const { data: channels } = useChannels();
const { data: albyBalance } = useAlbyBalance();
const { data: info, hasChannelManagement, hasMnemonic } = useInfo();
const { data: albyMe } = useAlbyMe();
const { isLoading, checklistItems } = useOnboardingData();
const { order } = useChannelOrderStore();
const location = useLocation();
const { data: nodeConnectionInfo } = useNodeConnectionInfo();

// Don't distract with hints while opening a channel or on the settings page
// User has a channel order
if (
location.pathname.startsWith("/channels/") ||
location.pathname.startsWith("/settings")
!location.pathname.startsWith("/channels/") &&
order &&
order.status !== "pay"
) {
return null;
}

// User has a channel order
if (order && order.status !== "pay") {
return (
<SidebarHintCard
icon={Zap}
Expand All @@ -46,78 +35,43 @@ function SidebarHint() {
);
}

// User has funds to migrate
if (
hasChannelManagement &&
info?.backendType === "LDK" &&
albyBalance &&
albyBalance.sats * (1 - ALBY_SERVICE_FEE) >
ALBY_MIN_BALANCE + 50000 /* accomodate for onchain fees */
) {
return (
<SidebarHintCard
icon={Plane}
title="Migrate Alby Funds"
description="You have funds on your Alby shared account ready to migrate to your new node"
buttonText="Migrate Now"
buttonLink="/onboarding/lightning/migrate-alby"
/>
);
}

// User has no channels yet
if (hasChannelManagement && channels?.length === 0) {
return (
<SidebarHintCard
icon={Zap}
title="Open Your First Channel"
description="Deposit bitcoin by onchain or lightning payment to start using your new wallet."
buttonText="Begin Now"
buttonLink="/channels/outgoing"
/>
);
}

// User has not linked their hub to their Alby Account
if (
albyMe &&
nodeConnectionInfo &&
albyMe?.keysend_pubkey !== nodeConnectionInfo?.pubkey &&
!location.pathname.startsWith("/apps")
) {
return (
<SidebarHintCard
icon={Link2}
title="Link to your Alby Account"
description="Finish the setup by linking this Hub to your Alby Account."
buttonText="Link now"
buttonLink="/apps"
/>
);
}

const openChecklistItems = checklistItems.filter((x) => !x.checked);
if (
hasMnemonic &&
info &&
(!info.nextBackupReminder ||
new Date(info.nextBackupReminder).getTime() < new Date().getTime())
!location.pathname.startsWith("/home") &&
!location.pathname.startsWith("/channels/order") &&
!isLoading &&
openChecklistItems.length
) {
return (
<SidebarHintCard
icon={ShieldAlert}
title="Backup Your Keys"
description=" Not backing up your key might result in permanently losing
access to your funds."
buttonText="Backup Now"
buttonLink="/settings/key-backup"
icon={ListTodo}
title="Finish Setup"
description={
<>
<Progress
className="mt-2"
value={
((checklistItems.length - openChecklistItems.length) /
checklistItems.length) *
100
}
/>
<div className="text-xs mt-2">
{checklistItems.length - openChecklistItems.length} out of{" "}
{checklistItems.length} completed
</div>
</>
}
buttonText="See Next Steps"
buttonLink="/home"
/>
);
}
}

type SidebarHintCardProps = {
title: string;
description: string;
description: string | ReactElement;
buttonText: string;
buttonLink: string;
icon: LucideIcon;
Expand Down
122 changes: 122 additions & 0 deletions frontend/src/hooks/useOnboardingData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// src/hooks/useOnboardingData.ts

import { ALBY_MIN_BALANCE, ALBY_SERVICE_FEE } from "src/constants";
import { useAlbyBalance } from "src/hooks/useAlbyBalance";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useApps } from "src/hooks/useApps";
import { useChannels } from "src/hooks/useChannels";
import { useInfo } from "src/hooks/useInfo";
import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
import { useTransactions } from "src/hooks/useTransactions";

interface ChecklistItem {
title: string;
description: string;
checked: boolean;
to: string;
disabled: boolean;
}

interface UseOnboardingDataResponse {
isLoading: boolean;
checklistItems: ChecklistItem[];
}

export const useOnboardingData = (): UseOnboardingDataResponse => {
const { data: albyBalance } = useAlbyBalance();
const { data: albyMe } = useAlbyMe();
const { data: apps } = useApps();
const { data: channels } = useChannels();
const { data: info, hasChannelManagement, hasMnemonic } = useInfo();
const { data: nodeConnectionInfo } = useNodeConnectionInfo();
const { data: transactions } = useTransactions(false, 1);

const isLoading =
!albyMe ||
!apps ||
!channels ||
!info ||
!nodeConnectionInfo ||
!transactions ||
!albyBalance;

if (isLoading) {
return { isLoading: true, checklistItems: [] };
}

const isLinked =
albyMe &&
nodeConnectionInfo &&
albyMe?.keysend_pubkey === nodeConnectionInfo?.pubkey;
const hasChannel =
!hasChannelManagement || (hasChannelManagement && channels.length > 0);
const hasBackedUp =
hasMnemonic === true &&
info &&
info.nextBackupReminder !== "" &&
new Date(info.nextBackupReminder).getTime() > new Date().getTime();
const hasCustomApp =
apps && apps.find((x) => x.name !== "getalby.com") !== undefined;
const hasTransaction = transactions.length > 0;

const canMigrateAlbyFundsToNewChannel =
hasChannelManagement &&
info.backendType === "LDK" &&
albyBalance.sats * (1 - ALBY_SERVICE_FEE) >
ALBY_MIN_BALANCE + 50000; /* accommodate for onchain fees */

const checklistItems: Omit<ChecklistItem, "disabled">[] = [
{
title: "1. Open your first channel",
description:
"Establish a new Lightning channel to enable fast and low-fee Bitcoin transactions.",
checked: hasChannel,
to: canMigrateAlbyFundsToNewChannel
? "/onboarding/lightning/migrate-alby"
: "/channels/outgoing",
},
{
title: "2. Link to your Alby Account",
description: "Link your lightning address & other apps to this Hub.",
checked: isLinked,
to: "/apps",
},
{
title: "3. Send or receive your first payment",
description:
"Use your newly opened channel to make a transaction on the Lightning Network.",
checked: hasTransaction,
to: "/wallet",
},
{
title: "4. Connect your first app",
description:
"Seamlessly connect apps and integrate your wallet with other apps from your Hub.",
checked: hasCustomApp,
to: "/appstore",
},
...(hasMnemonic
? [
{
title: "5. Backup your keys",
description:
"Secure your keys by creating a backup to ensure you don't lose access.",
checked: hasBackedUp === true,
to: "/settings/key-backup",
},
]
: []),
];

const sortedChecklistItems = checklistItems.sort(
(a, b) => Number(b.checked) - Number(a.checked)
);
const nextStep = checklistItems.find((x) => !x.checked);

const sortedChecklistItemsWithDisabled = sortedChecklistItems.map((item) => ({
...item,
disabled: item !== nextStep,
}));

return { isLoading: false, checklistItems: sortedChecklistItemsWithDisabled };
};
Loading

0 comments on commit d58f21b

Please sign in to comment.