Skip to content

Commit

Permalink
feat: improving masp loading states (#1583)
Browse files Browse the repository at this point in the history
* fix: fixing button z-index

* feat: improving loadings on masp overview
  • Loading branch information
pedrorezende authored Jan 27, 2025
1 parent 58bb84d commit 9879db0
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 28 deletions.
19 changes: 19 additions & 0 deletions apps/namadillo/src/App/Common/MaspContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { lastCompletedShieldedSyncAtom } from "atoms/balance";
import { useRequiresNewShieldedSync } from "hooks/useRequiresNewShieldedSync";
import { useAtomValue } from "jotai";
import { MaspSyncCover } from "./MaspSyncCover";

type MaspContainerProps = React.PropsWithChildren;

export const MaspContainer = ({
children,
}: MaspContainerProps): JSX.Element => {
const requiresNewSync = useRequiresNewShieldedSync();
const lastSync = useAtomValue(lastCompletedShieldedSyncAtom);
return (
<div className="relative">
{children}
{requiresNewSync && <MaspSyncCover longSync={lastSync === undefined} />}
</div>
);
};
45 changes: 45 additions & 0 deletions apps/namadillo/src/App/Common/MaspSyncCover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import clsx from "clsx";
import { PulsingRing } from "./PulsingRing";

type MaspSyncCoverProps = {
longSync?: boolean;
};

export const MaspSyncCover = ({
longSync,
}: MaspSyncCoverProps): JSX.Element => {
return (
<div
className={clsx(
"absolute w-full h-full backdrop-blur-sm left-0 top-0 z-50 rounded-sm overflow-hidden",
"bg-black/70"
)}
>
<div
className={clsx(
"text-yellow text-center w-full",
"absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
)}
>
<header className="flex w-full items-center justify-center mb-4 h-42">
<PulsingRing />
<h2 className="absolute text-3xl font-medium text">
Shielded Sync in Progress
</h2>
</header>
<div className="relative z-40">
<div>
<p>
{longSync && (
<>
Shielded sync can take a few minutes. <br />
</>
)}
Please wait to perform shielded actions.
</p>
</div>
</div>
</div>
</div>
);
};
72 changes: 72 additions & 0 deletions apps/namadillo/src/App/Common/PulsingRing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import anime from "animejs";
import clsx from "clsx";
import { useScope } from "hooks/useScope";
import { useRef } from "react";
import { twMerge } from "tailwind-merge";

type PulsingRingProps = { className?: string };

export const PulsingRing = ({ className }: PulsingRingProps): JSX.Element => {
const containerRef = useRef<HTMLDivElement>(null);

useScope(
(query) => {
const items = Array.from(query("[data-animation=ring]"));

const timeline = anime.timeline({
easing: "easeOutExpo",
duration: 800,
loop: true,
});

timeline.add({
targets: items,
duration: 0,
translateY: "-50%",
translateX: "-50%",
});

timeline.add({
targets: items,
opacity: [0, 1],
scale: [0, 1],
easing: "easeOutExpo",
delay: anime.stagger(150),
duration: 1100,
});

timeline.add({
targets: items,
opacity: 0,
easing: "easeOutExpo",
delay: anime.stagger(150, { direction: "reverse" }),
});
},
containerRef,
[]
);

const renderRing = (className: string): JSX.Element => {
return (
<span
data-animation="ring"
className={clsx(
"block absolute aspect-square border border-yellow rounded-full",
"left-1/2 top-1/2 leading-[0]",
className
)}
/>
);
};

return (
<span
ref={containerRef}
className={twMerge("block relative leading-0 bg-red-500", className)}
>
{renderRing("h-18")}
{renderRing("h-30")}
{renderRing("h-42")}
</span>
);
};
5 changes: 4 additions & 1 deletion apps/namadillo/src/App/Masp/MaspLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ConnectPanel } from "App/Common/ConnectPanel";
import { MaspContainer } from "App/Common/MaspContainer";
import { PageWithSidebar } from "App/Common/PageWithSidebar";
import { routes } from "App/routes";
import { ShieldAllBanner } from "App/Sidebars/ShieldAllBanner";
Expand All @@ -25,7 +26,9 @@ export const MaspLayout: React.FC = () => {

return (
<PageWithSidebar>
<Outlet />
<MaspContainer>
<Outlet />
</MaspContainer>
<aside className="w-full mt-2 flex flex-col sm:flex-row lg:mt-0 lg:flex-col gap-2">
<ShieldedSyncProgress />
<ShieldAllBanner />
Expand Down
41 changes: 29 additions & 12 deletions apps/namadillo/src/App/Masp/ShieldedBalanceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Heading, PieChart } from "@namada/components";
import { Heading, PieChart, SkeletonLoading } from "@namada/components";
import { AtomErrorBoundary } from "App/Common/AtomErrorBoundary";
import { FiatCurrency } from "App/Common/FiatCurrency";
import { shieldedTokensAtom } from "atoms/balance/atoms";
import { getTotalDollar } from "atoms/balance/functions";
import { applicationFeaturesAtom } from "atoms/settings";
import clsx from "clsx";
import { useAtomValue } from "jotai";
import { twMerge } from "tailwind-merge";
import { colors } from "theme";
Expand All @@ -23,25 +24,41 @@ export const ShieldedBalanceChart = (): JSX.Element => {
>
<PieChart
id="balance-chart"
data={[{ value: 100, color: colors.shielded }]}
data={[
{
value: 100,
color:
shieldedTokensQuery.isSuccess ?
colors.shielded
: colors.empty,
},
]}
strokeWidth={24}
radius={125}
segmentMargin={0}
className={clsx({ "animate-pulse": shieldedTokensQuery.isPending })}
>
<div className="flex flex-col gap-1 items-center leading-tight max-w-[180px]">
{!shieldedDollars ?
"N/A"
: <>
<Heading className="text-sm" level="h3">
Shielded Balance
</Heading>
<FiatCurrency
className={twMerge(
"text-2xl sm:text-3xl whitespace-nowrap",
!namTransfersEnabled && "after:content-['*']"
)}
amount={shieldedDollars}
/>
{shieldedTokensQuery.isPending && (
<SkeletonLoading width="80px" height="40px" />
)}
{shieldedTokensQuery.isSuccess && (
<>
<Heading className="text-sm" level="h3">
Shielded Balance
</Heading>
<FiatCurrency
className={twMerge(
"text-2xl sm:text-3xl whitespace-nowrap",
!namTransfersEnabled && "after:content-['*']"
)}
amount={shieldedDollars}
/>
</>
)}
</>
}
</div>
Expand Down
16 changes: 11 additions & 5 deletions apps/namadillo/src/App/Masp/ShieldedNamBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,26 @@ import { twMerge } from "tailwind-merge";
import namBalanceIcon from "./assets/nam-balance-icon.png";
import namadaShieldedSvg from "./assets/namada-shielded.svg";

const AsyncNamCurrency = ({ amount }: { amount?: BigNumber }): JSX.Element => {
const AsyncNamCurrency = ({
amount,
className = "",
}: {
amount?: BigNumber;
className?: string;
}): JSX.Element => {
if (amount === undefined) {
return (
<Stack gap={2.5} className="h-[76px] items-center">
<SkeletonLoading height="26px" width="100px" />
<SkeletonLoading height="16px" width="50px" />
<Stack gap={1.5} className="items-center">
<SkeletonLoading height="24px" width="100px" />
<SkeletonLoading height="18px" width="50px" />
</Stack>
);
}

return (
<NamCurrency
amount={new BigNumber(amount)}
className="block text-center text-3xl leading-none"
className={twMerge("block text-center text-3xl leading-none", className)}
currencySymbolClassName="block text-xs mt-1"
/>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/namadillo/src/App/Masp/ShieldedOverviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const AssetTable = (): JSX.Element => {
const shieldedTokensQuery = useAtomValue(shieldedTokensAtom);

if (shieldedTokensQuery.data === undefined) {
return <SkeletonLoading height="100%" width="100%" />;
return <SkeletonLoading height="125px" width="100%" />;
}

if (!shieldedTokensQuery.data.length) {
Expand Down
9 changes: 8 additions & 1 deletion apps/namadillo/src/App/Settings/SettingsMASP.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { ActionButton, Stack } from "@namada/components";
import { routes } from "App/routes";
import { storageShieldedBalanceAtom } from "atoms/balance/atoms";
import {
lastCompletedShieldedSyncAtom,
storageShieldedBalanceAtom,
} from "atoms/balance/atoms";
import { clearShieldedContextAtom } from "atoms/settings";
import { useAtom, useSetAtom } from "jotai";
import { RESET } from "jotai/utils";

export const SettingsMASP = (): JSX.Element => {
const [clearShieldedContext] = useAtom(clearShieldedContextAtom);
const setStorageShieldedBalance = useSetAtom(storageShieldedBalanceAtom);
const setLastCompletedShieldedSync = useSetAtom(
lastCompletedShieldedSyncAtom
);

const onInvalidateShieldedContext = async (): Promise<void> => {
await clearShieldedContext.mutateAsync();
setStorageShieldedBalance(RESET);
setLastCompletedShieldedSync(undefined);
location.href = routes.root;
};

Expand Down
23 changes: 16 additions & 7 deletions apps/namadillo/src/atoms/balance/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ export const storageShieldedBalanceAtom = atomWithStorage<

export const shieldedSyncProgress = atom(0);

export const lastCompletedShieldedSyncAtom = atomWithStorage<Date | undefined>(
"namadillo:last-shielded-sync",
undefined
);

export const isShieldedSyncCompleteAtom = atom(
(get) => get(shieldedSyncProgress) === 1
);

export const shieldedBalanceAtom = atomWithQuery((get) => {
const enablePolling = get(shouldUpdateBalanceAtom);
const viewingKeysQuery = get(viewingKeysAtom);
Expand Down Expand Up @@ -166,6 +175,8 @@ export const shieldedBalanceAtom = atomWithQuery((get) => {
[viewingKey.key]: shieldedBalance,
});

set(lastCompletedShieldedSyncAtom, new Date());

return shieldedBalance;
}, [
viewingKeysQuery,
Expand Down Expand Up @@ -199,7 +210,7 @@ export const namadaShieldedAssetsAtom = atomWithQuery((get) => {
chainTokensQuery.data!,
chainParameters.data!.chainId
),
[chainTokensQuery, chainParameters]
[chainTokensQuery, chainParameters, viewingKeysQuery]
),
};
});
Expand Down Expand Up @@ -239,12 +250,10 @@ export const shieldedTokensAtom = atomWithQuery<TokenBalance[]>((get) => {
return {
queryKey: ["shielded-tokens", shieldedAssets.data, tokenPrices.data],
...queryDependentFn(
() =>
Promise.resolve(
mapNamadaAssetsToTokenBalances(
shieldedAssets.data ?? {},
tokenPrices.data ?? {}
)
async () =>
mapNamadaAssetsToTokenBalances(
shieldedAssets.data ?? {},
tokenPrices.data ?? {}
),
[shieldedAssets, tokenPrices]
),
Expand Down
24 changes: 24 additions & 0 deletions apps/namadillo/src/hooks/useRequiresNewShieldedSync.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
isShieldedSyncCompleteAtom,
lastCompletedShieldedSyncAtom,
} from "atoms/balance";
import { subMinutes } from "date-fns";
import { useAtomValue } from "jotai";

type RequiresNewShieldedSyncProps = {
minutesToNextSync?: number;
};

export const useRequiresNewShieldedSync = ({
minutesToNextSync = 3,
}: RequiresNewShieldedSyncProps = {}): boolean => {
const isComplete = useAtomValue(isShieldedSyncCompleteAtom);
const lastSync = useAtomValue(lastCompletedShieldedSyncAtom);
const requiresNewSync =
lastSync === undefined ||
(!isComplete &&
lastSync &&
subMinutes(new Date(), minutesToNextSync) > lastSync);

return requiresNewSync;
};
2 changes: 1 addition & 1 deletion packages/components/src/ActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const actionButtonShape = tv({
outlined: {
true: clsx(
"before:transition-colors before:border before:border-[var(--outline)] before:absolute",
"before:left-0 before:top-0 before:w-full before:h-full before:z-[1000]"
"before:left-0 before:top-0 before:w-full before:h-full before:z-50"
),
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/PieChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const PieChart = ({
const path = (
<circle
fill="none"
className="transition-all duration-200 ease-out"
key={`pie-chart-${id}-${index}`}
cx={radius}
cy={radius}
Expand Down

0 comments on commit 9879db0

Please sign in to comment.